@dynamic-labs-wallet/browser 0.0.257 → 0.0.259
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 +493 -196
- package/index.esm.js +488 -198
- package/package.json +5 -4
- package/src/client.d.ts +42 -11
- package/src/client.d.ts.map +1 -1
- package/src/constants.d.ts +0 -1
- package/src/constants.d.ts.map +1 -1
- package/src/mpc/mpc.d.ts +8 -1
- package/src/mpc/mpc.d.ts.map +1 -1
- package/src/queue.d.ts +96 -0
- package/src/queue.d.ts.map +1 -0
- package/src/types.d.ts +39 -1
- package/src/types.d.ts.map +1 -1
package/index.cjs.js
CHANGED
|
@@ -9,6 +9,7 @@ var sdkApiCore = require('@dynamic-labs/sdk-api-core');
|
|
|
9
9
|
var loadArgon2idWasm = require('argon2id');
|
|
10
10
|
var axios = require('axios');
|
|
11
11
|
var createHttpError = require('http-errors');
|
|
12
|
+
var PQueue = require('p-queue');
|
|
12
13
|
|
|
13
14
|
function _extends() {
|
|
14
15
|
_extends = Object.assign || function assign(target) {
|
|
@@ -21,6 +22,33 @@ function _extends() {
|
|
|
21
22
|
return _extends.apply(this, arguments);
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Infers the Bitcoin address type from a BIP-44 derivation path
|
|
27
|
+
*
|
|
28
|
+
* @param derivationPathStr - The derivation path string as JSON (e.g. '{"0":84,"1":0,"2":0,"3":0,"4":0}')
|
|
29
|
+
* @returns The inferred BitcoinAddressType, or undefined if it cannot be determined
|
|
30
|
+
*/ const getBitcoinAddressTypeFromDerivationPath = (derivationPathStr)=>{
|
|
31
|
+
try {
|
|
32
|
+
const derivationPathObj = JSON.parse(derivationPathStr);
|
|
33
|
+
const path = Object.values(derivationPathObj).map(Number);
|
|
34
|
+
if (path.length < 2) return undefined;
|
|
35
|
+
const purpose = path[0];
|
|
36
|
+
// Handle both raw and hardened purpose values
|
|
37
|
+
// 84 = Native SegWit (BIP-84), 86 = Taproot (BIP-86)
|
|
38
|
+
// Hardened values have 0x80000000 added
|
|
39
|
+
const purposeIndex = purpose >= 0x80000000 ? purpose - 0x80000000 : purpose;
|
|
40
|
+
switch(purposeIndex){
|
|
41
|
+
case 84:
|
|
42
|
+
return core.BitcoinAddressType.NATIVE_SEGWIT;
|
|
43
|
+
case 86:
|
|
44
|
+
return core.BitcoinAddressType.TAPROOT;
|
|
45
|
+
default:
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
} catch (e) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
24
52
|
const getMPCSignatureScheme = ({ signingAlgorithm, baseRelayUrl = core.MPC_RELAY_PROD_API_URL })=>{
|
|
25
53
|
switch(signingAlgorithm){
|
|
26
54
|
case core.SigningAlgorithm.ECDSA:
|
|
@@ -465,7 +493,6 @@ const SIGNED_SESSION_ID_MIN_VERSION_BY_NAMESPACE = {
|
|
|
465
493
|
};
|
|
466
494
|
const ROOM_EXPIRATION_TIME = 1000 * 60 * 10; // 10 minutes
|
|
467
495
|
const ROOM_CACHE_COUNT = 5;
|
|
468
|
-
const WALLET_BUSY_LOCK_TIMEOUT_MS = 20000; // 20 seconds
|
|
469
496
|
|
|
470
497
|
const ERROR_KEYGEN_FAILED = '[DynamicWaasWalletClient]: Error with keygen';
|
|
471
498
|
const ERROR_CREATE_WALLET_ACCOUNT = '[DynamicWaasWalletClient]: Error creating wallet account';
|
|
@@ -1084,6 +1111,341 @@ const initializeCloudKit = async (config, signInButtonId, onSignInRequired, onSi
|
|
|
1084
1111
|
}
|
|
1085
1112
|
};
|
|
1086
1113
|
|
|
1114
|
+
class WalletNotReadyError extends Error {
|
|
1115
|
+
constructor(accountAddress, walletReadyState){
|
|
1116
|
+
super(`Wallet ${accountAddress} is not ready and requires password to be unlocked`);
|
|
1117
|
+
this.name = 'WalletNotReadyError';
|
|
1118
|
+
this.accountAddress = accountAddress;
|
|
1119
|
+
this.walletReadyState = walletReadyState;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Set of allowed heavy queue operations for runtime validation.
|
|
1124
|
+
*/ const HEAVY_QUEUE_OPERATIONS = new Set([
|
|
1125
|
+
core.WalletOperation.REFRESH,
|
|
1126
|
+
core.WalletOperation.RESHARE,
|
|
1127
|
+
core.WalletOperation.RECOVER
|
|
1128
|
+
]);
|
|
1129
|
+
/**
|
|
1130
|
+
* Type guard to validate that an operation is a valid heavy queue operation.
|
|
1131
|
+
*/ const isHeavyQueueOperation = (operation)=>HEAVY_QUEUE_OPERATIONS.has(operation);
|
|
1132
|
+
/**
|
|
1133
|
+
* Set of allowed sign queue operations for runtime validation.
|
|
1134
|
+
*/ const SIGN_QUEUE_OPERATIONS = new Set([
|
|
1135
|
+
core.WalletOperation.SIGN_MESSAGE,
|
|
1136
|
+
core.WalletOperation.SIGN_TRANSACTION
|
|
1137
|
+
]);
|
|
1138
|
+
/**
|
|
1139
|
+
* Type guard to validate that an operation is a valid sign queue operation.
|
|
1140
|
+
*/ const isSignQueueOperation = (operation)=>SIGN_QUEUE_OPERATIONS.has(operation);
|
|
1141
|
+
/**
|
|
1142
|
+
* Set of allowed recover queue operations for runtime validation.
|
|
1143
|
+
*/ const RECOVER_QUEUE_OPERATIONS = new Set([
|
|
1144
|
+
core.WalletOperation.RECOVER
|
|
1145
|
+
]);
|
|
1146
|
+
/**
|
|
1147
|
+
* Type guard to validate that an operation is a valid recover queue operation.
|
|
1148
|
+
*/ const isRecoverQueueOperation = (operation)=>RECOVER_QUEUE_OPERATIONS.has(operation);
|
|
1149
|
+
class WalletBusyError extends Error {
|
|
1150
|
+
constructor(accountAddress, operation){
|
|
1151
|
+
super(`Wallet ${accountAddress} is currently performing a ${operation} operation`);
|
|
1152
|
+
this.name = 'WalletBusyError';
|
|
1153
|
+
this.operation = operation;
|
|
1154
|
+
this.accountAddress = accountAddress;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Creates distribution where shares go to specified cloud providers
|
|
1159
|
+
* Last share goes to cloud providers, rest to Dynamic backend
|
|
1160
|
+
* @param providers - Array of cloud providers to backup to
|
|
1161
|
+
* @param allShares - All key shares to distribute
|
|
1162
|
+
*/ const createCloudProviderDistribution = ({ providers, allShares })=>{
|
|
1163
|
+
const cloudProviderShares = {};
|
|
1164
|
+
// Last share goes to cloud providers, rest to Dynamic
|
|
1165
|
+
const sharesForCloud = allShares.slice(-1);
|
|
1166
|
+
providers.forEach((provider)=>{
|
|
1167
|
+
cloudProviderShares[provider] = sharesForCloud;
|
|
1168
|
+
});
|
|
1169
|
+
return {
|
|
1170
|
+
clientShares: allShares.slice(0, -1),
|
|
1171
|
+
cloudProviderShares
|
|
1172
|
+
};
|
|
1173
|
+
};
|
|
1174
|
+
/**
|
|
1175
|
+
* Creates distribution with delegation + cloud backup
|
|
1176
|
+
* Client shares backed up to Dynamic AND cloud providers
|
|
1177
|
+
* Delegated share goes to webhook
|
|
1178
|
+
*/ const createDelegationWithCloudProviderDistribution = ({ providers, existingShares, delegatedShare })=>{
|
|
1179
|
+
const cloudProviderShares = {};
|
|
1180
|
+
providers.forEach((provider)=>{
|
|
1181
|
+
cloudProviderShares[provider] = existingShares;
|
|
1182
|
+
});
|
|
1183
|
+
return {
|
|
1184
|
+
clientShares: existingShares,
|
|
1185
|
+
cloudProviderShares,
|
|
1186
|
+
delegatedShare
|
|
1187
|
+
};
|
|
1188
|
+
};
|
|
1189
|
+
/**
|
|
1190
|
+
* Creates distribution for adding cloud backup to existing delegation
|
|
1191
|
+
* Client shares go to both Dynamic AND cloud providers
|
|
1192
|
+
* delegatedShare is undefined - we don't re-publish, but preserve the location
|
|
1193
|
+
*/ const createAddCloudProviderToExistingDelegationDistribution = ({ providers, clientShares })=>{
|
|
1194
|
+
const cloudProviderShares = {};
|
|
1195
|
+
providers.forEach((provider)=>{
|
|
1196
|
+
cloudProviderShares[provider] = clientShares;
|
|
1197
|
+
});
|
|
1198
|
+
return {
|
|
1199
|
+
clientShares,
|
|
1200
|
+
cloudProviderShares
|
|
1201
|
+
};
|
|
1202
|
+
};
|
|
1203
|
+
/**
|
|
1204
|
+
* Checks if wallet has backup on any of the specified providers
|
|
1205
|
+
*/ const hasCloudProviderBackup = (backupInfo, providers)=>{
|
|
1206
|
+
if (!(backupInfo == null ? void 0 : backupInfo.backups)) return false;
|
|
1207
|
+
return providers.some((provider)=>{
|
|
1208
|
+
var _backupInfo_backups_provider;
|
|
1209
|
+
var _backupInfo_backups_provider_length;
|
|
1210
|
+
return ((_backupInfo_backups_provider_length = (_backupInfo_backups_provider = backupInfo.backups[provider]) == null ? void 0 : _backupInfo_backups_provider.length) != null ? _backupInfo_backups_provider_length : 0) > 0;
|
|
1211
|
+
});
|
|
1212
|
+
};
|
|
1213
|
+
/**
|
|
1214
|
+
* Gets all cloud providers that have backups for this wallet
|
|
1215
|
+
*/ const getActiveCloudProviders = (backupInfo)=>{
|
|
1216
|
+
if (!(backupInfo == null ? void 0 : backupInfo.backups)) return [];
|
|
1217
|
+
return Object.entries(backupInfo.backups).filter(([location, backups])=>location !== core.BackupLocation.DYNAMIC && location !== core.BackupLocation.DELEGATED && backups.length > 0).map(([location])=>location);
|
|
1218
|
+
};
|
|
1219
|
+
const createDelegationOnlyDistribution = ({ existingShares, delegatedShare })=>({
|
|
1220
|
+
clientShares: existingShares,
|
|
1221
|
+
cloudProviderShares: {},
|
|
1222
|
+
delegatedShare
|
|
1223
|
+
});
|
|
1224
|
+
const createDynamicOnlyDistribution = ({ allShares })=>({
|
|
1225
|
+
clientShares: allShares,
|
|
1226
|
+
cloudProviderShares: {}
|
|
1227
|
+
});
|
|
1228
|
+
const hasDelegatedBackup = (backupInfo)=>{
|
|
1229
|
+
var _backupInfo_backups_BackupLocation_DELEGATED, _backupInfo_backups;
|
|
1230
|
+
var _backupInfo_backups_BackupLocation_DELEGATED_length;
|
|
1231
|
+
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;
|
|
1232
|
+
};
|
|
1233
|
+
|
|
1234
|
+
/**
|
|
1235
|
+
* Queue manager for wallet operations.
|
|
1236
|
+
* Manages heavy operation queues (refresh/reshare/recover) and sign queues per wallet.
|
|
1237
|
+
*/ class WalletQueueManager {
|
|
1238
|
+
/**
|
|
1239
|
+
* Get or create the heavy operation queue for a wallet.
|
|
1240
|
+
* Heavy operations (refresh/reshare/recover) run with concurrency=1.
|
|
1241
|
+
*/ static getHeavyOpQueue(accountAddress) {
|
|
1242
|
+
if (!this.heavyOpQueues.has(accountAddress)) {
|
|
1243
|
+
const queue = new PQueue({
|
|
1244
|
+
concurrency: 1,
|
|
1245
|
+
timeout: 20000
|
|
1246
|
+
});
|
|
1247
|
+
queue.on('error', (error)=>{
|
|
1248
|
+
logger.error('[WalletQueueManager] Heavy operation queue error', {
|
|
1249
|
+
accountAddress,
|
|
1250
|
+
error: error instanceof Error ? error.message : error
|
|
1251
|
+
});
|
|
1252
|
+
});
|
|
1253
|
+
this.heavyOpQueues.set(accountAddress, queue);
|
|
1254
|
+
}
|
|
1255
|
+
return this.heavyOpQueues.get(accountAddress);
|
|
1256
|
+
}
|
|
1257
|
+
/**
|
|
1258
|
+
* Get or create the sign queue for a wallet.
|
|
1259
|
+
* Sign operations run with unlimited concurrency (they're read-only on key shares).
|
|
1260
|
+
*/ static getSignQueue(accountAddress) {
|
|
1261
|
+
if (!this.signQueues.has(accountAddress)) {
|
|
1262
|
+
const queue = new PQueue({
|
|
1263
|
+
concurrency: Infinity,
|
|
1264
|
+
timeout: 20000
|
|
1265
|
+
});
|
|
1266
|
+
queue.on('error', (error)=>{
|
|
1267
|
+
logger.error('[WalletQueueManager] Sign queue error', {
|
|
1268
|
+
accountAddress,
|
|
1269
|
+
error: error instanceof Error ? error.message : error
|
|
1270
|
+
});
|
|
1271
|
+
});
|
|
1272
|
+
this.signQueues.set(accountAddress, queue);
|
|
1273
|
+
}
|
|
1274
|
+
return this.signQueues.get(accountAddress);
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Check if wallet has heavy operations in progress
|
|
1278
|
+
*/ static isHeavyOpInProgress(accountAddress) {
|
|
1279
|
+
const queue = this.heavyOpQueues.get(accountAddress);
|
|
1280
|
+
return queue ? queue.size > 0 || queue.pending > 0 : false;
|
|
1281
|
+
}
|
|
1282
|
+
/**
|
|
1283
|
+
* Check if wallet has operations in any queue (heavy or sign)
|
|
1284
|
+
*/ static isWalletBusy(accountAddress) {
|
|
1285
|
+
const heavyQueue = this.heavyOpQueues.get(accountAddress);
|
|
1286
|
+
const signQueue = this.signQueues.get(accountAddress);
|
|
1287
|
+
const heavyBusy = heavyQueue ? heavyQueue.size > 0 || heavyQueue.pending > 0 : false;
|
|
1288
|
+
const signBusy = signQueue ? signQueue.size > 0 || signQueue.pending > 0 : false;
|
|
1289
|
+
return heavyBusy || signBusy;
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Check if recovery is in progress for a wallet
|
|
1293
|
+
*/ static isRecoveryInProgress(accountAddress) {
|
|
1294
|
+
return this.pendingRecoveryPromises.has(accountAddress);
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* Get existing pending recovery promise if one exists
|
|
1298
|
+
*/ static getPendingRecoveryPromise(accountAddress) {
|
|
1299
|
+
return this.pendingRecoveryPromises.get(accountAddress);
|
|
1300
|
+
}
|
|
1301
|
+
/**
|
|
1302
|
+
* Track a pending recovery promise
|
|
1303
|
+
*/ static setPendingRecoveryPromise(accountAddress, promise) {
|
|
1304
|
+
this.pendingRecoveryPromises.set(accountAddress, promise);
|
|
1305
|
+
}
|
|
1306
|
+
/**
|
|
1307
|
+
* Clear pending recovery promise for a wallet
|
|
1308
|
+
*/ static clearPendingRecoveryPromise(accountAddress) {
|
|
1309
|
+
this.pendingRecoveryPromises.delete(accountAddress);
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* Queue a heavy operation with type validation.
|
|
1313
|
+
* Ensures only valid heavy operations (REFRESH, RESHARE, RECOVER) can be queued.
|
|
1314
|
+
* Waits for sign operations to complete before executing.
|
|
1315
|
+
*
|
|
1316
|
+
* @param accountAddress - The wallet address
|
|
1317
|
+
* @param operation - The operation type (must be a valid HeavyQueueOperation)
|
|
1318
|
+
* @param callback - The operation to execute
|
|
1319
|
+
* @throws Error if operation is not a valid heavy queue operation
|
|
1320
|
+
*/ static async queueHeavyOperation(accountAddress, operation, callback) {
|
|
1321
|
+
// Runtime validation to catch any type assertion bypasses
|
|
1322
|
+
if (!isHeavyQueueOperation(operation)) {
|
|
1323
|
+
throw new Error(`Invalid heavy queue operation: ${operation}. Must be REFRESH, RESHARE, or RECOVER.`);
|
|
1324
|
+
}
|
|
1325
|
+
const heavyQueue = this.getHeavyOpQueue(accountAddress);
|
|
1326
|
+
const signQueue = this.getSignQueue(accountAddress);
|
|
1327
|
+
// Wait for any in-progress sign operations to complete
|
|
1328
|
+
await signQueue.onIdle();
|
|
1329
|
+
// Add to heavy queue - will wait if other heavy operations are in progress
|
|
1330
|
+
return heavyQueue.add(async ()=>{
|
|
1331
|
+
this.walletsWithActiveHeavyOp.set(accountAddress, true);
|
|
1332
|
+
try {
|
|
1333
|
+
return await callback();
|
|
1334
|
+
} finally{
|
|
1335
|
+
this.walletsWithActiveHeavyOp.delete(accountAddress);
|
|
1336
|
+
}
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Queue a sign operation with type validation.
|
|
1341
|
+
* Ensures only valid sign operations (SIGN_MESSAGE, SIGN_TRANSACTION) can be queued.
|
|
1342
|
+
* Waits for heavy operations to complete before executing.
|
|
1343
|
+
* Allows concurrent sign operations.
|
|
1344
|
+
*
|
|
1345
|
+
* @param accountAddress - The wallet address
|
|
1346
|
+
* @param operation - The operation type (must be a valid SignQueueOperation)
|
|
1347
|
+
* @param callback - The sign operation to execute
|
|
1348
|
+
* @throws Error if operation is not a valid sign queue operation
|
|
1349
|
+
* @returns Promise resolving to the sign result
|
|
1350
|
+
*/ static async queueSignOperation(accountAddress, operation, callback) {
|
|
1351
|
+
// Runtime validation to catch any type assertion bypasses
|
|
1352
|
+
if (!isSignQueueOperation(operation)) {
|
|
1353
|
+
throw new Error(`Invalid sign queue operation: ${operation}. Must be SIGN_MESSAGE or SIGN_TRANSACTION.`);
|
|
1354
|
+
}
|
|
1355
|
+
const heavyQueue = this.getHeavyOpQueue(accountAddress);
|
|
1356
|
+
const signQueue = this.getSignQueue(accountAddress);
|
|
1357
|
+
// Wait for any running heavy operations to complete before signing.
|
|
1358
|
+
// This prevents signing with stale key shares during refresh/reshare/recover.
|
|
1359
|
+
// Note: There's a small race window between this check and adding to signQueue,
|
|
1360
|
+
// but if a collision occurs, the MPC protocol will detect the public key mismatch
|
|
1361
|
+
// and our recovery handler will automatically retry with fresh key shares.
|
|
1362
|
+
if (heavyQueue.pending > 0 || heavyQueue.size > 0) {
|
|
1363
|
+
await heavyQueue.onIdle();
|
|
1364
|
+
}
|
|
1365
|
+
// Add to sign queue - allows concurrent signing
|
|
1366
|
+
return signQueue.add(async ()=>{
|
|
1367
|
+
this.walletsWithActiveSignOp.set(accountAddress, true);
|
|
1368
|
+
try {
|
|
1369
|
+
return await callback();
|
|
1370
|
+
} finally{
|
|
1371
|
+
this.walletsWithActiveSignOp.delete(accountAddress);
|
|
1372
|
+
}
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
/**
|
|
1376
|
+
* Queue a recovery operation with deduplication and type validation.
|
|
1377
|
+
* If a recovery is already in progress for the wallet, returns that promise.
|
|
1378
|
+
* If called from within a heavy operation, executes directly to avoid deadlock.
|
|
1379
|
+
*
|
|
1380
|
+
* @param accountAddress - The wallet address
|
|
1381
|
+
* @param operation - The operation type (must be RECOVER)
|
|
1382
|
+
* @param callback - The recovery operation to execute
|
|
1383
|
+
* @throws Error if operation is not a valid recover queue operation
|
|
1384
|
+
* @returns Promise resolving to the recovery result
|
|
1385
|
+
*/ static async queueRecoverOperation(accountAddress, operation, callback) {
|
|
1386
|
+
// Runtime validation to catch any type assertion bypasses
|
|
1387
|
+
if (!isRecoverQueueOperation(operation)) {
|
|
1388
|
+
throw new Error(`Invalid recover queue operation: ${operation}. Must be RECOVER.`);
|
|
1389
|
+
}
|
|
1390
|
+
// Deduplication: if recovery already in progress, return that promise
|
|
1391
|
+
const existing = this.getPendingRecoveryPromise(accountAddress);
|
|
1392
|
+
if (existing) {
|
|
1393
|
+
logger.debug(`[WalletQueueManager] Recovery already in progress for ${accountAddress}, returning existing promise`);
|
|
1394
|
+
return existing;
|
|
1395
|
+
}
|
|
1396
|
+
// If we're already inside a heavy or sign operation for this wallet, execute directly
|
|
1397
|
+
// to avoid deadlock (heavy queue has concurrency=1, sign queue waits for idle)
|
|
1398
|
+
if (this.walletsWithActiveHeavyOp.get(accountAddress) || this.walletsWithActiveSignOp.get(accountAddress)) {
|
|
1399
|
+
logger.debug(`[WalletQueueManager] Recovery called from within active op for ${accountAddress}, executing directly`);
|
|
1400
|
+
const directPromise = (async ()=>{
|
|
1401
|
+
try {
|
|
1402
|
+
return await callback();
|
|
1403
|
+
} finally{
|
|
1404
|
+
this.clearPendingRecoveryPromise(accountAddress);
|
|
1405
|
+
}
|
|
1406
|
+
})();
|
|
1407
|
+
this.setPendingRecoveryPromise(accountAddress, directPromise);
|
|
1408
|
+
return directPromise;
|
|
1409
|
+
}
|
|
1410
|
+
const heavyQueue = this.getHeavyOpQueue(accountAddress);
|
|
1411
|
+
const signQueue = this.getSignQueue(accountAddress);
|
|
1412
|
+
// Create promise and track it
|
|
1413
|
+
const recoveryPromise = (async ()=>{
|
|
1414
|
+
// Wait for any in-progress sign operations to complete
|
|
1415
|
+
await signQueue.onIdle();
|
|
1416
|
+
// Add to heavy queue - will wait if other heavy operations are in progress
|
|
1417
|
+
return heavyQueue.add(async ()=>{
|
|
1418
|
+
try {
|
|
1419
|
+
return await callback();
|
|
1420
|
+
} finally{
|
|
1421
|
+
// Clean up tracking when done
|
|
1422
|
+
this.clearPendingRecoveryPromise(accountAddress);
|
|
1423
|
+
}
|
|
1424
|
+
});
|
|
1425
|
+
})();
|
|
1426
|
+
// Track this recovery
|
|
1427
|
+
this.setPendingRecoveryPromise(accountAddress, recoveryPromise);
|
|
1428
|
+
return recoveryPromise;
|
|
1429
|
+
}
|
|
1430
|
+
/**
|
|
1431
|
+
* Reset static state for testing purposes.
|
|
1432
|
+
* This clears all wallet queues and pending recovery promises.
|
|
1433
|
+
*/ static resetForTesting() {
|
|
1434
|
+
this.heavyOpQueues.clear();
|
|
1435
|
+
this.signQueues.clear();
|
|
1436
|
+
this.pendingRecoveryPromises.clear();
|
|
1437
|
+
this.walletsWithActiveHeavyOp.clear();
|
|
1438
|
+
this.walletsWithActiveSignOp.clear();
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
/** Heavy operation queues per wallet address. These run with concurrency=1. */ WalletQueueManager.heavyOpQueues = new Map();
|
|
1442
|
+
/** Sign queues per wallet address. These allow concurrent operations. */ WalletQueueManager.signQueues = new Map();
|
|
1443
|
+
/** Track pending recovery promises for deduplication */ WalletQueueManager.pendingRecoveryPromises = new Map();
|
|
1444
|
+
/** Track which wallets are currently executing inside a heavy operation.
|
|
1445
|
+
* Used to prevent deadlocks when recovery is called from within a heavy op. */ WalletQueueManager.walletsWithActiveHeavyOp = new Map();
|
|
1446
|
+
/** Track which wallets are currently executing inside a sign operation.
|
|
1447
|
+
* Used to prevent deadlocks when recovery is called from within a sign op. */ WalletQueueManager.walletsWithActiveSignOp = new Map();
|
|
1448
|
+
|
|
1087
1449
|
const ALG_LABEL_RSA = 'HYBRID-RSA-AES-256';
|
|
1088
1450
|
/**
|
|
1089
1451
|
* Convert base64 to base64url encoding
|
|
@@ -1231,99 +1593,6 @@ const localStorageWriteTest = {
|
|
|
1231
1593
|
}
|
|
1232
1594
|
});
|
|
1233
1595
|
|
|
1234
|
-
class WalletNotReadyError extends Error {
|
|
1235
|
-
constructor(accountAddress, walletReadyState){
|
|
1236
|
-
super(`Wallet ${accountAddress} is not ready and requires password to be unlocked`);
|
|
1237
|
-
this.name = 'WalletNotReadyError';
|
|
1238
|
-
this.accountAddress = accountAddress;
|
|
1239
|
-
this.walletReadyState = walletReadyState;
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
class WalletBusyError extends Error {
|
|
1243
|
-
constructor(accountAddress, operation){
|
|
1244
|
-
super(`Wallet ${accountAddress} is currently performing a ${operation} operation`);
|
|
1245
|
-
this.name = 'WalletBusyError';
|
|
1246
|
-
this.operation = operation;
|
|
1247
|
-
this.accountAddress = accountAddress;
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
/**
|
|
1251
|
-
* Creates distribution where shares go to specified cloud providers
|
|
1252
|
-
* Last share goes to cloud providers, rest to Dynamic backend
|
|
1253
|
-
* @param providers - Array of cloud providers to backup to
|
|
1254
|
-
* @param allShares - All key shares to distribute
|
|
1255
|
-
*/ const createCloudProviderDistribution = ({ providers, allShares })=>{
|
|
1256
|
-
const cloudProviderShares = {};
|
|
1257
|
-
// Last share goes to cloud providers, rest to Dynamic
|
|
1258
|
-
const sharesForCloud = allShares.slice(-1);
|
|
1259
|
-
providers.forEach((provider)=>{
|
|
1260
|
-
cloudProviderShares[provider] = sharesForCloud;
|
|
1261
|
-
});
|
|
1262
|
-
return {
|
|
1263
|
-
clientShares: allShares.slice(0, -1),
|
|
1264
|
-
cloudProviderShares
|
|
1265
|
-
};
|
|
1266
|
-
};
|
|
1267
|
-
/**
|
|
1268
|
-
* Creates distribution with delegation + cloud backup
|
|
1269
|
-
* Client shares backed up to Dynamic AND cloud providers
|
|
1270
|
-
* Delegated share goes to webhook
|
|
1271
|
-
*/ const createDelegationWithCloudProviderDistribution = ({ providers, existingShares, delegatedShare })=>{
|
|
1272
|
-
const cloudProviderShares = {};
|
|
1273
|
-
providers.forEach((provider)=>{
|
|
1274
|
-
cloudProviderShares[provider] = existingShares;
|
|
1275
|
-
});
|
|
1276
|
-
return {
|
|
1277
|
-
clientShares: existingShares,
|
|
1278
|
-
cloudProviderShares,
|
|
1279
|
-
delegatedShare
|
|
1280
|
-
};
|
|
1281
|
-
};
|
|
1282
|
-
/**
|
|
1283
|
-
* Creates distribution for adding cloud backup to existing delegation
|
|
1284
|
-
* Client shares go to both Dynamic AND cloud providers
|
|
1285
|
-
* delegatedShare is undefined - we don't re-publish, but preserve the location
|
|
1286
|
-
*/ const createAddCloudProviderToExistingDelegationDistribution = ({ providers, clientShares })=>{
|
|
1287
|
-
const cloudProviderShares = {};
|
|
1288
|
-
providers.forEach((provider)=>{
|
|
1289
|
-
cloudProviderShares[provider] = clientShares;
|
|
1290
|
-
});
|
|
1291
|
-
return {
|
|
1292
|
-
clientShares,
|
|
1293
|
-
cloudProviderShares
|
|
1294
|
-
};
|
|
1295
|
-
};
|
|
1296
|
-
/**
|
|
1297
|
-
* Checks if wallet has backup on any of the specified providers
|
|
1298
|
-
*/ const hasCloudProviderBackup = (backupInfo, providers)=>{
|
|
1299
|
-
if (!(backupInfo == null ? void 0 : backupInfo.backups)) return false;
|
|
1300
|
-
return providers.some((provider)=>{
|
|
1301
|
-
var _backupInfo_backups_provider;
|
|
1302
|
-
var _backupInfo_backups_provider_length;
|
|
1303
|
-
return ((_backupInfo_backups_provider_length = (_backupInfo_backups_provider = backupInfo.backups[provider]) == null ? void 0 : _backupInfo_backups_provider.length) != null ? _backupInfo_backups_provider_length : 0) > 0;
|
|
1304
|
-
});
|
|
1305
|
-
};
|
|
1306
|
-
/**
|
|
1307
|
-
* Gets all cloud providers that have backups for this wallet
|
|
1308
|
-
*/ const getActiveCloudProviders = (backupInfo)=>{
|
|
1309
|
-
if (!(backupInfo == null ? void 0 : backupInfo.backups)) return [];
|
|
1310
|
-
return Object.entries(backupInfo.backups).filter(([location, backups])=>location !== core.BackupLocation.DYNAMIC && location !== core.BackupLocation.DELEGATED && backups.length > 0).map(([location])=>location);
|
|
1311
|
-
};
|
|
1312
|
-
const createDelegationOnlyDistribution = ({ existingShares, delegatedShare })=>({
|
|
1313
|
-
clientShares: existingShares,
|
|
1314
|
-
cloudProviderShares: {},
|
|
1315
|
-
delegatedShare
|
|
1316
|
-
});
|
|
1317
|
-
const createDynamicOnlyDistribution = ({ allShares })=>({
|
|
1318
|
-
clientShares: allShares,
|
|
1319
|
-
cloudProviderShares: {}
|
|
1320
|
-
});
|
|
1321
|
-
const hasDelegatedBackup = (backupInfo)=>{
|
|
1322
|
-
var _backupInfo_backups_BackupLocation_DELEGATED, _backupInfo_backups;
|
|
1323
|
-
var _backupInfo_backups_BackupLocation_DELEGATED_length;
|
|
1324
|
-
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;
|
|
1325
|
-
};
|
|
1326
|
-
|
|
1327
1596
|
/**
|
|
1328
1597
|
* Determines the recovery state of a wallet based on backup info and local shares.
|
|
1329
1598
|
*
|
|
@@ -1376,48 +1645,27 @@ class DynamicWalletClient {
|
|
|
1376
1645
|
});
|
|
1377
1646
|
}
|
|
1378
1647
|
}
|
|
1379
|
-
|
|
1380
|
-
|
|
1648
|
+
/**
|
|
1649
|
+
* Check if wallet has heavy operations in progress
|
|
1650
|
+
*/ static isHeavyOpInProgress(accountAddress) {
|
|
1651
|
+
return WalletQueueManager.isHeavyOpInProgress(accountAddress);
|
|
1381
1652
|
}
|
|
1382
|
-
|
|
1383
|
-
|
|
1653
|
+
/**
|
|
1654
|
+
* Check if wallet has operations in any queue (heavy or sign)
|
|
1655
|
+
*/ static isWalletBusy(accountAddress) {
|
|
1656
|
+
return WalletQueueManager.isWalletBusy(accountAddress);
|
|
1384
1657
|
}
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
}
|
|
1390
|
-
const currentOperation = DynamicWalletClient.walletBusyMap[accountAddress];
|
|
1391
|
-
this.logger.debug(`[DynamicWaasWalletClient] Waiting for wallet ${accountAddress} to complete ${currentOperation} operation`);
|
|
1392
|
-
try {
|
|
1393
|
-
await busyPromise;
|
|
1394
|
-
} catch (e) {
|
|
1395
|
-
// Intentionally swallowing the error - we proceed with the queued operation regardless of whether the previous one succeeded or failed
|
|
1396
|
-
this.logger.debug(`[DynamicWaasWalletClient] Previous ${currentOperation} operation on wallet ${accountAddress} failed, proceeding with queued operation`);
|
|
1397
|
-
}
|
|
1658
|
+
/**
|
|
1659
|
+
* Check if recovery is in progress for a wallet
|
|
1660
|
+
*/ static isRecoveryInProgress(accountAddress) {
|
|
1661
|
+
return WalletQueueManager.isRecoveryInProgress(accountAddress);
|
|
1398
1662
|
}
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
const executionPromise = (async ()=>{
|
|
1406
|
-
try {
|
|
1407
|
-
return await Promise.race([
|
|
1408
|
-
fn(),
|
|
1409
|
-
timeoutPromise({
|
|
1410
|
-
timeInMs: timeoutMs,
|
|
1411
|
-
activity: `${operation} operation`
|
|
1412
|
-
})
|
|
1413
|
-
]);
|
|
1414
|
-
} finally{
|
|
1415
|
-
delete DynamicWalletClient.walletBusyMap[accountAddress];
|
|
1416
|
-
delete DynamicWalletClient.walletBusyPromiseMap[accountAddress];
|
|
1417
|
-
}
|
|
1418
|
-
})();
|
|
1419
|
-
DynamicWalletClient.walletBusyPromiseMap[accountAddress] = executionPromise.then(()=>undefined, ()=>undefined);
|
|
1420
|
-
return executionPromise;
|
|
1663
|
+
/**
|
|
1664
|
+
* Reset static state for testing purposes.
|
|
1665
|
+
* This clears all wallet queues and in-flight recovery tracking.
|
|
1666
|
+
* @internal For testing only
|
|
1667
|
+
*/ static resetStaticState() {
|
|
1668
|
+
WalletQueueManager.resetForTesting();
|
|
1421
1669
|
}
|
|
1422
1670
|
getAuthMode() {
|
|
1423
1671
|
return this.authMode;
|
|
@@ -1938,7 +2186,8 @@ class DynamicWalletClient {
|
|
|
1938
2186
|
dynamicRequestId
|
|
1939
2187
|
}, this.getTraceContext(traceContext)));
|
|
1940
2188
|
// Recover key shares from backup (don't auto-store, we'll overwrite below)
|
|
1941
|
-
|
|
2189
|
+
// Use internal method directly to bypass queueing - we're already inside a queued operation
|
|
2190
|
+
const recoveredKeyShares = await this.internalRecoverEncryptedBackupByWallet({
|
|
1942
2191
|
accountAddress,
|
|
1943
2192
|
password: password != null ? password : this.environmentId,
|
|
1944
2193
|
walletOperation,
|
|
@@ -1961,24 +2210,23 @@ class DynamicWalletClient {
|
|
|
1961
2210
|
}
|
|
1962
2211
|
//todo: need to modify with imported flag
|
|
1963
2212
|
async sign({ accountAddress, message, chainName, password = undefined, isFormatted = false, signedSessionId, mfaToken, context, onError, traceContext, bitcoinConfig }) {
|
|
1964
|
-
return this.internalSign({
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
2213
|
+
return WalletQueueManager.queueSignOperation(accountAddress, core.WalletOperation.SIGN_MESSAGE, ()=>this.internalSign({
|
|
2214
|
+
accountAddress,
|
|
2215
|
+
message,
|
|
2216
|
+
chainName,
|
|
2217
|
+
password,
|
|
2218
|
+
isFormatted,
|
|
2219
|
+
signedSessionId,
|
|
2220
|
+
mfaToken,
|
|
2221
|
+
context,
|
|
2222
|
+
onError,
|
|
2223
|
+
traceContext,
|
|
2224
|
+
bitcoinConfig
|
|
2225
|
+
}));
|
|
1977
2226
|
}
|
|
1978
2227
|
async internalSign({ accountAddress, message, chainName, password = undefined, isFormatted = false, signedSessionId, mfaToken, context, onError, traceContext, bitcoinConfig, hasAttemptedKeyShareRecovery = false }) {
|
|
1979
2228
|
const dynamicRequestId = uuid.v4();
|
|
1980
2229
|
try {
|
|
1981
|
-
await this.waitForWalletNotBusy(accountAddress);
|
|
1982
2230
|
await this.verifyPassword({
|
|
1983
2231
|
accountAddress,
|
|
1984
2232
|
password,
|
|
@@ -2103,18 +2351,29 @@ class DynamicWalletClient {
|
|
|
2103
2351
|
}
|
|
2104
2352
|
}
|
|
2105
2353
|
async refreshWalletAccountShares({ accountAddress, chainName, password = undefined, signedSessionId, mfaToken, traceContext }) {
|
|
2106
|
-
return this.
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2354
|
+
return WalletQueueManager.queueHeavyOperation(accountAddress, core.WalletOperation.REFRESH, ()=>this.internalRefreshWalletAccountShares({
|
|
2355
|
+
accountAddress,
|
|
2356
|
+
chainName,
|
|
2357
|
+
password,
|
|
2358
|
+
signedSessionId,
|
|
2359
|
+
mfaToken,
|
|
2360
|
+
traceContext
|
|
2361
|
+
}));
|
|
2362
|
+
}
|
|
2363
|
+
/**
|
|
2364
|
+
* Gets the Bitcoin config for MPC operations by looking up addressType
|
|
2365
|
+
* from walletMap, with fallback to deriving it from derivationPath.
|
|
2366
|
+
*/ getBitcoinConfigForChain(chainName, accountAddress) {
|
|
2367
|
+
if (chainName !== 'BTC') return undefined;
|
|
2368
|
+
const walletProperties = this.walletMap[accountAddress];
|
|
2369
|
+
let addressType = walletProperties == null ? void 0 : walletProperties.addressType;
|
|
2370
|
+
// Fallback: derive addressType from derivationPath if not explicitly set
|
|
2371
|
+
if (!addressType && (walletProperties == null ? void 0 : walletProperties.derivationPath)) {
|
|
2372
|
+
addressType = getBitcoinAddressTypeFromDerivationPath(walletProperties.derivationPath);
|
|
2373
|
+
}
|
|
2374
|
+
return addressType ? {
|
|
2375
|
+
addressType: addressType
|
|
2376
|
+
} : undefined;
|
|
2118
2377
|
}
|
|
2119
2378
|
async internalRefreshWalletAccountShares({ accountAddress, chainName, password = undefined, signedSessionId, mfaToken, traceContext, hasAttemptedKeyShareRecovery = false }) {
|
|
2120
2379
|
const dynamicRequestId = uuid.v4();
|
|
@@ -2131,13 +2390,7 @@ class DynamicWalletClient {
|
|
|
2131
2390
|
password,
|
|
2132
2391
|
signedSessionId
|
|
2133
2392
|
});
|
|
2134
|
-
const
|
|
2135
|
-
let bitcoinConfig;
|
|
2136
|
-
if (chainName === 'BTC' && (walletProperties == null ? void 0 : walletProperties.addressType)) {
|
|
2137
|
-
bitcoinConfig = {
|
|
2138
|
-
addressType: walletProperties.addressType
|
|
2139
|
-
};
|
|
2140
|
-
}
|
|
2393
|
+
const bitcoinConfig = this.getBitcoinConfigForChain(chainName, accountAddress);
|
|
2141
2394
|
const mpcSigner = getMPCSigner({
|
|
2142
2395
|
chainName,
|
|
2143
2396
|
baseRelayUrl: this.baseMPCRelayApiUrl,
|
|
@@ -2281,22 +2534,18 @@ class DynamicWalletClient {
|
|
|
2281
2534
|
};
|
|
2282
2535
|
}
|
|
2283
2536
|
async reshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined, signedSessionId, cloudProviders = [], delegateToProjectEnvironment = false, mfaToken, revokeDelegation = false }) {
|
|
2284
|
-
return this.
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
mfaToken,
|
|
2297
|
-
revokeDelegation
|
|
2298
|
-
})
|
|
2299
|
-
});
|
|
2537
|
+
return WalletQueueManager.queueHeavyOperation(accountAddress, core.WalletOperation.RESHARE, ()=>this.internalReshare({
|
|
2538
|
+
chainName,
|
|
2539
|
+
accountAddress,
|
|
2540
|
+
oldThresholdSignatureScheme,
|
|
2541
|
+
newThresholdSignatureScheme,
|
|
2542
|
+
password,
|
|
2543
|
+
signedSessionId,
|
|
2544
|
+
cloudProviders,
|
|
2545
|
+
delegateToProjectEnvironment,
|
|
2546
|
+
mfaToken,
|
|
2547
|
+
revokeDelegation
|
|
2548
|
+
}));
|
|
2300
2549
|
}
|
|
2301
2550
|
async internalReshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined, signedSessionId, cloudProviders = [], delegateToProjectEnvironment = false, mfaToken, revokeDelegation = false, hasAttemptedKeyShareRecovery = false }) {
|
|
2302
2551
|
const dynamicRequestId = uuid.v4();
|
|
@@ -2353,13 +2602,7 @@ class DynamicWalletClient {
|
|
|
2353
2602
|
...serverKeygenIds,
|
|
2354
2603
|
...newServerKeygenIds
|
|
2355
2604
|
];
|
|
2356
|
-
const
|
|
2357
|
-
let bitcoinConfig;
|
|
2358
|
-
if (chainName === 'BTC' && (walletProperties == null ? void 0 : walletProperties.addressType)) {
|
|
2359
|
-
bitcoinConfig = {
|
|
2360
|
-
addressType: walletProperties.addressType
|
|
2361
|
-
};
|
|
2362
|
-
}
|
|
2605
|
+
const bitcoinConfig = this.getBitcoinConfigForChain(chainName, accountAddress);
|
|
2363
2606
|
const mpcSigner = getMPCSigner({
|
|
2364
2607
|
chainName,
|
|
2365
2608
|
baseRelayUrl: this.baseMPCRelayApiUrl,
|
|
@@ -2851,7 +3094,10 @@ class DynamicWalletClient {
|
|
|
2851
3094
|
/**
|
|
2852
3095
|
* Ensures that client key shares exist for the given account address.
|
|
2853
3096
|
* Throws an error if no shares are found.
|
|
2854
|
-
*
|
|
3097
|
+
*
|
|
3098
|
+
* Note: This method only checks for existing shares in storage.
|
|
3099
|
+
* Auto-recovery logic has been removed in favor of the queue pattern.
|
|
3100
|
+
* Callers should handle recovery explicitly if needed.
|
|
2855
3101
|
*/ async ensureClientShare(accountAddress) {
|
|
2856
3102
|
const shares = await this.getClientKeySharesFromStorage({
|
|
2857
3103
|
accountAddress
|
|
@@ -3178,6 +3424,17 @@ class DynamicWalletClient {
|
|
|
3178
3424
|
};
|
|
3179
3425
|
}
|
|
3180
3426
|
async recoverEncryptedBackupByWallet({ accountAddress, password, walletOperation, signedSessionId, shareCount = undefined, storeRecoveredShares = true, mfaToken }) {
|
|
3427
|
+
return WalletQueueManager.queueRecoverOperation(accountAddress, core.WalletOperation.RECOVER, ()=>this.internalRecoverEncryptedBackupByWallet({
|
|
3428
|
+
accountAddress,
|
|
3429
|
+
password,
|
|
3430
|
+
walletOperation,
|
|
3431
|
+
signedSessionId,
|
|
3432
|
+
shareCount,
|
|
3433
|
+
storeRecoveredShares,
|
|
3434
|
+
mfaToken
|
|
3435
|
+
}));
|
|
3436
|
+
}
|
|
3437
|
+
async internalRecoverEncryptedBackupByWallet({ accountAddress, password, walletOperation, signedSessionId, shareCount = undefined, storeRecoveredShares = true, mfaToken }) {
|
|
3181
3438
|
try {
|
|
3182
3439
|
const wallet = this.walletMap[accountAddress];
|
|
3183
3440
|
this.logger.debug(`recoverEncryptedBackupByWallet wallet: ${walletOperation}`, wallet);
|
|
@@ -3777,20 +4034,55 @@ class DynamicWalletClient {
|
|
|
3777
4034
|
}
|
|
3778
4035
|
}
|
|
3779
4036
|
/**
|
|
3780
|
-
* Gets the recovery state of a wallet
|
|
4037
|
+
* Gets the recovery state of a wallet, optionally recovering shares if not available.
|
|
3781
4038
|
* This method is useful for detecting if a wallet is password-encrypted
|
|
3782
4039
|
* and needs unlocking before use.
|
|
3783
4040
|
*
|
|
4041
|
+
* If shares are not available locally and recovery parameters are provided,
|
|
4042
|
+
* this method will call getWallet with RECOVER operation to fetch shares.
|
|
4043
|
+
* The underlying recoverEncryptedBackupByWallet handles deduplication via
|
|
4044
|
+
* inFlightRecovery, so concurrent calls won't start multiple recoveries.
|
|
4045
|
+
*
|
|
3784
4046
|
* @param accountAddress - The account address of the wallet
|
|
4047
|
+
* @param signedSessionId - Optional signed session ID for triggering recovery if shares not available
|
|
4048
|
+
* @param password - Optional password for decrypting recovered shares
|
|
3785
4049
|
* @returns WalletRecoveryState indicating the wallet's lock state and share availability
|
|
3786
|
-
|
|
4050
|
+
* @throws Error if recovery fails or no shares available after recovery
|
|
4051
|
+
*/ async getWalletRecoveryState({ accountAddress, signedSessionId, password }) {
|
|
3787
4052
|
const walletData = this.walletMap[accountAddress];
|
|
3788
4053
|
if (!walletData) {
|
|
3789
4054
|
throw new Error(`Wallet not found for address: ${accountAddress}`);
|
|
3790
4055
|
}
|
|
3791
|
-
|
|
4056
|
+
let clientKeyShares = await this.getClientKeySharesFromStorage({
|
|
3792
4057
|
accountAddress
|
|
3793
4058
|
});
|
|
4059
|
+
// If no local shares and signedSessionId provided, trigger recovery via getWallet
|
|
4060
|
+
// getWallet -> recoverEncryptedBackupByWallet handles deduplication via inFlightRecovery
|
|
4061
|
+
if (clientKeyShares.length === 0 && signedSessionId) {
|
|
4062
|
+
var _walletData_clientKeySharesBackupInfo;
|
|
4063
|
+
await this.getWallet({
|
|
4064
|
+
accountAddress,
|
|
4065
|
+
walletOperation: core.WalletOperation.RECOVER,
|
|
4066
|
+
signedSessionId,
|
|
4067
|
+
password
|
|
4068
|
+
});
|
|
4069
|
+
// Re-fetch shares after recovery
|
|
4070
|
+
clientKeyShares = await this.getClientKeySharesFromStorage({
|
|
4071
|
+
accountAddress
|
|
4072
|
+
});
|
|
4073
|
+
var _walletData_clientKeySharesBackupInfo_passwordEncrypted;
|
|
4074
|
+
// For password-encrypted wallets, also check for cached encrypted shares
|
|
4075
|
+
const isPasswordEncrypted = (_walletData_clientKeySharesBackupInfo_passwordEncrypted = (_walletData_clientKeySharesBackupInfo = walletData.clientKeySharesBackupInfo) == null ? void 0 : _walletData_clientKeySharesBackupInfo.passwordEncrypted) != null ? _walletData_clientKeySharesBackupInfo_passwordEncrypted : false;
|
|
4076
|
+
let hasEncryptedShares = false;
|
|
4077
|
+
if (isPasswordEncrypted && clientKeyShares.length === 0) {
|
|
4078
|
+
const encryptedStorageKey = `${accountAddress}${core.ENCRYPTED_SHARES_STORAGE_SUFFIX}`;
|
|
4079
|
+
const encryptedData = await this.storage.getItem(encryptedStorageKey);
|
|
4080
|
+
hasEncryptedShares = !!encryptedData;
|
|
4081
|
+
}
|
|
4082
|
+
if (clientKeyShares.length === 0 && !hasEncryptedShares) {
|
|
4083
|
+
throw new Error(`No key shares available for wallet ${accountAddress} after recovery`);
|
|
4084
|
+
}
|
|
4085
|
+
}
|
|
3794
4086
|
return determineWalletRecoveryState({
|
|
3795
4087
|
clientKeySharesBackupInfo: walletData.clientKeySharesBackupInfo,
|
|
3796
4088
|
hasLocalShares: clientKeyShares.length > 0
|
|
@@ -4086,8 +4378,6 @@ class DynamicWalletClient {
|
|
|
4086
4378
|
}
|
|
4087
4379
|
DynamicWalletClient.rooms = {};
|
|
4088
4380
|
DynamicWalletClient.roomsInitializing = {};
|
|
4089
|
-
DynamicWalletClient.walletBusyMap = {};
|
|
4090
|
-
DynamicWalletClient.walletBusyPromiseMap = {};
|
|
4091
4381
|
|
|
4092
4382
|
Object.defineProperty(exports, "BIP340", {
|
|
4093
4383
|
enumerable: true,
|
|
@@ -4152,6 +4442,9 @@ exports.ERROR_SIGN_MESSAGE = ERROR_SIGN_MESSAGE;
|
|
|
4152
4442
|
exports.ERROR_SIGN_TYPED_DATA = ERROR_SIGN_TYPED_DATA;
|
|
4153
4443
|
exports.ERROR_VERIFY_MESSAGE_SIGNATURE = ERROR_VERIFY_MESSAGE_SIGNATURE;
|
|
4154
4444
|
exports.ERROR_VERIFY_TRANSACTION_SIGNATURE = ERROR_VERIFY_TRANSACTION_SIGNATURE;
|
|
4445
|
+
exports.HEAVY_QUEUE_OPERATIONS = HEAVY_QUEUE_OPERATIONS;
|
|
4446
|
+
exports.RECOVER_QUEUE_OPERATIONS = RECOVER_QUEUE_OPERATIONS;
|
|
4447
|
+
exports.SIGN_QUEUE_OPERATIONS = SIGN_QUEUE_OPERATIONS;
|
|
4155
4448
|
exports.WalletBusyError = WalletBusyError;
|
|
4156
4449
|
exports.WalletNotReadyError = WalletNotReadyError;
|
|
4157
4450
|
exports.cancelICloudAuth = cancelICloudAuth;
|
|
@@ -4167,6 +4460,7 @@ exports.extractPubkey = extractPubkey;
|
|
|
4167
4460
|
exports.formatEvmMessage = formatEvmMessage;
|
|
4168
4461
|
exports.formatMessage = formatMessage;
|
|
4169
4462
|
exports.getActiveCloudProviders = getActiveCloudProviders;
|
|
4463
|
+
exports.getBitcoinAddressTypeFromDerivationPath = getBitcoinAddressTypeFromDerivationPath;
|
|
4170
4464
|
exports.getClientKeyShareBackupInfo = getClientKeyShareBackupInfo;
|
|
4171
4465
|
exports.getClientKeyShareExportFileName = getClientKeyShareExportFileName;
|
|
4172
4466
|
exports.getGoogleOAuthAccountId = getGoogleOAuthAccountId;
|
|
@@ -4177,9 +4471,12 @@ exports.hasCloudProviderBackup = hasCloudProviderBackup;
|
|
|
4177
4471
|
exports.hasDelegatedBackup = hasDelegatedBackup;
|
|
4178
4472
|
exports.initializeCloudKit = initializeCloudKit;
|
|
4179
4473
|
exports.isBrowser = isBrowser;
|
|
4474
|
+
exports.isHeavyQueueOperation = isHeavyQueueOperation;
|
|
4180
4475
|
exports.isHexString = isHexString;
|
|
4181
4476
|
exports.isICloudAuthenticated = isICloudAuthenticated;
|
|
4182
4477
|
exports.isPublicKeyMismatchError = isPublicKeyMismatchError;
|
|
4478
|
+
exports.isRecoverQueueOperation = isRecoverQueueOperation;
|
|
4479
|
+
exports.isSignQueueOperation = isSignQueueOperation;
|
|
4183
4480
|
exports.listICloudBackups = listICloudBackups;
|
|
4184
4481
|
exports.mergeUniqueKeyShares = mergeUniqueKeyShares;
|
|
4185
4482
|
exports.retryPromise = retryPromise;
|