@dynamic-labs-wallet/browser 0.0.338 → 0.0.340
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 +250 -157
- package/index.esm.js +249 -157
- package/package.json +3 -3
- package/src/backup/providers/googleDrive.d.ts +17 -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 +21 -10
- package/src/client.d.ts.map +1 -1
- package/src/utils.d.ts +13 -8
- package/src/utils.d.ts.map +1 -1
package/index.esm.js
CHANGED
|
@@ -356,6 +356,21 @@ class InvalidPasswordError extends Error {
|
|
|
356
356
|
};
|
|
357
357
|
|
|
358
358
|
const GOOGLE_DRIVE_UPLOAD_API = 'https://www.googleapis.com';
|
|
359
|
+
const USER_ACTIONABLE_REASONS = new Set([
|
|
360
|
+
'auth_denied',
|
|
361
|
+
'storage_full',
|
|
362
|
+
// Google Drive credentials are provided per-customer (each project's own
|
|
363
|
+
// Google Cloud OAuth app), so 429s hit the customer's project quota, not
|
|
364
|
+
// Dynamic's. From an oncall perspective there is nothing to act on -- the
|
|
365
|
+
// end user just needs to wait and retry.
|
|
366
|
+
'rate_limited'
|
|
367
|
+
]);
|
|
368
|
+
/**
|
|
369
|
+
* Returns true when the failure reason represents a problem the end user
|
|
370
|
+
* has to wait out or resolve themselves (e.g. grant Drive permission, free
|
|
371
|
+
* up storage, wait for a per-user/per-project rate limit to clear) and not
|
|
372
|
+
* a system-side issue oncall should investigate.
|
|
373
|
+
*/ const isUserActionableGoogleDriveBackupErrorReason = (reason)=>USER_ACTIONABLE_REASONS.has(reason);
|
|
359
374
|
const NON_RETRYABLE_AUTH_REASONS = new Set([
|
|
360
375
|
'insufficientPermissions',
|
|
361
376
|
'unauthorized',
|
|
@@ -373,7 +388,8 @@ const RATE_LIMIT_REASONS = new Set([
|
|
|
373
388
|
]);
|
|
374
389
|
const NETWORK_ERROR = {
|
|
375
390
|
message: 'Could not reach Google Drive. Please check your internet connection and try again.',
|
|
376
|
-
isRetryable: true
|
|
391
|
+
isRetryable: true,
|
|
392
|
+
errorReason: 'network'
|
|
377
393
|
};
|
|
378
394
|
/**
|
|
379
395
|
* Maps a Google Drive API error to an actionable user-facing message and a retry hint.
|
|
@@ -386,31 +402,36 @@ const NETWORK_ERROR = {
|
|
|
386
402
|
if (httpStatus === 401 || reason && NON_RETRYABLE_AUTH_REASONS.has(reason) || detailReason && NON_RETRYABLE_AUTH_DETAIL_REASONS.has(detailReason)) {
|
|
387
403
|
return {
|
|
388
404
|
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.',
|
|
389
|
-
isRetryable: false
|
|
405
|
+
isRetryable: false,
|
|
406
|
+
errorReason: 'auth_denied'
|
|
390
407
|
};
|
|
391
408
|
}
|
|
392
409
|
if (reason === 'storageQuotaExceeded') {
|
|
393
410
|
return {
|
|
394
411
|
message: 'Google Drive storage is full. Free up space in your Google Drive and try again.',
|
|
395
|
-
isRetryable: false
|
|
412
|
+
isRetryable: false,
|
|
413
|
+
errorReason: 'storage_full'
|
|
396
414
|
};
|
|
397
415
|
}
|
|
398
416
|
if (httpStatus === 429 || reason && RATE_LIMIT_REASONS.has(reason)) {
|
|
399
417
|
return {
|
|
400
418
|
message: 'Google Drive is temporarily rate-limited. Please try again in a few moments.',
|
|
401
|
-
isRetryable: true
|
|
419
|
+
isRetryable: true,
|
|
420
|
+
errorReason: 'rate_limited'
|
|
402
421
|
};
|
|
403
422
|
}
|
|
404
423
|
if (httpStatus >= 500 && httpStatus < 600) {
|
|
405
424
|
return {
|
|
406
425
|
message: 'Google Drive is temporarily unavailable. Please try again shortly.',
|
|
407
|
-
isRetryable: true
|
|
426
|
+
isRetryable: true,
|
|
427
|
+
errorReason: 'service_unavailable'
|
|
408
428
|
};
|
|
409
429
|
}
|
|
410
430
|
const detail = reason != null ? reason : error == null ? void 0 : error.status;
|
|
411
431
|
return {
|
|
412
432
|
message: detail ? `Google Drive upload failed (HTTP ${httpStatus}: ${detail}).` : `Google Drive upload failed (HTTP ${httpStatus}).`,
|
|
413
|
-
isRetryable: false
|
|
433
|
+
isRetryable: false,
|
|
434
|
+
errorReason: 'unknown'
|
|
414
435
|
};
|
|
415
436
|
};
|
|
416
437
|
const parseGoogleDriveErrorBody = async (response)=>{
|
|
@@ -423,6 +444,7 @@ const parseGoogleDriveErrorBody = async (response)=>{
|
|
|
423
444
|
const createGoogleDriveError = (mapped)=>{
|
|
424
445
|
const error = new Error(mapped.message);
|
|
425
446
|
error.isRetryable = mapped.isRetryable;
|
|
447
|
+
error.errorReason = mapped.errorReason;
|
|
426
448
|
return error;
|
|
427
449
|
};
|
|
428
450
|
const uploadFileToGoogleDriveAppStorage = async ({ accessToken, fileName, jsonData })=>{
|
|
@@ -587,6 +609,62 @@ const ERROR_REFRESH_NOT_SUPPORTED_FOR_TWO_OF_THREE = '[DynamicWaasWalletClient]:
|
|
|
587
609
|
* resolve to the same wallet entry.
|
|
588
610
|
*/ const normalizeAddress = (address)=>address.toLowerCase();
|
|
589
611
|
|
|
612
|
+
const ERROR_PASSWORD_MISMATCH = '[DynamicWaasWalletClient]: Password does not match the password used for existing wallets. All wallets must use the same password for encrypted backups.';
|
|
613
|
+
const ERROR_PASSWORD_REQUIRED_FOR_ENCRYPTED_WALLET = '[DynamicWaasWalletClient]: Password is required for refresh/reshare of a password-encrypted wallet.';
|
|
614
|
+
const ERROR_EXISTING_PASSWORD_REQUIRED = '[DynamicWaasWalletClient]: Existing password is required to update the password for encrypted wallets.';
|
|
615
|
+
const ERROR_WALLETS_ALREADY_ENCRYPTED = '[DynamicWaasWalletClient]: Cannot set password: wallets are already password-encrypted. Use updatePassword instead.';
|
|
616
|
+
/**
|
|
617
|
+
* Checks if password consistency validation is needed.
|
|
618
|
+
* Returns false if no password provided or password is the environmentId default.
|
|
619
|
+
*/ function shouldValidatePassword(password, environmentId) {
|
|
620
|
+
if (!password || password === environmentId) {
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
return true;
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Finds the first password-encrypted wallet in the walletMap.
|
|
627
|
+
* Returns [normalizedAddress, walletProperties] or undefined.
|
|
628
|
+
*/ function findPasswordEncryptedWallet(walletMap) {
|
|
629
|
+
for (const [address, wallet] of Object.entries(walletMap)){
|
|
630
|
+
var _wallet_clientKeySharesBackupInfo;
|
|
631
|
+
if ((_wallet_clientKeySharesBackupInfo = wallet.clientKeySharesBackupInfo) == null ? void 0 : _wallet_clientKeySharesBackupInfo.passwordEncrypted) {
|
|
632
|
+
return [
|
|
633
|
+
address,
|
|
634
|
+
wallet
|
|
635
|
+
];
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
return undefined;
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Extracts the first encrypted key share credential from cached encrypted data.
|
|
642
|
+
* Returns the base64-encoded encrypted credential string, or undefined if none found.
|
|
643
|
+
*/ function extractFirstEncryptedCredential(encryptedData) {
|
|
644
|
+
try {
|
|
645
|
+
const parsed = JSON.parse(encryptedData);
|
|
646
|
+
const keyShares = parsed == null ? void 0 : parsed.keyShares;
|
|
647
|
+
if (!Array.isArray(keyShares) || keyShares.length === 0) {
|
|
648
|
+
return undefined;
|
|
649
|
+
}
|
|
650
|
+
return keyShares[0].encryptedAccountCredential;
|
|
651
|
+
} catch (e) {
|
|
652
|
+
return undefined;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Checks if a wallet uses password-based encryption for its key share backups.
|
|
657
|
+
*/ function isWalletPasswordEncrypted(wallet) {
|
|
658
|
+
var _wallet_clientKeySharesBackupInfo;
|
|
659
|
+
var _wallet_clientKeySharesBackupInfo_passwordEncrypted;
|
|
660
|
+
return (_wallet_clientKeySharesBackupInfo_passwordEncrypted = wallet == null ? void 0 : (_wallet_clientKeySharesBackupInfo = wallet.clientKeySharesBackupInfo) == null ? void 0 : _wallet_clientKeySharesBackupInfo.passwordEncrypted) != null ? _wallet_clientKeySharesBackupInfo_passwordEncrypted : false;
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Checks if an error indicates a password mismatch (wrong password during decryption).
|
|
664
|
+
*/ function isPasswordMismatchError(error) {
|
|
665
|
+
return error instanceof InvalidPasswordError;
|
|
666
|
+
}
|
|
667
|
+
|
|
590
668
|
const isBrowser = ()=>typeof window !== 'undefined';
|
|
591
669
|
/**
|
|
592
670
|
* Helper function to extract pubkey from potentially nested structure
|
|
@@ -644,36 +722,6 @@ const getClientKeyShareBackupInfo = (params)=>{
|
|
|
644
722
|
passwordEncrypted
|
|
645
723
|
};
|
|
646
724
|
};
|
|
647
|
-
/**
|
|
648
|
-
* Helper function to merge keyshares and remove duplicates based on pubkey and secretShare
|
|
649
|
-
* @param existingKeyShares - Array of existing keyshares
|
|
650
|
-
* @param newKeyShares - Array of new keyshares to merge
|
|
651
|
-
* @returns Array of merged unique keyshares
|
|
652
|
-
*/ const mergeUniqueKeyShares = (existingKeyShares, newKeyShares)=>{
|
|
653
|
-
const hasDegeneratePubkeyToString = [
|
|
654
|
-
...existingKeyShares,
|
|
655
|
-
...newKeyShares
|
|
656
|
-
].some((share)=>{
|
|
657
|
-
var _share_pubkey;
|
|
658
|
-
return 'pubkey' in share && ((_share_pubkey = share.pubkey) == null ? void 0 : _share_pubkey.toString()) === '[object Object]' // NOSONAR
|
|
659
|
-
;
|
|
660
|
-
});
|
|
661
|
-
if (hasDegeneratePubkeyToString) {
|
|
662
|
-
Logger.warn('[mergeUniqueKeyShares] pubkey.toString() returned "[object Object]" — dedup falls back to secretShare-only comparison', {
|
|
663
|
-
existingCount: existingKeyShares.length,
|
|
664
|
-
newCount: newKeyShares.length
|
|
665
|
-
});
|
|
666
|
-
}
|
|
667
|
-
const uniqueKeyShares = newKeyShares.filter((newShare)=>!existingKeyShares.some((existingShare)=>{
|
|
668
|
-
if (!('pubkey' in newShare) || !('pubkey' in existingShare)) return false;
|
|
669
|
-
if (!newShare.pubkey || !existingShare.pubkey) return false;
|
|
670
|
-
return newShare.pubkey.toString() === existingShare.pubkey.toString() && newShare.secretShare === existingShare.secretShare;
|
|
671
|
-
}));
|
|
672
|
-
return [
|
|
673
|
-
...existingKeyShares,
|
|
674
|
-
...uniqueKeyShares
|
|
675
|
-
];
|
|
676
|
-
};
|
|
677
725
|
const timeoutPromise = ({ timeInMs, activity = 'Ceremony' })=>{
|
|
678
726
|
return new Promise((_, reject)=>setTimeout(()=>reject(new Error(`${activity} did not complete in ${timeInMs}ms`)), timeInMs));
|
|
679
727
|
};
|
|
@@ -743,6 +791,31 @@ const logRetryExhausted = (operationName, maxAttempts, errorContext, logContext)
|
|
|
743
791
|
// TypeScript needs this even though it's unreachable
|
|
744
792
|
throw new Error('Unreachable code');
|
|
745
793
|
}
|
|
794
|
+
/**
|
|
795
|
+
* Classifies whether a wallet-creation / wallet-import ceremony error is
|
|
796
|
+
* worth retrying. Returns true for terminal errors that won't get better on
|
|
797
|
+
* retry (wrong password, missing passcode, public-key mismatch, etc.) so the
|
|
798
|
+
* caller can mark them with `isRetryable: false` and short-circuit
|
|
799
|
+
* `retryPromise`.
|
|
800
|
+
*/ const isNonRetryableCeremonyError = (error)=>{
|
|
801
|
+
if (!error) return false;
|
|
802
|
+
if ((error == null ? void 0 : error.isRetryable) === false) return true;
|
|
803
|
+
if (isPasswordMismatchError(error)) return true;
|
|
804
|
+
if (error instanceof InvalidPasswordError) return true;
|
|
805
|
+
const message = error instanceof Error ? error.message : '';
|
|
806
|
+
if (!message) return false;
|
|
807
|
+
if (message.includes(ERROR_PASSCODE_REQUIRED)) return true;
|
|
808
|
+
if (message.includes(ERROR_PUBLIC_KEY_MISMATCH)) return true;
|
|
809
|
+
return false;
|
|
810
|
+
};
|
|
811
|
+
/**
|
|
812
|
+
* Marks an error as non-retryable in place so `retryPromise` will skip
|
|
813
|
+
* remaining attempts. Used at ceremony retry boundaries.
|
|
814
|
+
*/ const markCeremonyErrorNonRetryable = (error)=>{
|
|
815
|
+
if (error && typeof error === 'object' && isNonRetryableCeremonyError(error)) {
|
|
816
|
+
error.isRetryable = false;
|
|
817
|
+
}
|
|
818
|
+
};
|
|
746
819
|
const formatEvmMessage = (message)=>{
|
|
747
820
|
if (typeof message === 'string' && message.startsWith('0x')) {
|
|
748
821
|
const serializedTxBytes = Uint8Array.from(Buffer.from(message.slice(2), 'hex'));
|
|
@@ -1251,21 +1324,25 @@ const initializeCloudKit = async (config, signInButtonId, onSignInRequired, onSi
|
|
|
1251
1324
|
* Processes a single upload result and logs appropriately
|
|
1252
1325
|
* @returns Failure details if rejected, undefined if successful
|
|
1253
1326
|
*/ const processUploadResult = (result, locationName, logContext, logger)=>{
|
|
1254
|
-
var _result_reason;
|
|
1255
1327
|
if (result.status === 'fulfilled') {
|
|
1256
1328
|
logger.info(`[DynamicWaasWalletClient] Successfully uploaded keyshares to ${locationName}`, logContext);
|
|
1257
1329
|
return undefined;
|
|
1258
1330
|
}
|
|
1259
1331
|
const { message, stack } = getErrorDetails(result.reason);
|
|
1260
|
-
const
|
|
1332
|
+
const reasonProps = result.reason;
|
|
1333
|
+
const isRetryable = (reasonProps == null ? void 0 : reasonProps.isRetryable) !== false;
|
|
1334
|
+
var _reasonProps_errorReason;
|
|
1335
|
+
const errorReason = (_reasonProps_errorReason = reasonProps == null ? void 0 : reasonProps.errorReason) != null ? _reasonProps_errorReason : 'unknown';
|
|
1261
1336
|
logger.error(`[DynamicWaasWalletClient] Failed to upload keyshares to ${locationName}`, _extends({}, logContext, {
|
|
1262
1337
|
error: message,
|
|
1263
|
-
errorStack: stack
|
|
1338
|
+
errorStack: stack,
|
|
1339
|
+
errorReason
|
|
1264
1340
|
}));
|
|
1265
1341
|
return {
|
|
1266
1342
|
locationName,
|
|
1267
1343
|
message,
|
|
1268
|
-
isRetryable
|
|
1344
|
+
isRetryable,
|
|
1345
|
+
errorReason
|
|
1269
1346
|
};
|
|
1270
1347
|
};
|
|
1271
1348
|
/**
|
|
@@ -1311,8 +1388,24 @@ const initializeCloudKit = async (config, signInButtonId, onSignInRequired, onSi
|
|
|
1311
1388
|
processUploadResult(results[1], 'Google Drive Personal', logContext, logger)
|
|
1312
1389
|
].filter((failure)=>failure !== undefined);
|
|
1313
1390
|
if (failures.length > 0) {
|
|
1314
|
-
|
|
1391
|
+
const uniqueReasons = [
|
|
1392
|
+
...new Set(failures.map((f)=>f.errorReason))
|
|
1393
|
+
];
|
|
1394
|
+
const isUserActionable = failures.every((f)=>isUserActionableGoogleDriveBackupErrorReason(f.errorReason));
|
|
1395
|
+
// OAuth scope and storage-quota failures are end-user issues (grant
|
|
1396
|
+
// Drive permission, free up storage) — not anything oncall can fix.
|
|
1397
|
+
// Downgrade them to warn so monitors can filter on status:error and
|
|
1398
|
+
// still catch real system-side failures (5xx/network/unknown) without
|
|
1399
|
+
// the user-side noise drowning out the signal.
|
|
1400
|
+
const logFn = isUserActionable ? logger.warn : logger.error;
|
|
1401
|
+
// When every location failed for the same reason (the dominant case)
|
|
1402
|
+
// surface a single string; otherwise emit the array so consumers can
|
|
1403
|
+
// see the mix.
|
|
1404
|
+
const logErrorReason = uniqueReasons.length === 1 ? uniqueReasons[0] : uniqueReasons;
|
|
1405
|
+
logFn.call(logger, '[DynamicWaasWalletClient] Google Drive backup failed', _extends({}, logContext, {
|
|
1315
1406
|
errorCount: failures.length,
|
|
1407
|
+
errorReason: logErrorReason,
|
|
1408
|
+
isUserActionable,
|
|
1316
1409
|
errors: failures.map((f)=>`Failed to backup keyshares to ${f.locationName}: ${f.message}`)
|
|
1317
1410
|
}));
|
|
1318
1411
|
// Both upload destinations (appDataFolder + personal) commonly fail with
|
|
@@ -1326,67 +1419,12 @@ const initializeCloudKit = async (config, signInButtonId, onSignInRequired, onSi
|
|
|
1326
1419
|
const finalMessage = uniqueMessages.length === 1 ? uniqueMessages[0] : failures.map((f)=>`${f.locationName}: ${f.message}`).join(' | ');
|
|
1327
1420
|
const aggregatedError = new Error(finalMessage);
|
|
1328
1421
|
aggregatedError.isRetryable = failures.every((f)=>f.isRetryable);
|
|
1422
|
+
aggregatedError.errorReason = uniqueReasons.length === 1 ? uniqueReasons[0] : 'unknown';
|
|
1329
1423
|
throw aggregatedError;
|
|
1330
1424
|
}
|
|
1331
1425
|
logger.info('[DynamicWaasWalletClient] Google Drive backup completed successfully', logContext);
|
|
1332
1426
|
};
|
|
1333
1427
|
|
|
1334
|
-
const ERROR_PASSWORD_MISMATCH = '[DynamicWaasWalletClient]: Password does not match the password used for existing wallets. All wallets must use the same password for encrypted backups.';
|
|
1335
|
-
const ERROR_PASSWORD_REQUIRED_FOR_ENCRYPTED_WALLET = '[DynamicWaasWalletClient]: Password is required for refresh/reshare of a password-encrypted wallet.';
|
|
1336
|
-
const ERROR_EXISTING_PASSWORD_REQUIRED = '[DynamicWaasWalletClient]: Existing password is required to update the password for encrypted wallets.';
|
|
1337
|
-
const ERROR_WALLETS_ALREADY_ENCRYPTED = '[DynamicWaasWalletClient]: Cannot set password: wallets are already password-encrypted. Use updatePassword instead.';
|
|
1338
|
-
/**
|
|
1339
|
-
* Checks if password consistency validation is needed.
|
|
1340
|
-
* Returns false if no password provided or password is the environmentId default.
|
|
1341
|
-
*/ function shouldValidatePassword(password, environmentId) {
|
|
1342
|
-
if (!password || password === environmentId) {
|
|
1343
|
-
return false;
|
|
1344
|
-
}
|
|
1345
|
-
return true;
|
|
1346
|
-
}
|
|
1347
|
-
/**
|
|
1348
|
-
* Finds the first password-encrypted wallet in the walletMap.
|
|
1349
|
-
* Returns [normalizedAddress, walletProperties] or undefined.
|
|
1350
|
-
*/ function findPasswordEncryptedWallet(walletMap) {
|
|
1351
|
-
for (const [address, wallet] of Object.entries(walletMap)){
|
|
1352
|
-
var _wallet_clientKeySharesBackupInfo;
|
|
1353
|
-
if ((_wallet_clientKeySharesBackupInfo = wallet.clientKeySharesBackupInfo) == null ? void 0 : _wallet_clientKeySharesBackupInfo.passwordEncrypted) {
|
|
1354
|
-
return [
|
|
1355
|
-
address,
|
|
1356
|
-
wallet
|
|
1357
|
-
];
|
|
1358
|
-
}
|
|
1359
|
-
}
|
|
1360
|
-
return undefined;
|
|
1361
|
-
}
|
|
1362
|
-
/**
|
|
1363
|
-
* Extracts the first encrypted key share credential from cached encrypted data.
|
|
1364
|
-
* Returns the base64-encoded encrypted credential string, or undefined if none found.
|
|
1365
|
-
*/ function extractFirstEncryptedCredential(encryptedData) {
|
|
1366
|
-
try {
|
|
1367
|
-
const parsed = JSON.parse(encryptedData);
|
|
1368
|
-
const keyShares = parsed == null ? void 0 : parsed.keyShares;
|
|
1369
|
-
if (!Array.isArray(keyShares) || keyShares.length === 0) {
|
|
1370
|
-
return undefined;
|
|
1371
|
-
}
|
|
1372
|
-
return keyShares[0].encryptedAccountCredential;
|
|
1373
|
-
} catch (e) {
|
|
1374
|
-
return undefined;
|
|
1375
|
-
}
|
|
1376
|
-
}
|
|
1377
|
-
/**
|
|
1378
|
-
* Checks if a wallet uses password-based encryption for its key share backups.
|
|
1379
|
-
*/ function isWalletPasswordEncrypted(wallet) {
|
|
1380
|
-
var _wallet_clientKeySharesBackupInfo;
|
|
1381
|
-
var _wallet_clientKeySharesBackupInfo_passwordEncrypted;
|
|
1382
|
-
return (_wallet_clientKeySharesBackupInfo_passwordEncrypted = wallet == null ? void 0 : (_wallet_clientKeySharesBackupInfo = wallet.clientKeySharesBackupInfo) == null ? void 0 : _wallet_clientKeySharesBackupInfo.passwordEncrypted) != null ? _wallet_clientKeySharesBackupInfo_passwordEncrypted : false;
|
|
1383
|
-
}
|
|
1384
|
-
/**
|
|
1385
|
-
* Checks if an error indicates a password mismatch (wrong password during decryption).
|
|
1386
|
-
*/ function isPasswordMismatchError(error) {
|
|
1387
|
-
return error instanceof InvalidPasswordError;
|
|
1388
|
-
}
|
|
1389
|
-
|
|
1390
1428
|
/**
|
|
1391
1429
|
* Returns true for errors that represent expected user-facing conditions
|
|
1392
1430
|
* (e.g. wrong password). These are logged at `info` instead of `error`
|
|
@@ -2314,7 +2352,18 @@ class DynamicWalletClient {
|
|
|
2314
2352
|
clientKeygenResults
|
|
2315
2353
|
};
|
|
2316
2354
|
}
|
|
2317
|
-
async keyGen(
|
|
2355
|
+
async keyGen(args) {
|
|
2356
|
+
return retryPromise(()=>this.runKeyGenAttempt(args), {
|
|
2357
|
+
maxAttempts: 5,
|
|
2358
|
+
retryInterval: 500,
|
|
2359
|
+
operationName: 'keyGen',
|
|
2360
|
+
logContext: _extends({
|
|
2361
|
+
chainName: args.chainName,
|
|
2362
|
+
thresholdSignatureScheme: args.thresholdSignatureScheme
|
|
2363
|
+
}, this.getTraceContext(args.traceContext))
|
|
2364
|
+
});
|
|
2365
|
+
}
|
|
2366
|
+
async runKeyGenAttempt({ chainName, thresholdSignatureScheme, bitcoinConfig, onCeremonyComplete, traceContext, password, signedSessionId }) {
|
|
2318
2367
|
const dynamicRequestId = v4();
|
|
2319
2368
|
try {
|
|
2320
2369
|
this.assertPasswordRequired(password);
|
|
@@ -2376,6 +2425,7 @@ class DynamicWalletClient {
|
|
|
2376
2425
|
clientKeyShares
|
|
2377
2426
|
};
|
|
2378
2427
|
} catch (error) {
|
|
2428
|
+
markCeremonyErrorNonRetryable(error);
|
|
2379
2429
|
logError({
|
|
2380
2430
|
message: 'Error in keyGen',
|
|
2381
2431
|
error: error,
|
|
@@ -2388,7 +2438,18 @@ class DynamicWalletClient {
|
|
|
2388
2438
|
throw error;
|
|
2389
2439
|
}
|
|
2390
2440
|
}
|
|
2391
|
-
async importRawPrivateKey(
|
|
2441
|
+
async importRawPrivateKey(args) {
|
|
2442
|
+
return retryPromise(()=>this.runImportRawPrivateKeyAttempt(args), {
|
|
2443
|
+
maxAttempts: 5,
|
|
2444
|
+
retryInterval: 500,
|
|
2445
|
+
operationName: 'importRawPrivateKey',
|
|
2446
|
+
logContext: _extends({
|
|
2447
|
+
chainName: args.chainName,
|
|
2448
|
+
thresholdSignatureScheme: args.thresholdSignatureScheme
|
|
2449
|
+
}, this.getTraceContext(args.traceContext))
|
|
2450
|
+
});
|
|
2451
|
+
}
|
|
2452
|
+
async runImportRawPrivateKeyAttempt({ chainName, privateKey, thresholdSignatureScheme, bitcoinConfig, onError, onCeremonyComplete, traceContext, legacyWalletId, password, signedSessionId }) {
|
|
2392
2453
|
const dynamicRequestId = v4();
|
|
2393
2454
|
try {
|
|
2394
2455
|
this.assertPasswordRequired(password);
|
|
@@ -2474,6 +2535,7 @@ class DynamicWalletClient {
|
|
|
2474
2535
|
clientKeyShares: clientKeygenResults
|
|
2475
2536
|
};
|
|
2476
2537
|
} catch (error) {
|
|
2538
|
+
markCeremonyErrorNonRetryable(error);
|
|
2477
2539
|
logError({
|
|
2478
2540
|
message: 'Error in importRawPrivateKey',
|
|
2479
2541
|
error: error,
|
|
@@ -2674,11 +2736,9 @@ class DynamicWalletClient {
|
|
|
2674
2736
|
mfaToken,
|
|
2675
2737
|
storeRecoveredShares: false
|
|
2676
2738
|
});
|
|
2677
|
-
// Overwrite local key shares with recovered ones (not merge)
|
|
2678
2739
|
await this.setClientKeySharesToStorage({
|
|
2679
2740
|
accountAddress,
|
|
2680
|
-
clientKeyShares: recoveredKeyShares
|
|
2681
|
-
overwriteOrMerge: 'overwrite'
|
|
2741
|
+
clientKeyShares: recoveredKeyShares
|
|
2682
2742
|
});
|
|
2683
2743
|
this.logger.info('[DynamicWaasWalletClient] Key shares recovered, retrying operation', _extends({
|
|
2684
2744
|
accountAddress,
|
|
@@ -3012,8 +3072,7 @@ class DynamicWalletClient {
|
|
|
3012
3072
|
});
|
|
3013
3073
|
await this.setClientKeySharesToStorage({
|
|
3014
3074
|
accountAddress,
|
|
3015
|
-
clientKeyShares: refreshResults
|
|
3016
|
-
overwriteOrMerge: 'overwrite'
|
|
3075
|
+
clientKeyShares: refreshResults
|
|
3017
3076
|
});
|
|
3018
3077
|
} catch (error) {
|
|
3019
3078
|
// Expected condition — not a server error, no need to page on this.
|
|
@@ -3368,8 +3427,7 @@ class DynamicWalletClient {
|
|
|
3368
3427
|
// only after backup succeeds.
|
|
3369
3428
|
await this.setClientKeySharesToStorage({
|
|
3370
3429
|
accountAddress,
|
|
3371
|
-
clientKeyShares: distribution.clientShares
|
|
3372
|
-
overwriteOrMerge: 'overwrite'
|
|
3430
|
+
clientKeyShares: distribution.clientShares
|
|
3373
3431
|
});
|
|
3374
3432
|
} catch (error) {
|
|
3375
3433
|
logError({
|
|
@@ -3392,8 +3450,7 @@ class DynamicWalletClient {
|
|
|
3392
3450
|
});
|
|
3393
3451
|
await this.setClientKeySharesToStorage({
|
|
3394
3452
|
accountAddress,
|
|
3395
|
-
clientKeyShares: []
|
|
3396
|
-
overwriteOrMerge: 'overwrite'
|
|
3453
|
+
clientKeyShares: []
|
|
3397
3454
|
});
|
|
3398
3455
|
throw error;
|
|
3399
3456
|
}
|
|
@@ -3739,63 +3796,44 @@ class DynamicWalletClient {
|
|
|
3739
3796
|
* Helper function to store client key shares in storage.
|
|
3740
3797
|
* Uses secureStorage when available (mobile), otherwise falls back to localStorage (browser).
|
|
3741
3798
|
*/ /**
|
|
3742
|
-
*
|
|
3743
|
-
|
|
3744
|
-
* persistence-info log so we can audit storage operations from traces.
|
|
3745
|
-
*/ async resolveSharesToPersist({ accountAddress, clientKeyShares, overwriteOrMerge, source, readExisting }) {
|
|
3746
|
-
let sharesToStore;
|
|
3747
|
-
let existingCount = 0;
|
|
3748
|
-
if (overwriteOrMerge === 'overwrite') {
|
|
3749
|
-
sharesToStore = clientKeyShares;
|
|
3750
|
-
} else {
|
|
3751
|
-
const existing = await readExisting();
|
|
3752
|
-
existingCount = existing.length;
|
|
3753
|
-
sharesToStore = mergeUniqueKeyShares(existing, clientKeyShares);
|
|
3754
|
-
}
|
|
3799
|
+
* Emits a persistence-info log so storage writes are auditable from traces.
|
|
3800
|
+
*/ logSharePersistence({ accountAddress, clientKeyShares, source }) {
|
|
3755
3801
|
this.logger.info('[DynamicWaasWalletClient] Persisting client key shares', {
|
|
3756
3802
|
accountAddress,
|
|
3757
3803
|
source,
|
|
3758
|
-
|
|
3759
|
-
inputCount: clientKeyShares.length,
|
|
3760
|
-
existingCount,
|
|
3761
|
-
finalCount: sharesToStore.length
|
|
3804
|
+
inputCount: clientKeyShares.length
|
|
3762
3805
|
});
|
|
3763
|
-
return sharesToStore;
|
|
3764
3806
|
}
|
|
3765
|
-
async setClientKeySharesToLocalStorage({ accountAddress, clientKeyShares
|
|
3807
|
+
async setClientKeySharesToLocalStorage({ accountAddress, clientKeyShares }) {
|
|
3766
3808
|
accountAddress = normalizeAddress(accountAddress);
|
|
3767
|
-
|
|
3809
|
+
this.logSharePersistence({
|
|
3768
3810
|
accountAddress,
|
|
3769
3811
|
clientKeyShares,
|
|
3770
|
-
|
|
3771
|
-
source: 'localStorage',
|
|
3772
|
-
readExisting: ()=>this.getClientKeySharesFromLocalStorage({
|
|
3773
|
-
accountAddress
|
|
3774
|
-
})
|
|
3812
|
+
source: 'localStorage'
|
|
3775
3813
|
});
|
|
3776
3814
|
const stringifiedClientKeyShares = JSON.stringify({
|
|
3777
|
-
clientKeyShares
|
|
3815
|
+
clientKeyShares
|
|
3778
3816
|
});
|
|
3779
3817
|
await this.storage.setItem(accountAddress, stringifiedClientKeyShares);
|
|
3780
3818
|
}
|
|
3781
3819
|
/**
|
|
3782
|
-
*
|
|
3820
|
+
* Replaces the client key shares stored for `accountAddress` with `clientKeyShares`.
|
|
3783
3821
|
* Uses secureStorage when available (mobile), otherwise falls back to localStorage (browser).
|
|
3784
|
-
|
|
3822
|
+
*
|
|
3823
|
+
* A WaaS wallet holds exactly one client share per device (see share-distribution
|
|
3824
|
+
* factories in types.ts), so writes are always a full replacement — there is no
|
|
3825
|
+
* merge mode. Callers that need to clear local shares pass `clientKeyShares: []`.
|
|
3826
|
+
*/ async setClientKeySharesToStorage({ accountAddress, clientKeyShares }) {
|
|
3785
3827
|
accountAddress = normalizeAddress(accountAddress);
|
|
3786
3828
|
// Use secure storage if available (mobile)
|
|
3787
3829
|
if (this.secureStorage) {
|
|
3788
3830
|
try {
|
|
3789
|
-
|
|
3831
|
+
this.logSharePersistence({
|
|
3790
3832
|
accountAddress,
|
|
3791
3833
|
clientKeyShares,
|
|
3792
|
-
|
|
3793
|
-
source: 'secureStorage',
|
|
3794
|
-
readExisting: ()=>this.getClientKeySharesFromStorage({
|
|
3795
|
-
accountAddress
|
|
3796
|
-
})
|
|
3834
|
+
source: 'secureStorage'
|
|
3797
3835
|
});
|
|
3798
|
-
await this.secureStorage.setClientKeyShare(accountAddress,
|
|
3836
|
+
await this.secureStorage.setClientKeyShare(accountAddress, clientKeyShares);
|
|
3799
3837
|
return;
|
|
3800
3838
|
} catch (error) {
|
|
3801
3839
|
logError({
|
|
@@ -3811,8 +3849,7 @@ class DynamicWalletClient {
|
|
|
3811
3849
|
// Fallback to localStorage (browser)
|
|
3812
3850
|
await this.setClientKeySharesToLocalStorage({
|
|
3813
3851
|
accountAddress,
|
|
3814
|
-
clientKeyShares
|
|
3815
|
-
overwriteOrMerge
|
|
3852
|
+
clientKeyShares
|
|
3816
3853
|
});
|
|
3817
3854
|
}
|
|
3818
3855
|
/**
|
|
@@ -4604,8 +4641,7 @@ class DynamicWalletClient {
|
|
|
4604
4641
|
if (storeRecoveredShares) {
|
|
4605
4642
|
await this.setClientKeySharesToStorage({
|
|
4606
4643
|
accountAddress,
|
|
4607
|
-
clientKeyShares: decryptedKeyShares
|
|
4608
|
-
overwriteOrMerge: 'merge'
|
|
4644
|
+
clientKeyShares: decryptedKeyShares
|
|
4609
4645
|
});
|
|
4610
4646
|
await this.storage.setItem(this.storageKey, JSON.stringify(this.walletMap));
|
|
4611
4647
|
}
|
|
@@ -5241,13 +5277,8 @@ class DynamicWalletClient {
|
|
|
5241
5277
|
signedSessionId,
|
|
5242
5278
|
shareCount
|
|
5243
5279
|
});
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
});
|
|
5247
|
-
await this.setClientKeySharesToStorage({
|
|
5248
|
-
accountAddress,
|
|
5249
|
-
clientKeyShares: mergeUniqueKeyShares(existingKeyShares, decryptedKeyShares)
|
|
5250
|
-
});
|
|
5280
|
+
// recoverEncryptedBackupByWallet (storeRecoveredShares: true by default) has
|
|
5281
|
+
// already written decryptedKeyShares to storage. No further write needed.
|
|
5251
5282
|
this.logger.debug('[DynamicWaasWalletClient] Recovered backup', {
|
|
5252
5283
|
decryptedKeyShares
|
|
5253
5284
|
});
|
|
@@ -5332,6 +5363,45 @@ class DynamicWalletClient {
|
|
|
5332
5363
|
});
|
|
5333
5364
|
}
|
|
5334
5365
|
/**
|
|
5366
|
+
* Fetches the encrypted-shares blob from the server and caches it in this.storage.
|
|
5367
|
+
* Returns the serialized blob, or null if the wallet is unknown or the API returns
|
|
5368
|
+
* no data. Does not decrypt — callers (e.g. unlockWallet, getWallet eager-load)
|
|
5369
|
+
* own the decrypt step.
|
|
5370
|
+
*/ async fetchAndCacheEncryptedShares({ accountAddress, signedSessionId, mfaToken }) {
|
|
5371
|
+
const walletData = this.getWalletFromMap(accountAddress);
|
|
5372
|
+
if (!walletData) {
|
|
5373
|
+
return null;
|
|
5374
|
+
}
|
|
5375
|
+
const { shares } = this.recoverStrategy({
|
|
5376
|
+
clientKeyShareBackupInfo: walletData.clientKeySharesBackupInfo,
|
|
5377
|
+
thresholdSignatureScheme: walletData.thresholdSignatureScheme,
|
|
5378
|
+
walletOperation: WalletOperation.RECOVER
|
|
5379
|
+
});
|
|
5380
|
+
const externalKeyShareIds = shares[BackupLocation.DYNAMIC] || [];
|
|
5381
|
+
this.logger.info('[unlockWallet] cache miss after getWallet, fetching encrypted shares from server', {
|
|
5382
|
+
context: {
|
|
5383
|
+
accountAddress,
|
|
5384
|
+
walletId: walletData.walletId,
|
|
5385
|
+
externalKeyShareIds
|
|
5386
|
+
}
|
|
5387
|
+
});
|
|
5388
|
+
const data = await this.apiClient.recoverEncryptedBackupByWallet({
|
|
5389
|
+
walletId: walletData.walletId,
|
|
5390
|
+
externalKeyShareIds,
|
|
5391
|
+
signedSessionId,
|
|
5392
|
+
mfaToken,
|
|
5393
|
+
requiresSignedSessionId: this.requiresSignedSessionId(),
|
|
5394
|
+
userId: this.userId
|
|
5395
|
+
});
|
|
5396
|
+
if (!data) {
|
|
5397
|
+
return null;
|
|
5398
|
+
}
|
|
5399
|
+
const serialized = JSON.stringify(data);
|
|
5400
|
+
const encryptedStorageKey = `${normalizeAddress(accountAddress)}${ENCRYPTED_SHARES_STORAGE_SUFFIX}`;
|
|
5401
|
+
await this.storage.setItem(encryptedStorageKey, serialized);
|
|
5402
|
+
return serialized;
|
|
5403
|
+
}
|
|
5404
|
+
/**
|
|
5335
5405
|
* Unlocks a password-encrypted wallet by decrypting cached encrypted shares.
|
|
5336
5406
|
* This method should be called after getWalletRecoveryState returns LOCKED state.
|
|
5337
5407
|
*
|
|
@@ -5339,7 +5409,7 @@ class DynamicWalletClient {
|
|
|
5339
5409
|
* @param password - The password to decrypt the shares
|
|
5340
5410
|
* @param signedSessionId - The signed session ID for authentication
|
|
5341
5411
|
* @returns The unlocked wallet properties
|
|
5342
|
-
*/ async unlockWallet({ accountAddress, password, signedSessionId }) {
|
|
5412
|
+
*/ async unlockWallet({ accountAddress, password, signedSessionId, mfaToken }) {
|
|
5343
5413
|
const dynamicRequestId = v4();
|
|
5344
5414
|
try {
|
|
5345
5415
|
await this.requireWalletFromMap(accountAddress, signedSessionId);
|
|
@@ -5359,6 +5429,18 @@ class DynamicWalletClient {
|
|
|
5359
5429
|
});
|
|
5360
5430
|
encryptedData = await this.storage.getItem(encryptedStorageKey);
|
|
5361
5431
|
}
|
|
5432
|
+
// getWallet short-circuits via checkWalletFields when client key shares are
|
|
5433
|
+
// durable but the encrypted-shares cache in this.storage was evicted (e.g. RN
|
|
5434
|
+
// secureStorage survives a WebKit content-process recycle while localStorage
|
|
5435
|
+
// does not). Populate the cache directly so the single decrypt path below
|
|
5436
|
+
// can proceed.
|
|
5437
|
+
if (!encryptedData) {
|
|
5438
|
+
encryptedData = await this.fetchAndCacheEncryptedShares({
|
|
5439
|
+
accountAddress,
|
|
5440
|
+
signedSessionId,
|
|
5441
|
+
mfaToken
|
|
5442
|
+
});
|
|
5443
|
+
}
|
|
5362
5444
|
if (!encryptedData) {
|
|
5363
5445
|
throw new Error('No encrypted shares found for wallet');
|
|
5364
5446
|
}
|
|
@@ -5372,7 +5454,17 @@ class DynamicWalletClient {
|
|
|
5372
5454
|
const otherEncryptedWallets = Object.entries(this.walletMap).filter(([addr, w])=>addr !== normalizedAccountAddress && w.walletReadyState !== WalletReadyState.READY && isWalletPasswordEncrypted(w));
|
|
5373
5455
|
const results = await Promise.allSettled(otherEncryptedWallets.map(async ([otherAddr])=>{
|
|
5374
5456
|
const otherEncryptedStorageKey = `${otherAddr}${ENCRYPTED_SHARES_STORAGE_SUFFIX}`;
|
|
5375
|
-
|
|
5457
|
+
let otherEncryptedData = await this.storage.getItem(otherEncryptedStorageKey);
|
|
5458
|
+
// Same cache-eviction scenario as the main wallet above: on RN, the
|
|
5459
|
+
// encrypted-shares blob can be gone while client shares survive in
|
|
5460
|
+
// secureStorage. Re-fetch from the server before giving up.
|
|
5461
|
+
if (!otherEncryptedData) {
|
|
5462
|
+
otherEncryptedData = await this.fetchAndCacheEncryptedShares({
|
|
5463
|
+
accountAddress: otherAddr,
|
|
5464
|
+
signedSessionId,
|
|
5465
|
+
mfaToken
|
|
5466
|
+
});
|
|
5467
|
+
}
|
|
5376
5468
|
if (!otherEncryptedData) {
|
|
5377
5469
|
return;
|
|
5378
5470
|
}
|
|
@@ -5761,4 +5853,4 @@ DynamicWalletClient.rooms = {};
|
|
|
5761
5853
|
DynamicWalletClient.roomsInitializing = {};
|
|
5762
5854
|
DynamicWalletClient.roomsPersistChain = Promise.resolve();
|
|
5763
5855
|
|
|
5764
|
-
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,
|
|
5856
|
+
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, isNonRetryableCeremonyError, isPublicKeyMismatchError, isRecoverQueueOperation, isSignQueueOperation, listICloudBackups, markCeremonyErrorNonRetryable, readEnvironmentSettings, retryPromise, shouldReshareToSameBackups, timeoutPromise };
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dynamic-labs-wallet/browser",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.340",
|
|
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.340",
|
|
8
8
|
"@dynamic-labs-wallet/forward-mpc-client": "0.9.0",
|
|
9
|
-
"@dynamic-labs-wallet/primitives": "0.0.
|
|
9
|
+
"@dynamic-labs-wallet/primitives": "0.0.340",
|
|
10
10
|
"@dynamic-labs/sdk-api-core": "^0.0.964",
|
|
11
11
|
"argon2id": "1.0.1",
|
|
12
12
|
"axios": "1.15.2",
|