@dynamic-labs-wallet/browser 0.0.325 → 0.0.327
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{index.cjs.js → index.cjs} +285 -53
- package/index.esm.js +286 -55
- package/package.json +5 -4
- package/src/backup/providers/googleDrive.d.ts +25 -0
- package/src/backup/providers/googleDrive.d.ts.map +1 -1
- package/src/backup/utils.d.ts.map +1 -1
- package/src/client.d.ts +11 -0
- package/src/client.d.ts.map +1 -1
- package/src/utils.d.ts +24 -1
- package/src/utils.d.ts.map +1 -1
- /package/{index.cjs.d.ts → index.d.ts} +0 -0
package/index.esm.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BitcoinAddressType, SigningAlgorithm, MPC_RELAY_PROD_API_URL, getMPCChainConfig, BackupLocation, ENCRYPTED_SHARES_STORAGE_SUFFIX, Logger, handleAxiosError, WalletOperation, WalletReadyState, parseNamespacedVersion, FEATURE_FLAGS, ThresholdSignatureScheme, getClientThreshold, MPC_CONFIG, getTSSConfig, serializeMessageForForwardMPC, getReshareConfig, getRequiredExternalKeyShareId, verifiedCredentialNameToChainEnum, AuthMode, NoopLogger, DynamicApiClient, getEnvironmentFromUrl, IFRAME_DOMAIN_MAP } from '@dynamic-labs-wallet/core';
|
|
1
|
+
import { BitcoinAddressType, SigningAlgorithm, MPC_RELAY_PROD_API_URL, getMPCChainConfig, BackupLocation, ENCRYPTED_SHARES_STORAGE_SUFFIX, Logger, handleAxiosError, WalletOperation, WalletReadyState, parseNamespacedVersion, FEATURE_FLAGS, ThresholdSignatureScheme, getClientThreshold, MPC_CONFIG, classifyForwardMpcError, getTSSConfig, serializeMessageForForwardMPC, getReshareConfig, getRequiredExternalKeyShareId, verifiedCredentialNameToChainEnum, AuthMode, NoopLogger, DynamicApiClient, getEnvironmentFromUrl, IFRAME_DOMAIN_MAP } from '@dynamic-labs-wallet/core';
|
|
2
2
|
export * from '@dynamic-labs-wallet/core';
|
|
3
3
|
export { Logger } from '@dynamic-labs-wallet/core';
|
|
4
4
|
import { BIP340, ExportableEd25519, Ecdsa, MessageHash, EcdsaSignature, EcdsaKeygenResult, ExportableEd25519KeygenResult, BIP340KeygenResult } from '#internal/web';
|
|
@@ -353,6 +353,75 @@ class InvalidPasswordError extends Error {
|
|
|
353
353
|
};
|
|
354
354
|
|
|
355
355
|
const GOOGLE_DRIVE_UPLOAD_API = 'https://www.googleapis.com';
|
|
356
|
+
const NON_RETRYABLE_AUTH_REASONS = new Set([
|
|
357
|
+
'insufficientPermissions',
|
|
358
|
+
'unauthorized',
|
|
359
|
+
'authError'
|
|
360
|
+
]);
|
|
361
|
+
const NON_RETRYABLE_AUTH_DETAIL_REASONS = new Set([
|
|
362
|
+
'ACCESS_TOKEN_SCOPE_INSUFFICIENT',
|
|
363
|
+
'ACCESS_TOKEN_EXPIRED',
|
|
364
|
+
'CREDENTIALS_MISSING'
|
|
365
|
+
]);
|
|
366
|
+
const RATE_LIMIT_REASONS = new Set([
|
|
367
|
+
'rateLimitExceeded',
|
|
368
|
+
'userRateLimitExceeded',
|
|
369
|
+
'dailyLimitExceeded'
|
|
370
|
+
]);
|
|
371
|
+
const NETWORK_ERROR = {
|
|
372
|
+
message: 'Could not reach Google Drive. Please check your internet connection and try again.',
|
|
373
|
+
isRetryable: true
|
|
374
|
+
};
|
|
375
|
+
/**
|
|
376
|
+
* Maps a Google Drive API error to an actionable user-facing message and a retry hint.
|
|
377
|
+
* The retry hint is consumed by retryPromise to short-circuit on non-transient failures.
|
|
378
|
+
*/ const mapGoogleDriveUploadError = (httpStatus, body)=>{
|
|
379
|
+
var _error_errors_, _error_errors, _error_details_, _error_details;
|
|
380
|
+
const error = body == null ? void 0 : body.error;
|
|
381
|
+
const reason = error == null ? void 0 : (_error_errors = error.errors) == null ? void 0 : (_error_errors_ = _error_errors[0]) == null ? void 0 : _error_errors_.reason;
|
|
382
|
+
const detailReason = error == null ? void 0 : (_error_details = error.details) == null ? void 0 : (_error_details_ = _error_details[0]) == null ? void 0 : _error_details_.reason;
|
|
383
|
+
if (httpStatus === 401 || reason && NON_RETRYABLE_AUTH_REASONS.has(reason) || detailReason && NON_RETRYABLE_AUTH_DETAIL_REASONS.has(detailReason)) {
|
|
384
|
+
return {
|
|
385
|
+
message: 'Google Drive access denied: missing or insufficient permissions. Make sure the app is requesting Drive permission and that you allowed it during sign-in, then try again.',
|
|
386
|
+
isRetryable: false
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
if (reason === 'storageQuotaExceeded') {
|
|
390
|
+
return {
|
|
391
|
+
message: 'Google Drive storage is full. Free up space in your Google Drive and try again.',
|
|
392
|
+
isRetryable: false
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
if (httpStatus === 429 || reason && RATE_LIMIT_REASONS.has(reason)) {
|
|
396
|
+
return {
|
|
397
|
+
message: 'Google Drive is temporarily rate-limited. Please try again in a few moments.',
|
|
398
|
+
isRetryable: true
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
if (httpStatus >= 500 && httpStatus < 600) {
|
|
402
|
+
return {
|
|
403
|
+
message: 'Google Drive is temporarily unavailable. Please try again shortly.',
|
|
404
|
+
isRetryable: true
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
const detail = reason != null ? reason : error == null ? void 0 : error.status;
|
|
408
|
+
return {
|
|
409
|
+
message: detail ? `Google Drive upload failed (HTTP ${httpStatus}: ${detail}).` : `Google Drive upload failed (HTTP ${httpStatus}).`,
|
|
410
|
+
isRetryable: false
|
|
411
|
+
};
|
|
412
|
+
};
|
|
413
|
+
const parseGoogleDriveErrorBody = async (response)=>{
|
|
414
|
+
try {
|
|
415
|
+
return await response.json();
|
|
416
|
+
} catch (e) {
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
const createGoogleDriveError = (mapped)=>{
|
|
421
|
+
const error = new Error(mapped.message);
|
|
422
|
+
error.isRetryable = mapped.isRetryable;
|
|
423
|
+
return error;
|
|
424
|
+
};
|
|
356
425
|
const uploadFileToGoogleDriveAppStorage = async ({ accessToken, fileName, jsonData })=>{
|
|
357
426
|
return uploadFileToGoogleDrive({
|
|
358
427
|
accessToken,
|
|
@@ -374,19 +443,24 @@ const uploadFileToGoogleDrivePersonal = async ({ accessToken, fileName, jsonData
|
|
|
374
443
|
});
|
|
375
444
|
};
|
|
376
445
|
/**
|
|
377
|
-
* Verifies that a file exists on Google Drive by
|
|
378
|
-
|
|
379
|
-
|
|
446
|
+
* Verifies that a file exists on Google Drive by listing files
|
|
447
|
+
* Uses appropriate space parameter based on upload location
|
|
448
|
+
*/ const verifyUpload = async ({ accessToken, fileId, fileName, isAppDataFolder })=>{
|
|
449
|
+
var _verifyResult_files;
|
|
450
|
+
const spacesParam = isAppDataFolder ? "appDataFolder" : "drive";
|
|
451
|
+
const nameQuery = `name='${fileName}'`;
|
|
452
|
+
const verifyResponse = await fetch(`${GOOGLE_DRIVE_UPLOAD_API}/drive/v3/files?q=${encodeURIComponent(nameQuery)}&spaces=${spacesParam}&fields=files(id,name)`, {
|
|
380
453
|
headers: {
|
|
381
454
|
Authorization: `Bearer ${accessToken}`
|
|
382
455
|
}
|
|
383
456
|
});
|
|
384
457
|
if (!verifyResponse.ok) {
|
|
385
|
-
throw new Error(`Upload verification failed:
|
|
458
|
+
throw new Error(`Upload verification failed: could not list files in ${spacesParam}`);
|
|
386
459
|
}
|
|
387
460
|
const verifyResult = await verifyResponse.json();
|
|
388
|
-
|
|
389
|
-
|
|
461
|
+
const uploadedFile = (_verifyResult_files = verifyResult.files) == null ? void 0 : _verifyResult_files.find((f)=>f.id === fileId);
|
|
462
|
+
if (!uploadedFile) {
|
|
463
|
+
throw new Error(`Upload verification failed: file ${fileId} not found in ${spacesParam}`);
|
|
390
464
|
}
|
|
391
465
|
};
|
|
392
466
|
const uploadFileToGoogleDrive = async ({ accessToken, fileName, jsonData, parents })=>{
|
|
@@ -412,9 +486,12 @@ const uploadFileToGoogleDrive = async ({ accessToken, fileName, jsonData, parent
|
|
|
412
486
|
Authorization: `Bearer ${accessToken}`
|
|
413
487
|
},
|
|
414
488
|
body: form
|
|
489
|
+
}).catch(()=>{
|
|
490
|
+
throw createGoogleDriveError(NETWORK_ERROR);
|
|
415
491
|
});
|
|
416
492
|
if (!response.ok) {
|
|
417
|
-
|
|
493
|
+
const body = await parseGoogleDriveErrorBody(response);
|
|
494
|
+
throw createGoogleDriveError(mapGoogleDriveUploadError(response.status, body));
|
|
418
495
|
}
|
|
419
496
|
const result = await response.json();
|
|
420
497
|
const fileId = result.id;
|
|
@@ -423,13 +500,16 @@ const uploadFileToGoogleDrive = async ({ accessToken, fileName, jsonData, parent
|
|
|
423
500
|
}
|
|
424
501
|
await verifyUpload({
|
|
425
502
|
accessToken,
|
|
426
|
-
fileId
|
|
503
|
+
fileId,
|
|
504
|
+
fileName,
|
|
505
|
+
isAppDataFolder: parents.includes('appDataFolder')
|
|
427
506
|
});
|
|
428
507
|
return result; // Return file metadata, including file ID
|
|
429
508
|
};
|
|
430
509
|
const listFilesFromGoogleDrive = async ({ accessToken, fileName })=>{
|
|
431
|
-
//
|
|
432
|
-
const
|
|
510
|
+
// List all files inside appDataFolder with the specified backup filename
|
|
511
|
+
const nameQuery = `name='${fileName}'`;
|
|
512
|
+
const resp = await fetch(`${GOOGLE_DRIVE_UPLOAD_API}/drive/v3/files?q=${encodeURIComponent(nameQuery)}&spaces=${"appDataFolder"}&orderBy=createdTime desc`, {
|
|
433
513
|
headers: {
|
|
434
514
|
Authorization: `Bearer ${accessToken}`
|
|
435
515
|
}
|
|
@@ -593,11 +673,11 @@ const logRetrySuccess = (operationName, attempts, logContext)=>{
|
|
|
593
673
|
attemptsTaken: attempts + 1
|
|
594
674
|
}));
|
|
595
675
|
};
|
|
596
|
-
const logRetryFailure = (operationName, attempts, maxAttempts, errorContext, logContext)=>{
|
|
676
|
+
const logRetryFailure = (operationName, attempts, maxAttempts, errorContext, logContext, isNonRetryable = false)=>{
|
|
597
677
|
Logger.warn(`Failed to execute ${operationName} on attempt ${attempts}/${maxAttempts}`, _extends({}, logContext, errorContext, {
|
|
598
678
|
attempt: attempts,
|
|
599
679
|
maxAttempts,
|
|
600
|
-
willRetry: attempts < maxAttempts
|
|
680
|
+
willRetry: !isNonRetryable && attempts < maxAttempts
|
|
601
681
|
}));
|
|
602
682
|
};
|
|
603
683
|
const logRetryExhausted = (operationName, maxAttempts, errorContext, logContext)=>{
|
|
@@ -624,7 +704,12 @@ const logRetryExhausted = (operationName, maxAttempts, errorContext, logContext)
|
|
|
624
704
|
} catch (error) {
|
|
625
705
|
attempts++;
|
|
626
706
|
const errorContext = buildErrorContext(error);
|
|
627
|
-
|
|
707
|
+
const isNonRetryable = (error == null ? void 0 : error.isRetryable) === false;
|
|
708
|
+
logRetryFailure(operationName, attempts, maxAttempts, errorContext, logContext, isNonRetryable);
|
|
709
|
+
if (isNonRetryable) {
|
|
710
|
+
Logger.debug(`Skipping retry for ${operationName}: error marked non-retryable`, _extends({}, logContext, errorContext));
|
|
711
|
+
throw error;
|
|
712
|
+
}
|
|
628
713
|
if (attempts === maxAttempts) {
|
|
629
714
|
logRetryExhausted(operationName, maxAttempts, errorContext, logContext);
|
|
630
715
|
throw error;
|
|
@@ -740,6 +825,27 @@ const downloadStringAsFile = ({ filename, content, mimeType = 'application/json'
|
|
|
740
825
|
}
|
|
741
826
|
return false;
|
|
742
827
|
};
|
|
828
|
+
/**
|
|
829
|
+
* Determines whether a reshare operation should reuse the wallet's existing backup locations.
|
|
830
|
+
*
|
|
831
|
+
* **Why this exists:**
|
|
832
|
+
* When `reshareOnNextSignOn` is triggered (e.g., after Google OAuth refresh), the reshare happens
|
|
833
|
+
* automatically without the caller specifying backup parameters. Without this detection, the reshare
|
|
834
|
+
* would proceed with empty backup locations, potentially losing the user's Google Drive or
|
|
835
|
+
* delegation backup configuration.
|
|
836
|
+
*
|
|
837
|
+
* **When it returns true:**
|
|
838
|
+
* - Same threshold (not upgrading/downgrading the signature scheme)
|
|
839
|
+
* - No cloud providers explicitly passed (caller didn't request backup changes)
|
|
840
|
+
* - No delegation explicitly passed (caller didn't request delegation changes)
|
|
841
|
+
*
|
|
842
|
+
* When true, the reshare will auto-populate backup locations from the wallet's existing
|
|
843
|
+
* `clientKeySharesBackupInfo` state, ensuring backups are preserved.
|
|
844
|
+
*/ const shouldReshareToSameBackups = ({ oldThresholdSignatureScheme, newThresholdSignatureScheme, cloudProviders = [], delegateToProjectEnvironment = false })=>{
|
|
845
|
+
const isSameThreshold = oldThresholdSignatureScheme === newThresholdSignatureScheme;
|
|
846
|
+
const noBackupChangesRequested = cloudProviders.length === 0 && !delegateToProjectEnvironment;
|
|
847
|
+
return isSameThreshold && noBackupChangesRequested;
|
|
848
|
+
};
|
|
743
849
|
|
|
744
850
|
const CLOUDKIT_CDN_URL = 'https://cdn.apple-cloudkit.com/ck/2/cloudkit.js';
|
|
745
851
|
const BACKUP_RECORD_TYPE = 'Backup';
|
|
@@ -1125,18 +1231,24 @@ const initializeCloudKit = async (config, signInButtonId, onSignInRequired, onSi
|
|
|
1125
1231
|
};
|
|
1126
1232
|
/**
|
|
1127
1233
|
* Processes a single upload result and logs appropriately
|
|
1128
|
-
* @returns
|
|
1234
|
+
* @returns Failure details if rejected, undefined if successful
|
|
1129
1235
|
*/ const processUploadResult = (result, locationName, logContext, logger)=>{
|
|
1236
|
+
var _result_reason;
|
|
1130
1237
|
if (result.status === 'fulfilled') {
|
|
1131
1238
|
logger.info(`[DynamicWaasWalletClient] Successfully uploaded keyshares to ${locationName}`, logContext);
|
|
1132
1239
|
return undefined;
|
|
1133
1240
|
}
|
|
1134
1241
|
const { message, stack } = getErrorDetails(result.reason);
|
|
1242
|
+
const isRetryable = ((_result_reason = result.reason) == null ? void 0 : _result_reason.isRetryable) !== false;
|
|
1135
1243
|
logger.error(`[DynamicWaasWalletClient] Failed to upload keyshares to ${locationName}`, _extends({}, logContext, {
|
|
1136
1244
|
error: message,
|
|
1137
1245
|
errorStack: stack
|
|
1138
1246
|
}));
|
|
1139
|
-
return
|
|
1247
|
+
return {
|
|
1248
|
+
locationName,
|
|
1249
|
+
message,
|
|
1250
|
+
isRetryable
|
|
1251
|
+
};
|
|
1140
1252
|
};
|
|
1141
1253
|
/**
|
|
1142
1254
|
* Uploads a backup to Google Drive App
|
|
@@ -1176,16 +1288,27 @@ const initializeCloudKit = async (config, signInButtonId, onSignInRequired, onSi
|
|
|
1176
1288
|
})
|
|
1177
1289
|
];
|
|
1178
1290
|
const results = await Promise.allSettled(uploadPromises);
|
|
1179
|
-
const
|
|
1291
|
+
const failures = [
|
|
1180
1292
|
processUploadResult(results[0], 'Google Drive App Storage', logContext, logger),
|
|
1181
1293
|
processUploadResult(results[1], 'Google Drive Personal', logContext, logger)
|
|
1182
|
-
].filter((
|
|
1183
|
-
if (
|
|
1294
|
+
].filter((failure)=>failure !== undefined);
|
|
1295
|
+
if (failures.length > 0) {
|
|
1184
1296
|
logger.error('[DynamicWaasWalletClient] Google Drive backup failed', _extends({}, logContext, {
|
|
1185
|
-
errorCount:
|
|
1186
|
-
errors
|
|
1297
|
+
errorCount: failures.length,
|
|
1298
|
+
errors: failures.map((f)=>`Failed to backup keyshares to ${f.locationName}: ${f.message}`)
|
|
1187
1299
|
}));
|
|
1188
|
-
|
|
1300
|
+
// Both upload destinations (appDataFolder + personal) commonly fail with
|
|
1301
|
+
// the same underlying error (e.g., missing OAuth scope). When all failures
|
|
1302
|
+
// share one message, surface it once without per-location wrapping;
|
|
1303
|
+
// otherwise keep a short "Location: message" prefix per failure so the
|
|
1304
|
+
// user can see what differed.
|
|
1305
|
+
const uniqueMessages = [
|
|
1306
|
+
...new Set(failures.map((f)=>f.message))
|
|
1307
|
+
];
|
|
1308
|
+
const finalMessage = uniqueMessages.length === 1 ? uniqueMessages[0] : failures.map((f)=>`${f.locationName}: ${f.message}`).join(' | ');
|
|
1309
|
+
const aggregatedError = new Error(finalMessage);
|
|
1310
|
+
aggregatedError.isRetryable = failures.every((f)=>f.isRetryable);
|
|
1311
|
+
throw aggregatedError;
|
|
1189
1312
|
}
|
|
1190
1313
|
logger.info('[DynamicWaasWalletClient] Google Drive backup completed successfully', logContext);
|
|
1191
1314
|
};
|
|
@@ -2103,18 +2226,33 @@ class DynamicWalletClient {
|
|
|
2103
2226
|
}
|
|
2104
2227
|
}
|
|
2105
2228
|
async clientKeyGen({ chainName, roomId, serverKeygenIds, clientKeygenInitResults, thresholdSignatureScheme, bitcoinConfig, dynamicRequestId, traceContext }) {
|
|
2229
|
+
// Try forward MPC first if enabled
|
|
2106
2230
|
if (this.forwardMPCEnabled) {
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2231
|
+
try {
|
|
2232
|
+
return await this.forwardMPCClientKeygen({
|
|
2233
|
+
chainName,
|
|
2234
|
+
roomId,
|
|
2235
|
+
serverKeygenIds,
|
|
2236
|
+
clientKeygenInitResults,
|
|
2237
|
+
thresholdSignatureScheme,
|
|
2238
|
+
bitcoinConfig,
|
|
2239
|
+
dynamicRequestId,
|
|
2240
|
+
traceContext
|
|
2241
|
+
});
|
|
2242
|
+
} catch (error) {
|
|
2243
|
+
const errorInfo = classifyForwardMpcError(error);
|
|
2244
|
+
this.logger.warn('Forward MPC keygen failed', _extends({}, errorInfo, {
|
|
2245
|
+
chainName,
|
|
2246
|
+
environmentId: this.environmentId,
|
|
2247
|
+
dynamicRequestId
|
|
2248
|
+
}));
|
|
2249
|
+
if (!errorInfo.shouldFallback) {
|
|
2250
|
+
throw error;
|
|
2251
|
+
}
|
|
2252
|
+
// Fall through to relay-based keygen below
|
|
2253
|
+
}
|
|
2117
2254
|
}
|
|
2255
|
+
// Relay-based keygen (fallback or when forward MPC is disabled)
|
|
2118
2256
|
// Get the chain config and the mpc signer
|
|
2119
2257
|
const mpcSigner = getMPCSigner({
|
|
2120
2258
|
chainName,
|
|
@@ -2432,20 +2570,35 @@ class DynamicWalletClient {
|
|
|
2432
2570
|
derivationPath,
|
|
2433
2571
|
isFormatted
|
|
2434
2572
|
}, this.getTraceContext(traceContext)));
|
|
2573
|
+
// Try forward MPC first if enabled, with fallback to relay-based signing on attestation failure
|
|
2435
2574
|
if (this.forwardMPCEnabled) {
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2575
|
+
try {
|
|
2576
|
+
return await this.forwardMPCClientSign({
|
|
2577
|
+
chainName,
|
|
2578
|
+
message,
|
|
2579
|
+
roomId,
|
|
2580
|
+
keyShare,
|
|
2581
|
+
derivationPath,
|
|
2582
|
+
formattedMessage,
|
|
2583
|
+
dynamicRequestId,
|
|
2584
|
+
isFormatted,
|
|
2585
|
+
traceContext,
|
|
2586
|
+
bitcoinConfig
|
|
2587
|
+
});
|
|
2588
|
+
} catch (error) {
|
|
2589
|
+
const errorInfo = classifyForwardMpcError(error);
|
|
2590
|
+
this.logger.warn('Forward MPC signing failed', _extends({}, errorInfo, {
|
|
2591
|
+
chainName,
|
|
2592
|
+
environmentId: this.environmentId,
|
|
2593
|
+
dynamicRequestId
|
|
2594
|
+
}));
|
|
2595
|
+
if (!errorInfo.shouldFallback) {
|
|
2596
|
+
throw error;
|
|
2597
|
+
}
|
|
2598
|
+
// Fall through to relay-based signing below
|
|
2599
|
+
}
|
|
2448
2600
|
}
|
|
2601
|
+
// Relay-based signing (fallback or when forward MPC is disabled)
|
|
2449
2602
|
// Unwrap MessageHash for BIP340 as it expects Uint8Array or hex string
|
|
2450
2603
|
let messageToSign = formattedMessage;
|
|
2451
2604
|
if (mpcSigner instanceof BIP340 && formattedMessage instanceof MessageHash) {
|
|
@@ -2692,6 +2845,52 @@ class DynamicWalletClient {
|
|
|
2692
2845
|
}));
|
|
2693
2846
|
}
|
|
2694
2847
|
/**
|
|
2848
|
+
* Resolves backup locations for a reshare operation.
|
|
2849
|
+
*
|
|
2850
|
+
* When reshareOnNextSignOn triggers (e.g., after Google OAuth refresh), the caller
|
|
2851
|
+
* doesn't pass backup parameters. This method detects that case and preserves the
|
|
2852
|
+
* wallet's existing backup locations (Google Drive, iCloud, delegation) instead of
|
|
2853
|
+
* proceeding with empty backup locations.
|
|
2854
|
+
*
|
|
2855
|
+
* @returns The backup locations to use for the reshare operation
|
|
2856
|
+
*/ resolveBackupLocationsForReshare({ oldThresholdSignatureScheme, newThresholdSignatureScheme, cloudProviders = [], delegateToProjectEnvironment = false, accountAddress, dynamicRequestId }) {
|
|
2857
|
+
const shouldPreserveExisting = shouldReshareToSameBackups({
|
|
2858
|
+
oldThresholdSignatureScheme,
|
|
2859
|
+
newThresholdSignatureScheme,
|
|
2860
|
+
cloudProviders,
|
|
2861
|
+
delegateToProjectEnvironment
|
|
2862
|
+
});
|
|
2863
|
+
if (!shouldPreserveExisting) {
|
|
2864
|
+
return {
|
|
2865
|
+
cloudProviders,
|
|
2866
|
+
delegateToProjectEnvironment
|
|
2867
|
+
};
|
|
2868
|
+
}
|
|
2869
|
+
// Wallet is guaranteed to be in map - verifyPassword calls requireWalletFromMap upstream
|
|
2870
|
+
const walletProperties = this.getWalletFromMap(accountAddress);
|
|
2871
|
+
if (!walletProperties) {
|
|
2872
|
+
this.logger.warn('[resolveBackupLocationsForReshare] Wallet not in map, using passed params', {
|
|
2873
|
+
dynamicRequestId,
|
|
2874
|
+
accountAddress
|
|
2875
|
+
});
|
|
2876
|
+
return {
|
|
2877
|
+
cloudProviders,
|
|
2878
|
+
delegateToProjectEnvironment
|
|
2879
|
+
};
|
|
2880
|
+
}
|
|
2881
|
+
const preservedCloudProviders = getActiveCloudProviders(walletProperties.clientKeySharesBackupInfo);
|
|
2882
|
+
const preservedDelegation = hasDelegatedBackup(walletProperties.clientKeySharesBackupInfo);
|
|
2883
|
+
this.logger.info('[resolveBackupLocationsForReshare] Preserving existing backup locations', {
|
|
2884
|
+
dynamicRequestId,
|
|
2885
|
+
cloudProviders: preservedCloudProviders,
|
|
2886
|
+
delegateToProjectEnvironment: preservedDelegation
|
|
2887
|
+
});
|
|
2888
|
+
return {
|
|
2889
|
+
cloudProviders: preservedCloudProviders,
|
|
2890
|
+
delegateToProjectEnvironment: preservedDelegation
|
|
2891
|
+
};
|
|
2892
|
+
}
|
|
2893
|
+
/**
|
|
2695
2894
|
* Gets the Bitcoin config for MPC operations by looking up addressType
|
|
2696
2895
|
* from walletMap, with fallback to deriving it from derivationPath.
|
|
2697
2896
|
*/ getBitcoinConfigForChain(chainName, accountAddress) {
|
|
@@ -2908,6 +3107,8 @@ class DynamicWalletClient {
|
|
|
2908
3107
|
}
|
|
2909
3108
|
async internalReshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined, signedSessionId, cloudProviders = [], delegateToProjectEnvironment = false, mfaToken, elevatedAccessToken, revokeDelegation = false, hasAttemptedKeyShareRecovery = false }) {
|
|
2910
3109
|
const dynamicRequestId = v4();
|
|
3110
|
+
// Password validation - wrapped in try-catch for consistent error handling
|
|
3111
|
+
// This path should NOT wipe key shares on failure (shares remain valid)
|
|
2911
3112
|
try {
|
|
2912
3113
|
this.assertPasswordRequired(password);
|
|
2913
3114
|
await this.verifyPassword({
|
|
@@ -2920,6 +3121,30 @@ class DynamicWalletClient {
|
|
|
2920
3121
|
password,
|
|
2921
3122
|
signedSessionId
|
|
2922
3123
|
});
|
|
3124
|
+
} catch (error) {
|
|
3125
|
+
// Log password validation error but do NOT wipe key shares
|
|
3126
|
+
logError({
|
|
3127
|
+
message: '[DynamicWaasWalletClient]: Password validation failed during reshare',
|
|
3128
|
+
error: error,
|
|
3129
|
+
context: {
|
|
3130
|
+
accountAddress,
|
|
3131
|
+
dynamicRequestId
|
|
3132
|
+
}
|
|
3133
|
+
});
|
|
3134
|
+
throw error;
|
|
3135
|
+
}
|
|
3136
|
+
// Resolve backup locations for this reshare - may preserve existing locations
|
|
3137
|
+
// when reshareOnNextSignOn triggers without explicit backup parameters
|
|
3138
|
+
const { cloudProviders: resolvedCloudProviders, delegateToProjectEnvironment: resolvedDelegation } = this.resolveBackupLocationsForReshare({
|
|
3139
|
+
oldThresholdSignatureScheme,
|
|
3140
|
+
newThresholdSignatureScheme,
|
|
3141
|
+
cloudProviders,
|
|
3142
|
+
delegateToProjectEnvironment,
|
|
3143
|
+
accountAddress,
|
|
3144
|
+
dynamicRequestId
|
|
3145
|
+
});
|
|
3146
|
+
// MPC ceremony path - errors here SHOULD wipe shares as they may be inconsistent
|
|
3147
|
+
try {
|
|
2923
3148
|
const { existingClientShareCount } = getReshareConfig({
|
|
2924
3149
|
oldThresholdSignatureScheme,
|
|
2925
3150
|
newThresholdSignatureScheme
|
|
@@ -2954,7 +3179,7 @@ class DynamicWalletClient {
|
|
|
2954
3179
|
oldThresholdSignatureScheme,
|
|
2955
3180
|
newThresholdSignatureScheme,
|
|
2956
3181
|
dynamicRequestId,
|
|
2957
|
-
delegateToProjectEnvironment,
|
|
3182
|
+
delegateToProjectEnvironment: resolvedDelegation,
|
|
2958
3183
|
mfaToken,
|
|
2959
3184
|
elevatedAccessToken,
|
|
2960
3185
|
revokeDelegation
|
|
@@ -3047,6 +3272,7 @@ class DynamicWalletClient {
|
|
|
3047
3272
|
dynamicRequestId
|
|
3048
3273
|
});
|
|
3049
3274
|
// Retry the entire reshare operation (need new room and keygen IDs from server)
|
|
3275
|
+
// Pass effective* values so retry uses same backup locations
|
|
3050
3276
|
return this.internalReshare({
|
|
3051
3277
|
chainName,
|
|
3052
3278
|
accountAddress,
|
|
@@ -3054,8 +3280,8 @@ class DynamicWalletClient {
|
|
|
3054
3280
|
newThresholdSignatureScheme,
|
|
3055
3281
|
password,
|
|
3056
3282
|
signedSessionId,
|
|
3057
|
-
cloudProviders,
|
|
3058
|
-
delegateToProjectEnvironment,
|
|
3283
|
+
cloudProviders: resolvedCloudProviders,
|
|
3284
|
+
delegateToProjectEnvironment: resolvedDelegation,
|
|
3059
3285
|
mfaToken,
|
|
3060
3286
|
elevatedAccessToken,
|
|
3061
3287
|
revokeDelegation,
|
|
@@ -3070,26 +3296,27 @@ class DynamicWalletClient {
|
|
|
3070
3296
|
];
|
|
3071
3297
|
let distribution;
|
|
3072
3298
|
// Generic distribution logic - works with any cloud providers
|
|
3073
|
-
|
|
3299
|
+
// Use effective* variables which may have been populated from existing wallet state
|
|
3300
|
+
if (resolvedDelegation && resolvedCloudProviders.length > 0) {
|
|
3074
3301
|
// Delegation + Cloud Providers: Client's existing share backs up to both Dynamic and cloud providers.
|
|
3075
3302
|
// The new share goes to the webhook for delegation.
|
|
3076
3303
|
distribution = createDelegationWithCloudProviderDistribution({
|
|
3077
|
-
providers:
|
|
3304
|
+
providers: resolvedCloudProviders,
|
|
3078
3305
|
existingShares: existingReshareResults,
|
|
3079
3306
|
delegatedShare: newReshareResults[0]
|
|
3080
3307
|
});
|
|
3081
|
-
} else if (
|
|
3308
|
+
} else if (resolvedDelegation) {
|
|
3082
3309
|
// Delegation only: Client's existing share backs up to Dynamic.
|
|
3083
3310
|
// The new share goes to the webhook for delegation. No cloud provider backup.
|
|
3084
3311
|
distribution = createDelegationOnlyDistribution({
|
|
3085
3312
|
existingShares: existingReshareResults,
|
|
3086
3313
|
delegatedShare: newReshareResults[0]
|
|
3087
3314
|
});
|
|
3088
|
-
} else if (
|
|
3315
|
+
} else if (resolvedCloudProviders.length > 0) {
|
|
3089
3316
|
// Cloud Providers only: Split shares between Dynamic (N-1) and cloud providers (1).
|
|
3090
3317
|
// The last share (new share) goes to cloud providers.
|
|
3091
3318
|
distribution = createCloudProviderDistribution({
|
|
3092
|
-
providers:
|
|
3319
|
+
providers: resolvedCloudProviders,
|
|
3093
3320
|
allShares: allClientShares
|
|
3094
3321
|
});
|
|
3095
3322
|
} else {
|
|
@@ -3127,6 +3354,8 @@ class DynamicWalletClient {
|
|
|
3127
3354
|
oldThresholdSignatureScheme,
|
|
3128
3355
|
newThresholdSignatureScheme,
|
|
3129
3356
|
cloudProviders,
|
|
3357
|
+
resolvedCloudProviders,
|
|
3358
|
+
resolvedDelegation,
|
|
3130
3359
|
dynamicRequestId
|
|
3131
3360
|
}
|
|
3132
3361
|
});
|
|
@@ -4170,12 +4399,14 @@ class DynamicWalletClient {
|
|
|
4170
4399
|
const oauthAccountId = getGoogleOAuthAccountId(user == null ? void 0 : user.verifiedCredentials);
|
|
4171
4400
|
if (!oauthAccountId) {
|
|
4172
4401
|
const error = new Error('No Google OAuth account ID found');
|
|
4402
|
+
// PII: intentionally omitting full user object
|
|
4173
4403
|
logError({
|
|
4174
4404
|
message: 'No Google OAuth account ID found',
|
|
4175
4405
|
error,
|
|
4176
4406
|
context: {
|
|
4177
|
-
|
|
4178
|
-
|
|
4407
|
+
accountAddress,
|
|
4408
|
+
userId: user == null ? void 0 : user.id,
|
|
4409
|
+
projectEnvironmentId: user == null ? void 0 : user.projectEnvironmentId
|
|
4179
4410
|
}
|
|
4180
4411
|
});
|
|
4181
4412
|
throw error;
|
|
@@ -5439,4 +5670,4 @@ DynamicWalletClient.rooms = {};
|
|
|
5439
5670
|
DynamicWalletClient.roomsInitializing = {};
|
|
5440
5671
|
DynamicWalletClient.roomsPersistChain = Promise.resolve();
|
|
5441
5672
|
|
|
5442
|
-
export { BACKUP_FILENAME, CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX, DynamicWalletClient, ENVIRONMENT_SETTINGS_STORAGE_KEY, ERROR_ACCOUNT_ADDRESS_REQUIRED, ERROR_CREATE_WALLET_ACCOUNT, ERROR_EXPORT_PRIVATE_KEY, ERROR_IMPORT_PRIVATE_KEY, ERROR_KEYGEN_FAILED, ERROR_PASSCODE_REQUIRED, ERROR_PASSWORD_MISMATCH, ERROR_PASSWORD_REQUIRED_FOR_ENCRYPTED_WALLET, ERROR_PUBLIC_KEY_MISMATCH, ERROR_REFRESH_NOT_SUPPORTED_FOR_TWO_OF_THREE, ERROR_SIGN_MESSAGE, ERROR_SIGN_TYPED_DATA, ERROR_VERIFY_MESSAGE_SIGNATURE, ERROR_VERIFY_TRANSACTION_SIGNATURE, HEAVY_QUEUE_OPERATIONS, RECOVER_QUEUE_OPERATIONS, ROOM_CACHE_COUNT, ROOM_EXPIRATION_TIME, SIGNED_SESSION_ID_MIN_VERSION, SIGNED_SESSION_ID_MIN_VERSION_BY_NAMESPACE, SIGN_QUEUE_OPERATIONS, STORAGE_KEY, WalletBusyError, WalletNotReadyError, cancelICloudAuth, createAddCloudProviderToExistingDelegationDistribution, createBackupData, createCloudProviderDistribution, createDelegationOnlyDistribution, createDelegationWithCloudProviderDistribution, createDynamicOnlyDistribution, deleteICloudBackup, downloadStringAsFile, extractPubkey, formatEvmMessage, formatMessage, getActiveCloudProviders, getBitcoinAddressTypeFromDerivationPath, getClientKeyShareBackupInfo, getClientKeyShareExportFileName, getGoogleOAuthAccountId, getICloudBackup, getMPCSignatureScheme, getMPCSigner, hasCloudProviderBackup, hasDelegatedBackup, hasEncryptedSharesCached, initializeCloudKit, isBrowser, isHeavyQueueOperation, isHexString, isICloudAuthenticated, isPublicKeyMismatchError, isRecoverQueueOperation, isSignQueueOperation, listICloudBackups, mergeUniqueKeyShares, readEnvironmentSettings, retryPromise, timeoutPromise };
|
|
5673
|
+
export { BACKUP_FILENAME, CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX, DynamicWalletClient, ENVIRONMENT_SETTINGS_STORAGE_KEY, ERROR_ACCOUNT_ADDRESS_REQUIRED, ERROR_CREATE_WALLET_ACCOUNT, ERROR_EXPORT_PRIVATE_KEY, ERROR_IMPORT_PRIVATE_KEY, ERROR_KEYGEN_FAILED, ERROR_PASSCODE_REQUIRED, ERROR_PASSWORD_MISMATCH, ERROR_PASSWORD_REQUIRED_FOR_ENCRYPTED_WALLET, ERROR_PUBLIC_KEY_MISMATCH, ERROR_REFRESH_NOT_SUPPORTED_FOR_TWO_OF_THREE, ERROR_SIGN_MESSAGE, ERROR_SIGN_TYPED_DATA, ERROR_VERIFY_MESSAGE_SIGNATURE, ERROR_VERIFY_TRANSACTION_SIGNATURE, HEAVY_QUEUE_OPERATIONS, RECOVER_QUEUE_OPERATIONS, ROOM_CACHE_COUNT, ROOM_EXPIRATION_TIME, SIGNED_SESSION_ID_MIN_VERSION, SIGNED_SESSION_ID_MIN_VERSION_BY_NAMESPACE, SIGN_QUEUE_OPERATIONS, STORAGE_KEY, WalletBusyError, WalletNotReadyError, cancelICloudAuth, createAddCloudProviderToExistingDelegationDistribution, createBackupData, createCloudProviderDistribution, createDelegationOnlyDistribution, createDelegationWithCloudProviderDistribution, createDynamicOnlyDistribution, deleteICloudBackup, downloadStringAsFile, extractPubkey, formatEvmMessage, formatMessage, getActiveCloudProviders, getBitcoinAddressTypeFromDerivationPath, getClientKeyShareBackupInfo, getClientKeyShareExportFileName, getGoogleOAuthAccountId, getICloudBackup, getMPCSignatureScheme, getMPCSigner, hasCloudProviderBackup, hasDelegatedBackup, hasEncryptedSharesCached, initializeCloudKit, isBrowser, isHeavyQueueOperation, isHexString, isICloudAuthenticated, isPublicKeyMismatchError, isRecoverQueueOperation, isSignQueueOperation, listICloudBackups, mergeUniqueKeyShares, readEnvironmentSettings, retryPromise, shouldReshareToSameBackups, timeoutPromise };
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dynamic-labs-wallet/browser",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.327",
|
|
4
4
|
"license": "Licensed under the Dynamic Labs, Inc. Terms Of Service (https://www.dynamic.xyz/terms-conditions)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@dynamic-labs-wallet/core": "0.0.
|
|
7
|
+
"@dynamic-labs-wallet/core": "0.0.327",
|
|
8
|
+
"@dynamic-labs-wallet/forward-mpc-client": "0.7.0",
|
|
8
9
|
"@dynamic-labs/sdk-api-core": "^0.0.900",
|
|
9
10
|
"argon2id": "1.0.1",
|
|
10
11
|
"axios": "1.15.0",
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
"build": {}
|
|
30
31
|
}
|
|
31
32
|
},
|
|
32
|
-
"main": "./index.cjs
|
|
33
|
+
"main": "./index.cjs",
|
|
33
34
|
"module": "./index.esm.js",
|
|
34
35
|
"types": "./index.esm.d.ts",
|
|
35
36
|
"exports": {
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
".": {
|
|
38
39
|
"types": "./index.esm.d.ts",
|
|
39
40
|
"import": "./index.esm.js",
|
|
40
|
-
"require": "./index.cjs
|
|
41
|
+
"require": "./index.cjs",
|
|
41
42
|
"default": "./index.esm.js"
|
|
42
43
|
}
|
|
43
44
|
},
|
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
type GoogleDriveErrorBody = {
|
|
2
|
+
error?: {
|
|
3
|
+
code?: number;
|
|
4
|
+
status?: string;
|
|
5
|
+
message?: string;
|
|
6
|
+
errors?: Array<{
|
|
7
|
+
reason?: string;
|
|
8
|
+
message?: string;
|
|
9
|
+
}>;
|
|
10
|
+
details?: Array<{
|
|
11
|
+
reason?: string;
|
|
12
|
+
'@type'?: string;
|
|
13
|
+
}>;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
type MappedUploadError = {
|
|
17
|
+
message: string;
|
|
18
|
+
isRetryable: boolean;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Maps a Google Drive API error to an actionable user-facing message and a retry hint.
|
|
22
|
+
* The retry hint is consumed by retryPromise to short-circuit on non-transient failures.
|
|
23
|
+
*/
|
|
24
|
+
export declare const mapGoogleDriveUploadError: (httpStatus: number, body: GoogleDriveErrorBody | null) => MappedUploadError;
|
|
1
25
|
export declare const uploadFileToGoogleDriveAppStorage: ({ accessToken, fileName, jsonData, }: {
|
|
2
26
|
accessToken: string;
|
|
3
27
|
fileName: string;
|
|
@@ -16,4 +40,5 @@ export declare const downloadFileFromGoogleDrive: ({ accessToken, fileName, }: {
|
|
|
16
40
|
accessToken: string;
|
|
17
41
|
fileName: string;
|
|
18
42
|
}) => Promise<unknown | null>;
|
|
43
|
+
export {};
|
|
19
44
|
//# sourceMappingURL=googleDrive.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"googleDrive.d.ts","sourceRoot":"","sources":["../../../src/backup/providers/googleDrive.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"googleDrive.d.ts","sourceRoot":"","sources":["../../../src/backup/providers/googleDrive.ts"],"names":[],"mappings":"AAWA,KAAK,oBAAoB,GAAG;IAC1B,KAAK,CAAC,EAAE;QACN,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,KAAK,CAAC;YAAE,MAAM,CAAC,EAAE,MAAM,CAAC;YAAC,OAAO,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QACtD,OAAO,CAAC,EAAE,KAAK,CAAC;YAAE,MAAM,CAAC,EAAE,MAAM,CAAC;YAAC,OAAO,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KACxD,CAAC;CACH,CAAC;AAEF,KAAK,iBAAiB,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC;AAiBnE;;;GAGG;AACH,eAAO,MAAM,yBAAyB,eAAgB,MAAM,QAAQ,oBAAoB,GAAG,IAAI,KAAG,iBA6CjG,CAAC;AAgBF,eAAO,MAAM,iCAAiC,yCAI3C;IACD,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;CACnB,iBAOA,CAAC;AAEF,eAAO,MAAM,+BAA+B,yCAIzC;IACD,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;CACnB,iBAOA,CAAC;AA0FF,eAAO,MAAM,wBAAwB,+BAGlC;IACD,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB,iBAuBA,CAAC;AAEF,eAAO,MAAM,2BAA2B,+BAGrC;IACD,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB,KAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CA6BzB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/backup/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/backup/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AA8CzD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,uGASnC;IACD,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,GAAG,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;CACjB,kBA+DA,CAAC"}
|
package/src/client.d.ts
CHANGED
|
@@ -276,6 +276,17 @@ export declare class DynamicWalletClient {
|
|
|
276
276
|
elevatedAccessToken?: string;
|
|
277
277
|
traceContext?: TraceContext;
|
|
278
278
|
}): Promise<void>;
|
|
279
|
+
/**
|
|
280
|
+
* Resolves backup locations for a reshare operation.
|
|
281
|
+
*
|
|
282
|
+
* When reshareOnNextSignOn triggers (e.g., after Google OAuth refresh), the caller
|
|
283
|
+
* doesn't pass backup parameters. This method detects that case and preserves the
|
|
284
|
+
* wallet's existing backup locations (Google Drive, iCloud, delegation) instead of
|
|
285
|
+
* proceeding with empty backup locations.
|
|
286
|
+
*
|
|
287
|
+
* @returns The backup locations to use for the reshare operation
|
|
288
|
+
*/
|
|
289
|
+
private resolveBackupLocationsForReshare;
|
|
279
290
|
/**
|
|
280
291
|
* Gets the Bitcoin config for MPC operations by looking up addressType
|
|
281
292
|
* from walletMap, with fallback to deriving it from derivationPath.
|