@dynamic-labs-wallet/browser 0.0.0-preview.57 → 0.0.1-paolo-1

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 (80) hide show
  1. package/index.cjs.js +2081 -992
  2. package/index.esm.js +2061 -992
  3. package/internal/core/bip340.cjs +1 -0
  4. package/internal/core/common.cjs +1 -0
  5. package/internal/core/ecdsa.cjs +1 -0
  6. package/internal/core/ecdsa.d.ts +1 -1
  7. package/internal/core/ed25519.cjs +1 -0
  8. package/internal/core/ed25519_exportable.cjs +1 -0
  9. package/internal/core/ed25519_exportable.d.ts +21 -0
  10. package/internal/core/index.cjs +1 -0
  11. package/internal/core/index.d.ts +4 -2
  12. package/internal/core/native.d.ts +65 -0
  13. package/internal/core/sr25519.cjs +1 -0
  14. package/internal/core/sr25519.d.ts +22 -0
  15. package/internal/core/types.cjs +1 -0
  16. package/internal/core/types.d.ts +20 -0
  17. package/internal/web/generated/libmpc_executor.d.ts +60 -99
  18. package/internal/web/generated/libmpc_executor.js +2 -2
  19. package/internal/web/generated/libmpc_executor_bg.wasm +0 -0
  20. package/internal/web/index.d.ts +8 -2
  21. package/internal/web/index.js +1 -1
  22. package/package.json +22 -5
  23. package/src/backup/encryption/argon2.d.ts +10 -0
  24. package/src/backup/encryption/argon2.d.ts.map +1 -0
  25. package/src/backup/encryption/config.d.ts +39 -0
  26. package/src/backup/encryption/config.d.ts.map +1 -0
  27. package/src/backup/encryption/constants.d.ts +35 -0
  28. package/src/backup/encryption/constants.d.ts.map +1 -0
  29. package/src/backup/encryption/core.d.ts +27 -0
  30. package/src/backup/encryption/core.d.ts.map +1 -0
  31. package/src/backup/encryption/pbkdf2.d.ts +10 -0
  32. package/src/backup/encryption/pbkdf2.d.ts.map +1 -0
  33. package/src/backup/encryption/types.d.ts +46 -0
  34. package/src/backup/encryption/types.d.ts.map +1 -0
  35. package/src/backup/encryption/utils.d.ts +9 -0
  36. package/src/backup/encryption/utils.d.ts.map +1 -0
  37. package/src/backup/providers/googleDrive.d.ts +4 -4
  38. package/src/backup/providers/googleDrive.d.ts.map +1 -1
  39. package/src/backup/utils.d.ts +14 -0
  40. package/src/backup/utils.d.ts.map +1 -0
  41. package/src/client.d.ts +213 -127
  42. package/src/client.d.ts.map +1 -1
  43. package/src/constants.d.ts +4 -1
  44. package/src/constants.d.ts.map +1 -1
  45. package/src/errorConstants.d.ts +10 -0
  46. package/src/errorConstants.d.ts.map +1 -0
  47. package/src/index.d.ts +7 -4
  48. package/src/index.d.ts.map +1 -1
  49. package/src/mpc/index.d.ts +3 -3
  50. package/src/mpc/index.d.ts.map +1 -1
  51. package/src/mpc/mpc.d.ts +3 -3
  52. package/src/mpc/mpc.d.ts.map +1 -1
  53. package/src/mpc/types.d.ts +4 -4
  54. package/src/mpc/types.d.ts.map +1 -1
  55. package/src/services/axiosErrorResponse.d.ts +4 -0
  56. package/src/services/axiosErrorResponse.d.ts.map +1 -0
  57. package/src/services/encryption.d.ts +19 -0
  58. package/src/services/encryption.d.ts.map +1 -0
  59. package/src/services/logger.d.ts +13 -0
  60. package/src/services/logger.d.ts.map +1 -1
  61. package/src/types.d.ts +30 -4
  62. package/src/types.d.ts.map +1 -1
  63. package/src/utils.d.ts +32 -8
  64. package/src/utils.d.ts.map +1 -1
  65. package/internal/core/bip340.js +0 -1
  66. package/internal/core/common.js +0 -1
  67. package/internal/core/ecdsa.js +0 -1
  68. package/internal/core/ed25519.js +0 -1
  69. package/internal/core/index.js +0 -1
  70. package/internal/core/types.js +0 -1
  71. package/internal/web/generated/libmpc_executor_bg.wasm.d.ts +0 -64
  72. package/src/backup/encryption.d.ts +0 -22
  73. package/src/backup/encryption.d.ts.map +0 -1
  74. package/src/services/iframeDisplay.d.ts +0 -9
  75. package/src/services/iframeDisplay.d.ts.map +0 -1
  76. package/src/services/iframeLocalStorage.d.ts +0 -13
  77. package/src/services/iframeLocalStorage.d.ts.map +0 -1
  78. package/src/services/messageTransportBridge.d.ts +0 -3
  79. package/src/services/messageTransportBridge.d.ts.map +0 -1
  80. /package/internal/core/{native.js → native.cjs} +0 -0
package/index.cjs.js CHANGED
@@ -1,9 +1,14 @@
1
1
  'use strict';
2
2
 
3
3
  var core = require('@dynamic-labs-wallet/core');
4
- var web = require('./internal/web');
4
+ var web = require('#internal/web');
5
+ var semver = require('semver');
6
+ var uuid = require('uuid');
5
7
  var logger$1 = require('@dynamic-labs/logger');
6
- var messageTransport = require('@dynamic-labs/message-transport');
8
+ var loadArgon2idWasm = require('argon2id');
9
+ var axios = require('axios');
10
+ var createHttpError = require('http-errors');
11
+ var sdkApiCore = require('@dynamic-labs/sdk-api-core');
7
12
 
