@dynamic-labs-wallet/browser 0.0.0-preview-120.0

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 +1805 -0
  3. package/index.esm.d.ts +1 -0
  4. package/index.esm.js +1738 -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 +370 -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,1738 @@
1
+ import { SigningAlgorithm, MPC_RELAY_PROD_API_URL, getMPCChainConfig, BackupLocation, getClientThreshold, MPC_CONFIG, getTSSConfig, WalletOperation, getReshareConfig, DynamicApiClient, ENVIRONMENT_ENUM, DYNAMIC_AUTH_DEV_BASE_API_URL, DYNAMIC_AUTH_PREPROD_BASE_API_URL, DYNAMIC_AUTH_PROD_BASE_API_URL, IFRAME_DOMAIN_MAP } 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
+ iframe == null ? void 0 : (_iframe_contentWindow = iframe.contentWindow) == null ? void 0 : _iframe_contentWindow.postMessage(message, iframeOrigin);
420
+ }
421
+ });
422
+ const handleIncomingMessage = (message)=>{
423
+ const { data } = message;
424
+ if (!data) return;
425
+ if ((data == null ? void 0 : data.origin) !== 'webview') {
426
+ return;
427
+ }
428
+ if (typeof data !== 'object') {
429
+ return;
430
+ }
431
+ try {
432
+ const message = parseMessageTransportData(data);
433
+ messageTransport.emit(message);
434
+ } catch (error) {
435
+ if (!(error instanceof SyntaxError)) {
436
+ logger.error('Error handling incoming message:', error);
437
+ }
438
+ }
439
+ };
440
+ /**
441
+ * Handle incoming message from android client
442
+ */ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
443
+ // @ts-ignore
444
+ document.addEventListener('message', handleIncomingMessage);
445
+ /**
446
+ * Handle incoming message from iOS client
447
+ */ window.addEventListener('message', handleIncomingMessage);
448
+ };
449
+
450
+ class IframeDisplayChannelAdapter {
451
+ async displayClientShares(accountAddress) {
452
+ await this.requestChannel.request('displayClientShares', accountAddress);
453
+ }
454
+ async displayPrivateKey(accountAddress, privateKey) {
455
+ await this.requestChannel.request('displayPrivateKey', accountAddress, privateKey);
456
+ }
457
+ constructor(messageTransport){
458
+ this.requestChannel = createRequestChannel(messageTransport);
459
+ }
460
+ }
461
+
462
+ class DynamicWalletClient {
463
+ async initialize() {
464
+ if (this.initializePromise) {
465
+ return await this.initializePromise;
466
+ }
467
+ this.logger.debug('Initializing Dynamic Waas Wallet SDK');
468
+ this.initializePromise = this._initialize();
469
+ const result = await this.initializePromise;
470
+ this.logger.debug('Dynamic Waas Wallet SDK initialized');
471
+ return result;
472
+ }
473
+ /**
474
+ * Initializes the iframe display for a specific container.
475
+ *
476
+ * @param {HTMLElement} container - The container to which the iframe will be attached.
477
+ * @returns {Promise<{
478
+ * iframe: HTMLIFrameElement;
479
+ * iframeDisplay: IframeDisplayChannelAdapter;
480
+ * cleanup: () => void;
481
+ * }>} A promise that resolves when the iframe is loaded.
482
+ */ async initializeIframeDisplayForContainer({ container }) {
483
+ try {
484
+ const iframe = await this.loadIframeForContainer(container);
485
+ const transport = applyDefaultMessageOrigin({
486
+ defaultOrigin: 'host',
487
+ messageTransport: createMessageTransport()
488
+ });
489
+ setupMessageTransportBridge(transport, iframe, this.iframeDomain);
490
+ const iframeDisplay = new IframeDisplayChannelAdapter(transport);
491
+ return {
492
+ iframe,
493
+ iframeDisplay,
494
+ cleanup: ()=>{
495
+ container.removeChild(iframe);
496
+ }
497
+ };
498
+ } catch (error) {
499
+ this.logger.error('Error initializing iframe:', error);
500
+ throw error;
501
+ }
502
+ }
503
+ /**
504
+ * this is called on class construction time
505
+ * @returns {Promise<void>} that resolves when the iframe is loaded and the message transport and iframe storage are initialized
506
+ */ initializeIframeCommunication() {
507
+ if (!this.iframeLoadPromise) {
508
+ this.iframeLoadPromise = this.doInitializeIframeCommunication();
509
+ }
510
+ return this.iframeLoadPromise;
511
+ }
512
+ /**
513
+ * initialize the iframe communication by awaiting the iframe load promise
514
+ * and initializing the message transport and iframe storage after iframe is successfully loaded
515
+ */ async doInitializeIframeCommunication() {
516
+ try {
517
+ await this.loadIframe();
518
+ this.initializeMessageTransport();
519
+ this.initializeIframeStorage();
520
+ } catch (error) {
521
+ this.logger.error('Error initializing iframe:', error);
522
+ throw error;
523
+ }
524
+ }
525
+ /**
526
+ * create a promise to load an iframe
527
+ * @returns {Promise<void>} that resolves when the iframe is loaded
528
+ */ loadIframe() {
529
+ return new Promise((resolve, reject)=>{
530
+ const iframe = document.createElement('iframe');
531
+ const iframeTimeoutId = setTimeout(()=>{
532
+ reject(new Error('Iframe load timeout'));
533
+ }, 10000);
534
+ iframe.style.display = 'none';
535
+ iframe.setAttribute('title', 'Dynamic Wallet Iframe');
536
+ iframe.style.position = 'fixed';
537
+ iframe.style.top = '0';
538
+ iframe.style.left = '0';
539
+ iframe.style.width = '0';
540
+ iframe.style.height = '0';
541
+ iframe.style.border = 'none';
542
+ iframe.style.pointerEvents = 'none';
543
+ const params = new URLSearchParams({
544
+ instanceId: this.instanceId,
545
+ hostOrigin: window.location.origin
546
+ });
547
+ iframe.src = `${this.iframeDomain}/waas/${this.environmentId}?${params.toString()}`;
548
+ this.logger.debug('Creating iframe with src:', iframe.src);
549
+ document.body.appendChild(iframe);
550
+ iframe.onload = ()=>{
551
+ clearTimeout(iframeTimeoutId);
552
+ this.logger.debug('Iframe loaded successfully');
553
+ this.iframe = iframe;
554
+ resolve();
555
+ };
556
+ iframe.onerror = (error)=>{
557
+ clearTimeout(iframeTimeoutId);
558
+ this.logger.error('Iframe failed to load:', error);
559
+ reject(new Error('Failed to load iframe'));
560
+ };
561
+ });
562
+ }
563
+ /**
564
+ * Load an iframe for a specific container
565
+ * @param {HTMLElement} container - The container to which the iframe will be attached
566
+ * @returns {Promise<HTMLIFrameElement>} that resolves when the iframe is loaded
567
+ */ loadIframeForContainer(container) {
568
+ return new Promise((resolve, reject)=>{
569
+ const iframe = document.createElement('iframe');
570
+ const iframeTimeoutId = setTimeout(()=>{
571
+ reject(new Error('Iframe load timeout'));
572
+ }, 10000);
573
+ iframe.style.display = 'block';
574
+ iframe.style.width = '100%';
575
+ iframe.style.height = '100%';
576
+ iframe.setAttribute('title', 'Dynamic Wallet Storage');
577
+ iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin');
578
+ const params = new URLSearchParams({
579
+ instanceId: this.instanceId,
580
+ hostOrigin: window.location.origin
581
+ });
582
+ iframe.src = `${this.iframeDomain}/waas/${this.environmentId}?${params.toString()}`;
583
+ this.logger.debug('Creating iframe with src:', iframe.src);
584
+ // Add iframe to the provided container
585
+ container.appendChild(iframe);
586
+ iframe.onload = ()=>{
587
+ clearTimeout(iframeTimeoutId);
588
+ this.logger.debug('Iframe loaded successfully');
589
+ this.iframe = iframe;
590
+ resolve(iframe);
591
+ };
592
+ iframe.onerror = (error)=>{
593
+ clearTimeout(iframeTimeoutId);
594
+ this.logger.error('Iframe failed to load:', error);
595
+ reject(new Error('Failed to load iframe'));
596
+ };
597
+ });
598
+ }
599
+ /**
600
+ * initialize the message transport after iframe is successfully loaded
601
+ */ initializeMessageTransport() {
602
+ const transport = applyDefaultMessageOrigin({
603
+ defaultOrigin: 'host',
604
+ messageTransport: createMessageTransport()
605
+ });
606
+ this.messageTransport = transport;
607
+ if (!this.iframe) {
608
+ throw new Error('Iframe not available');
609
+ }
610
+ setupMessageTransportBridge(this.messageTransport, this.iframe, this.iframeDomain);
611
+ }
612
+ /**
613
+ * initialize the iframe storage after iframe is successfully loaded
614
+ */ initializeIframeStorage() {
615
+ if (!this.messageTransport) {
616
+ throw new Error('Message transport not initialized');
617
+ }
618
+ this.iframeStorage = new StorageRequestChannelAdapter(this.messageTransport);
619
+ }
620
+ /**
621
+ * Gets the initialized iframe instance. This method ensures the iframe is properly loaded and configured.
622
+ * The first call will initialize and await the iframe loading process.
623
+ * Subsequent calls will return the same iframe instance immediately.
624
+ *
625
+ * @throws {Error} If iframe initialization fails
626
+ * @throws {Error} If message transport initialization fails
627
+ * @throws {Error} If iframe storage initialization fails
628
+ * @returns {Promise<HTMLIFrameElement>} The initialized iframe element
629
+ */ async getIframe() {
630
+ await this.initializeIframeCommunication();
631
+ if (!this.iframe) {
632
+ throw new Error('Failed to initialize iframe');
633
+ }
634
+ if (!this.messageTransport) {
635
+ throw new Error('Failed to initialize message transport');
636
+ }
637
+ if (!this.iframeStorage) {
638
+ throw new Error('Failed to initialize iframe storage');
639
+ }
640
+ return this.iframe;
641
+ }
642
+ /**
643
+ * Gets the initialized iframe storage instance. This method ensures the iframe storage is properly configured.
644
+ * The first call will initialize and await the iframe communication process.
645
+ * Subsequent calls will return the same storage instance immediately.
646
+ *
647
+ * @throws {Error} If iframe storage initialization fails
648
+ * @returns {Promise<SupportedStorage>} The initialized iframe storage instance
649
+ */ async getIframeStorage() {
650
+ await this.initializeIframeCommunication();
651
+ if (!this.iframeStorage) {
652
+ throw new Error('Failed to initialize iframe storage');
653
+ }
654
+ return this.iframeStorage;
655
+ }
656
+ /**
657
+ * Client initialization logic
658
+ */ async _initialize() {
659
+ try {
660
+ const initializePromises = [
661
+ this.restoreWallets(),
662
+ this.initializeIframeCommunication()
663
+ ];
664
+ await Promise.all(initializePromises);
665
+ return {
666
+ error: null
667
+ };
668
+ } catch (error) {
669
+ return {
670
+ error
671
+ };
672
+ }
673
+ }
674
+ async serverInitializeKeyGen({ chainName, clientKeygenIds, thresholdSignatureScheme, onError, onCeremonyComplete }) {
675
+ // Initialize keygen, create room, and create the wallet account on the server
676
+ const data = await this.apiClient.createWalletAccount({
677
+ chainName,
678
+ clientKeygenIds,
679
+ thresholdSignatureScheme,
680
+ onError,
681
+ onCeremonyComplete
682
+ });
683
+ return data;
684
+ }
685
+ async clientInitializeKeyGen({ chainName, thresholdSignatureScheme }) {
686
+ // Get the mpc signer
687
+ const mpcSigner = getMPCSigner({
688
+ chainName,
689
+ baseRelayUrl: this.baseMPCRelayApiUrl
690
+ });
691
+ const clientThreshold = getClientThreshold(thresholdSignatureScheme);
692
+ const keygenInitResults = await Promise.all(Array(clientThreshold).fill(null).map(()=>mpcSigner.initKeygen()));
693
+ return keygenInitResults;
694
+ }
695
+ async derivePublicKey({ chainName, keyShare, derivationPath }) {
696
+ const mpcSigner = getMPCSigner({
697
+ chainName,
698
+ baseRelayUrl: this.baseMPCRelayApiUrl
699
+ });
700
+ let publicKey;
701
+ if (mpcSigner instanceof Ecdsa) {
702
+ publicKey = await mpcSigner.derivePubkey(keyShare, derivationPath);
703
+ } else if (mpcSigner instanceof Ed25519) {
704
+ publicKey = await mpcSigner.derivePubkey(keyShare, derivationPath);
705
+ }
706
+ return publicKey;
707
+ }
708
+ async clientKeyGen({ chainName, roomId, serverKeygenIds, clientKeygenInitResults, thresholdSignatureScheme }) {
709
+ // Get the chain config and the mpc signer
710
+ const mpcSigner = getMPCSigner({
711
+ chainName,
712
+ baseRelayUrl: this.baseMPCRelayApiUrl
713
+ });
714
+ // Get the MPC config for the threshold signature scheme
715
+ const mpcConfig = MPC_CONFIG[thresholdSignatureScheme];
716
+ // For each client keygen init result, create an array of other parties' keygenIds
717
+ const clientKeygenResults = await Promise.all(clientKeygenInitResults.map((currentInit)=>{
718
+ // Get all other client keygenIds (excluding current one)
719
+ const otherClientKeygenIds = clientKeygenInitResults.filter((init)=>init.keygenId !== currentInit.keygenId).map((init)=>init.keygenId);
720
+ // Combine server keygenIds with other client keygenIds
721
+ const allOtherKeygenIds = [
722
+ ...serverKeygenIds,
723
+ ...otherClientKeygenIds
724
+ ];
725
+ return mpcSigner.keygen(roomId, mpcConfig.numberOfParties, mpcConfig.threshold, currentInit, allOtherKeygenIds);
726
+ }));
727
+ // only need one client keygen result to derive the public key
728
+ const [clientKeygenResult] = clientKeygenResults;
729
+ const chainConfig = getMPCChainConfig(chainName);
730
+ const derivationPath = new Uint32Array(chainConfig.derivationPath);
731
+ const rawPublicKey = await this.derivePublicKey({
732
+ chainName,
733
+ keyShare: clientKeygenResult,
734
+ derivationPath
735
+ });
736
+ return {
737
+ rawPublicKey,
738
+ clientKeygenResults
739
+ };
740
+ }
741
+ async keyGen({ chainName, thresholdSignatureScheme, onError, onCeremonyComplete }) {
742
+ try {
743
+ const clientKeygenInitResults = await this.clientInitializeKeyGen({
744
+ chainName,
745
+ thresholdSignatureScheme
746
+ });
747
+ const clientKeygenIds = clientKeygenInitResults.map((result)=>result.keygenId);
748
+ const { roomId, serverKeygenIds } = await this.serverInitializeKeyGen({
749
+ chainName,
750
+ clientKeygenIds,
751
+ thresholdSignatureScheme,
752
+ onCeremonyComplete
753
+ });
754
+ const { rawPublicKey, clientKeygenResults: clientKeyShares } = await this.clientKeyGen({
755
+ chainName,
756
+ roomId,
757
+ serverKeygenIds,
758
+ clientKeygenInitResults,
759
+ thresholdSignatureScheme
760
+ });
761
+ return {
762
+ rawPublicKey,
763
+ clientKeyShares
764
+ };
765
+ } catch (error) {
766
+ this.logger.error('Error creating wallet account', error);
767
+ throw new Error('Error creating wallet account');
768
+ }
769
+ }
770
+ async importRawPrivateKey({ chainName, privateKey, thresholdSignatureScheme, onError, onCeremonyComplete }) {
771
+ const mpcSigner = getMPCSigner({
772
+ chainName,
773
+ baseRelayUrl: this.baseMPCRelayApiUrl
774
+ });
775
+ const clientKeygenInitResults = await this.clientInitializeKeyGen({
776
+ chainName,
777
+ thresholdSignatureScheme
778
+ });
779
+ const clientKeygenIds = clientKeygenInitResults.map((result)=>result.keygenId);
780
+ const { roomId, serverKeygenIds } = await this.apiClient.importPrivateKey({
781
+ chainName,
782
+ clientKeygenIds,
783
+ thresholdSignatureScheme,
784
+ onError,
785
+ onCeremonyComplete
786
+ });
787
+ const { threshold } = getTSSConfig(thresholdSignatureScheme);
788
+ const clientKeygenResults = await Promise.all(clientKeygenInitResults.map(async (currentInit, index)=>{
789
+ const otherClientKeygenIds = clientKeygenInitResults.filter((init)=>init.keygenId !== currentInit.keygenId).map((init)=>init.keygenId);
790
+ if (index === 0) {
791
+ const otherKeyGenIds = [
792
+ ...serverKeygenIds,
793
+ ...otherClientKeygenIds
794
+ ];
795
+ const importerKeygenResult = await mpcSigner.importPrivateKeyImporter(roomId, threshold, privateKey, currentInit, otherKeyGenIds);
796
+ return importerKeygenResult;
797
+ } else {
798
+ const recipientKeygenResult = await mpcSigner.importPrivateKeyRecipient(roomId, threshold, currentInit, [
799
+ ...serverKeygenIds,
800
+ ...otherClientKeygenIds
801
+ ]);
802
+ return recipientKeygenResult;
803
+ }
804
+ }));
805
+ const [clientKeygenResult] = clientKeygenResults;
806
+ const rawPublicKey = await this.derivePublicKey({
807
+ chainName,
808
+ keyShare: clientKeygenResult,
809
+ derivationPath: undefined
810
+ });
811
+ return {
812
+ rawPublicKey,
813
+ clientKeyShares: clientKeygenResults
814
+ };
815
+ }
816
+ async serverSign({ walletId, message }) {
817
+ // Create the room and sign the message
818
+ if (typeof message !== 'string') {
819
+ message = '0x' + Buffer.from(message).toString('hex');
820
+ }
821
+ const data = await this.apiClient.signMessage({
822
+ walletId,
823
+ message
824
+ });
825
+ return data;
826
+ }
827
+ async clientSign({ chainName, message, roomId, keyShare, derivationPath }) {
828
+ try {
829
+ const mpcSigner = getMPCSigner({
830
+ chainName,
831
+ baseRelayUrl: this.baseMPCRelayApiUrl
832
+ });
833
+ let formattedMessage;
834
+ //note: Ecdsa can also be used by bitcoin, but only keccak256 is used by ethereum
835
+ if (mpcSigner instanceof Ecdsa) {
836
+ formattedMessage = MessageHash.keccak256(message);
837
+ } else if (mpcSigner instanceof Ed25519) {
838
+ if (typeof message === 'string') {
839
+ if (!isHexString(message)) {
840
+ formattedMessage = Buffer.from(message).toString('hex');
841
+ } else {
842
+ formattedMessage = Buffer.from(message, 'hex');
843
+ }
844
+ } else {
845
+ formattedMessage = message;
846
+ }
847
+ } else if (mpcSigner instanceof BIP340 && typeof message === 'string') {
848
+ formattedMessage = new TextEncoder().encode(message);
849
+ } else {
850
+ throw new Error('Unsupported signer type');
851
+ }
852
+ const signature = await mpcSigner.sign(roomId, keyShare, formattedMessage, derivationPath);
853
+ return signature;
854
+ } catch (error) {
855
+ this.logger.error('Error in clientSign:', error);
856
+ throw error;
857
+ }
858
+ }
859
+ //todo: need to modify with imported flag
860
+ async sign({ accountAddress, message, chainName, password = undefined }) {
861
+ await this.verifyPassword({
862
+ accountAddress,
863
+ password,
864
+ walletOperation: WalletOperation.SIGN_MESSAGE
865
+ });
866
+ const wallet = await this.getWallet({
867
+ accountAddress,
868
+ password,
869
+ walletOperation: WalletOperation.SIGN_MESSAGE
870
+ });
871
+ // Perform the server sign
872
+ const data = await this.serverSign({
873
+ walletId: wallet.walletId,
874
+ message
875
+ });
876
+ const derivationPath = wallet.derivationPath && wallet.derivationPath != '' ? new Uint32Array(Object.values(JSON.parse(wallet.derivationPath))) : undefined;
877
+ // Perform the client sign and return the signature
878
+ const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
879
+ accountAddress
880
+ });
881
+ const signature = await this.clientSign({
882
+ chainName,
883
+ message,
884
+ roomId: data.roomId,
885
+ keyShare: clientKeyShares[0],
886
+ derivationPath
887
+ });
888
+ return signature;
889
+ }
890
+ async refreshWalletAccountShares({ accountAddress, chainName, password = undefined }) {
891
+ await this.verifyPassword({
892
+ accountAddress,
893
+ password,
894
+ walletOperation: WalletOperation.REFRESH
895
+ });
896
+ const wallet = await this.getWallet({
897
+ accountAddress,
898
+ walletOperation: WalletOperation.NO_OPERATION,
899
+ password
900
+ });
901
+ const mpcSigner = getMPCSigner({
902
+ chainName,
903
+ baseRelayUrl: this.baseMPCRelayApiUrl
904
+ });
905
+ // Create the room and refresh the shares
906
+ const data = await this.apiClient.refreshWalletAccountShares({
907
+ walletId: wallet.walletId
908
+ });
909
+ const roomId = data.roomId;
910
+ const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
911
+ accountAddress
912
+ });
913
+ const refreshResults = await Promise.all(clientKeyShares.map((clientKeyShare)=>mpcSigner.refresh(roomId, clientKeyShare)));
914
+ this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
915
+ clientKeySharesBackupInfo: getClientKeyShareBackupInfo()
916
+ });
917
+ await this.setClientKeySharesToLocalStorage({
918
+ accountAddress,
919
+ clientKeyShares: refreshResults,
920
+ overwriteOrMerge: 'overwrite'
921
+ });
922
+ await this.storeEncryptedBackupByWallet({
923
+ accountAddress,
924
+ password: password != null ? password : this.environmentId
925
+ });
926
+ return refreshResults;
927
+ }
928
+ async getExportId({ chainName, clientKeyShare }) {
929
+ const mpcSigner = getMPCSigner({
930
+ chainName,
931
+ baseRelayUrl: this.baseMPCRelayApiUrl
932
+ });
933
+ const exportId = await mpcSigner.exportID(clientKeyShare);
934
+ return exportId;
935
+ }
936
+ /**
937
+ * Helper function to create client shares required to complete a reshare ceremony.
938
+ * @param {string} chainName - The chain to create shares for
939
+ * @param {WalletProperties} wallet - The wallet to reshare
940
+ * @param {ThresholdSignatureScheme} oldThresholdSignatureScheme - The current threshold signature scheme
941
+ * @param {ThresholdSignatureScheme} newThresholdSignatureScheme - The target threshold signature scheme
942
+ * @returns {Promise<{
943
+ * newClientInitKeygenResults: ClientInitKeygenResult[],
944
+ * newClientKeygenIds: string[],
945
+ * existingClientKeygenIds: string[],
946
+ * existingClientKeyShares: ClientKeyShare[]
947
+ * }>} Object containing new and existing client keygen results, IDs and shares
948
+ * @todo Support higher to lower reshare strategies
949
+ */ async reshareStrategy({ chainName, wallet, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme }) {
950
+ const mpcSigner = getMPCSigner({
951
+ chainName,
952
+ baseRelayUrl: this.baseMPCRelayApiUrl
953
+ });
954
+ // Determine share counts based on threshold signature schemes
955
+ const { newClientShareCount, existingClientShareCount } = getReshareConfig({
956
+ oldThresholdSignatureScheme,
957
+ newThresholdSignatureScheme
958
+ });
959
+ // Create new client shares
960
+ const newClientInitKeygenResults = await Promise.all(Array.from({
961
+ length: newClientShareCount
962
+ }, ()=>mpcSigner.initKeygen()));
963
+ const newClientKeygenIds = newClientInitKeygenResults.map((result)=>result.keygenId);
964
+ // Get existing client shares
965
+ const existingClientKeyShares = (await this.getClientKeySharesFromLocalStorage({
966
+ accountAddress
967
+ })).slice(0, existingClientShareCount);
968
+ const existingClientKeygenIds = await Promise.all(existingClientKeyShares.map(async (keyShare)=>await this.getExportId({
969
+ chainName,
970
+ clientKeyShare: keyShare
971
+ })));
972
+ return {
973
+ newClientInitKeygenResults,
974
+ newClientKeygenIds,
975
+ existingClientKeygenIds,
976
+ existingClientKeyShares
977
+ };
978
+ }
979
+ async reshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined }) {
980
+ await this.verifyPassword({
981
+ accountAddress,
982
+ password,
983
+ walletOperation: WalletOperation.RESHARE
984
+ });
985
+ const { existingClientShareCount } = getReshareConfig({
986
+ oldThresholdSignatureScheme,
987
+ newThresholdSignatureScheme
988
+ });
989
+ const wallet = await this.getWallet({
990
+ accountAddress,
991
+ walletOperation: WalletOperation.NO_OPERATION,
992
+ shareCount: existingClientShareCount,
993
+ password
994
+ });
995
+ const { newClientInitKeygenResults, newClientKeygenIds, existingClientKeygenIds, existingClientKeyShares } = await this.reshareStrategy({
996
+ chainName,
997
+ accountAddress,
998
+ wallet,
999
+ oldThresholdSignatureScheme,
1000
+ newThresholdSignatureScheme
1001
+ });
1002
+ const clientKeygenIds = [
1003
+ ...newClientKeygenIds,
1004
+ ...existingClientKeygenIds
1005
+ ];
1006
+ // Server to create the room and complete the server reshare logics
1007
+ const data = await this.apiClient.reshare({
1008
+ walletId: wallet.walletId,
1009
+ clientKeygenIds: clientKeygenIds,
1010
+ oldThresholdSignatureScheme,
1011
+ newThresholdSignatureScheme
1012
+ });
1013
+ const { roomId, serverKeygenIds, newServerKeygenIds = [] } = data;
1014
+ // Get the MPC config for the threshold signature scheme
1015
+ const oldMpcConfig = MPC_CONFIG[oldThresholdSignatureScheme];
1016
+ const newMpcConfig = MPC_CONFIG[newThresholdSignatureScheme];
1017
+ const allPartyKeygenIds = [
1018
+ ...clientKeygenIds,
1019
+ ...serverKeygenIds,
1020
+ ...newServerKeygenIds
1021
+ ];
1022
+ const mpcSigner = getMPCSigner({
1023
+ chainName,
1024
+ baseRelayUrl: this.baseMPCRelayApiUrl
1025
+ });
1026
+ const reshareResults = await Promise.all([
1027
+ ...newClientInitKeygenResults.map((keygenResult)=>mpcSigner.reshareNewParty(roomId, oldMpcConfig.threshold, newMpcConfig.threshold, keygenResult, allPartyKeygenIds)),
1028
+ ...existingClientKeyShares.map((keyShare)=>mpcSigner.reshareRemainingParty(roomId, newMpcConfig.threshold, keyShare, allPartyKeygenIds))
1029
+ ]);
1030
+ await this.setClientKeySharesToLocalStorage({
1031
+ accountAddress,
1032
+ clientKeyShares: reshareResults,
1033
+ overwriteOrMerge: 'overwrite'
1034
+ });
1035
+ await this.storeEncryptedBackupByWallet({
1036
+ accountAddress,
1037
+ password
1038
+ });
1039
+ return reshareResults;
1040
+ }
1041
+ async exportKey({ accountAddress, chainName, password = undefined }) {
1042
+ const wallet = await this.getWallet({
1043
+ accountAddress,
1044
+ password,
1045
+ walletOperation: WalletOperation.EXPORT_PRIVATE_KEY
1046
+ });
1047
+ const mpcSigner = getMPCSigner({
1048
+ chainName,
1049
+ baseRelayUrl: this.baseMPCRelayApiUrl
1050
+ });
1051
+ const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
1052
+ accountAddress
1053
+ });
1054
+ const exportId = await this.getExportId({
1055
+ chainName,
1056
+ clientKeyShare: clientKeyShares[0]
1057
+ });
1058
+ const data = await this.apiClient.exportKey({
1059
+ walletId: wallet.walletId,
1060
+ exportId
1061
+ });
1062
+ const keyExportRaw = await mpcSigner.exportFullPrivateKey(data.roomId, clientKeyShares[0], exportId);
1063
+ if (!keyExportRaw) {
1064
+ throw new Error('Error exporting private key');
1065
+ }
1066
+ const derivationPath = wallet.derivationPath && wallet.derivationPath != '' ? new Uint32Array(Object.values(JSON.parse(wallet.derivationPath))) : undefined;
1067
+ let derivedPrivateKey;
1068
+ if (mpcSigner instanceof Ecdsa) {
1069
+ derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, derivationPath);
1070
+ } else if (mpcSigner instanceof Ed25519) {
1071
+ derivedPrivateKey = keyExportRaw;
1072
+ } else if (mpcSigner instanceof BIP340) {
1073
+ derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, derivationPath);
1074
+ }
1075
+ return {
1076
+ derivedPrivateKey
1077
+ };
1078
+ }
1079
+ async offlineExportKey({ chainName, keyShares, derivationPath }) {
1080
+ try {
1081
+ if (!keyShares || keyShares.length < 2) {
1082
+ throw new Error(`Must provide at least min threshold of key shares`);
1083
+ }
1084
+ const mpcSigner = getMPCSigner({
1085
+ chainName,
1086
+ baseRelayUrl: this.baseMPCRelayApiUrl
1087
+ });
1088
+ const walletKeyShares = keyShares.map((keyShare)=>{
1089
+ 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);
1090
+ });
1091
+ const keyExportRaw = await mpcSigner.offlineExportFullPrivateKey(walletKeyShares);
1092
+ if (!keyExportRaw) {
1093
+ throw new Error('Error exporting private key: Export returned null');
1094
+ }
1095
+ const chainConfig = getMPCChainConfig(chainName);
1096
+ const walletDerivationPath = !derivationPath ? undefined : new Uint32Array(chainConfig.derivationPath);
1097
+ let derivedPrivateKey;
1098
+ if (mpcSigner instanceof Ecdsa) {
1099
+ derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, walletDerivationPath);
1100
+ } else if (mpcSigner instanceof Ed25519) {
1101
+ derivedPrivateKey = keyExportRaw;
1102
+ } else if (mpcSigner instanceof BIP340) {
1103
+ derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, walletDerivationPath);
1104
+ }
1105
+ const rawPublicKey = await this.derivePublicKey({
1106
+ chainName,
1107
+ keyShare: walletKeyShares[0],
1108
+ derivationPath: walletDerivationPath
1109
+ });
1110
+ return {
1111
+ derivedPrivateKey,
1112
+ rawPublicKey
1113
+ };
1114
+ } catch (error) {
1115
+ this.logger.error('Error in offlineExportKey:', error);
1116
+ throw error;
1117
+ }
1118
+ }
1119
+ async encryptKeyShare({ keyShare, password }) {
1120
+ const serializedKeyShare = JSON.stringify(keyShare);
1121
+ const encryptedKeyShare = await encryptData({
1122
+ data: serializedKeyShare,
1123
+ password: password != null ? password : this.environmentId
1124
+ });
1125
+ // stringify the encrypted key share, convert to base64, and store it
1126
+ const serializedEncryptedKeyShare = Buffer.from(JSON.stringify(encryptedKeyShare)).toString('base64');
1127
+ return serializedEncryptedKeyShare;
1128
+ }
1129
+ /**
1130
+ * helper function to store encrypted backup by wallet from iframe local storage
1131
+ */ async getClientKeySharesFromLocalStorage({ accountAddress }) {
1132
+ var _this_iframeStorage;
1133
+ await this.initializeIframeCommunication();
1134
+ const walletObject = await ((_this_iframeStorage = this.iframeStorage) == null ? void 0 : _this_iframeStorage.getItem(accountAddress));
1135
+ if (!walletObject) {
1136
+ this.logger.debug(`No item found in iframe local storage for accountAddress: ${accountAddress}`);
1137
+ return [];
1138
+ }
1139
+ try {
1140
+ return (walletObject == null ? void 0 : walletObject.clientKeyShares) || [];
1141
+ } catch (error) {
1142
+ this.logger.error(`Error parsing clientKeyShares: ${error} for accountAddress: ${accountAddress}`);
1143
+ return [];
1144
+ }
1145
+ }
1146
+ /**
1147
+ * helper function to store encrypted backup by wallet from iframe local storage
1148
+ */ async setClientKeySharesToLocalStorage({ accountAddress, clientKeyShares, overwriteOrMerge = 'merge' }) {
1149
+ var _this_iframeStorage;
1150
+ await this.initializeIframeCommunication();
1151
+ await ((_this_iframeStorage = this.iframeStorage) == null ? void 0 : _this_iframeStorage.setItem(accountAddress, {
1152
+ clientKeyShares: overwriteOrMerge === 'overwrite' ? clientKeyShares : mergeUniqueKeyShares(await this.getClientKeySharesFromLocalStorage({
1153
+ accountAddress
1154
+ }), clientKeyShares)
1155
+ }));
1156
+ }
1157
+ /**
1158
+ * Encrypts and stores wallet key shares as backups.
1159
+ *
1160
+ * This method encrypts all client key shares for a specific wallet and stores them
1161
+ * in Dynamic's backend. If Google Drive backup is already configured for this wallet,
1162
+ * it will also update the backup in Google Drive.
1163
+ *
1164
+ * The method performs the following steps:
1165
+ * 1. Encrypts all client key shares with the provided password (or environment ID if no password)
1166
+ * 2. Stores the encrypted key shares in Dynamic's backend
1167
+ * 3. If Google Drive backup exists, updates the backup there as well
1168
+ * 4. Updates the local wallet map with the new backup information
1169
+ * 5. Persists the updated wallet map to storage
1170
+ *
1171
+ * @param {Object} params - The parameters for the backup operation
1172
+ * @param {string} params.accountAddress - The account address of the wallet to backup
1173
+ * @param {string} [params.password] - Optional password for encrypting the key shares
1174
+ */ async storeEncryptedBackupByWallet({ accountAddress, clientKeyShares = undefined, password = undefined }) {
1175
+ const keySharesToBackup = clientKeyShares != null ? clientKeyShares : await this.getClientKeySharesFromLocalStorage({
1176
+ accountAddress
1177
+ });
1178
+ const encryptedKeyShares = await Promise.all(keySharesToBackup.map((keyShare)=>this.encryptKeyShare({
1179
+ keyShare,
1180
+ password
1181
+ })));
1182
+ if (!this.walletMap[accountAddress].walletId) {
1183
+ throw new Error(`WalletId not found for accountAddress: ${accountAddress}`);
1184
+ }
1185
+ const data = await this.apiClient.storeEncryptedBackupByWallet({
1186
+ walletId: this.walletMap[accountAddress].walletId,
1187
+ encryptedKeyShares,
1188
+ passwordEncrypted: Boolean(password) && password !== this.environmentId
1189
+ });
1190
+ const hasGoogleDriveBackup = this.walletMap[accountAddress].clientKeySharesBackupInfo.backups[BackupLocation.GOOGLE_DRIVE].length > 0;
1191
+ if (hasGoogleDriveBackup) {
1192
+ var _user_verifiedCredentials_find;
1193
+ const user = await this.apiClient.getUser();
1194
+ const oauthAccountId = user == null ? void 0 : (_user_verifiedCredentials_find = user.verifiedCredentials.find((credential)=>credential.oauthProvider === 'google')) == null ? void 0 : _user_verifiedCredentials_find.id;
1195
+ const googleDriveKeyShareIds = await this.backupKeySharesToGoogleDrive({
1196
+ accountAddress,
1197
+ password,
1198
+ oauthAccountId
1199
+ });
1200
+ data.keyShares.push({
1201
+ backupLocation: BackupLocation.GOOGLE_DRIVE,
1202
+ id: googleDriveKeyShareIds
1203
+ });
1204
+ }
1205
+ const updatedBackupInfo = getClientKeyShareBackupInfo({
1206
+ walletProperties: {
1207
+ derivationPath: this.walletMap[accountAddress].derivationPath,
1208
+ keyShares: data.keyShares,
1209
+ thresholdSignatureScheme: this.walletMap[accountAddress].thresholdSignatureScheme
1210
+ }
1211
+ });
1212
+ this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
1213
+ clientKeySharesBackupInfo: updatedBackupInfo
1214
+ });
1215
+ await this.storage.setItem(this.storageKey, JSON.stringify(this.walletMap));
1216
+ return data;
1217
+ }
1218
+ async storeEncryptedBackupByWalletWithRetry({ accountAddress, clientKeyShares, password }) {
1219
+ await retryPromise(()=>this.storeEncryptedBackupByWallet({
1220
+ accountAddress,
1221
+ clientKeyShares,
1222
+ password
1223
+ }), {
1224
+ operationName: 'store encrypted backup',
1225
+ logContext: {
1226
+ walletAddress: accountAddress,
1227
+ keyShares: clientKeyShares == null ? void 0 : clientKeyShares.map((keyShare)=>this.encryptKeyShare({
1228
+ keyShare,
1229
+ password
1230
+ }))
1231
+ }
1232
+ });
1233
+ }
1234
+ async updatePassword({ accountAddress, existingPassword, newPassword }) {
1235
+ await this.getWallet({
1236
+ accountAddress,
1237
+ password: existingPassword,
1238
+ walletOperation: WalletOperation.REACH_ALL_PARTIES
1239
+ });
1240
+ await this.storeEncryptedBackupByWallet({
1241
+ accountAddress,
1242
+ password: newPassword
1243
+ });
1244
+ }
1245
+ async decryptKeyShare({ keyShare, password }) {
1246
+ const decodedKeyShare = JSON.parse(Buffer.from(keyShare, 'base64').toString());
1247
+ const decryptedKeyShare = await decryptData({
1248
+ data: decodedKeyShare,
1249
+ password: password != null ? password : this.environmentId
1250
+ });
1251
+ const deserializedKeyShare = JSON.parse(decryptedKeyShare);
1252
+ return deserializedKeyShare;
1253
+ }
1254
+ /**
1255
+ * Helper function to determine keyshare recovery strategy for dynamic shares.
1256
+ * For REFRESH operations, retrieves enough shares to meet the client threshold.
1257
+ * For all other operations, retrieves just 1 share.
1258
+ *
1259
+ * @param clientKeyShareBackupInfo - Information about backed up key shares
1260
+ * @param thresholdSignatureScheme - The signature scheme being used (2-of-2, 2-of-3, etc)
1261
+ * @param walletOperation - The operation being performed (REFRESH, SIGN_MESSAGE, etc)
1262
+ * @param shareCount - The number of shares to recover if specified for reshare operations
1263
+ * @returns @shares: Object mapping backup locations to arrays of share IDs to recover
1264
+ * @returns @requiredShareCount: The number of shares required to recover
1265
+ */ recoverStrategy({ clientKeyShareBackupInfo, thresholdSignatureScheme, walletOperation, shareCount = undefined }) {
1266
+ const { backups } = clientKeyShareBackupInfo;
1267
+ const { clientThreshold } = MPC_CONFIG[thresholdSignatureScheme];
1268
+ let requiredShareCount = walletOperation === WalletOperation.REFRESH || walletOperation === WalletOperation.REACH_ALL_PARTIES || walletOperation === WalletOperation.RESHARE ? clientThreshold : 1;
1269
+ // Override requiredShareCount if shareCount is provided
1270
+ if (shareCount !== undefined) {
1271
+ requiredShareCount = shareCount;
1272
+ }
1273
+ const dynamicShares = backups[BackupLocation.DYNAMIC].slice(0, requiredShareCount);
1274
+ return {
1275
+ shares: {
1276
+ [BackupLocation.DYNAMIC]: dynamicShares
1277
+ },
1278
+ requiredShareCount
1279
+ };
1280
+ }
1281
+ async recoverEncryptedBackupByWallet({ accountAddress, password, walletOperation, shareCount = undefined, storeRecoveredShares = true }) {
1282
+ const wallet = this.walletMap[accountAddress];
1283
+ this.logger.debug(`recoverEncryptedBackupByWallet wallet: ${walletOperation}`, wallet);
1284
+ const { shares } = this.recoverStrategy({
1285
+ clientKeyShareBackupInfo: wallet.clientKeySharesBackupInfo,
1286
+ thresholdSignatureScheme: wallet.thresholdSignatureScheme,
1287
+ walletOperation,
1288
+ shareCount
1289
+ });
1290
+ const { dynamic: dynamicKeyShareIds } = shares;
1291
+ const data = await this.apiClient.recoverEncryptedBackupByWallet({
1292
+ walletId: wallet.walletId,
1293
+ keyShareIds: dynamicKeyShareIds
1294
+ });
1295
+ const dynamicKeyShares = data.keyShares.filter((keyShare)=>keyShare.encryptedAccountCredential !== null && keyShare.backupLocation === BackupLocation.DYNAMIC);
1296
+ const decryptedKeyShares = await Promise.all(dynamicKeyShares.map((keyShare)=>this.decryptKeyShare({
1297
+ keyShare: keyShare.encryptedAccountCredential,
1298
+ password: password != null ? password : this.environmentId
1299
+ })));
1300
+ if (storeRecoveredShares) {
1301
+ await this.setClientKeySharesToLocalStorage({
1302
+ accountAddress,
1303
+ clientKeyShares: decryptedKeyShares,
1304
+ overwriteOrMerge: 'merge'
1305
+ });
1306
+ await this.storage.setItem(this.storageKey, JSON.stringify(this.walletMap));
1307
+ }
1308
+ return decryptedKeyShares;
1309
+ }
1310
+ async restoreWallets() {
1311
+ const wallets = await this.storage.getItem(this.storageKey);
1312
+ if (!wallets) {
1313
+ return;
1314
+ }
1315
+ this.walletMap = JSON.parse(wallets);
1316
+ }
1317
+ async backupKeySharesToGoogleDrive({ accountAddress, fileName, oauthAccountId, password }) {
1318
+ await this.getWallet({
1319
+ accountAddress,
1320
+ walletOperation: WalletOperation.REACH_ALL_PARTIES,
1321
+ password
1322
+ });
1323
+ const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
1324
+ accountAddress
1325
+ });
1326
+ if (clientKeyShares.length === 0) {
1327
+ throw new Error('No key shares found');
1328
+ }
1329
+ const encryptedKeyShares = await Promise.all(clientKeyShares.map((keyShare)=>this.encryptKeyShare({
1330
+ keyShare,
1331
+ password
1332
+ })));
1333
+ const accessToken = await this.apiClient.getAccessToken({
1334
+ oauthAccountId
1335
+ });
1336
+ const thresholdSignatureScheme = this.walletMap[accountAddress].thresholdSignatureScheme;
1337
+ const suggestedFileName = getClientKeyShareExportFileName({
1338
+ thresholdSignatureScheme,
1339
+ accountAddress
1340
+ });
1341
+ const backupData = {
1342
+ keyShares: encryptedKeyShares,
1343
+ metadata: {
1344
+ version: '1.0',
1345
+ createdAt: new Date().toISOString(),
1346
+ accountAddress,
1347
+ thresholdSignatureScheme,
1348
+ hasPassword: true,
1349
+ encryption: {
1350
+ algorithm: AES_GCM_ALGORITHM,
1351
+ keyDerivation: PBKDF2_ALGORITHM,
1352
+ iterations: PBKDF2_ITERATIONS,
1353
+ hashAlgorithm: PBKDF2_HASH_ALGORITHM,
1354
+ algorithmLength: AES_GCM_LENGTH
1355
+ },
1356
+ shareCount: encryptedKeyShares.length
1357
+ }
1358
+ };
1359
+ // TODO: handle errors
1360
+ await Promise.all([
1361
+ uploadFileToGoogleDriveAppStorage({
1362
+ accessToken,
1363
+ fileName: fileName != null ? fileName : suggestedFileName,
1364
+ jsonData: backupData
1365
+ }),
1366
+ uploadFileToGoogleDrivePersonal({
1367
+ accessToken,
1368
+ fileName: fileName != null ? fileName : suggestedFileName,
1369
+ jsonData: backupData
1370
+ })
1371
+ ]);
1372
+ const data = await this.apiClient.markKeySharesAsBackedUpGoogleDrive({
1373
+ walletId: this.walletMap[accountAddress].walletId
1374
+ });
1375
+ const ids = data.keyShares.map((keyShare)=>keyShare.id);
1376
+ this.walletMap[accountAddress].clientKeySharesBackupInfo.backups[BackupLocation.GOOGLE_DRIVE] = ids;
1377
+ await this.storage.setItem(this.storageKey, JSON.stringify(this.walletMap));
1378
+ return ids;
1379
+ }
1380
+ async restoreBackupFromGoogleDrive({ accountAddress, oauthAccountId, name, displayContainer, password }) {
1381
+ await this.getWallet({
1382
+ accountAddress
1383
+ });
1384
+ const accessToken = await this.apiClient.getAccessToken({
1385
+ oauthAccountId
1386
+ });
1387
+ const thresholdSignatureScheme = this.walletMap[accountAddress].thresholdSignatureScheme;
1388
+ const suggestedFileName = getClientKeyShareExportFileName({
1389
+ thresholdSignatureScheme,
1390
+ accountAddress
1391
+ });
1392
+ const backupData = await downloadFileFromGoogleDrive({
1393
+ accessToken,
1394
+ name: name != null ? name : suggestedFileName
1395
+ });
1396
+ if (!backupData) {
1397
+ throw new Error('No backup file found');
1398
+ }
1399
+ // Validate the backup data structure
1400
+ if (!backupData.keyShares || !backupData.metadata) {
1401
+ throw new Error('Invalid backup format: missing keyShares or metadata');
1402
+ }
1403
+ const { keyShares } = backupData;
1404
+ const decryptedKeyShares = await Promise.all(keyShares.map((keyShare)=>this.decryptKeyShare({
1405
+ keyShare,
1406
+ password
1407
+ })));
1408
+ await this.setClientKeySharesToLocalStorage({
1409
+ accountAddress,
1410
+ clientKeyShares: decryptedKeyShares,
1411
+ overwriteOrMerge: 'merge'
1412
+ });
1413
+ // Display the client shares in the container via iframe
1414
+ const { iframeDisplay } = await this.initializeIframeDisplayForContainer({
1415
+ container: displayContainer
1416
+ });
1417
+ iframeDisplay.displayClientShares(accountAddress);
1418
+ return decryptedKeyShares;
1419
+ }
1420
+ async exportClientKeyshares({ accountAddress, password }) {
1421
+ await this.verifyPassword({
1422
+ accountAddress,
1423
+ password,
1424
+ walletOperation: WalletOperation.REACH_ALL_PARTIES
1425
+ });
1426
+ const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
1427
+ accountAddress
1428
+ });
1429
+ if (!accountAddress) {
1430
+ throw new Error('Must provide an account address');
1431
+ }
1432
+ const derivationPath = this.walletMap[accountAddress].derivationPath;
1433
+ const text = JSON.stringify({
1434
+ keyShares: clientKeyShares,
1435
+ derivationPath
1436
+ });
1437
+ const blob = new Blob([
1438
+ text
1439
+ ], {
1440
+ type: 'text/plain'
1441
+ });
1442
+ const url = URL.createObjectURL(blob);
1443
+ const a = document.createElement('a');
1444
+ a.href = url;
1445
+ a.download = `${CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX}-${accountAddress}.txt`;
1446
+ a.click();
1447
+ }
1448
+ async getClientKeyShares({ accountAddress, password }) {
1449
+ await this.getWallet({
1450
+ accountAddress,
1451
+ password,
1452
+ walletOperation: WalletOperation.REACH_THRESHOLD
1453
+ });
1454
+ return this.getClientKeySharesFromLocalStorage({
1455
+ accountAddress
1456
+ });
1457
+ }
1458
+ /**
1459
+ * Helper function to check if the required wallet fields are present and valid
1460
+ * @param accountAddress - The account address of the wallet to check
1461
+ * @param walletOperation - The wallet operation that determines required fields
1462
+ * @returns boolean indicating if wallet needs to be re-fetched and restored from server
1463
+ */ async checkWalletFields({ accountAddress, walletOperation = WalletOperation.REACH_THRESHOLD, shareCount }) {
1464
+ let keyshareCheck = false;
1465
+ let walletCheck = false;
1466
+ let thresholdSignatureSchemeCheck = false;
1467
+ let derivationPathCheck = false;
1468
+ // check if wallet exists
1469
+ const existingWallet = this.walletMap[accountAddress];
1470
+ if (existingWallet) {
1471
+ walletCheck = true;
1472
+ }
1473
+ // check if threshold signature scheme exists
1474
+ if (existingWallet == null ? void 0 : existingWallet.thresholdSignatureScheme) {
1475
+ thresholdSignatureSchemeCheck = true;
1476
+ }
1477
+ // check if derivation path exists
1478
+ if ((existingWallet == null ? void 0 : existingWallet.derivationPath) || (existingWallet == null ? void 0 : existingWallet.derivationPath) === '') {
1479
+ derivationPathCheck = true;
1480
+ }
1481
+ // check if wallet already exists with sufficient keyshares
1482
+ if (existingWallet) {
1483
+ const { shares } = this.recoverStrategy({
1484
+ clientKeyShareBackupInfo: existingWallet.clientKeySharesBackupInfo || {
1485
+ backups: getClientKeyShareBackupInfo()
1486
+ },
1487
+ thresholdSignatureScheme: existingWallet.thresholdSignatureScheme,
1488
+ walletOperation,
1489
+ shareCount
1490
+ });
1491
+ const { dynamic: requiredDynamicKeyShareIds = [] } = shares;
1492
+ const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
1493
+ accountAddress
1494
+ });
1495
+ if (requiredDynamicKeyShareIds.length <= ((clientKeyShares == null ? void 0 : clientKeyShares.length) || 0)) {
1496
+ keyshareCheck = true;
1497
+ }
1498
+ }
1499
+ return walletCheck && thresholdSignatureSchemeCheck && keyshareCheck && derivationPathCheck;
1500
+ }
1501
+ /**
1502
+ * verifyPassword attempts to recover and decrypt a single client key share using the provided password.
1503
+ * If successful, the key share is encrypted with the new password. This method solely performs the recovery
1504
+ * and decryption without storing the restored key shares. If unsuccessful, it throws an error.
1505
+ */ async verifyPassword({ accountAddress, password = undefined, walletOperation = WalletOperation.NO_OPERATION }) {
1506
+ await this.getWallet({
1507
+ accountAddress,
1508
+ password,
1509
+ walletOperation
1510
+ });
1511
+ if (await this.requiresPasswordForOperation({
1512
+ accountAddress,
1513
+ walletOperation
1514
+ }) && !password) {
1515
+ throw new Error('Password is required for operation but not provided');
1516
+ }
1517
+ // silent return if no password is provided and operation does not require a password
1518
+ if (!password) {
1519
+ return;
1520
+ }
1521
+ const { backups } = await this.getWalletClientKeyShareBackupInfo({
1522
+ accountAddress
1523
+ });
1524
+ const { dynamic: dynamicKeyShareIds = [] } = backups;
1525
+ if (!dynamicKeyShareIds || dynamicKeyShareIds.length === 0) {
1526
+ throw new Error('No dynamic key shares found');
1527
+ }
1528
+ try {
1529
+ await this.recoverEncryptedBackupByWallet({
1530
+ accountAddress,
1531
+ password,
1532
+ walletOperation,
1533
+ shareCount: 1,
1534
+ storeRecoveredShares: false
1535
+ });
1536
+ } catch (error) {
1537
+ this.logger.error('Error in verifying password', error);
1538
+ throw new Error('Incorrect password');
1539
+ }
1540
+ }
1541
+ async isPasswordEncrypted({ accountAddress }) {
1542
+ const clientKeySharesBackupInfo = await this.getWalletClientKeyShareBackupInfo({
1543
+ accountAddress
1544
+ });
1545
+ return clientKeySharesBackupInfo == null ? void 0 : clientKeySharesBackupInfo.passwordEncrypted;
1546
+ }
1547
+ /**
1548
+ * check if the operation requires a password
1549
+ */ async requiresPasswordForOperation({ accountAddress, walletOperation = WalletOperation.REACH_THRESHOLD }) {
1550
+ const isEncrypted = await this.isPasswordEncrypted({
1551
+ accountAddress
1552
+ });
1553
+ if (!isEncrypted) {
1554
+ return false;
1555
+ }
1556
+ return this.requiresRestoreBackupSharesForOperation({
1557
+ accountAddress,
1558
+ walletOperation
1559
+ });
1560
+ }
1561
+ /**
1562
+ * check if the operation requires restoring backup shares
1563
+ */ async requiresRestoreBackupSharesForOperation({ accountAddress, walletOperation = WalletOperation.REACH_THRESHOLD }) {
1564
+ const clientKeySharesBackupInfo = await this.getWalletClientKeyShareBackupInfo({
1565
+ accountAddress
1566
+ });
1567
+ const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
1568
+ accountAddress
1569
+ });
1570
+ if (walletOperation === WalletOperation.REACH_ALL_PARTIES || walletOperation === WalletOperation.REFRESH || walletOperation === WalletOperation.RESHARE) {
1571
+ return true;
1572
+ }
1573
+ const { requiredShareCount } = this.recoverStrategy({
1574
+ clientKeyShareBackupInfo: clientKeySharesBackupInfo,
1575
+ thresholdSignatureScheme: this.walletMap[accountAddress].thresholdSignatureScheme,
1576
+ walletOperation
1577
+ });
1578
+ if (clientKeyShares.length >= requiredShareCount) {
1579
+ return false;
1580
+ }
1581
+ return true;
1582
+ }
1583
+ async getWalletClientKeyShareBackupInfo({ accountAddress }) {
1584
+ var _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups_BackupLocation_DYNAMIC, _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups, _this_walletMap_accountAddress_clientKeySharesBackupInfo, _this_walletMap_accountAddress, _user_verifiedCredentials;
1585
+ // Return existing backup info if it exists
1586
+ 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) {
1587
+ return this.walletMap[accountAddress].clientKeySharesBackupInfo;
1588
+ }
1589
+ // Get backup info from server
1590
+ const user = await this.apiClient.getUser();
1591
+ const wallet = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.find((vc)=>vc.address.toLowerCase() === accountAddress.toLowerCase());
1592
+ return getClientKeyShareBackupInfo({
1593
+ walletProperties: wallet == null ? void 0 : wallet.walletProperties
1594
+ });
1595
+ }
1596
+ async getWallet({ accountAddress, walletOperation = WalletOperation.NO_OPERATION, shareCount = undefined, password = undefined }) {
1597
+ var _user_verifiedCredentials;
1598
+ const existingWalletCheck = await this.checkWalletFields({
1599
+ accountAddress,
1600
+ walletOperation,
1601
+ shareCount
1602
+ });
1603
+ if (existingWalletCheck) {
1604
+ this.logger.debug(`Wallet ${accountAddress} already exists`);
1605
+ return this.walletMap[accountAddress];
1606
+ }
1607
+ // Fetch and restore wallet from server
1608
+ const user = await this.apiClient.getUser();
1609
+ const wallet = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.find((vc)=>vc.address.toLowerCase() === accountAddress.toLowerCase());
1610
+ this.logger.debug('Restoring wallet', wallet);
1611
+ const walletProperties = wallet.walletProperties;
1612
+ this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
1613
+ walletId: wallet.id,
1614
+ chainName: wallet.chainName,
1615
+ accountAddress,
1616
+ thresholdSignatureScheme: walletProperties.thresholdSignatureScheme,
1617
+ derivationPath: walletProperties.derivationPath,
1618
+ clientKeySharesBackupInfo: getClientKeyShareBackupInfo({
1619
+ walletProperties
1620
+ })
1621
+ });
1622
+ if (walletOperation !== WalletOperation.NO_OPERATION && await this.requiresRestoreBackupSharesForOperation({
1623
+ accountAddress,
1624
+ walletOperation
1625
+ })) {
1626
+ const decryptedKeyShares = await this.recoverEncryptedBackupByWallet({
1627
+ accountAddress,
1628
+ password: password != null ? password : this.environmentId,
1629
+ walletOperation: walletOperation,
1630
+ shareCount
1631
+ });
1632
+ const existingKeyShares = await this.getClientKeySharesFromLocalStorage({
1633
+ accountAddress
1634
+ });
1635
+ await this.setClientKeySharesToLocalStorage({
1636
+ accountAddress,
1637
+ clientKeyShares: mergeUniqueKeyShares(existingKeyShares, decryptedKeyShares)
1638
+ });
1639
+ this.logger.debug('Recovered backup', decryptedKeyShares);
1640
+ }
1641
+ const walletCount = Object.keys(this.walletMap).length;
1642
+ if (walletCount === 0) {
1643
+ throw new Error('No wallets found');
1644
+ }
1645
+ // Return the only wallet if there's just one
1646
+ if (walletCount === 1) {
1647
+ return Object.values(this.walletMap)[0];
1648
+ }
1649
+ return this.walletMap[accountAddress];
1650
+ }
1651
+ async getWallets() {
1652
+ var _user_verifiedCredentials;
1653
+ const user = await this.apiClient.getUser();
1654
+ const waasWallets = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.filter((vc)=>vc.walletName === 'dynamicwaas');
1655
+ const wallets = waasWallets.map((vc)=>{
1656
+ var _this_walletMap_vc_address, _vc_walletProperties;
1657
+ var _this_walletMap_vc_address_derivationPath;
1658
+ return {
1659
+ walletId: vc.id,
1660
+ chainName: vc.chain,
1661
+ accountAddress: vc.address,
1662
+ clientKeySharesBackupInfo: getClientKeyShareBackupInfo({
1663
+ walletProperties: vc.walletProperties || {}
1664
+ }),
1665
+ 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,
1666
+ thresholdSignatureScheme: (_vc_walletProperties = vc.walletProperties) == null ? void 0 : _vc_walletProperties.thresholdSignatureScheme
1667
+ };
1668
+ });
1669
+ this.walletMap = wallets.reduce((acc, wallet)=>{
1670
+ var _acc_accountAddress;
1671
+ const accountAddress = wallet.accountAddress;
1672
+ acc[wallet.accountAddress] = {
1673
+ walletId: wallet.walletId,
1674
+ chainName: wallet.chainName,
1675
+ accountAddress: wallet.accountAddress,
1676
+ clientKeySharesBackupInfo: wallet.clientKeySharesBackupInfo,
1677
+ derivationPath: ((_acc_accountAddress = acc[accountAddress]) == null ? void 0 : _acc_accountAddress.derivationPath) || undefined,
1678
+ thresholdSignatureScheme: wallet.thresholdSignatureScheme
1679
+ };
1680
+ return acc;
1681
+ }, {});
1682
+ return wallets;
1683
+ }
1684
+ constructor({ environmentId, authToken, baseApiUrl, baseMPCRelayApiUrl, storageKey, debug }){
1685
+ this.initializePromise = null;
1686
+ this.logger = logger;
1687
+ this.walletMap = {} // todo: store in session storage
1688
+ ;
1689
+ this.iframeStorage = null;
1690
+ this.iframeDisplay = null;
1691
+ this.memoryStorage = null;
1692
+ this.messageTransport = null;
1693
+ this.iframe = null;
1694
+ this.iframeLoadPromise = null;
1695
+ this.environmentId = environmentId;
1696
+ this.storageKey = `${STORAGE_KEY}-${storageKey != null ? storageKey : environmentId}`;
1697
+ this.baseMPCRelayApiUrl = baseMPCRelayApiUrl;
1698
+ this.apiClient = new DynamicApiClient({
1699
+ environmentId,
1700
+ authToken,
1701
+ baseApiUrl
1702
+ });
1703
+ this.debug = Boolean(debug);
1704
+ this.logger.setLogLevel(this.debug ? 'DEBUG' : DEFAULT_LOG_LEVEL);
1705
+ // setup storage
1706
+ if (supportsLocalStorage()) {
1707
+ this.storage = localStorageAdapter;
1708
+ } else {
1709
+ this.memoryStorage = {};
1710
+ this.storage = memoryLocalStorageAdapter(this.memoryStorage);
1711
+ }
1712
+ let environment;
1713
+ switch(baseApiUrl){
1714
+ case undefined:
1715
+ environment = ENVIRONMENT_ENUM.production;
1716
+ break;
1717
+ case DYNAMIC_AUTH_PROD_BASE_API_URL:
1718
+ environment = ENVIRONMENT_ENUM.production;
1719
+ break;
1720
+ case DYNAMIC_AUTH_PREPROD_BASE_API_URL:
1721
+ environment = ENVIRONMENT_ENUM.preprod;
1722
+ break;
1723
+ case DYNAMIC_AUTH_DEV_BASE_API_URL:
1724
+ environment = ENVIRONMENT_ENUM.development;
1725
+ break;
1726
+ default:
1727
+ environment = ENVIRONMENT_ENUM.development;
1728
+ break;
1729
+ }
1730
+ this.iframeDomain = IFRAME_DOMAIN_MAP[environment];
1731
+ // Generate unique instanceId when client is created
1732
+ this.instanceId = crypto.randomUUID();
1733
+ // initialize the client
1734
+ this.initialize();
1735
+ }
1736
+ }
1737
+
1738
+ export { DynamicWalletClient, base64ToBytes, bytesToBase64, ensureBase64Padding, getClientKeyShareBackupInfo, getClientKeyShareExportFileName, getMPCSignatureScheme, getMPCSigner, isBrowser, isHexString, mergeUniqueKeyShares, retryPromise, stringToBytes, timeoutPromise };