8
13
  function _extends() {
9
14
  _extends = Object.assign || function assign(target) {
@@ -21,7 +26,7 @@ const getMPCSignatureScheme = ({ signingAlgorithm, baseRelayUrl = core.MPC_RELAY
21
26
  case core.SigningAlgorithm.ECDSA:
22
27
  return new web.Ecdsa(baseRelayUrl);
23
28
  case core.SigningAlgorithm.ED25519:
24
- return new web.Ed25519(baseRelayUrl);
29
+ return new web.ExportableEd25519(baseRelayUrl);
25
30
  case core.SigningAlgorithm.BIP340:
26
31
  return new web.BIP340(baseRelayUrl);
27
32
  default:
@@ -37,13 +42,118 @@ const getMPCSigner = ({ chainName, baseRelayUrl })=>{
37
42
  return signatureScheme;
38
43
  };
39
44
 
40
- const DEFAULT_LOG_LEVEL = 'INFO';
41
- const STORAGE_KEY = 'dynamic-waas-wallet-client';
42
- const CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX = 'dynamicWalletKeyShareBackup';
45
+ /**
46
+ * Encryption version identifiers
47
+ */ var EncryptionVersion = /*#__PURE__*/ function(EncryptionVersion) {
48
+ EncryptionVersion["V1_LEGACY"] = "v1";
49
+ EncryptionVersion["V2_PBKDF2"] = "v2";
50
+ EncryptionVersion["V3_ARGON2"] = "v3";
51
+ return EncryptionVersion;
52
+ }({});
53
+ /**
54
+ * Current default version for new encryptions
55
+ */ const ENCRYPTION_VERSION_CURRENT = "v3";
56
+ /**
57
+ * Algorithm constants
58
+ */ const PBKDF2_ALGORITHM = 'PBKDF2';
59
+ const HASH_ALGORITHM = 'SHA-256'; // Generic hash algorithm constant
60
+ const ARGON2_ALGORITHM = 'Argon2id';
61
+ const AES_GCM_ALGORITHM = 'AES-GCM';
62
+ const AES_GCM_LENGTH = 256;
63
+ /**
64
+ * Argon2 configuration constants, values were chosen based on RFC (https://www.rfc-editor.org/rfc/rfc9106.html#name-parameter-choice)
65
+ * taking into account that this runs in the client, possibly in smartphones with limited resources
66
+ */ const ARGON2_MEMORY_SIZE = 65536; // 64 MB in KiB
67
+ const ARGON2_ITERATIONS = 3;
68
+ const ARGON2_PARALLELISM = 2;
69
+ const ARGON2_HASH_LENGTH = 32;
70
+ /**
71
+ * PBKDF2 configuration constants
72
+ */ const PBKDF2_ITERATIONS_V1 = 100000;
73
+ const PBKDF2_ITERATIONS_V2 = 1000000;
74
+
75
+ /**
76
+ * Derives a key using Argon2id algorithm
77
+ * @param params - Key derivation parameters
78
+ * @param encryptionConfig - Encryption configuration
79
+ * @returns Promise<CryptoKey>
80
+ */ const deriveArgon2Key = async ({ password, salt }, encryptionConfig)=>{
81
+ const argon2id = await loadArgon2idWasm();
82
+ const argon2Config = encryptionConfig;
83
+ const passwordBytes = new TextEncoder().encode(password);
84
+ const hash = argon2id({
85
+ password: passwordBytes,
86
+ salt: salt,
87
+ parallelism: argon2Config.parallelism || ARGON2_PARALLELISM,
88
+ passes: argon2Config.iterations,
89
+ memorySize: argon2Config.memorySize || ARGON2_MEMORY_SIZE,
90
+ tagLength: argon2Config.hashLength || ARGON2_HASH_LENGTH
91
+ });
92
+ return crypto.subtle.importKey('raw', new Uint8Array(hash), {
93
+ name: encryptionConfig.algorithm,
94
+ length: encryptionConfig.algorithmLength
95
+ }, false, [
96
+ 'encrypt',
97
+ 'decrypt'
98
+ ]);
99
+ };
43
100
 
44
- const logger = new logger$1.Logger('DynamicWaasWalletClient');
101
+ /**
102
+ * Encryption configuration for each version
103
+ */ const ENCRYPTION_VERSIONS = {
104
+ [EncryptionVersion.V1_LEGACY]: {
105
+ version: EncryptionVersion.V1_LEGACY,
106
+ algorithm: AES_GCM_ALGORITHM,
107
+ keyDerivation: PBKDF2_ALGORITHM,
108
+ iterations: PBKDF2_ITERATIONS_V1,
109
+ hashAlgorithm: HASH_ALGORITHM,
110
+ algorithmLength: AES_GCM_LENGTH
111
+ },
112
+ [EncryptionVersion.V2_PBKDF2]: {
113
+ version: EncryptionVersion.V2_PBKDF2,
114
+ algorithm: AES_GCM_ALGORITHM,
115
+ keyDerivation: PBKDF2_ALGORITHM,
116
+ iterations: PBKDF2_ITERATIONS_V2,
117
+ hashAlgorithm: HASH_ALGORITHM,
118
+ algorithmLength: AES_GCM_LENGTH
119
+ },
120
+ [EncryptionVersion.V3_ARGON2]: {
121
+ version: EncryptionVersion.V3_ARGON2,
122
+ algorithm: AES_GCM_ALGORITHM,
123
+ keyDerivation: ARGON2_ALGORITHM,
124
+ iterations: ARGON2_ITERATIONS,
125
+ hashAlgorithm: ARGON2_ALGORITHM,
126
+ algorithmLength: AES_GCM_LENGTH,
127
+ memorySize: ARGON2_MEMORY_SIZE,
128
+ parallelism: ARGON2_PARALLELISM,
129
+ hashLength: ARGON2_HASH_LENGTH
130
+ }
131
+ };
132
+ /**
133
+ * Helper function to get encryption configuration by version string
134
+ * @param version - The version string (e.g., 'v1', 'v2', 'v3')
135
+ * @returns The encryption configuration for the specified version
136
+ */ const getEncryptionConfig = (version)=>{
137
+ // If no version specified, use legacy for backward compatibility
138
+ if (!version) {
139
+ return ENCRYPTION_VERSIONS[EncryptionVersion.V1_LEGACY];
140
+ }
141
+ const config = ENCRYPTION_VERSIONS[version];
142
+ if (!config) {
143
+ throw new Error(`Unsupported encryption version: ${version}`);
144
+ }
145
+ return config;
146
+ };
147
+ /**
148
+ * Check if a configuration uses Argon2
149
+ */ const isArgon2Config = (config)=>{
150
+ return config.keyDerivation === ARGON2_ALGORITHM;
151
+ };
45
152
 
46
- const bytesToBase64 = (arr)=>{
153
+ /**
154
+ * Utility functions for encryption operations
155
+ * These functions are separated to avoid circular dependencies
156
+ */ const bytesToBase64 = (arr)=>{
47
157
  return btoa(Array.from(arr, (b)=>String.fromCharCode(b)).join(''));
48
158
  };
49
159
  const stringToBytes = (str)=>{
@@ -56,93 +166,13 @@ const base64ToBytes = (base64)=>{
56
166
  const ensureBase64Padding = (str)=>{
57
167
  return str.padEnd(Math.ceil(str.length / 4) * 4, '=');
58
168
  };
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
- [core.BackupLocation.DYNAMIC]: [],
73
- [core.BackupLocation.GOOGLE_DRIVE]: [],
74
- [core.BackupLocation.ICLOUD]: [],
75
- [core.BackupLocation.USER]: [],
76
- [core.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
169
 
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 })=>{
170
+ /**
171
+ * Derives a key using PBKDF2 algorithm
172
+ * @param params - Key derivation parameters
173
+ * @param encryptionConfig - Encryption configuration
174
+ * @returns Promise<CryptoKey>
175
+ */ const derivePBKDF2Key = async ({ password, salt }, encryptionConfig)=>{
146
176
  const passwordBytes = stringToBytes(password);
147
177
  const initialKey = await crypto.subtle.importKey('raw', passwordBytes, {
148
178
  name: 'PBKDF2'
@@ -150,19 +180,34 @@ const getKey = async ({ password, salt })=>{
150
180
  'deriveKey'
151
181
  ]);
152
182
  return crypto.subtle.deriveKey({
153
- name: PBKDF2_ALGORITHM,
154
- salt,
155
- iterations: PBKDF2_ITERATIONS,
156
- hash: PBKDF2_HASH_ALGORITHM
183
+ name: encryptionConfig.keyDerivation,
184
+ salt: salt,
185
+ iterations: encryptionConfig.iterations,
186
+ hash: encryptionConfig.hashAlgorithm
157
187
  }, initialKey, {
158
- name: AES_GCM_ALGORITHM,
159
- length: AES_GCM_LENGTH
188
+ name: encryptionConfig.algorithm,
189
+ length: encryptionConfig.algorithmLength
160
190
  }, false, [
161
191
  'encrypt',
162
192
  'decrypt'
163
193
  ]);
164
194
  };
165
- const encryptData = async ({ data, password })=>{
195
+
196
+ /**
197
+ * Get the appropriate key derivation function based on the encryption config
198
+ */ const getKey = async (params, encryptionConfig)=>{
199
+ // Use Argon2 for v3, PBKDF2 for v1 and v2
200
+ if (encryptionConfig.keyDerivation === ARGON2_ALGORITHM) {
201
+ return deriveArgon2Key(params, encryptionConfig);
202
+ } else {
203
+ return derivePBKDF2Key(params, encryptionConfig);
204
+ }
205
+ };
206
+ /**
207
+ * Encrypts data using the specified encryption version.
208
+ * Always uses the latest encryption configuration for new encryptions by default.
209
+ */ const encryptData = async ({ data, password, version = ENCRYPTION_VERSION_CURRENT })=>{
210
+ const encryptionConfig = getEncryptionConfig(version);
166
211
  try {
167
212
  // Generate a random salt and IV
168
213
  const salt = crypto.getRandomValues(new Uint8Array(16));
@@ -170,7 +215,7 @@ const encryptData = async ({ data, password })=>{
170
215
  const key = await getKey({
171
216
  password,
172
217
  salt
173
- });
218
+ }, encryptionConfig);
174
219
  // Convert the input string to bytes
175
220
  const dataBytes = new TextEncoder().encode(data);
176
221
  // Encrypt the data
@@ -182,34 +227,86 @@ const encryptData = async ({ data, password })=>{
182
227
  return {
183
228
  salt: bytesToBase64(salt),
184
229
  iv: bytesToBase64(iv),
185
- cipher: bytesToBase64(new Uint8Array(encryptedData))
230
+ cipher: bytesToBase64(new Uint8Array(encryptedData)),
231
+ version
186
232
  };
187
233
  } catch (error) {
188
- throw new Error('Error encrypting data');
234
+ const errorMessage = error instanceof Error ? error.message : String(error);
235
+ throw new Error(`Error encrypting data: ${errorMessage} (version: ${version})`);
189
236
  }
190
237
  };
191
- const decryptData = async ({ data, password })=>{
238
+ /**
239
+ * Decrypts data with version-based configuration.
240
+ * Uses the version field from the data to determine encryption parameters.
241
+ * Falls back to legacy version for backward compatibility if no version is specified.
242
+ * For v3 (Argon2), retries with parallelism=1 if an OperationError occurs.
243
+ */ const decryptData = async ({ data, password })=>{
244
+ const { salt, iv, cipher, version } = data;
245
+ // Ensure proper base64 padding for all values
246
+ const paddedSalt = ensureBase64Padding(salt);
247
+ const paddedIv = ensureBase64Padding(iv);
248
+ const paddedCipher = ensureBase64Padding(cipher);
249
+ const saltBytes = base64ToBytes(paddedSalt);
250
+ const ivBytes = base64ToBytes(paddedIv);
251
+ const cipherBytes = base64ToBytes(paddedCipher);
252
+ // Determine which encryption configuration to use
253
+ const encryptionConfig = getEncryptionConfig(version);
192
254
  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
255
  const key = await getKey({
202
256
  password,
203
257
  salt: saltBytes
204
- });
258
+ }, encryptionConfig);
205
259
  const decryptedData = await crypto.subtle.decrypt({
206
260
  name: AES_GCM_ALGORITHM,
207
261
  iv: ivBytes
208
262
  }, key, cipherBytes);
209
263
  return new TextDecoder().decode(decryptedData);
210
264
  } catch (error) {
211
- throw new Error('Decryption failed');
265
+ // For a short period of time we lowered the parallelism for v3 (Argon2) to 1 to try to fix issues
266
+ // for users with limited resources, however this introduced a new issue that the decryption would fail
267
+ // for existing users with v3 (Argon2) encryption, this is a fallback for a few users.
268
+ if (error instanceof Error && error.name === 'OperationError' && version === 'v3' && isArgon2Config(encryptionConfig)) {
269
+ try {
270
+ // Create a modified config with parallelism=1
271
+ const modifiedConfig = _extends({}, encryptionConfig, {
272
+ parallelism: 1
273
+ });
274
+ const key = await getKey({
275
+ password,
276
+ salt: saltBytes
277
+ }, modifiedConfig);
278
+ const decryptedData = await crypto.subtle.decrypt({
279
+ name: AES_GCM_ALGORITHM,
280
+ iv: ivBytes
281
+ }, key, cipherBytes);
282
+ return new TextDecoder().decode(decryptedData);
283
+ } catch (retryError) {
284
+ // If retry also fails, throw the original error with additional context
285
+ throw new Error(`Decryption failed after retry with parallelism=1: ${retryError}`);
286
+ }
287
+ }
288
+ throw new Error('Decryption failed: ' + error);
289
+ }
290
+ };
291
+ /**
292
+ * Gets encryption metadata for a specific version.
293
+ * Used when we need to include metadata in legacy systems or APIs that require it.
294
+ */ const getEncryptionMetadataForVersion = (version)=>{
295
+ const encryptionConfig = getEncryptionConfig(version);
296
+ const metadata = {
297
+ algorithm: encryptionConfig.algorithm,
298
+ keyDerivation: encryptionConfig.keyDerivation,
299
+ iterations: encryptionConfig.iterations,
300
+ hashAlgorithm: encryptionConfig.hashAlgorithm,
301
+ algorithmLength: encryptionConfig.algorithmLength
302
+ };
303
+ // Add Argon2-specific metadata if applicable
304
+ if (isArgon2Config(encryptionConfig)) {
305
+ metadata.memorySize = encryptionConfig.memorySize;
306
+ metadata.parallelism = encryptionConfig.parallelism;
307
+ metadata.hashLength = encryptionConfig.hashLength;
212
308
  }
309
+ return metadata;
213
310
  };
214
311
 
215
312
  const GOOGLE_DRIVE_UPLOAD_API = 'https://www.googleapis.com';
@@ -263,9 +360,9 @@ const uploadFileToGoogleDrive = async ({ accessToken, fileName, jsonData, parent
263
360
  const result = await response.json();
264
361
  return result; // Return file metadata, including file ID
265
362
  };
266
- const listFilesFromGoogleDrive = async ({ accessToken, name })=>{
363
+ const listFilesFromGoogleDrive = async ({ accessToken, fileName })=>{
267
364
  // 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`, {
365
+ const resp = await fetch(`${GOOGLE_DRIVE_UPLOAD_API}/drive/v3/files?q=${encodeURIComponent(`name='${fileName}'`)}&spaces=appDataFolder&orderBy=createdTime desc`, {
269
366
  headers: {
270
367
  Authorization: `Bearer ${accessToken}`
271
368
  }
@@ -278,10 +375,10 @@ const listFilesFromGoogleDrive = async ({ accessToken, name })=>{
278
375
  const files = data.files;
279
376
  return files;
280
377
  };
281
- const downloadFileFromGoogleDrive = async ({ accessToken, name })=>{
378
+ const downloadFileFromGoogleDrive = async ({ accessToken, fileName })=>{
282
379
  const files = await listFilesFromGoogleDrive({
283
380
  accessToken,
284
- name
381
+ fileName
285
382
  });
286
383
  if (!files || files.length === 0) {
287
384
  return null;
@@ -308,382 +405,580 @@ const downloadFileFromGoogleDrive = async ({ accessToken, name })=>{
308
405
  }
309
406
  };
310
407
 
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);
408
+ const handleAxiosError = (error, message, context)=>{
409
+ var _error_response, _error_response1, _error_response2;
410
+ logger.error('[DynamicWaasWalletClient] Axios error: ', {
411
+ message,
412
+ error: (_error_response = error.response) == null ? void 0 : _error_response.data,
413
+ status: (_error_response1 = error.response) == null ? void 0 : _error_response1.status,
414
+ context
415
+ });
416
+ switch((_error_response2 = error.response) == null ? void 0 : _error_response2.status){
417
+ case 400:
418
+ throw createHttpError(400, 'Invalid request');
419
+ case 401:
420
+ throw createHttpError(401, 'Authorization header or cookie is required');
421
+ case 403:
422
+ throw createHttpError(403, 'Forbidden');
423
+ case 422:
424
+ throw createHttpError(422, 'Unprocessable content');
425
+ case 500:
426
+ throw createHttpError(500, 'Internal server error');
427
+ default:
428
+ throw createHttpError(500, 'Internal server error');
365
429
  }
366
430
  };
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
431
 
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
432
+ const logger = new logger$1.Logger('DynamicWaasWalletClient', logger$1.LogLevel.DEBUG);
433
+ const setLoggerContext = ({ environmentId, authMode = core.AuthMode.HEADER, sessionId = undefined, userId = undefined })=>{
434
+ try {
435
+ logger$1.Logger.setEnvironmentId(environmentId);
436
+ logger$1.Logger.globalMetaData.set('sid', sessionId);
437
+ logger$1.Logger.globalMetaData.set('user_id', userId);
438
+ logger$1.Logger.globalMetaData.set('auth_mode', authMode);
439
+ } catch (error) {
440
+ logError({
441
+ message: '[DynamicWaasWalletClient] Error setting logger context',
442
+ error: error,
443
+ context: {}
403
444
  });
404
445
  }
405
- constructor(messageTransport$1){
406
- this.requestChannel = messageTransport.createRequestChannel(messageTransport$1);
446
+ };
447
+ const logError = ({ message, error, context })=>{
448
+ if (error instanceof axios.AxiosError) {
449
+ handleAxiosError(error, message, context);
407
450
  }
408
- }
409
-
410
- const setupMessageTransportBridge = (messageTransport$1, 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$1.Logger('debug');
415
- messageTransport$1.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
- }
451
+ logger.error('[DynamicWaasWalletClient] Error in browser client', {
452
+ error: error instanceof Error ? error.message : String(error),
453
+ context
422
454
  });
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 = messageTransport.parseMessageTransportData(data);
436
- logger.debug(`[host bridge] iframe --> bridge --> host`, message);
437
- messageTransport$1.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
455
  };
453
456
 
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$1){
462
- this.requestChannel = messageTransport.createRequestChannel(messageTransport$1);
463
- }
464
- }
457
+ const DEFAULT_LOG_LEVEL = logger$1.LogLevel.DEBUG; //todo: change back to info when done debugging
458
+ const STORAGE_KEY = 'dynamic-waas-wallet-client';
459
+ const CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX = 'dynamicWalletKeyShareBackup';
460
+ const SIGNED_SESSION_ID_MIN_VERSION = '4.25.4';
461
+ // Namespace-specific version requirements
462
+ const SIGNED_SESSION_ID_MIN_VERSION_BY_NAMESPACE = {
463
+ WalletKit: '4.25.4',
464
+ ClientSDK: '0.1.0-alpha.0'
465
+ };
465
466
 
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;
467
+ const isBrowser = ()=>typeof window !== 'undefined';
468
+ const getClientKeyShareExportFileName = ({ thresholdSignatureScheme, accountAddress, isGoogleDrive = false })=>{
469
+ const suffix = isGoogleDrive ? '-google-drive' : '';
470
+ return `${CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX}-${thresholdSignatureScheme}-${accountAddress}${suffix}.json`;
471
+ };
472
+ const getClientKeyShareBackupInfo = (params)=>{
473
+ var _params_walletProperties, _params_walletProperties_keyShares_;
474
+ const backups = {
475
+ [core.BackupLocation.DYNAMIC]: [],
476
+ [core.BackupLocation.GOOGLE_DRIVE]: [],
477
+ [core.BackupLocation.ICLOUD]: [],
478
+ [core.BackupLocation.USER]: [],
479
+ [core.BackupLocation.EXTERNAL]: [],
480
+ [core.BackupLocation.DELEGATED]: []
481
+ };
482
+ if (!(params == null ? void 0 : (_params_walletProperties = params.walletProperties) == null ? void 0 : _params_walletProperties.keyShares)) {
483
+ return {
484
+ backups,
485
+ passwordEncrypted: false
486
+ };
476
487
  }
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 = messageTransport.applyDefaultMessageOrigin({
490
- defaultOrigin: 'host',
491
- messageTransport: messageTransport.createMessageTransport()
488
+ params.walletProperties.keyShares.forEach((keyShare)=>{
489
+ if (backups[keyShare.backupLocation]) {
490
+ backups[keyShare.backupLocation].push({
491
+ location: keyShare.backupLocation,
492
+ keyShareId: keyShare.id,
493
+ externalKeyShareId: keyShare == null ? void 0 : keyShare.externalKeyShareId
492
494
  });
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;
495
+ }
496
+ });
497
+ const passwordEncrypted = Boolean((_params_walletProperties_keyShares_ = params.walletProperties.keyShares[0]) == null ? void 0 : _params_walletProperties_keyShares_.passwordEncrypted);
498
+ return {
499
+ backups,
500
+ passwordEncrypted
501
+ };
502
+ };
503
+ /**
504
+ * Helper function to merge keyshares and remove duplicates based on pubkey and secretShare
505
+ * @param existingKeyShares - Array of existing keyshares
506
+ * @param newKeyShares - Array of new keyshares to merge
507
+ * @returns Array of merged unique keyshares
508
+ */ const mergeUniqueKeyShares = (existingKeyShares, newKeyShares)=>{
509
+ const uniqueKeyShares = newKeyShares.filter((newShare)=>!existingKeyShares.some((existingShare)=>{
510
+ if (!(newShare == null ? void 0 : newShare.pubkey) || !(existingShare == null ? void 0 : existingShare.pubkey)) return false;
511
+ return newShare.pubkey.toString() === existingShare.pubkey.toString() && newShare.secretShare === existingShare.secretShare;
512
+ }));
513
+ return [
514
+ ...existingKeyShares,
515
+ ...uniqueKeyShares
516
+ ];
517
+ };
518
+ const timeoutPromise = ({ timeInMs, activity = 'Ceremony' })=>{
519
+ return new Promise((_, reject)=>setTimeout(()=>reject(new Error(`${activity} did not complete in ${timeInMs}ms`)), timeInMs));
520
+ };
521
+ /**
522
+ * Generic helper function to retry a promise-based operations
523
+ *
524
+ * @param operation - The async operation to retry
525
+ * @param config - Configuration options for retry behavior
526
+ * @returns Promise with the operation result
527
+ * @throws Last error encountered after all retries are exhausted
528
+ */ async function retryPromise(operation, { maxAttempts = 5, retryInterval = 500, operationName = 'operation', logContext = {} } = {}) {
529
+ let attempts = 0;
530
+ while(attempts < maxAttempts){
531
+ try {
532
+ return await operation();
533
+ } catch (error) {
534
+ var _error_response;
535
+ attempts++;
536
+ logger.warn(`Failed to execute ${operationName} on attempt ${attempts}`, _extends({}, logContext, {
537
+ error: error instanceof Error ? error.message : 'Unknown error',
538
+ axiosError: error instanceof axios.AxiosError ? (_error_response = error.response) == null ? void 0 : _error_response.data : undefined
539
+ }));
540
+ if (attempts === maxAttempts) {
541
+ var _error_response1;
542
+ logger.error(`Failed to execute ${operationName} after ${maxAttempts} attempts`, _extends({}, logContext, {
543
+ error: error instanceof Error ? error.message : 'Unknown error',
544
+ axiosError: error instanceof axios.AxiosError ? (_error_response1 = error.response) == null ? void 0 : _error_response1.data : undefined
545
+ }));
546
+ throw error;
547
+ }
548
+ // Calculate exponential backoff delay
549
+ const exponentialDelay = retryInterval * 2 ** (attempts - 1);
550
+ await new Promise((resolve)=>setTimeout(resolve, exponentialDelay));
527
551
  }
528
552
  }
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
- });
553
+ // TypeScript needs this even though it's unreachable
554
+ throw new Error('Unreachable code');
555
+ }
556
+ const formatEvmMessage = (message)=>{
557
+ if (typeof message === 'string' && message.startsWith('0x')) {
558
+ const serializedTxBytes = Uint8Array.from(Buffer.from(message.slice(2), 'hex'));
559
+ return web.MessageHash.keccak256(serializedTxBytes);
566
560
  }
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
- };
561
+ return web.MessageHash.keccak256(message);
562
+ };
563
+ const isHexString = (str)=>{
564
+ // Remove 0x prefix if present
565
+ const hex = str.startsWith('0x') ? str.slice(2) : str;
566
+ // Check if string contains only hex characters
567
+ return /^[0-9A-Fa-f]+$/.test(hex);
568
+ };
569
+ const formatSolanaMessage = (message)=>{
570
+ if (typeof message === 'string') {
571
+ if (!isHexString(message)) {
572
+ return Buffer.from(message).toString('hex');
573
+ } else {
574
+ return new Uint8Array(Buffer.from(message, 'hex'));
575
+ }
576
+ } else {
577
+ return message;
578
+ }
579
+ };
580
+ const formatMessage = (chainName, message)=>{
581
+ switch(chainName){
582
+ case 'EVM':
583
+ return formatEvmMessage(message);
584
+ case 'SVM':
585
+ return formatSolanaMessage(message);
586
+ case 'SUI':
587
+ return message;
588
+ default:
589
+ throw new Error('Unsupported chain name');
590
+ }
591
+ };
592
+ const getGoogleOAuthAccountId = (verifiedCredentials)=>{
593
+ const googleVerifiedCredential = verifiedCredentials == null ? void 0 : verifiedCredentials.find((credential)=>credential.oauthProvider === sdkApiCore.ProviderEnum.Google);
594
+ return googleVerifiedCredential == null ? void 0 : googleVerifiedCredential.id;
595
+ };
596
+ const createBackupData = ({ encryptedKeyShares, accountAddress, thresholdSignatureScheme, hasPassword = true })=>{
597
+ return {
598
+ keyShares: encryptedKeyShares,
599
+ metadata: {
600
+ createdAt: new Date().toISOString(),
601
+ accountAddress,
602
+ thresholdSignatureScheme,
603
+ hasPassword,
604
+ encryption: getEncryptionMetadataForVersion(ENCRYPTION_VERSION_CURRENT),
605
+ encryptionVersion: ENCRYPTION_VERSION_CURRENT,
606
+ shareCount: encryptedKeyShares.length
607
+ }
608
+ };
609
+ };
610
+ const downloadStringAsFile = ({ filename, content, mimeType = 'application/json' })=>{
611
+ const blob = new Blob([
612
+ content
613
+ ], {
614
+ type: mimeType
615
+ });
616
+ const url = URL.createObjectURL(blob);
617
+ const a = document.createElement('a');
618
+ a.href = url;
619
+ a.download = filename;
620
+ a.click();
621
+ URL.revokeObjectURL(url);
622
+ };
623
+
624
+ /**
625
+ * Uploads a backup to Google Drive App
626
+ * @param accessToken - The access token for the Google Drive API
627
+ * @param fileName - The name of the file to upload
628
+ * @param backupData - The data to upload
629
+ * @param accountAddress - The account address associated with the backup
630
+ */ const uploadBackupToGoogleDrive = async ({ accessToken, fileName, backupData, accountAddress })=>{
631
+ const uploadPromises = [
632
+ retryPromise(()=>uploadFileToGoogleDriveAppStorage({
633
+ accessToken,
634
+ fileName,
635
+ jsonData: backupData
636
+ })),
637
+ retryPromise(()=>uploadFileToGoogleDrivePersonal({
638
+ accessToken,
639
+ fileName,
640
+ jsonData: backupData
641
+ }))
642
+ ];
643
+ const results = await Promise.allSettled(uploadPromises);
644
+ const errors = [];
645
+ // Check App Storage result
646
+ if (results[0].status === 'rejected') {
647
+ const error = results[0].reason;
648
+ logger.error('[DynamicWaasWalletClient] Failed to upload keyshares to Google Drive App Storage', {
649
+ accountAddress,
650
+ error
601
651
  });
652
+ errors.push(`Failed to backup keyshares to Google Drive App Storage: ${error instanceof Error ? error.message : String(error)}`);
602
653
  }
603
- /**
604
- * initialize the message transport after iframe is successfully loaded
605
- */ initializeMessageTransport() {
606
- const transport = messageTransport.applyDefaultMessageOrigin({
607
- defaultOrigin: 'host',
608
- messageTransport: messageTransport.createMessageTransport()
654
+ // Check Personal Drive result
655
+ if (results[1].status === 'rejected') {
656
+ const error = results[1].reason;
657
+ logger.error('[DynamicWaasWalletClient] Failed to upload keyshares to Google Drive Personal', {
658
+ accountAddress,
659
+ error
609
660
  });
610
- this.messageTransport = transport;
611
- if (!this.iframe) {
612
- throw new Error('Iframe not available');
661
+ errors.push(`Failed to backup keyshares to Google Drive Personal: ${error instanceof Error ? error.message : String(error)}`);
662
+ }
663
+ // Throw if any uploads failed
664
+ if (errors.length > 0) {
665
+ throw new Error(`[DynamicWaasWalletClient] ${errors.join('; ')}`);
666
+ }
667
+ };
668
+
669
+ const ALG_LABEL_RSA = 'HYBRID-RSA-AES-256';
670
+ /**
671
+ * Convert base64 to base64url encoding
672
+ */ const toBase64Url = (buffer)=>{
673
+ const base64 = Buffer.from(buffer).toString('base64');
674
+ return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
675
+ };
676
+ /**
677
+ * Convert ArrayBuffer to base64url
678
+ */ const arrayBufferToBase64Url = (buffer)=>{
679
+ return toBase64Url(buffer);
680
+ };
681
+ /**
682
+ * Import RSA public key from PEM format
683
+ */ const importRSAPublicKey = async (publicKeyPem)=>{
684
+ // Remove PEM headers and decode base64
685
+ const pemHeader = '-----BEGIN PUBLIC KEY-----';
686
+ const pemFooter = '-----END PUBLIC KEY-----';
687
+ const pemContents = publicKeyPem.replace(pemHeader, '').replace(pemFooter, '').replace(/\s/g, '');
688
+ const binaryDer = Buffer.from(pemContents, 'base64').toString('binary');
689
+ const keyData = new Uint8Array(binaryDer.length);
690
+ for(let i = 0; i < binaryDer.length; i++){
691
+ keyData[i] = binaryDer.charCodeAt(i);
692
+ }
693
+ return await crypto.subtle.importKey('spki', keyData, {
694
+ name: 'RSA-OAEP',
695
+ hash: 'SHA-256'
696
+ }, false, [
697
+ 'encrypt'
698
+ ]);
699
+ };
700
+ // encodedEnvelopeBytes intentionally omitted; alg/ct/ek/iv/tag/kid is sufficient
701
+ /**
702
+ * Encrypts data using HYBRID-RSA-AES-256 encryption scheme with Web Crypto API.
703
+ * 1. Generate random AES-256 key
704
+ * 2. Encrypt AES key with RSA public key
705
+ * 3. Encrypt data with AES-256-GCM
706
+ */ const encryptDelegatedKeyShare = async (data, publicKeyPem, keyId)=>{
707
+ try {
708
+ // Step 1: Generate a random AES-256 key and 16-byte IV
709
+ const aesKey = await crypto.subtle.generateKey({
710
+ name: 'AES-GCM',
711
+ length: 256
712
+ }, true, [
713
+ 'encrypt'
714
+ ]);
715
+ const iv = crypto.getRandomValues(new Uint8Array(16)); // 128-bit IV for GCM
716
+ // Step 2: Encrypt the data with AES-256-GCM
717
+ const plaintext = new TextEncoder().encode(data);
718
+ const encryptedData = await crypto.subtle.encrypt({
719
+ name: 'AES-GCM',
720
+ iv: iv
721
+ }, aesKey, plaintext);
722
+ // Extract the auth tag from the encrypted data (last 16 bytes)
723
+ const encryptedDataArray = new Uint8Array(encryptedData);
724
+ const authTag = encryptedDataArray.slice(-16);
725
+ const ciphertext = encryptedDataArray.slice(0, -16);
726
+ // Step 3: Encrypt the AES key with RSA public key
727
+ const rsaPublicKey = await importRSAPublicKey(publicKeyPem);
728
+ // Export the AES key to encrypt it
729
+ const aesKeyData = await crypto.subtle.exportKey('raw', aesKey);
730
+ const encryptedAesKey = await crypto.subtle.encrypt({
731
+ name: 'RSA-OAEP'
732
+ }, rsaPublicKey, aesKeyData);
733
+ return _extends({
734
+ alg: ALG_LABEL_RSA,
735
+ iv: arrayBufferToBase64Url(iv.buffer),
736
+ ct: arrayBufferToBase64Url(ciphertext.buffer),
737
+ tag: arrayBufferToBase64Url(authTag.buffer),
738
+ ek: arrayBufferToBase64Url(encryptedAesKey)
739
+ }, keyId ? {
740
+ kid: keyId
741
+ } : {});
742
+ } catch (error) {
743
+ throw new Error(`Encryption failed: ${error instanceof Error ? error.message : String(error)}`);
744
+ }
745
+ };
746
+
747
+ const localStorageWriteTest = {
748
+ tested: false,
749
+ writable: false
750
+ };
751
+ /**
752
+ * Checks whether localStorage is supported on this browser.
753
+ */ const supportsLocalStorage = ()=>{
754
+ if (!isBrowser()) {
755
+ return false;
756
+ }
757
+ try {
758
+ if (typeof globalThis.localStorage !== 'object') {
759
+ return false;
613
760
  }
614
- setupMessageTransportBridge(this.messageTransport, this.iframe, this.iframeDomain);
761
+ } catch (e) {
762
+ // DOM exception when accessing `localStorage`
763
+ return false;
615
764
  }
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');
765
+ if (localStorageWriteTest.tested) {
766
+ return localStorageWriteTest.writable;
767
+ }
768
+ const randomKey = `lswt-${Math.random()}${Math.random()}`;
769
+ try {
770
+ globalThis.localStorage.setItem(randomKey, randomKey);
771
+ globalThis.localStorage.removeItem(randomKey);
772
+ localStorageWriteTest.tested = true;
773
+ localStorageWriteTest.writable = true;
774
+ } catch (e) {
775
+ // localStorage can't be written to
776
+ localStorageWriteTest.tested = true;
777
+ localStorageWriteTest.writable = false;
778
+ }
779
+ return localStorageWriteTest.writable;
780
+ };
781
+ /**
782
+ * Provides safe access to the globalThis.localStorage property.
783
+ */ const localStorageAdapter = {
784
+ getItem: (key)=>{
785
+ if (!supportsLocalStorage()) {
786
+ return null;
787
+ }
788
+ return globalThis.localStorage.getItem(key);
789
+ },
790
+ removeItem: (key)=>{
791
+ if (!supportsLocalStorage()) {
792
+ return;
793
+ }
794
+ globalThis.localStorage.removeItem(key);
795
+ },
796
+ setItem: (key, value)=>{
797
+ if (!supportsLocalStorage()) {
798
+ return;
799
+ }
800
+ globalThis.localStorage.setItem(key, value);
801
+ }
802
+ };
803
+ /**
804
+ * Returns a localStorage-like object that stores the key-value pairs in
805
+ * memory.
806
+ */ const memoryLocalStorageAdapter = (store = {})=>({
807
+ getItem: (key)=>store[key] || null,
808
+ removeItem: (key)=>{
809
+ delete store[key];
810
+ },
811
+ setItem: (key, value)=>{
812
+ store[key] = value;
813
+ }
814
+ });
815
+
816
+ const createDelegationWithGoogleDriveDistribution = ({ existingShares, delegatedShare })=>({
817
+ dynamicBackendShares: existingShares,
818
+ googleDriveShares: existingShares,
819
+ delegatedShare
820
+ });
821
+ const createDelegationOnlyDistribution = ({ existingShares, delegatedShare })=>({
822
+ dynamicBackendShares: existingShares,
823
+ googleDriveShares: [],
824
+ delegatedShare
825
+ });
826
+ const createGoogleDriveOnlyDistribution = ({ allShares })=>({
827
+ dynamicBackendShares: allShares.slice(0, -1),
828
+ googleDriveShares: allShares.slice(-1)
829
+ });
830
+ const createDynamicOnlyDistribution = ({ allShares })=>({
831
+ dynamicBackendShares: allShares,
832
+ googleDriveShares: []
833
+ });
834
+ const hasGoogleDriveBackup = (backupInfo)=>{
835
+ var _backupInfo_backups_BackupLocation_GOOGLE_DRIVE, _backupInfo_backups;
836
+ var _backupInfo_backups_BackupLocation_GOOGLE_DRIVE_length;
837
+ return ((_backupInfo_backups_BackupLocation_GOOGLE_DRIVE_length = backupInfo == null ? void 0 : (_backupInfo_backups = backupInfo.backups) == null ? void 0 : (_backupInfo_backups_BackupLocation_GOOGLE_DRIVE = _backupInfo_backups[core.BackupLocation.GOOGLE_DRIVE]) == null ? void 0 : _backupInfo_backups_BackupLocation_GOOGLE_DRIVE.length) != null ? _backupInfo_backups_BackupLocation_GOOGLE_DRIVE_length : 0) > 0;
838
+ };
839
+ const hasDelegatedBackup = (backupInfo)=>{
840
+ var _backupInfo_backups_BackupLocation_DELEGATED, _backupInfo_backups;
841
+ var _backupInfo_backups_BackupLocation_DELEGATED_length;
842
+ return ((_backupInfo_backups_BackupLocation_DELEGATED_length = backupInfo == null ? void 0 : (_backupInfo_backups = backupInfo.backups) == null ? void 0 : (_backupInfo_backups_BackupLocation_DELEGATED = _backupInfo_backups[core.BackupLocation.DELEGATED]) == null ? void 0 : _backupInfo_backups_BackupLocation_DELEGATED.length) != null ? _backupInfo_backups_BackupLocation_DELEGATED_length : 0) > 0;
843
+ };
844
+ /**
845
+ * Distribution for adding Google Drive backup to an existing delegation.
846
+ * Client's shares go to both Dynamic and Google Drive.
847
+ * delegatedShare is undefined - we don't re-publish, but preserve the location.
848
+ */ const createAddGoogleDriveToExistingDelegationDistribution = ({ clientShares })=>({
849
+ dynamicBackendShares: clientShares,
850
+ googleDriveShares: clientShares
851
+ });
852
+
853
+ class DynamicWalletClient {
854
+ async initializeForwardMPCClient() {
855
+ try {
856
+ await this.apiClient.forwardMPCClient.connect();
857
+ this.logger.info('Connected to ForwardMPC enclave websocket. Instance: ' + this.instanceId);
858
+ try {
859
+ await this.apiClient.forwardMPCClient.handshake();
860
+ this.logger.debug('Handshaked with ForwardMPC enclave websocket. Instance: ' + this.instanceId);
861
+ } catch (error) {
862
+ this.logger.error('Error handshaking with ForwardMPC enclave websocket. Instance: ' + this.instanceId, error);
863
+ }
864
+ } catch (error) {
865
+ this.logger.error('Error connecting to ForwardMPC enclave websocket. Instance: ' + this.instanceId, {
866
+ error,
867
+ environmentId: this.environmentId
868
+ });
621
869
  }
622
- this.iframeStorage = new StorageRequestChannelAdapter(this.messageTransport);
870
+ }
871
+ getAuthMode() {
872
+ return this.authMode;
623
873
  }
624
874
  /**
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');
875
+ * Check if the SDK version meets the requirement for signed session ID
876
+ * Uses namespace-specific version requirements when available
877
+ * @returns boolean indicating if requireSignedSessionId should be set to true
878
+ */ requiresSignedSessionId() {
879
+ if (!this.sdkVersion) {
880
+ return false;
881
+ }
882
+ try {
883
+ const parsedVersion = core.parseNamespacedVersion(this.sdkVersion);
884
+ if (!parsedVersion) {
885
+ return false;
886
+ }
887
+ const { namespace, version } = parsedVersion;
888
+ // Check if we have a namespace-specific version requirement
889
+ const namespaceMinVersion = SIGNED_SESSION_ID_MIN_VERSION_BY_NAMESPACE[namespace];
890
+ if (namespaceMinVersion) {
891
+ return semver.gte(version, namespaceMinVersion);
892
+ }
893
+ // Fall back to default version requirement
894
+ return semver.gte(version, SIGNED_SESSION_ID_MIN_VERSION);
895
+ } catch (error) {
896
+ logger.warn(`[DynamicWaasWalletClient] Error checking if requiresSignedSessionId should be set to true for version ${this.sdkVersion}`, error);
897
+ return false;
637
898
  }
638
- if (!this.messageTransport) {
639
- throw new Error('Failed to initialize message transport');
899
+ }
900
+ async initLoggerContext(authToken) {
901
+ // only decode jwt token in header auth mode
902
+ if (this.authMode === 'cookie') {
903
+ setLoggerContext({
904
+ environmentId: this.environmentId,
905
+ authMode: this.authMode
906
+ });
907
+ return;
640
908
  }
641
- if (!this.iframeStorage) {
642
- throw new Error('Failed to initialize iframe storage');
909
+ try {
910
+ // Split the JWT token into its parts (header.payload.signature)
911
+ const parts = authToken.split('.');
912
+ if (parts.length !== 3) {
913
+ logger.debug('[DynamicWaasWalletClient] Invalid JWT token when initializing logger context');
914
+ return;
915
+ }
916
+ // Decode the payload (second part)
917
+ const base64Payload = parts[1].replace(/-/g, '+').replace(/_/g, '/');
918
+ const jsonPayload = decodeURIComponent(Buffer.from(base64Payload, 'base64').toString('utf-8'));
919
+ const payload = JSON.parse(jsonPayload);
920
+ if (!payload || typeof payload !== 'object') {
921
+ logger.debug('[DynamicWaasWalletClient] Invalid JWT payload when initializing logger context');
922
+ return;
923
+ }
924
+ const userId = payload['sub'] || 'unknown';
925
+ const sessionId = payload['sid'] || 'unknown';
926
+ setLoggerContext({
927
+ environmentId: this.environmentId,
928
+ authMode: this.authMode,
929
+ sessionId,
930
+ userId
931
+ });
932
+ this.sessionId = sessionId;
933
+ } catch (error) {
934
+ logError({
935
+ message: 'Error initializing logger context',
936
+ error: error,
937
+ context: {}
938
+ });
939
+ throw error;
643
940
  }
644
- return this.iframe;
645
941
  }
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');
942
+ async initialize(traceContext) {
943
+ if (this.initializePromise) {
944
+ return await this.initializePromise;
657
945
  }
658
- return this.iframeStorage;
946
+ this.logger.debug('[DynamicWaasWalletClient] Initializing Dynamic Waas Wallet SDK');
947
+ this.initializePromise = this._initialize(traceContext);
948
+ const result = await this.initializePromise;
949
+ this.logger.debug('[DynamicWaasWalletClient] Dynamic Waas Wallet SDK initialized');
950
+ return result;
659
951
  }
660
952
  /**
661
953
  * Client initialization logic
662
- */ async _initialize() {
954
+ */ async _initialize(traceContext) {
663
955
  try {
664
956
  const initializePromises = [
665
- this.restoreWallets(),
666
- this.initializeIframeCommunication()
957
+ this.restoreWallets()
667
958
  ];
668
959
  await Promise.all(initializePromises);
669
960
  return {
670
- error: null
961
+ error: null,
962
+ traceContext
671
963
  };
672
964
  } catch (error) {
673
965
  return {
674
- error
966
+ error,
967
+ traceContext
675
968
  };
676
969
  }
677
970
  }
678
- async serverInitializeKeyGen({ chainName, clientKeygenIds, thresholdSignatureScheme, onError, onCeremonyComplete }) {
971
+ async serverInitializeKeyGen({ chainName, clientKeygenIds, dynamicRequestId, thresholdSignatureScheme, onError, onCeremonyComplete }) {
679
972
  // Initialize keygen, create room, and create the wallet account on the server
680
973
  const data = await this.apiClient.createWalletAccount({
681
974
  chainName,
682
975
  clientKeygenIds,
976
+ dynamicRequestId,
683
977
  thresholdSignatureScheme,
684
978
  onError,
685
979
  onCeremonyComplete
686
980
  });
981
+ this.logger.debug(`[DynamicWaasWalletClient] Initialized client key generation with IDs: ${clientKeygenIds.join(', ')} for chain: ${chainName} using scheme: ${thresholdSignatureScheme}`);
687
982
  return data;
688
983
  }
689
984
  async clientInitializeKeyGen({ chainName, thresholdSignatureScheme }) {
@@ -704,8 +999,8 @@ class DynamicWalletClient {
704
999
  let publicKey;
705
1000
  if (mpcSigner instanceof web.Ecdsa) {
706
1001
  publicKey = await mpcSigner.derivePubkey(keyShare, derivationPath);
707
- } else if (mpcSigner instanceof web.Ed25519) {
708
- publicKey = await mpcSigner.derivePubkey(keyShare, derivationPath);
1002
+ } else if (mpcSigner instanceof web.ExportableEd25519) {
1003
+ publicKey = await mpcSigner.getPubkey(keyShare);
709
1004
  }
710
1005
  return publicKey;
711
1006
  }
@@ -726,7 +1021,13 @@ class DynamicWalletClient {
726
1021
  ...serverKeygenIds,
727
1022
  ...otherClientKeygenIds
728
1023
  ];
729
- return mpcSigner.keygen(roomId, mpcConfig.numberOfParties, mpcConfig.threshold, currentInit, allOtherKeygenIds);
1024
+ if (!(mpcSigner instanceof web.ExportableEd25519)) {
1025
+ return mpcSigner.keygen(roomId, mpcConfig.numberOfParties, mpcConfig.threshold, currentInit, allOtherKeygenIds);
1026
+ } else {
1027
+ // One party joins the keygen room using acting as the sampler: (wallet-service)
1028
+ // The remaining parties join the key sampling ceremony using: (browser)
1029
+ return mpcSigner.receiveKey(roomId, mpcConfig.numberOfParties, mpcConfig.threshold, currentInit, allOtherKeygenIds);
1030
+ }
730
1031
  }));
731
1032
  // only need one client keygen result to derive the public key
732
1033
  const [clientKeygenResult] = clientKeygenResults;
@@ -742,19 +1043,35 @@ class DynamicWalletClient {
742
1043
  clientKeygenResults
743
1044
  };
744
1045
  }
745
- async keyGen({ chainName, thresholdSignatureScheme, onError, onCeremonyComplete }) {
1046
+ async keyGen({ chainName, thresholdSignatureScheme, onError, onCeremonyComplete, traceContext }) {
1047
+ const dynamicRequestId = uuid.v4();
746
1048
  try {
747
1049
  const clientKeygenInitResults = await this.clientInitializeKeyGen({
748
1050
  chainName,
749
1051
  thresholdSignatureScheme
750
1052
  });
751
1053
  const clientKeygenIds = clientKeygenInitResults.map((result)=>result.keygenId);
1054
+ this.instrument('[DynamicWaasWalletClient] Initialized client key generation', _extends({
1055
+ clientKeygenIds: clientKeygenIds.join(', '),
1056
+ chainName,
1057
+ thresholdSignatureScheme,
1058
+ key: 'keyGen'
1059
+ }, this.getTraceContext(traceContext)));
752
1060
  const { roomId, serverKeygenIds } = await this.serverInitializeKeyGen({
753
1061
  chainName,
754
1062
  clientKeygenIds,
1063
+ dynamicRequestId,
755
1064
  thresholdSignatureScheme,
756
1065
  onCeremonyComplete
757
1066
  });
1067
+ this.instrument('[DynamicWaasWalletClient] Server key generation initialized', _extends({
1068
+ roomId,
1069
+ clientKeygenIds: clientKeygenIds.join(', '),
1070
+ serverKeygenIds: serverKeygenIds.join(', '),
1071
+ chainName,
1072
+ key: 'keyGen',
1073
+ operation: 'serverKeyGen'
1074
+ }, this.getTraceContext(traceContext)));
758
1075
  const { rawPublicKey, clientKeygenResults: clientKeyShares } = await this.clientKeyGen({
759
1076
  chainName,
760
1077
  roomId,
@@ -762,172 +1079,351 @@ class DynamicWalletClient {
762
1079
  clientKeygenInitResults,
763
1080
  thresholdSignatureScheme
764
1081
  });
1082
+ this.instrument('[DynamicWaasWalletClient] Client key generation completed', _extends({
1083
+ roomId,
1084
+ serverKeygenIds: serverKeygenIds.join(', '),
1085
+ clientKeygenIds: clientKeygenIds.join(', '),
1086
+ chainName,
1087
+ thresholdSignatureScheme,
1088
+ rawPublicKey,
1089
+ clientKeySharesCount: clientKeyShares.length,
1090
+ key: 'keyGen',
1091
+ operation: 'clientKeyGen'
1092
+ }, this.getTraceContext(traceContext)));
765
1093
  return {
766
1094
  rawPublicKey,
767
1095
  clientKeyShares
768
1096
  };
769
1097
  } catch (error) {
770
- this.logger.error('Error creating wallet account', error);
771
- throw new Error('Error creating wallet account');
1098
+ logError({
1099
+ message: 'Error in keyGen',
1100
+ error: error,
1101
+ context: _extends({
1102
+ chainName,
1103
+ thresholdSignatureScheme,
1104
+ dynamicRequestId
1105
+ }, this.getTraceContext(traceContext))
1106
+ });
1107
+ throw error;
772
1108
  }
773
1109
  }
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 } = core.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
- };
1110
+ async importRawPrivateKey({ chainName, privateKey, thresholdSignatureScheme, onError, onCeremonyComplete, traceContext }) {
1111
+ const dynamicRequestId = uuid.v4();
1112
+ try {
1113
+ const mpcSigner = getMPCSigner({
1114
+ chainName,
1115
+ baseRelayUrl: this.baseMPCRelayApiUrl
1116
+ });
1117
+ const clientKeygenInitResults = await this.clientInitializeKeyGen({
1118
+ chainName,
1119
+ thresholdSignatureScheme
1120
+ });
1121
+ const clientKeygenIds = clientKeygenInitResults.map((result)=>result.keygenId);
1122
+ this.instrument('[DynamicWaasWalletClient] Client key generation initialized', _extends({
1123
+ clientKeygenIds: clientKeygenIds.join(', '),
1124
+ chainName,
1125
+ thresholdSignatureScheme,
1126
+ key: 'importRawPrivateKey',
1127
+ operation: 'clientKeyGen'
1128
+ }, this.getTraceContext(traceContext)));
1129
+ const { roomId, serverKeygenIds } = await this.apiClient.importPrivateKey({
1130
+ chainName,
1131
+ clientKeygenIds,
1132
+ dynamicRequestId,
1133
+ thresholdSignatureScheme,
1134
+ onError,
1135
+ onCeremonyComplete
1136
+ });
1137
+ this.instrument('[DynamicWaasWalletClient] Server key generation initialized', _extends({
1138
+ roomId,
1139
+ clientKeygenIds: clientKeygenIds.join(', '),
1140
+ serverKeygenIds: serverKeygenIds.join(', '),
1141
+ chainName,
1142
+ key: 'importRawPrivateKey',
1143
+ operation: 'serverImportPrivateKey'
1144
+ }, this.getTraceContext(traceContext)));
1145
+ const { threshold } = core.getTSSConfig(thresholdSignatureScheme);
1146
+ const clientKeygenResults = await Promise.all(clientKeygenInitResults.map(async (currentInit, index)=>{
1147
+ const otherClientKeygenIds = clientKeygenInitResults.filter((init)=>init.keygenId !== currentInit.keygenId).map((init)=>init.keygenId);
1148
+ if (index === 0) {
1149
+ const otherKeyGenIds = [
1150
+ ...serverKeygenIds,
1151
+ ...otherClientKeygenIds
1152
+ ];
1153
+ const importerKeygenResult = await mpcSigner.importPrivateKeyImporter(roomId, threshold, privateKey, currentInit, otherKeyGenIds);
1154
+ return importerKeygenResult;
1155
+ } else {
1156
+ const recipientKeygenResult = await mpcSigner.importPrivateKeyRecipient(roomId, threshold, currentInit, [
1157
+ ...serverKeygenIds,
1158
+ ...otherClientKeygenIds
1159
+ ]);
1160
+ return recipientKeygenResult;
1161
+ }
1162
+ }));
1163
+ const [clientKeygenResult] = clientKeygenResults;
1164
+ const rawPublicKey = await this.derivePublicKey({
1165
+ chainName,
1166
+ keyShare: clientKeygenResult,
1167
+ derivationPath: undefined
1168
+ });
1169
+ this.instrument('[DynamicWaasWalletClient] Completed import of raw private key', _extends({
1170
+ rawPublicKey,
1171
+ chainName,
1172
+ thresholdSignatureScheme,
1173
+ roomId,
1174
+ serverKeygenIds: serverKeygenIds.join(', '),
1175
+ clientKeygenIds: clientKeygenIds.join(', '),
1176
+ key: 'importRawPrivateKey',
1177
+ operation: 'importRawPrivateKey'
1178
+ }, this.getTraceContext(traceContext)));
1179
+ return {
1180
+ rawPublicKey,
1181
+ clientKeyShares: clientKeygenResults
1182
+ };
1183
+ } catch (error) {
1184
+ logError({
1185
+ message: 'Error in importRawPrivateKey',
1186
+ error: error,
1187
+ context: _extends({
1188
+ chainName,
1189
+ thresholdSignatureScheme,
1190
+ dynamicRequestId
1191
+ }, this.getTraceContext(traceContext))
1192
+ });
1193
+ throw error;
1194
+ }
819
1195
  }
820
- async serverSign({ walletId, message }) {
1196
+ async serverSign({ walletId, message, isFormatted, mfaToken, context, onError, dynamicRequestId, traceContext }) {
821
1197
  // Create the room and sign the message
822
1198
  if (typeof message !== 'string') {
823
- message = '0x' + Buffer.from(message).toString('hex');
1199
+ message = `0x${Buffer.from(message).toString('hex')}`;
824
1200
  }
825
1201
  const data = await this.apiClient.signMessage({
826
1202
  walletId,
827
- message
1203
+ message,
1204
+ isFormatted,
1205
+ dynamicRequestId,
1206
+ mfaToken,
1207
+ context: context ? JSON.parse(JSON.stringify(context, (_key, value)=>typeof value === 'bigint' ? value.toString() : value)) : undefined,
1208
+ onError,
1209
+ forwardMPCClientEnabled: this.forwardMPCEnabled,
1210
+ traceContext
828
1211
  });
829
1212
  return data;
830
1213
  }
831
- async clientSign({ chainName, message, roomId, keyShare, derivationPath }) {
1214
+ async forwardMPCClientSign({ chainName, message, roomId, keyShare, derivationPath, formattedMessage, dynamicRequestId, isFormatted, traceContext }) {
1215
+ try {
1216
+ if (!this.apiClient.forwardMPCClient.connected) {
1217
+ await this.initializeForwardMPCClient();
1218
+ }
1219
+ const messageForForwardMPC = core.serializeMessageForForwardMPC({
1220
+ message,
1221
+ isFormatted,
1222
+ chainName
1223
+ });
1224
+ this.logger.info('Forward MPC enabled, signing message with forward MPC', this.getTraceContext(traceContext));
1225
+ const signature = await this.apiClient.forwardMPCClient.signMessage({
1226
+ keyshare: keyShare,
1227
+ message: chainName === 'SVM' ? formattedMessage : messageForForwardMPC,
1228
+ relayDomain: this.baseMPCRelayApiUrl || '',
1229
+ signingAlgo: chainName === 'EVM' ? 'ECDSA' : 'ED25519',
1230
+ hashAlgo: chainName === 'EVM' && !isFormatted ? 'keccak256' : undefined,
1231
+ derivationPath: derivationPath,
1232
+ roomUuid: roomId
1233
+ });
1234
+ const signatureBytes = signature.data.signature;
1235
+ if (!(signatureBytes instanceof Uint8Array)) {
1236
+ throw new TypeError(`Invalid signature format: expected Uint8Array, got ${typeof signatureBytes}`);
1237
+ }
1238
+ // Convert to EcdsaSignature
1239
+ if (chainName === 'EVM') {
1240
+ const ecdsaSignature = web.EcdsaSignature.fromBuffer(signatureBytes);
1241
+ return ecdsaSignature;
1242
+ } else {
1243
+ return signatureBytes;
1244
+ }
1245
+ } catch (error) {
1246
+ this.logger.error('Error signing message with forward MPC client', {
1247
+ error,
1248
+ environmentId: this.environmentId,
1249
+ userId: this.userId,
1250
+ dynamicRequestId
1251
+ });
1252
+ throw error;
1253
+ }
1254
+ }
1255
+ async clientSign({ chainName, message, roomId, keyShare, derivationPath, isFormatted, dynamicRequestId, traceContext }) {
832
1256
  try {
833
1257
  const mpcSigner = getMPCSigner({
834
1258
  chainName,
835
1259
  baseRelayUrl: this.baseMPCRelayApiUrl
836
1260
  });
837
- let formattedMessage;
838
- //note: Ecdsa can also be used by bitcoin, but only keccak256 is used by ethereum
839
- if (mpcSigner instanceof web.Ecdsa) {
840
- formattedMessage = web.MessageHash.keccak256(message);
841
- } else if (mpcSigner instanceof web.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 web.BIP340 && typeof message === 'string') {
852
- formattedMessage = new TextEncoder().encode(message);
853
- } else {
854
- throw new Error('Unsupported signer type');
1261
+ const formattedMessage = isFormatted ? new web.MessageHash(message) : formatMessage(chainName, message);
1262
+ this.logger.debug('[DynamicWaasWalletClient] Starting client sign', _extends({
1263
+ chainName,
1264
+ message,
1265
+ roomId,
1266
+ derivationPath,
1267
+ isFormatted
1268
+ }, this.getTraceContext(traceContext)));
1269
+ if (this.forwardMPCEnabled) {
1270
+ return this.forwardMPCClientSign({
1271
+ chainName,
1272
+ message,
1273
+ roomId,
1274
+ keyShare,
1275
+ derivationPath,
1276
+ formattedMessage,
1277
+ dynamicRequestId,
1278
+ isFormatted,
1279
+ traceContext
1280
+ });
855
1281
  }
856
1282
  const signature = await mpcSigner.sign(roomId, keyShare, formattedMessage, derivationPath);
857
1283
  return signature;
858
1284
  } catch (error) {
859
- this.logger.error('Error in clientSign:', error);
1285
+ logError({
1286
+ message: 'Error in clientSign',
1287
+ error: error,
1288
+ context: _extends({
1289
+ chainName,
1290
+ roomId,
1291
+ derivationPath,
1292
+ isFormatted,
1293
+ dynamicRequestId
1294
+ }, this.getTraceContext(traceContext))
1295
+ });
860
1296
  throw error;
861
1297
  }
862
1298
  }
863
1299
  //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: core.WalletOperation.SIGN_MESSAGE
869
- });
870
- const wallet = await this.getWallet({
871
- accountAddress,
872
- password,
873
- walletOperation: core.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;
1300
+ async sign({ accountAddress, message, chainName, password = undefined, isFormatted = false, signedSessionId, mfaToken, context, onError, traceContext }) {
1301
+ const dynamicRequestId = uuid.v4();
1302
+ try {
1303
+ await this.verifyPassword({
1304
+ accountAddress,
1305
+ password,
1306
+ walletOperation: core.WalletOperation.SIGN_MESSAGE,
1307
+ signedSessionId
1308
+ });
1309
+ const wallet = await this.getWallet({
1310
+ accountAddress,
1311
+ password,
1312
+ walletOperation: core.WalletOperation.SIGN_MESSAGE,
1313
+ signedSessionId
1314
+ });
1315
+ // Perform the server sign
1316
+ const data = await this.serverSign({
1317
+ walletId: wallet.walletId,
1318
+ message,
1319
+ isFormatted,
1320
+ mfaToken,
1321
+ context,
1322
+ onError,
1323
+ dynamicRequestId,
1324
+ traceContext
1325
+ });
1326
+ this.instrument('[DynamicWaasWalletClient] Server sign completed', _extends({
1327
+ message,
1328
+ accountAddress,
1329
+ walletId: wallet.walletId,
1330
+ roomId: data.roomId,
1331
+ dynamicRequestId,
1332
+ key: 'sign',
1333
+ operation: 'serverSign'
1334
+ }, this.getTraceContext(traceContext)));
1335
+ const derivationPath = wallet.derivationPath && wallet.derivationPath != '' ? new Uint32Array(Object.values(JSON.parse(wallet.derivationPath))) : undefined;
1336
+ // Perform the client sign and return the signature
1337
+ const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
1338
+ accountAddress
1339
+ });
1340
+ const signature = await this.clientSign({
1341
+ chainName,
1342
+ message,
1343
+ roomId: data.roomId,
1344
+ keyShare: clientKeyShares[0],
1345
+ derivationPath,
1346
+ isFormatted,
1347
+ dynamicRequestId,
1348
+ traceContext
1349
+ });
1350
+ this.instrument('[DynamicWaasWalletClient] Client sign completed', _extends({
1351
+ accountAddress,
1352
+ chainName,
1353
+ message,
1354
+ roomId: data.roomId,
1355
+ derivationPath,
1356
+ isFormatted,
1357
+ key: 'sign',
1358
+ operation: 'clientSign'
1359
+ }, this.getTraceContext(traceContext)));
1360
+ return signature;
1361
+ } catch (error) {
1362
+ logError({
1363
+ message: 'Error in sign',
1364
+ error: error,
1365
+ context: _extends({
1366
+ accountAddress,
1367
+ chainName,
1368
+ isFormatted: isFormatted ? 'true' : 'false',
1369
+ dynamicRequestId
1370
+ }, this.getTraceContext(traceContext))
1371
+ });
1372
+ throw error;
1373
+ }
893
1374
  }
894
- async refreshWalletAccountShares({ accountAddress, chainName, password = undefined }) {
895
- await this.verifyPassword({
896
- accountAddress,
897
- password,
898
- walletOperation: core.WalletOperation.REFRESH
899
- });
900
- const wallet = await this.getWallet({
901
- accountAddress,
902
- walletOperation: core.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;
1375
+ async refreshWalletAccountShares({ accountAddress, chainName, password = undefined, signedSessionId, mfaToken, traceContext }) {
1376
+ const dynamicRequestId = uuid.v4();
1377
+ try {
1378
+ await this.verifyPassword({
1379
+ accountAddress,
1380
+ password,
1381
+ walletOperation: core.WalletOperation.REFRESH,
1382
+ signedSessionId
1383
+ });
1384
+ const wallet = await this.getWallet({
1385
+ accountAddress,
1386
+ walletOperation: core.WalletOperation.NO_OPERATION,
1387
+ password,
1388
+ signedSessionId
1389
+ });
1390
+ const mpcSigner = getMPCSigner({
1391
+ chainName,
1392
+ baseRelayUrl: this.baseMPCRelayApiUrl
1393
+ });
1394
+ // Create the room and refresh the shares
1395
+ const data = await this.apiClient.refreshWalletAccountShares({
1396
+ dynamicRequestId,
1397
+ walletId: wallet.walletId,
1398
+ mfaToken
1399
+ });
1400
+ const roomId = data.roomId;
1401
+ const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
1402
+ accountAddress
1403
+ });
1404
+ const refreshResults = await Promise.all(clientKeyShares.map((clientKeyShare)=>mpcSigner.refresh(roomId, clientKeyShare)));
1405
+ await this.setClientKeySharesToLocalStorage({
1406
+ accountAddress,
1407
+ clientKeyShares: refreshResults,
1408
+ overwriteOrMerge: 'overwrite'
1409
+ });
1410
+ await this.storeEncryptedBackupByWallet({
1411
+ accountAddress,
1412
+ password: password != null ? password : this.environmentId,
1413
+ signedSessionId
1414
+ });
1415
+ } catch (error) {
1416
+ logError({
1417
+ message: 'Error in refreshWalletAccountShares',
1418
+ error: error,
1419
+ context: _extends({
1420
+ accountAddress,
1421
+ chainName,
1422
+ dynamicRequestId
1423
+ }, this.getTraceContext(traceContext))
1424
+ });
1425
+ throw error;
1426
+ }
931
1427
  }
932
1428
  async getExportId({ chainName, clientKeyShare }) {
933
1429
  const mpcSigner = getMPCSigner({
@@ -980,105 +1476,277 @@ class DynamicWalletClient {
980
1476
  existingClientKeyShares
981
1477
  };
982
1478
  }
983
- async reshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined }) {
984
- await this.verifyPassword({
1479
+ async reshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined, signedSessionId, backupToGoogleDrive = false, delegateToProjectEnvironment = false, mfaToken, revokeDelegation = false }) {
1480
+ const dynamicRequestId = uuid.v4();
1481
+ try {
1482
+ await this.verifyPassword({
1483
+ accountAddress,
1484
+ password,
1485
+ walletOperation: core.WalletOperation.RESHARE,
1486
+ signedSessionId
1487
+ });
1488
+ const { existingClientShareCount } = core.getReshareConfig({
1489
+ oldThresholdSignatureScheme,
1490
+ newThresholdSignatureScheme
1491
+ });
1492
+ const wallet = await this.getWallet({
1493
+ accountAddress,
1494
+ walletOperation: core.WalletOperation.NO_OPERATION,
1495
+ shareCount: existingClientShareCount,
1496
+ password,
1497
+ signedSessionId
1498
+ });
1499
+ const { newClientInitKeygenResults, newClientKeygenIds, existingClientKeygenIds, existingClientKeyShares } = await this.reshareStrategy({
1500
+ chainName,
1501
+ accountAddress,
1502
+ wallet,
1503
+ oldThresholdSignatureScheme,
1504
+ newThresholdSignatureScheme
1505
+ });
1506
+ const clientKeygenIds = [
1507
+ ...newClientKeygenIds,
1508
+ ...existingClientKeygenIds
1509
+ ];
1510
+ // Server to create the room and complete the server reshare logics
1511
+ const data = await this.apiClient.reshare({
1512
+ walletId: wallet.walletId,
1513
+ clientKeygenIds: clientKeygenIds,
1514
+ oldThresholdSignatureScheme,
1515
+ newThresholdSignatureScheme,
1516
+ dynamicRequestId,
1517
+ delegateToProjectEnvironment,
1518
+ mfaToken,
1519
+ revokeDelegation
1520
+ });
1521
+ const { roomId, serverKeygenIds, newServerKeygenIds = [] } = data;
1522
+ // Get the MPC config for the threshold signature scheme
1523
+ const oldMpcConfig = core.MPC_CONFIG[oldThresholdSignatureScheme];
1524
+ const newMpcConfig = core.MPC_CONFIG[newThresholdSignatureScheme];
1525
+ const allPartyKeygenIds = [
1526
+ ...clientKeygenIds,
1527
+ ...serverKeygenIds,
1528
+ ...newServerKeygenIds
1529
+ ];
1530
+ const mpcSigner = getMPCSigner({
1531
+ chainName,
1532
+ baseRelayUrl: this.baseMPCRelayApiUrl
1533
+ });
1534
+ const existingResharePromises = existingClientKeyShares.map((keyShare)=>mpcSigner.reshareRemainingParty(roomId, newMpcConfig.threshold, keyShare, allPartyKeygenIds));
1535
+ const newResharePromises = newClientInitKeygenResults.map((keygenResult)=>mpcSigner.reshareNewParty(roomId, oldMpcConfig.threshold, newMpcConfig.threshold, keygenResult, allPartyKeygenIds));
1536
+ // Run both share parties in parallel by group
1537
+ const [existingReshareResults, newReshareResults] = await Promise.all([
1538
+ Promise.all(existingResharePromises),
1539
+ Promise.all(newResharePromises)
1540
+ ]);
1541
+ const clientKeysharesToLocalStorage = delegateToProjectEnvironment ? [
1542
+ ...existingReshareResults
1543
+ ] : [
1544
+ ...existingReshareResults,
1545
+ ...newReshareResults
1546
+ ];
1547
+ this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
1548
+ thresholdSignatureScheme: newThresholdSignatureScheme
1549
+ });
1550
+ // store client key shares to localStorage
1551
+ await this.setClientKeySharesToLocalStorage({
1552
+ accountAddress,
1553
+ clientKeyShares: clientKeysharesToLocalStorage,
1554
+ overwriteOrMerge: 'overwrite'
1555
+ });
1556
+ const allClientShares = [
1557
+ ...existingReshareResults,
1558
+ ...newReshareResults
1559
+ ];
1560
+ let distribution;
1561
+ if (delegateToProjectEnvironment && backupToGoogleDrive) {
1562
+ // Delegation + Google Drive: Client's existing share backs up to both Dynamic and Google Drive.
1563
+ // The new share goes to the webhook for delegation.
1564
+ distribution = createDelegationWithGoogleDriveDistribution({
1565
+ existingShares: existingReshareResults,
1566
+ delegatedShare: newReshareResults[0]
1567
+ });
1568
+ } else if (delegateToProjectEnvironment) {
1569
+ // Delegation only: Client's existing share backs up to Dynamic.
1570
+ // The new share goes to the webhook for delegation. No Google Drive backup.
1571
+ distribution = createDelegationOnlyDistribution({
1572
+ existingShares: existingReshareResults,
1573
+ delegatedShare: newReshareResults[0]
1574
+ });
1575
+ } else if (backupToGoogleDrive) {
1576
+ // Google Drive only: Split shares between Dynamic (N-1) and Google Drive (1).
1577
+ // The last share (new share) goes to Google Drive.
1578
+ distribution = createGoogleDriveOnlyDistribution({
1579
+ allShares: allClientShares
1580
+ });
1581
+ } else {
1582
+ // No delegation, no Google Drive: All shares go to Dynamic backend only.
1583
+ distribution = createDynamicOnlyDistribution({
1584
+ allShares: allClientShares
1585
+ });
1586
+ }
1587
+ await this.backupSharesWithDistribution({
1588
+ accountAddress,
1589
+ password,
1590
+ signedSessionId,
1591
+ distribution
1592
+ });
1593
+ } catch (error) {
1594
+ logError({
1595
+ message: 'Error in reshare, resetting wallet to previous state',
1596
+ error: error,
1597
+ context: {
1598
+ accountAddress,
1599
+ chainName,
1600
+ oldThresholdSignatureScheme,
1601
+ newThresholdSignatureScheme,
1602
+ backupToGoogleDrive,
1603
+ dynamicRequestId
1604
+ }
1605
+ });
1606
+ // reset user wallet when reshare fails, this would allow the client to recover wallets from an active state
1607
+ this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
1608
+ thresholdSignatureScheme: oldThresholdSignatureScheme
1609
+ });
1610
+ await this.setClientKeySharesToLocalStorage({
1611
+ accountAddress,
1612
+ clientKeyShares: [],
1613
+ overwriteOrMerge: 'overwrite'
1614
+ });
1615
+ throw error;
1616
+ }
1617
+ }
1618
+ async performDelegationOperation({ accountAddress, password, signedSessionId, mfaToken, newThresholdSignatureScheme, revokeDelegation = false, operationName }) {
1619
+ try {
1620
+ var _this_walletMap_accountAddress;
1621
+ const delegateToProjectEnvironment = this.featureFlags && this.featureFlags[core.FEATURE_FLAGS.ENABLE_DELEGATED_KEY_SHARES_FLAG] === true;
1622
+ if (!delegateToProjectEnvironment) {
1623
+ throw new Error('Delegation is not allowed for this project environment');
1624
+ }
1625
+ const wallet = await this.getWallet({
1626
+ accountAddress,
1627
+ walletOperation: core.WalletOperation.REACH_ALL_PARTIES,
1628
+ password,
1629
+ signedSessionId
1630
+ });
1631
+ if (wallet.chainName === 'SUI') {
1632
+ throw new Error('Delegation is not allowed for SUI');
1633
+ }
1634
+ const currentThresholdSignatureScheme = this.walletMap[accountAddress].thresholdSignatureScheme;
1635
+ await this.reshare({
1636
+ chainName: this.walletMap[accountAddress].chainName,
1637
+ accountAddress,
1638
+ oldThresholdSignatureScheme: currentThresholdSignatureScheme,
1639
+ newThresholdSignatureScheme,
1640
+ password,
1641
+ signedSessionId,
1642
+ backupToGoogleDrive: hasGoogleDriveBackup((_this_walletMap_accountAddress = this.walletMap[accountAddress]) == null ? void 0 : _this_walletMap_accountAddress.clientKeySharesBackupInfo),
1643
+ delegateToProjectEnvironment: true,
1644
+ mfaToken,
1645
+ revokeDelegation
1646
+ });
1647
+ } catch (error) {
1648
+ logError({
1649
+ message: `Error in ${operationName}`,
1650
+ error: error,
1651
+ context: {
1652
+ accountAddress
1653
+ }
1654
+ });
1655
+ throw error;
1656
+ }
1657
+ }
1658
+ async delegateKeyShares({ accountAddress, password = undefined, signedSessionId, mfaToken }) {
1659
+ await this.performDelegationOperation({
985
1660
  accountAddress,
986
1661
  password,
987
- walletOperation: core.WalletOperation.RESHARE
988
- });
989
- const { existingClientShareCount } = core.getReshareConfig({
990
- oldThresholdSignatureScheme,
991
- newThresholdSignatureScheme
992
- });
993
- const wallet = await this.getWallet({
994
- accountAddress,
995
- walletOperation: core.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 = core.MPC_CONFIG[oldThresholdSignatureScheme];
1020
- const newMpcConfig = core.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'
1662
+ signedSessionId,
1663
+ mfaToken,
1664
+ newThresholdSignatureScheme: core.ThresholdSignatureScheme.TWO_OF_THREE,
1665
+ operationName: 'delegateKeyShares'
1038
1666
  });
1039
- await this.storeEncryptedBackupByWallet({
1040
- accountAddress,
1041
- password
1042
- });
1043
- return reshareResults;
1667
+ const backupInfo = this.walletMap[accountAddress].clientKeySharesBackupInfo;
1668
+ const delegatedKeyShares = backupInfo.backups[core.BackupLocation.DELEGATED] || [];
1669
+ return delegatedKeyShares;
1044
1670
  }
1045
- async exportKey({ accountAddress, displayContainer, chainName, password = undefined }) {
1046
- const wallet = await this.getWallet({
1671
+ async revokeDelegation({ accountAddress, password = undefined, signedSessionId, mfaToken }) {
1672
+ await this.performDelegationOperation({
1047
1673
  accountAddress,
1048
1674
  password,
1049
- walletOperation: core.WalletOperation.EXPORT_PRIVATE_KEY
1050
- });
1051
- const mpcSigner = getMPCSigner({
1052
- chainName,
1053
- baseRelayUrl: this.baseMPCRelayApiUrl
1054
- });
1055
- const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
1056
- accountAddress
1675
+ signedSessionId,
1676
+ mfaToken,
1677
+ newThresholdSignatureScheme: core.ThresholdSignatureScheme.TWO_OF_TWO,
1678
+ revokeDelegation: true,
1679
+ operationName: 'revokeDelegation'
1057
1680
  });
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 web.Ecdsa) {
1073
- derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, derivationPath);
1074
- } else if (mpcSigner instanceof web.Ed25519) {
1075
- derivedPrivateKey = keyExportRaw;
1076
- } else if (mpcSigner instanceof web.BIP340) {
1077
- derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, derivationPath);
1681
+ }
1682
+ async exportKey({ accountAddress, chainName, password = undefined, signedSessionId, mfaToken, traceContext }) {
1683
+ const dynamicRequestId = uuid.v4();
1684
+ try {
1685
+ const wallet = await this.getWallet({
1686
+ accountAddress,
1687
+ password,
1688
+ walletOperation: core.WalletOperation.EXPORT_PRIVATE_KEY,
1689
+ signedSessionId
1690
+ });
1691
+ const mpcSigner = getMPCSigner({
1692
+ chainName,
1693
+ baseRelayUrl: this.baseMPCRelayApiUrl
1694
+ });
1695
+ const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
1696
+ accountAddress
1697
+ });
1698
+ const exportId = await this.getExportId({
1699
+ chainName,
1700
+ clientKeyShare: clientKeyShares[0]
1701
+ });
1702
+ const data = await this.apiClient.exportKey({
1703
+ walletId: wallet.walletId,
1704
+ exportId,
1705
+ dynamicRequestId,
1706
+ mfaToken
1707
+ });
1708
+ this.logger.debug('[DynamicWaasWalletClient] Starting export of private key', _extends({
1709
+ accountAddress,
1710
+ chainName,
1711
+ walletId: wallet.walletId,
1712
+ exportId,
1713
+ roomId: data.roomId
1714
+ }, this.getTraceContext(traceContext)));
1715
+ const keyExportRaw = await mpcSigner.exportFullPrivateKey(data.roomId, clientKeyShares[0], exportId);
1716
+ if (!keyExportRaw) {
1717
+ throw new Error('Error exporting private key');
1718
+ }
1719
+ this.logger.debug('[DynamicWaasWalletClient] Completed export of private key', _extends({
1720
+ accountAddress,
1721
+ chainName,
1722
+ walletId: wallet.walletId,
1723
+ exportId,
1724
+ roomId: data.roomId
1725
+ }, this.getTraceContext(traceContext)));
1726
+ const derivationPath = wallet.derivationPath && wallet.derivationPath != '' ? new Uint32Array(Object.values(JSON.parse(wallet.derivationPath))) : undefined;
1727
+ let derivedPrivateKey;
1728
+ if (mpcSigner instanceof web.Ecdsa) {
1729
+ derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, derivationPath);
1730
+ } else if (mpcSigner instanceof web.ExportableEd25519) {
1731
+ derivedPrivateKey = keyExportRaw;
1732
+ } else if (mpcSigner instanceof web.BIP340) {
1733
+ derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, derivationPath);
1734
+ }
1735
+ return {
1736
+ derivedPrivateKey
1737
+ };
1738
+ } catch (error) {
1739
+ logError({
1740
+ message: 'Error in exportKey',
1741
+ error: error,
1742
+ context: _extends({
1743
+ accountAddress,
1744
+ chainName,
1745
+ dynamicRequestId
1746
+ }, this.getTraceContext(traceContext))
1747
+ });
1748
+ throw error;
1078
1749
  }
1079
- return {
1080
- derivedPrivateKey
1081
- };
1082
1750
  }
1083
1751
  async offlineExportKey({ chainName, keyShares, derivationPath }) {
1084
1752
  try {
@@ -1090,7 +1758,7 @@ class DynamicWalletClient {
1090
1758
  baseRelayUrl: this.baseMPCRelayApiUrl
1091
1759
  });
1092
1760
  const walletKeyShares = keyShares.map((keyShare)=>{
1093
- return mpcSigner instanceof web.Ecdsa ? new web.EcdsaKeygenResult(keyShare.pubkey, keyShare.secretShare) : mpcSigner instanceof web.Ed25519 ? new web.Ed25519KeygenResult(keyShare.pubkey, keyShare.secretShare) : new web.BIP340KeygenResult(keyShare.pubkey, keyShare.secretShare);
1761
+ return mpcSigner instanceof web.Ecdsa ? new web.EcdsaKeygenResult(keyShare.pubkey, keyShare.secretShare) : mpcSigner instanceof web.ExportableEd25519 ? new web.ExportableEd25519KeygenResult(keyShare.pubkey, keyShare.secretShare) : new web.BIP340KeygenResult(keyShare.pubkey, keyShare.secretShare);
1094
1762
  });
1095
1763
  const keyExportRaw = await mpcSigner.offlineExportFullPrivateKey(walletKeyShares);
1096
1764
  if (!keyExportRaw) {
@@ -1101,7 +1769,7 @@ class DynamicWalletClient {
1101
1769
  let derivedPrivateKey;
1102
1770
  if (mpcSigner instanceof web.Ecdsa) {
1103
1771
  derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, walletDerivationPath);
1104
- } else if (mpcSigner instanceof web.Ed25519) {
1772
+ } else if (mpcSigner instanceof web.ExportableEd25519) {
1105
1773
  derivedPrivateKey = keyExportRaw;
1106
1774
  } else if (mpcSigner instanceof web.BIP340) {
1107
1775
  derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, walletDerivationPath);
@@ -1116,7 +1784,13 @@ class DynamicWalletClient {
1116
1784
  rawPublicKey
1117
1785
  };
1118
1786
  } catch (error) {
1119
- this.logger.error('Error in offlineExportKey:', error);
1787
+ logError({
1788
+ message: 'Error in offlineExportKey',
1789
+ error: error,
1790
+ context: {
1791
+ chainName
1792
+ }
1793
+ });
1120
1794
  throw error;
1121
1795
  }
1122
1796
  }
@@ -1133,117 +1807,265 @@ class DynamicWalletClient {
1133
1807
  /**
1134
1808
  * helper function to store encrypted backup by wallet from iframe local storage
1135
1809
  */ 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));
1810
+ var _this_storage;
1811
+ const walletObject = await ((_this_storage = this.storage) == null ? void 0 : _this_storage.getItem(accountAddress));
1139
1812
  if (!walletObject) {
1140
- this.logger.debug(`No item found in iframe local storage for accountAddress: ${accountAddress}`);
1813
+ this.logger.debug(`[DynamicWaasWalletClient] No item found in iframe local storage for accountAddress: ${accountAddress}`);
1141
1814
  return [];
1142
1815
  }
1143
1816
  try {
1144
- return (walletObject == null ? void 0 : walletObject.clientKeyShares) || [];
1817
+ let parsedWalletObject;
1818
+ if (typeof walletObject === 'string') {
1819
+ parsedWalletObject = JSON.parse(walletObject);
1820
+ } else {
1821
+ parsedWalletObject = walletObject;
1822
+ }
1823
+ return (parsedWalletObject == null ? void 0 : parsedWalletObject.clientKeyShares) || [];
1145
1824
  } catch (error) {
1146
- this.logger.error(`Error parsing clientKeyShares: ${error} for accountAddress: ${accountAddress}`);
1825
+ logError({
1826
+ message: `Error parsing clientKeyShares: Error for accountAddress:`,
1827
+ error: error,
1828
+ context: {
1829
+ accountAddress
1830
+ }
1831
+ });
1147
1832
  return [];
1148
1833
  }
1149
1834
  }
1150
1835
  /**
1151
1836
  * helper function to store encrypted backup by wallet from iframe local storage
1152
1837
  */ 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, {
1838
+ var _this_storage;
1839
+ const stringifiedClientKeyShares = JSON.stringify({
1156
1840
  clientKeyShares: overwriteOrMerge === 'overwrite' ? clientKeyShares : mergeUniqueKeyShares(await this.getClientKeySharesFromLocalStorage({
1157
1841
  accountAddress
1158
1842
  }), clientKeyShares)
1159
- }));
1843
+ });
1844
+ await ((_this_storage = this.storage) == null ? void 0 : _this_storage.setItem(accountAddress, stringifiedClientKeyShares));
1845
+ }
1846
+ async backupSharesWithDistribution({ accountAddress, password, signedSessionId, distribution, preserveDelegatedLocation = false }) {
1847
+ const dynamicRequestId = uuid.v4();
1848
+ try {
1849
+ var _this_walletMap_accountAddress;
1850
+ if (!((_this_walletMap_accountAddress = this.walletMap[accountAddress]) == null ? void 0 : _this_walletMap_accountAddress.walletId)) {
1851
+ const error = new Error(`WalletId not found for accountAddress ${accountAddress}`);
1852
+ logError({
1853
+ message: 'Error in backupSharesWithDistribution, wallet or walletId not found from the wallet map',
1854
+ error,
1855
+ context: {
1856
+ accountAddress,
1857
+ walletMap: this.walletMap
1858
+ }
1859
+ });
1860
+ throw error;
1861
+ }
1862
+ const locations = [];
1863
+ if (distribution.dynamicBackendShares.length > 0) {
1864
+ const encryptedDynamicShares = await Promise.all(distribution.dynamicBackendShares.map((keyShare)=>this.encryptKeyShare({
1865
+ keyShare,
1866
+ password
1867
+ })));
1868
+ const data = await this.apiClient.storeEncryptedBackupByWallet({
1869
+ walletId: this.walletMap[accountAddress].walletId,
1870
+ encryptedKeyShares: encryptedDynamicShares,
1871
+ passwordEncrypted: Boolean(password) && password !== this.environmentId,
1872
+ encryptionVersion: ENCRYPTION_VERSION_CURRENT,
1873
+ signedSessionId,
1874
+ authMode: this.authMode,
1875
+ requiresSignedSessionId: this.requiresSignedSessionId(),
1876
+ dynamicRequestId
1877
+ });
1878
+ if (data.keyShareIds.length === 0) {
1879
+ throw new Error('No key shares were backed up to Dynamic backend');
1880
+ }
1881
+ locations.push({
1882
+ location: core.BackupLocation.DYNAMIC,
1883
+ externalKeyShareId: data.keyShareIds[0]
1884
+ });
1885
+ }
1886
+ if (distribution.googleDriveShares.length > 0) {
1887
+ const encryptedGoogleDriveShares = await Promise.all(distribution.googleDriveShares.map((keyShare)=>this.encryptKeyShare({
1888
+ keyShare,
1889
+ password
1890
+ })));
1891
+ await this.uploadKeySharesToGoogleDrive({
1892
+ accountAddress,
1893
+ encryptedKeyShares: encryptedGoogleDriveShares
1894
+ });
1895
+ locations.push({
1896
+ location: core.BackupLocation.GOOGLE_DRIVE
1897
+ });
1898
+ }
1899
+ if (distribution.delegatedShare) {
1900
+ var _publicKey_key, _publicKey_key1, _publicKey_key2;
1901
+ const publicKey = await this.apiClient.getDelegatedEncryptionKey({
1902
+ environmentId: this.environmentId
1903
+ });
1904
+ if (!(publicKey == null ? void 0 : (_publicKey_key = publicKey.key) == null ? void 0 : _publicKey_key.publicKeyPemB64)) {
1905
+ throw new Error('Public key not found');
1906
+ }
1907
+ var _publicKey_key_keyId;
1908
+ const encryptedDelegatedKeyShareEnvelope = await encryptDelegatedKeyShare(JSON.stringify(distribution.delegatedShare), publicKey == null ? void 0 : (_publicKey_key1 = publicKey.key) == null ? void 0 : _publicKey_key1.publicKeyPemB64, (_publicKey_key_keyId = publicKey == null ? void 0 : (_publicKey_key2 = publicKey.key) == null ? void 0 : _publicKey_key2.keyId) != null ? _publicKey_key_keyId : publicKey == null ? void 0 : publicKey.keyId);
1909
+ const { status } = await this.apiClient.publishDelegatedKeyShare({
1910
+ walletId: this.walletMap[accountAddress].walletId,
1911
+ encryptedKeyShare: encryptedDelegatedKeyShareEnvelope,
1912
+ signedSessionId,
1913
+ requiresSignedSessionId: this.requiresSignedSessionId(),
1914
+ dynamicRequestId
1915
+ });
1916
+ if (status !== 200) {
1917
+ throw new Error('Failed to publish delegated key share');
1918
+ }
1919
+ locations.push({
1920
+ location: core.BackupLocation.DELEGATED
1921
+ });
1922
+ }
1923
+ // Preserve existing delegated location without re-publishing
1924
+ if (preserveDelegatedLocation && !distribution.delegatedShare) {
1925
+ locations.push({
1926
+ location: core.BackupLocation.DELEGATED
1927
+ });
1928
+ }
1929
+ const backupData = await this.apiClient.markKeySharesAsBackedUp({
1930
+ walletId: this.walletMap[accountAddress].walletId,
1931
+ locations,
1932
+ dynamicRequestId
1933
+ });
1934
+ const updatedBackupInfo = getClientKeyShareBackupInfo({
1935
+ walletProperties: {
1936
+ derivationPath: this.walletMap[accountAddress].derivationPath,
1937
+ keyShares: backupData.locationsWithKeyShares.map((ks)=>({
1938
+ id: ks.keyShareId,
1939
+ backupLocation: ks.location,
1940
+ externalKeyShareId: ks.externalKeyShareId,
1941
+ passwordEncrypted: Boolean(password) && password !== this.environmentId
1942
+ })),
1943
+ thresholdSignatureScheme: this.walletMap[accountAddress].thresholdSignatureScheme
1944
+ }
1945
+ });
1946
+ this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
1947
+ clientKeySharesBackupInfo: updatedBackupInfo
1948
+ });
1949
+ await this.storage.setItem(this.storageKey, JSON.stringify(this.walletMap));
1950
+ return backupData;
1951
+ } catch (error) {
1952
+ logError({
1953
+ message: 'Error in backupSharesWithDistribution',
1954
+ error: error,
1955
+ context: {
1956
+ accountAddress,
1957
+ dynamicRequestId
1958
+ }
1959
+ });
1960
+ throw error;
1961
+ }
1160
1962
  }
1161
1963
  /**
1162
- * Encrypts and stores wallet key shares as backups.
1964
+ * Central backup orchestrator that encrypts and stores wallet key shares.
1163
1965
  *
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.
1966
+ * This method serves as the main backup coordinator, handling the distribution of encrypted
1967
+ * key shares between Dynamic's backend and Google Drive based on the wallet's threshold scheme.
1968
+ * It is used by multiple operations including reshare, refresh, and manual backup requests.
1167
1969
  *
1168
- * The method performs the following steps:
1970
+ * **Backup Distribution Strategy:**
1971
+ * - **Single share wallets**: All shares stored on Dynamic's backend only
1972
+ * - **Multi-share wallets (2+)**: When backing up to Google Drive, N-1 shares on Dynamic's backend, 1 share on Google Drive
1973
+ * - **Multi-share wallets (2+)**: When not backing up to Google Drive, all shares on Dynamic's backend
1974
+ *
1975
+ * **Process Flow:**
1169
1976
  * 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
1977
+ * 2. For multi-share wallets (2+): conditionally distributes N-1 to backend, 1 to Google Drive
1978
+ * 3. For other configurations: stores all shares on Dynamic's backend
1979
+ * 4. Updates backup metadata and synchronizes wallet state
1980
+ * 5. Persists the updated wallet map to local storage
1981
+ *
1982
+ * **Delegated Key Shares:**
1983
+ * - When delegatedKeyshare is provided, the method will not store the delegated key share but it will mark the delegated share as backed up on Dynamic's backend
1984
+ * - and encrypt the delegated key share to publish it to the webhook
1174
1985
  *
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 }) {
1986
+ * @param params - The backup operation parameters
1987
+ * @param params.accountAddress - The account address of the wallet to backup
1988
+ * @param params.clientKeyShares - Optional specific key shares to backup (uses localStorage if not provided)
1989
+ * @param params.password - Optional password for encryption (uses environment ID if not provided)
1990
+ * @param params.signedSessionId - Optional signed session ID for authentication
1991
+ * @param params.backupToGoogleDrive - Whether to backup to Google Drive (defaults to false)
1992
+ * @returns Promise with backup metadata including share locations and IDs
1993
+ */ async storeEncryptedBackupByWallet({ accountAddress, clientKeyShares = undefined, password = undefined, signedSessionId, backupToGoogleDrive = false, delegatedKeyshare = undefined }) {
1994
+ var _this_walletMap_accountAddress, _this_walletMap_accountAddress1;
1179
1995
  const keySharesToBackup = clientKeyShares != null ? clientKeyShares : await this.getClientKeySharesFromLocalStorage({
1180
1996
  accountAddress
1181
1997
  });
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[core.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
1998
+ const shouldBackupToGoogleDrive = backupToGoogleDrive || hasGoogleDriveBackup((_this_walletMap_accountAddress = this.walletMap[accountAddress]) == null ? void 0 : _this_walletMap_accountAddress.clientKeySharesBackupInfo);
1999
+ const hasExistingDelegation = hasDelegatedBackup((_this_walletMap_accountAddress1 = this.walletMap[accountAddress]) == null ? void 0 : _this_walletMap_accountAddress1.clientKeySharesBackupInfo);
2000
+ let distribution;
2001
+ let preserveDelegatedLocation = false;
2002
+ if (delegatedKeyshare && shouldBackupToGoogleDrive) {
2003
+ // NEW delegation + Google Drive: Client's shares back up to both Dynamic and Google Drive.
2004
+ // The delegated share goes to the webhook.
2005
+ distribution = createDelegationWithGoogleDriveDistribution({
2006
+ existingShares: keySharesToBackup,
2007
+ delegatedShare: delegatedKeyshare
1203
2008
  });
1204
- data.keyShares.push({
1205
- backupLocation: core.BackupLocation.GOOGLE_DRIVE,
1206
- id: googleDriveKeyShareIds
2009
+ } else if (delegatedKeyshare) {
2010
+ // NEW delegation only: Client's shares back up to Dynamic.
2011
+ // The delegated share goes to the webhook. No Google Drive backup.
2012
+ distribution = createDelegationOnlyDistribution({
2013
+ existingShares: keySharesToBackup,
2014
+ delegatedShare: delegatedKeyshare
2015
+ });
2016
+ } else if (hasExistingDelegation && shouldBackupToGoogleDrive) {
2017
+ // ADD Google Drive to EXISTING delegation: Client's share backs up to both Dynamic and GD.
2018
+ // Don't re-publish delegated share, just preserve the location.
2019
+ distribution = createAddGoogleDriveToExistingDelegationDistribution({
2020
+ clientShares: keySharesToBackup
2021
+ });
2022
+ preserveDelegatedLocation = true;
2023
+ } else if (shouldBackupToGoogleDrive && keySharesToBackup.length >= 2) {
2024
+ // Google Drive only (no delegation): Split shares between Dynamic (N-1) and Google Drive (1).
2025
+ distribution = createGoogleDriveOnlyDistribution({
2026
+ allShares: keySharesToBackup
2027
+ });
2028
+ } else {
2029
+ // No delegation, no Google Drive: All shares go to Dynamic backend only.
2030
+ distribution = createDynamicOnlyDistribution({
2031
+ allShares: keySharesToBackup
1207
2032
  });
1208
2033
  }
1209
- const updatedBackupInfo = getClientKeyShareBackupInfo({
1210
- walletProperties: {
1211
- derivationPath: this.walletMap[accountAddress].derivationPath,
1212
- keyShares: data.keyShares,
1213
- thresholdSignatureScheme: this.walletMap[accountAddress].thresholdSignatureScheme
1214
- }
2034
+ const backupData = await this.backupSharesWithDistribution({
2035
+ accountAddress,
2036
+ password,
2037
+ signedSessionId,
2038
+ distribution,
2039
+ preserveDelegatedLocation
1215
2040
  });
1216
- this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
1217
- clientKeySharesBackupInfo: updatedBackupInfo
2041
+ return _extends({}, backupData, {
2042
+ keyShareIds: backupData.locationsWithKeyShares.map((ks)=>ks.keyShareId)
1218
2043
  });
1219
- await this.storage.setItem(this.storageKey, JSON.stringify(this.walletMap));
1220
- return data;
1221
2044
  }
1222
- async storeEncryptedBackupByWalletWithRetry({ accountAddress, clientKeyShares, password }) {
2045
+ async storeEncryptedBackupByWalletWithRetry({ accountAddress, clientKeyShares, password, signedSessionId }) {
1223
2046
  await retryPromise(()=>this.storeEncryptedBackupByWallet({
1224
2047
  accountAddress,
1225
2048
  clientKeyShares,
1226
- password
2049
+ password,
2050
+ signedSessionId
1227
2051
  }), {
1228
2052
  operationName: 'store encrypted backup',
1229
2053
  logContext: {
1230
- walletAddress: accountAddress,
1231
- keyShares: clientKeyShares == null ? void 0 : clientKeyShares.map((keyShare)=>this.encryptKeyShare({
1232
- keyShare,
1233
- password
1234
- }))
2054
+ walletAddress: accountAddress
1235
2055
  }
1236
2056
  });
1237
2057
  }
1238
- async updatePassword({ accountAddress, existingPassword, newPassword }) {
2058
+ async updatePassword({ accountAddress, existingPassword, newPassword, signedSessionId }) {
1239
2059
  await this.getWallet({
1240
2060
  accountAddress,
1241
2061
  password: existingPassword,
1242
- walletOperation: core.WalletOperation.REACH_ALL_PARTIES
2062
+ walletOperation: core.WalletOperation.REACH_ALL_PARTIES,
2063
+ signedSessionId
1243
2064
  });
1244
2065
  await this.storeEncryptedBackupByWallet({
1245
2066
  accountAddress,
1246
- password: newPassword
2067
+ password: newPassword,
2068
+ signedSessionId
1247
2069
  });
1248
2070
  }
1249
2071
  async decryptKeyShare({ keyShare, password }) {
@@ -1256,6 +2078,41 @@ class DynamicWalletClient {
1256
2078
  return deserializedKeyShare;
1257
2079
  }
1258
2080
  /**
2081
+ * Helper function to get Google OAuth Account ID or throw an error if not found.
2082
+ * @param accountAddress - The account address for logging purposes
2083
+ * @returns The Google OAuth Account ID
2084
+ * @throws Error if no Google OAuth account ID is found
2085
+ */ async getGoogleOauthAccountIdOrThrow(accountAddress) {
2086
+ const dynamicRequestId = uuid.v4();
2087
+ try {
2088
+ const user = await this.apiClient.getUser(dynamicRequestId);
2089
+ const oauthAccountId = getGoogleOAuthAccountId(user == null ? void 0 : user.verifiedCredentials);
2090
+ if (!oauthAccountId) {
2091
+ const error = new Error('No Google OAuth account ID found');
2092
+ logError({
2093
+ message: 'No Google OAuth account ID found',
2094
+ error,
2095
+ context: {
2096
+ user,
2097
+ accountAddress
2098
+ }
2099
+ });
2100
+ throw error;
2101
+ }
2102
+ return oauthAccountId;
2103
+ } catch (error) {
2104
+ logError({
2105
+ message: 'Error in getGoogleOauthAccountIdOrThrow',
2106
+ error: error,
2107
+ context: {
2108
+ accountAddress,
2109
+ dynamicRequestId
2110
+ }
2111
+ });
2112
+ throw error;
2113
+ }
2114
+ }
2115
+ /**
1259
2116
  * Helper function to determine keyshare recovery strategy for dynamic shares.
1260
2117
  * For REFRESH operations, retrieves enough shares to meet the client threshold.
1261
2118
  * For all other operations, retrieves just 1 share.
@@ -1277,39 +2134,58 @@ class DynamicWalletClient {
1277
2134
  const dynamicShares = backups[core.BackupLocation.DYNAMIC].slice(0, requiredShareCount);
1278
2135
  return {
1279
2136
  shares: {
1280
- [core.BackupLocation.DYNAMIC]: dynamicShares
2137
+ [core.BackupLocation.DYNAMIC]: dynamicShares.map((ks)=>{
2138
+ var _ks_externalKeyShareId, _ref;
2139
+ return (_ref = (_ks_externalKeyShareId = ks.externalKeyShareId) != null ? _ks_externalKeyShareId : ks.keyShareId) != null ? _ref : '';
2140
+ })
1281
2141
  },
1282
2142
  requiredShareCount
1283
2143
  };
1284
2144
  }
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 === core.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'
2145
+ async recoverEncryptedBackupByWallet({ accountAddress, password, walletOperation, signedSessionId, shareCount = undefined, storeRecoveredShares = true, mfaToken }) {
2146
+ try {
2147
+ const wallet = this.walletMap[accountAddress];
2148
+ this.logger.debug(`recoverEncryptedBackupByWallet wallet: ${walletOperation}`, wallet);
2149
+ const { shares } = this.recoverStrategy({
2150
+ clientKeyShareBackupInfo: wallet.clientKeySharesBackupInfo,
2151
+ thresholdSignatureScheme: wallet.thresholdSignatureScheme,
2152
+ walletOperation,
2153
+ shareCount
1309
2154
  });
1310
- await this.storage.setItem(this.storageKey, JSON.stringify(this.walletMap));
2155
+ const { dynamic: dynamicKeyShareIds } = shares;
2156
+ const data = await this.apiClient.recoverEncryptedBackupByWallet({
2157
+ walletId: wallet.walletId,
2158
+ keyShareIds: dynamicKeyShareIds,
2159
+ signedSessionId,
2160
+ mfaToken,
2161
+ requiresSignedSessionId: this.requiresSignedSessionId()
2162
+ });
2163
+ const dynamicKeyShares = data.keyShares.filter((keyShare)=>keyShare.encryptedAccountCredential !== null && keyShare.backupLocation === core.BackupLocation.DYNAMIC);
2164
+ const decryptedKeyShares = await Promise.all(dynamicKeyShares.map((keyShare)=>this.decryptKeyShare({
2165
+ keyShare: keyShare.encryptedAccountCredential,
2166
+ password: password != null ? password : this.environmentId
2167
+ })));
2168
+ if (storeRecoveredShares) {
2169
+ await this.setClientKeySharesToLocalStorage({
2170
+ accountAddress,
2171
+ clientKeyShares: decryptedKeyShares,
2172
+ overwriteOrMerge: 'merge'
2173
+ });
2174
+ await this.storage.setItem(this.storageKey, JSON.stringify(this.walletMap));
2175
+ }
2176
+ return decryptedKeyShares;
2177
+ } catch (error) {
2178
+ logError({
2179
+ message: 'Error in recoverEncryptedBackupByWallet',
2180
+ error: error,
2181
+ context: {
2182
+ accountAddress,
2183
+ walletOperation,
2184
+ shareCount
2185
+ }
2186
+ });
2187
+ throw error;
1311
2188
  }
1312
- return decryptedKeyShares;
1313
2189
  }
1314
2190
  async restoreWallets() {
1315
2191
  const wallets = await this.storage.getItem(this.storageKey);
@@ -1318,114 +2194,207 @@ class DynamicWalletClient {
1318
2194
  }
1319
2195
  this.walletMap = JSON.parse(wallets);
1320
2196
  }
1321
- async backupKeySharesToGoogleDrive({ accountAddress, fileName, oauthAccountId, password }) {
1322
- await this.getWallet({
1323
- accountAddress,
1324
- walletOperation: core.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(),
2197
+ /**
2198
+ * This method handles the complete flow for ensuring wallet key shares are backed up to Google Drive:
2199
+ * - For 2-of-2 wallets: Automatically reshares to 2-of-3 threshold, then distributes shares (1 to backend, 1 to Google Drive)
2200
+ * - For 2-of-3 wallets: Call storeEncryptedBackupByWallet to backup for backend and Google Drive
2201
+ *
2202
+ * @param params - The backup parameters
2203
+ * @param params.accountAddress - The wallet account address to backup
2204
+ * @param params.password - Optional password for encryption (uses environment ID if not provided)
2205
+ * @param params.signedSessionId - Optional signed session ID for authentication
2206
+ * @returns Promise<string[]> - Array of Google Drive key share IDs that were backed up
2207
+ */ async backupKeySharesToGoogleDrive({ accountAddress, password, signedSessionId }) {
2208
+ try {
2209
+ await this.getWallet({
1350
2210
  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
2211
+ walletOperation: core.WalletOperation.REACH_ALL_PARTIES,
2212
+ password,
2213
+ signedSessionId
2214
+ });
2215
+ const currentThresholdSignatureScheme = this.walletMap[accountAddress].thresholdSignatureScheme;
2216
+ if (currentThresholdSignatureScheme === core.ThresholdSignatureScheme.TWO_OF_TWO) {
2217
+ // Reshare to 2-of-3, which will automatically handle the backup distribution
2218
+ await this.reshare({
2219
+ chainName: this.walletMap[accountAddress].chainName,
2220
+ accountAddress,
2221
+ oldThresholdSignatureScheme: currentThresholdSignatureScheme,
2222
+ newThresholdSignatureScheme: core.ThresholdSignatureScheme.TWO_OF_THREE,
2223
+ password,
2224
+ signedSessionId,
2225
+ backupToGoogleDrive: true
2226
+ });
2227
+ const backupInfo = this.walletMap[accountAddress].clientKeySharesBackupInfo;
2228
+ const googleDriveShares = backupInfo.backups[core.BackupLocation.GOOGLE_DRIVE] || [];
2229
+ return googleDriveShares.map((ks)=>{
2230
+ var _ks_externalKeyShareId;
2231
+ return (_ks_externalKeyShareId = ks.externalKeyShareId) != null ? _ks_externalKeyShareId : '';
2232
+ });
2233
+ } else {
2234
+ await this.storeEncryptedBackupByWallet({
2235
+ accountAddress,
2236
+ password,
2237
+ signedSessionId,
2238
+ backupToGoogleDrive: true
2239
+ });
2240
+ const backupInfo = this.walletMap[accountAddress].clientKeySharesBackupInfo;
2241
+ const googleDriveShares = backupInfo.backups[core.BackupLocation.GOOGLE_DRIVE] || [];
2242
+ return googleDriveShares.map((ks)=>{
2243
+ var _ks_externalKeyShareId;
2244
+ return (_ks_externalKeyShareId = ks.externalKeyShareId) != null ? _ks_externalKeyShareId : '';
2245
+ });
1361
2246
  }
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[core.BackupLocation.GOOGLE_DRIVE] = ids;
1381
- await this.storage.setItem(this.storageKey, JSON.stringify(this.walletMap));
1382
- return ids;
2247
+ } catch (error) {
2248
+ logError({
2249
+ message: 'Error in backupKeySharesToGoogleDrive',
2250
+ error: error,
2251
+ context: {
2252
+ accountAddress
2253
+ }
2254
+ });
2255
+ throw error;
2256
+ }
1383
2257
  }
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');
2258
+ /**
2259
+ * This method handles only the Google Drive upload mechanics without any reshare logic.
2260
+ * It encrypts the provided key shares, uploads them to Google Drive, and updates the
2261
+ * backup metadata. This method is intended for internal use by storeEncryptedBackupByWallet
2262
+ * and should not be called directly from external code.
2263
+ *
2264
+ * @param params - The upload parameters
2265
+ * @param params.accountAddress - The wallet account address
2266
+ * @param params.password - Optional password for encryption (uses environment ID if not provided)
2267
+ * @param params.encryptedKeyShares - The specific key shares to upload to Google Drive
2268
+ * @returns Promise<string[]> - Array of Google Drive key share IDs that were uploaded
2269
+ */ async uploadKeySharesToGoogleDrive({ accountAddress, encryptedKeyShares }) {
2270
+ try {
2271
+ if (encryptedKeyShares.length === 0) {
2272
+ throw new Error('No key shares found');
2273
+ }
2274
+ const oauthAccountId = await this.getGoogleOauthAccountIdOrThrow(accountAddress);
2275
+ const accessToken = await this.apiClient.getAccessToken({
2276
+ oauthAccountId
2277
+ });
2278
+ const thresholdSignatureScheme = this.walletMap[accountAddress].thresholdSignatureScheme;
2279
+ const fileName = getClientKeyShareExportFileName({
2280
+ thresholdSignatureScheme,
2281
+ accountAddress,
2282
+ isGoogleDrive: true
2283
+ });
2284
+ const backupData = createBackupData({
2285
+ encryptedKeyShares,
2286
+ accountAddress,
2287
+ thresholdSignatureScheme
2288
+ });
2289
+ await uploadBackupToGoogleDrive({
2290
+ accessToken,
2291
+ fileName,
2292
+ backupData,
2293
+ accountAddress
2294
+ });
2295
+ return;
2296
+ } catch (error) {
2297
+ logError({
2298
+ message: 'Error in backupKeySharesToGoogleDrive',
2299
+ error: error,
2300
+ context: {
2301
+ accountAddress
2302
+ }
2303
+ });
2304
+ throw error;
1402
2305
  }
1403
- // Validate the backup data structure
1404
- if (!backupData.keyShares || !backupData.metadata) {
1405
- throw new Error('Invalid backup format: missing keyShares or metadata');
2306
+ }
2307
+ async exportClientKeysharesFromGoogleDrive({ accountAddress, password, signedSessionId }) {
2308
+ try {
2309
+ await this.getWallet({
2310
+ accountAddress,
2311
+ signedSessionId
2312
+ });
2313
+ const oauthAccountId = await this.getGoogleOauthAccountIdOrThrow(accountAddress);
2314
+ const accessToken = await this.apiClient.getAccessToken({
2315
+ oauthAccountId
2316
+ });
2317
+ const thresholdSignatureScheme = this.walletMap[accountAddress].thresholdSignatureScheme;
2318
+ const backupFileName = getClientKeyShareExportFileName({
2319
+ thresholdSignatureScheme,
2320
+ accountAddress,
2321
+ isGoogleDrive: true
2322
+ });
2323
+ let backupData = null;
2324
+ try {
2325
+ backupData = await retryPromise(()=>downloadFileFromGoogleDrive({
2326
+ accessToken,
2327
+ fileName: backupFileName
2328
+ }));
2329
+ } catch (error) {
2330
+ logError({
2331
+ message: 'Failed to download backup from Google Drive',
2332
+ error: error,
2333
+ context: {
2334
+ accountAddress,
2335
+ fileName: backupFileName
2336
+ }
2337
+ });
2338
+ throw error;
2339
+ }
2340
+ if (!backupData) {
2341
+ const error = new Error('No backup file found');
2342
+ logError({
2343
+ message: 'No backup file found',
2344
+ error: new Error('No backup file found'),
2345
+ context: {
2346
+ accountAddress,
2347
+ fileName: backupFileName
2348
+ }
2349
+ });
2350
+ throw error;
2351
+ }
2352
+ // Validate the backup data structure
2353
+ if (!backupData.keyShares || !backupData.metadata) {
2354
+ const error = new Error('Invalid backup format: missing keyShares or metadata');
2355
+ logError({
2356
+ message: 'Invalid backup format: missing keyShares or metadata',
2357
+ error,
2358
+ context: {
2359
+ accountAddress,
2360
+ fileName: backupFileName
2361
+ }
2362
+ });
2363
+ throw error;
2364
+ }
2365
+ const { keyShares } = backupData;
2366
+ const decryptedKeyShares = await Promise.all(keyShares.map((keyShare)=>this.decryptKeyShare({
2367
+ keyShare,
2368
+ password
2369
+ })));
2370
+ const text = JSON.stringify(decryptedKeyShares, null, 2);
2371
+ const exportFileName = getClientKeyShareExportFileName({
2372
+ thresholdSignatureScheme,
2373
+ accountAddress,
2374
+ isGoogleDrive: true
2375
+ });
2376
+ downloadStringAsFile({
2377
+ filename: exportFileName,
2378
+ content: text,
2379
+ mimeType: 'application/json'
2380
+ });
2381
+ } catch (error) {
2382
+ logError({
2383
+ message: 'Error in exportClientKeysharesFromGoogleDrive',
2384
+ error: error,
2385
+ context: {
2386
+ accountAddress
2387
+ }
2388
+ });
2389
+ throw error;
1406
2390
  }
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
2391
  }
1424
- async exportClientKeyshares({ accountAddress, password }) {
2392
+ async exportClientKeyshares({ accountAddress, password, signedSessionId }) {
1425
2393
  await this.verifyPassword({
1426
2394
  accountAddress,
1427
2395
  password,
1428
- walletOperation: core.WalletOperation.REACH_ALL_PARTIES
2396
+ walletOperation: core.WalletOperation.REACH_ALL_PARTIES,
2397
+ signedSessionId
1429
2398
  });
1430
2399
  const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
1431
2400
  accountAddress
@@ -1438,22 +2407,18 @@ class DynamicWalletClient {
1438
2407
  keyShares: clientKeyShares,
1439
2408
  derivationPath
1440
2409
  });
1441
- const blob = new Blob([
1442
- text
1443
- ], {
1444
- type: 'text/plain'
2410
+ downloadStringAsFile({
2411
+ filename: `${CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX}-${accountAddress}.txt`,
2412
+ content: text,
2413
+ mimeType: 'text/plain'
1445
2414
  });
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
2415
  }
1452
- async getClientKeyShares({ accountAddress, password }) {
2416
+ async getClientKeyShares({ accountAddress, password, signedSessionId }) {
1453
2417
  await this.getWallet({
1454
2418
  accountAddress,
1455
2419
  password,
1456
- walletOperation: core.WalletOperation.REACH_THRESHOLD
2420
+ walletOperation: core.WalletOperation.REACH_THRESHOLD,
2421
+ signedSessionId
1457
2422
  });
1458
2423
  return this.getClientKeySharesFromLocalStorage({
1459
2424
  accountAddress
@@ -1506,11 +2471,12 @@ class DynamicWalletClient {
1506
2471
  * verifyPassword attempts to recover and decrypt a single client key share using the provided password.
1507
2472
  * If successful, the key share is encrypted with the new password. This method solely performs the recovery
1508
2473
  * and decryption without storing the restored key shares. If unsuccessful, it throws an error.
1509
- */ async verifyPassword({ accountAddress, password = undefined, walletOperation = core.WalletOperation.NO_OPERATION }) {
2474
+ */ async verifyPassword({ accountAddress, password = undefined, walletOperation = core.WalletOperation.NO_OPERATION, signedSessionId }) {
1510
2475
  await this.getWallet({
1511
2476
  accountAddress,
1512
2477
  password,
1513
- walletOperation
2478
+ walletOperation,
2479
+ signedSessionId
1514
2480
  });
1515
2481
  if (await this.requiresPasswordForOperation({
1516
2482
  accountAddress,
@@ -1530,16 +2496,24 @@ class DynamicWalletClient {
1530
2496
  throw new Error('No dynamic key shares found');
1531
2497
  }
1532
2498
  try {
2499
+ // TODO(zfaizal2): throw error if signedSessionId is not provided after service deploy
1533
2500
  await this.recoverEncryptedBackupByWallet({
1534
2501
  accountAddress,
1535
2502
  password,
1536
2503
  walletOperation,
1537
2504
  shareCount: 1,
1538
- storeRecoveredShares: false
2505
+ storeRecoveredShares: false,
2506
+ signedSessionId
1539
2507
  });
1540
2508
  } catch (error) {
1541
- this.logger.error('Error in verifying password', error);
1542
- throw new Error('Incorrect password');
2509
+ logError({
2510
+ message: 'Error in verifyPassword',
2511
+ error: error,
2512
+ context: {
2513
+ accountAddress
2514
+ }
2515
+ });
2516
+ throw error;
1543
2517
  }
1544
2518
  }
1545
2519
  async isPasswordEncrypted({ accountAddress }) {
@@ -1585,127 +2559,204 @@ class DynamicWalletClient {
1585
2559
  return true;
1586
2560
  }
1587
2561
  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[core.BackupLocation.DYNAMIC]) == null ? void 0 : _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups_BackupLocation_DYNAMIC.length) > 0) {
1591
- return this.walletMap[accountAddress].clientKeySharesBackupInfo;
2562
+ const dynamicRequestId = uuid.v4();
2563
+ try {
2564
+ var _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups_BackupLocation_DYNAMIC, _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups, _this_walletMap_accountAddress_clientKeySharesBackupInfo, _this_walletMap_accountAddress, _user_verifiedCredentials;
2565
+ // Return existing backup info if it exists
2566
+ 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[core.BackupLocation.DYNAMIC]) == null ? void 0 : _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups_BackupLocation_DYNAMIC.length) > 0) {
2567
+ return this.walletMap[accountAddress].clientKeySharesBackupInfo;
2568
+ }
2569
+ // Get backup info from server
2570
+ const user = await this.apiClient.getUser(dynamicRequestId);
2571
+ const wallet = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.find((vc)=>vc.address.toLowerCase() === accountAddress.toLowerCase());
2572
+ return getClientKeyShareBackupInfo({
2573
+ walletProperties: wallet == null ? void 0 : wallet.walletProperties
2574
+ });
2575
+ } catch (error) {
2576
+ logError({
2577
+ message: 'Error in getWalletClientKeyShareBackupInfo',
2578
+ error: error,
2579
+ context: {
2580
+ accountAddress,
2581
+ dynamicRequestId
2582
+ }
2583
+ });
2584
+ throw error;
1592
2585
  }
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
2586
  }
1600
- async getWallet({ accountAddress, walletOperation = core.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 !== core.WalletOperation.NO_OPERATION && await this.requiresRestoreBackupSharesForOperation({
1627
- accountAddress,
1628
- walletOperation
1629
- })) {
1630
- const decryptedKeyShares = await this.recoverEncryptedBackupByWallet({
2587
+ async getWallet({ accountAddress, walletOperation = core.WalletOperation.NO_OPERATION, shareCount = undefined, password = undefined, signedSessionId }) {
2588
+ const dynamicRequestId = uuid.v4();
2589
+ try {
2590
+ var _user_verifiedCredentials;
2591
+ const existingWalletCheck = await this.checkWalletFields({
1631
2592
  accountAddress,
1632
- password: password != null ? password : this.environmentId,
1633
- walletOperation: walletOperation,
2593
+ walletOperation,
1634
2594
  shareCount
1635
2595
  });
1636
- const existingKeyShares = await this.getClientKeySharesFromLocalStorage({
1637
- accountAddress
2596
+ if (existingWalletCheck) {
2597
+ this.logger.debug(`[DynamicWaasWalletClient] Wallet ${accountAddress} already exists`);
2598
+ return this.walletMap[accountAddress];
2599
+ }
2600
+ // Fetch and restore wallet from server
2601
+ const user = await this.apiClient.getUser(dynamicRequestId);
2602
+ const wallet = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.find((vc)=>vc.address.toLowerCase() === accountAddress.toLowerCase());
2603
+ this.logger.debug('[DynamicWaasWalletClient] Restoring wallet', wallet);
2604
+ const walletProperties = wallet.walletProperties;
2605
+ this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
2606
+ walletId: wallet.id,
2607
+ chainName: core.verifiedCredentialNameToChainEnum[wallet.chain],
2608
+ accountAddress,
2609
+ thresholdSignatureScheme: walletProperties.thresholdSignatureScheme,
2610
+ derivationPath: walletProperties.derivationPath,
2611
+ clientKeySharesBackupInfo: getClientKeyShareBackupInfo({
2612
+ walletProperties
2613
+ })
1638
2614
  });
1639
- await this.setClientKeySharesToLocalStorage({
2615
+ if (walletOperation !== core.WalletOperation.NO_OPERATION && await this.requiresRestoreBackupSharesForOperation({
1640
2616
  accountAddress,
1641
- clientKeyShares: mergeUniqueKeyShares(existingKeyShares, decryptedKeyShares)
2617
+ walletOperation
2618
+ })) {
2619
+ // TODO(zfaizal2): throw error if signedSessionId is not provided after service deploy
2620
+ const decryptedKeyShares = await this.recoverEncryptedBackupByWallet({
2621
+ accountAddress,
2622
+ password: password != null ? password : this.environmentId,
2623
+ walletOperation: walletOperation,
2624
+ signedSessionId,
2625
+ shareCount
2626
+ });
2627
+ const existingKeyShares = await this.getClientKeySharesFromLocalStorage({
2628
+ accountAddress
2629
+ });
2630
+ await this.setClientKeySharesToLocalStorage({
2631
+ accountAddress,
2632
+ clientKeyShares: mergeUniqueKeyShares(existingKeyShares, decryptedKeyShares)
2633
+ });
2634
+ this.logger.debug('[DynamicWaasWalletClient] Recovered backup', decryptedKeyShares);
2635
+ }
2636
+ const walletCount = Object.keys(this.walletMap).length;
2637
+ if (walletCount === 0) {
2638
+ throw new Error('No wallets found');
2639
+ }
2640
+ // Return the only wallet if there's just one
2641
+ if (walletCount === 1) {
2642
+ return Object.values(this.walletMap)[0];
2643
+ }
2644
+ return this.walletMap[accountAddress];
2645
+ } catch (error) {
2646
+ logError({
2647
+ message: 'Error in getWallet',
2648
+ error: error,
2649
+ context: {
2650
+ accountAddress,
2651
+ walletOperation,
2652
+ shareCount,
2653
+ dynamicRequestId
2654
+ }
1642
2655
  });
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];
2656
+ throw error;
1652
2657
  }
1653
- return this.walletMap[accountAddress];
1654
2658
  }
1655
2659
  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;
2660
+ const dynamicRequestId = uuid.v4();
2661
+ try {
2662
+ var _user_verifiedCredentials;
2663
+ const user = await this.apiClient.getUser(dynamicRequestId);
2664
+ this.userId = user.id;
2665
+ const waasWallets = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.filter((vc)=>vc.walletName === 'dynamicwaas');
2666
+ const wallets = waasWallets.map((vc)=>{
2667
+ var _this_walletMap_vc_address, _vc_walletProperties;
2668
+ var _this_walletMap_vc_address_derivationPath;
2669
+ return {
2670
+ walletId: vc.id,
2671
+ chainName: vc.chain,
2672
+ accountAddress: vc.address,
2673
+ clientKeySharesBackupInfo: getClientKeyShareBackupInfo({
2674
+ walletProperties: vc.walletProperties || {}
2675
+ }),
2676
+ 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,
2677
+ thresholdSignatureScheme: (_vc_walletProperties = vc.walletProperties) == null ? void 0 : _vc_walletProperties.thresholdSignatureScheme
2678
+ };
2679
+ });
2680
+ this.walletMap = wallets.reduce((acc, wallet)=>{
2681
+ var _acc_accountAddress;
2682
+ const accountAddress = wallet.accountAddress;
2683
+ acc[wallet.accountAddress] = {
2684
+ walletId: wallet.walletId,
2685
+ chainName: wallet.chainName,
2686
+ accountAddress: wallet.accountAddress,
2687
+ clientKeySharesBackupInfo: wallet.clientKeySharesBackupInfo,
2688
+ derivationPath: ((_acc_accountAddress = acc[accountAddress]) == null ? void 0 : _acc_accountAddress.derivationPath) || undefined,
2689
+ thresholdSignatureScheme: wallet.thresholdSignatureScheme
2690
+ };
2691
+ return acc;
2692
+ }, {});
2693
+ return wallets;
2694
+ } catch (error) {
2695
+ logError({
2696
+ message: 'Error in getWallets',
2697
+ error: error,
2698
+ context: {
2699
+ dynamicRequestId
2700
+ }
2701
+ });
2702
+ throw error;
2703
+ }
2704
+ }
2705
+ /**
2706
+ * sync auth token with api client
2707
+ * @param authToken - auth token to sync
2708
+ */ syncAuthToken(authToken) {
2709
+ if (this.authMode === core.AuthMode.COOKIE) {
2710
+ return;
2711
+ }
2712
+ this.apiClient.syncAuthToken(authToken);
1687
2713
  }
1688
- constructor({ environmentId, authToken, baseApiUrl, baseMPCRelayApiUrl, storageKey, debug }){
2714
+ /**
2715
+ * Helper method to instrument with automatic properties inclusion
2716
+ */ instrument(message, context) {
2717
+ const defaultContext = {
2718
+ environmentId: context.environmentId || this.environmentId
2719
+ };
2720
+ this.logger.debug(message, _extends({}, defaultContext, context));
2721
+ this.logger.instrument(message, _extends({}, defaultContext, context));
2722
+ }
2723
+ getTraceContext(traceContext) {
2724
+ const now = Date.now();
2725
+ return _extends({
2726
+ now,
2727
+ time: (traceContext == null ? void 0 : traceContext.startTime) ? now - traceContext.startTime : 0
2728
+ }, traceContext);
2729
+ }
2730
+ constructor({ environmentId, baseApiUrl, baseMPCRelayApiUrl, storageKey, debug, featureFlags, authMode = core.AuthMode.HEADER, authToken = undefined, // Represents the version of the client SDK used by developer
2731
+ sdkVersion, forwardMPCClient, baseClientKeysharesRelayApiUrl }){
2732
+ this.userId = undefined;
2733
+ this.sessionId = undefined;
1689
2734
  this.initializePromise = null;
1690
2735
  this.logger = logger;
1691
2736
  this.walletMap = {} // todo: store in session storage
1692
2737
  ;
1693
- this.iframeStorage = null;
1694
- this.iframeDisplay = null;
1695
2738
  this.memoryStorage = null;
1696
- this.messageTransport = null;
1697
2739
  this.iframe = null;
1698
- this.iframeLoadPromise = null;
2740
+ this.forwardMPCEnabled = false;
2741
+ this.featureFlags = {};
1699
2742
  this.environmentId = environmentId;
1700
2743
  this.storageKey = `${STORAGE_KEY}-${storageKey != null ? storageKey : environmentId}`;
1701
2744
  this.baseMPCRelayApiUrl = baseMPCRelayApiUrl;
2745
+ this.authMode = authMode;
2746
+ this.sdkVersion = sdkVersion;
2747
+ this.baseClientKeysharesRelayApiUrl = baseClientKeysharesRelayApiUrl;
1702
2748
  this.apiClient = new core.DynamicApiClient({
1703
2749
  environmentId,
1704
2750
  authToken,
1705
- baseApiUrl
2751
+ baseApiUrl,
2752
+ authMode,
2753
+ sdkVersion,
2754
+ forwardMPCClient,
2755
+ baseClientKeysharesRelayApiUrl
1706
2756
  });
1707
2757
  this.debug = Boolean(debug);
1708
- this.logger.setLogLevel(this.debug ? 'DEBUG' : DEFAULT_LOG_LEVEL);
2758
+ this.logger.setLogLevel(this.debug ? logger$1.LogLevel.DEBUG : DEFAULT_LOG_LEVEL);
2759
+ this.featureFlags = featureFlags || {};
1709
2760
  // setup storage
1710
2761
  if (supportsLocalStorage()) {
1711
2762
  this.storage = localStorageAdapter;
@@ -1713,15 +2764,32 @@ class DynamicWalletClient {
1713
2764
  this.memoryStorage = {};
1714
2765
  this.storage = memoryLocalStorageAdapter(this.memoryStorage);
1715
2766
  }
1716
- // TODO: handle prod and preprod iframe domains
1717
- this.iframeDomain = core.IFRAME_DOMAIN_PREPROD;
2767
+ const environment = core.getEnvironmentFromUrl(baseApiUrl);
2768
+ this.iframeDomain = core.IFRAME_DOMAIN_MAP[environment];
1718
2769
  // Generate unique instanceId when client is created
1719
- this.instanceId = crypto.randomUUID();
1720
- // initialize the client
1721
- this.initialize();
2770
+ this.instanceId = uuid.v4();
2771
+ // initialize logger context
2772
+ if (authMode === core.AuthMode.HEADER && authToken) {
2773
+ this.initLoggerContext(authToken);
2774
+ }
2775
+ this.forwardMPCEnabled = this.featureFlags && this.featureFlags[core.FEATURE_FLAGS.ENABLE_FORWARD_MPC_CLIENT_FLAG] === true;
2776
+ // if forwardMPCEnabled is true and forwardMPCClient is not provided, initialize the forwardMPCClient
2777
+ if (this.forwardMPCEnabled && !forwardMPCClient) {
2778
+ this.initializeForwardMPCClient();
2779
+ }
1722
2780
  }
1723
2781
  }
1724
2782
 
2783
+ const ERROR_KEYGEN_FAILED = '[DynamicWaasWalletClient]: Error with keygen';
2784
+ const ERROR_CREATE_WALLET_ACCOUNT = '[DynamicWaasWalletClient]: Error creating wallet account';
2785
+ const ERROR_SIGN_MESSAGE = '[DynamicWaasWalletClient]: Error signing message';
2786
+ const ERROR_SIGN_TYPED_DATA = '[DynamicWaasWalletClient]: Error signing typed data';
2787
+ const ERROR_ACCOUNT_ADDRESS_REQUIRED = '[DynamicWaasWalletClient]: Account address is required';
2788
+ const ERROR_VERIFY_MESSAGE_SIGNATURE = '[DynamicWaasWalletClient]: Error verifying message signature';
2789
+ const ERROR_VERIFY_TRANSACTION_SIGNATURE = '[DynamicWaasWalletClient]: Error verifying transaction signature';
2790
+ const ERROR_EXPORT_PRIVATE_KEY = '[DynamicWaasWalletClient]: Error exporting private key';
2791
+ const ERROR_IMPORT_PRIVATE_KEY = '[DynamicWaasWalletClient]: Error importing private key';
2792
+
1725
2793
  Object.defineProperty(exports, "BIP340", {
1726
2794
  enumerable: true,
1727
2795
  get: function () { return web.BIP340; }
@@ -1770,19 +2838,40 @@ Object.defineProperty(exports, "MessageHash", {
1770
2838
  enumerable: true,
1771
2839
  get: function () { return web.MessageHash; }
1772
2840
  });
2841
+ Object.defineProperty(exports, "Logger", {
2842
+ enumerable: true,
2843
+ get: function () { return logger$1.Logger; }
2844
+ });
1773
2845
  exports.DynamicWalletClient = DynamicWalletClient;
1774
- exports.base64ToBytes = base64ToBytes;
1775
- exports.bytesToBase64 = bytesToBase64;
1776
- exports.ensureBase64Padding = ensureBase64Padding;
2846
+ exports.ERROR_ACCOUNT_ADDRESS_REQUIRED = ERROR_ACCOUNT_ADDRESS_REQUIRED;
2847
+ exports.ERROR_CREATE_WALLET_ACCOUNT = ERROR_CREATE_WALLET_ACCOUNT;
2848
+ exports.ERROR_EXPORT_PRIVATE_KEY = ERROR_EXPORT_PRIVATE_KEY;
2849
+ exports.ERROR_IMPORT_PRIVATE_KEY = ERROR_IMPORT_PRIVATE_KEY;
2850
+ exports.ERROR_KEYGEN_FAILED = ERROR_KEYGEN_FAILED;
2851
+ exports.ERROR_SIGN_MESSAGE = ERROR_SIGN_MESSAGE;
2852
+ exports.ERROR_SIGN_TYPED_DATA = ERROR_SIGN_TYPED_DATA;
2853
+ exports.ERROR_VERIFY_MESSAGE_SIGNATURE = ERROR_VERIFY_MESSAGE_SIGNATURE;
2854
+ exports.ERROR_VERIFY_TRANSACTION_SIGNATURE = ERROR_VERIFY_TRANSACTION_SIGNATURE;
2855
+ exports.createAddGoogleDriveToExistingDelegationDistribution = createAddGoogleDriveToExistingDelegationDistribution;
2856
+ exports.createBackupData = createBackupData;
2857
+ exports.createDelegationOnlyDistribution = createDelegationOnlyDistribution;
2858
+ exports.createDelegationWithGoogleDriveDistribution = createDelegationWithGoogleDriveDistribution;
2859
+ exports.createDynamicOnlyDistribution = createDynamicOnlyDistribution;
2860
+ exports.createGoogleDriveOnlyDistribution = createGoogleDriveOnlyDistribution;
2861
+ exports.downloadStringAsFile = downloadStringAsFile;
2862
+ exports.formatEvmMessage = formatEvmMessage;
2863
+ exports.formatMessage = formatMessage;
1777
2864
  exports.getClientKeyShareBackupInfo = getClientKeyShareBackupInfo;
1778
2865
  exports.getClientKeyShareExportFileName = getClientKeyShareExportFileName;
2866
+ exports.getGoogleOAuthAccountId = getGoogleOAuthAccountId;
1779
2867
  exports.getMPCSignatureScheme = getMPCSignatureScheme;
1780
2868
  exports.getMPCSigner = getMPCSigner;
2869
+ exports.hasDelegatedBackup = hasDelegatedBackup;
2870
+ exports.hasGoogleDriveBackup = hasGoogleDriveBackup;
1781
2871
  exports.isBrowser = isBrowser;
1782
2872
  exports.isHexString = isHexString;
1783
2873
  exports.mergeUniqueKeyShares = mergeUniqueKeyShares;
1784
2874
  exports.retryPromise = retryPromise;
1785
- exports.stringToBytes = stringToBytes;
1786
2875
  exports.timeoutPromise = timeoutPromise;
1787
2876
  Object.keys(core).forEach(function (k) {
1788
2877
  if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {