@dynamic-labs-wallet/browser 1.1.41 → 1.1.43
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 +220 -151
- package/index.esm.js +220 -151
- package/package.json +3 -3
- package/src/backup/password/errors.d.ts.map +1 -1
- package/src/client.d.ts +36 -30
- package/src/client.d.ts.map +1 -1
- package/src/queue.d.ts +17 -0
- package/src/queue.d.ts.map +1 -1
- package/src/services/signedSession.d.ts +5 -3
- package/src/services/signedSession.d.ts.map +1 -1
package/index.cjs
CHANGED
|
@@ -587,6 +587,10 @@ function meetsMinVersion(sdkVersion, { minVersionByNamespace, fallbackMinVersion
|
|
|
587
587
|
'invalid_nonce_signature',
|
|
588
588
|
'invalid_session_signature'
|
|
589
589
|
]);
|
|
590
|
+
/** Reasons a fresh nonce fixes, so they may refresh on every attempt, not just once. */ const REFRESHABLE_NONCE_FAILURE_REASONS = new Set([
|
|
591
|
+
'nonce_already_used',
|
|
592
|
+
'nonce_not_found_or_expired'
|
|
593
|
+
]);
|
|
590
594
|
/** Best-effort extraction of the server error `code`/`message` from a 4xx body. */ const readErrorBody = (error)=>{
|
|
591
595
|
var _error_response;
|
|
592
596
|
if (!(error instanceof axios.AxiosError)) {
|
|
@@ -617,6 +621,9 @@ function meetsMinVersion(sdkVersion, { minVersionByNamespace, fallbackMinVersion
|
|
|
617
621
|
};
|
|
618
622
|
/** Maps the server error `code`/`message` onto a {@link NonceFailureReason}. */ const classifyNonceFailure = ({ code, message })=>{
|
|
619
623
|
const haystack = `${code != null ? code : ''} ${message != null ? message : ''}`.toLowerCase();
|
|
624
|
+
if (haystack.includes('not_found_or_expired') || haystack.includes('not found or expired')) {
|
|
625
|
+
return 'nonce_not_found_or_expired';
|
|
626
|
+
}
|
|
620
627
|
if (haystack.includes('already_used') || haystack.includes('already used') || haystack.includes('consumption failed')) {
|
|
621
628
|
return 'nonce_already_used';
|
|
622
629
|
}
|
|
@@ -661,7 +668,8 @@ function meetsMinVersion(sdkVersion, { minVersionByNamespace, fallbackMinVersion
|
|
|
661
668
|
}
|
|
662
669
|
/**
|
|
663
670
|
* Builds a `retryPromise` `shouldRetry` predicate that refreshes a consumed
|
|
664
|
-
* single-use nonce via the reverse channel on a replay 400 and retries once
|
|
671
|
+
* single-use nonce via the reverse channel on a replay 400 and retries once
|
|
672
|
+
* (on every attempt when the body says the nonce was consumed or expired).
|
|
665
673
|
* The signed session carries a single-use replay nonce: it is valid on its
|
|
666
674
|
* first use, but once consumed (e.g. by a preceding operation) the server
|
|
667
675
|
* rejects with a 400, at which point a fresh session is fetched and the call
|
|
@@ -673,18 +681,24 @@ function meetsMinVersion(sdkVersion, { minVersionByNamespace, fallbackMinVersion
|
|
|
673
681
|
// triggered an `alsoRetry`), so the replay 400 can land on any attempt, not
|
|
674
682
|
// just the first. Refresh on the first such 400 seen, whichever attempt
|
|
675
683
|
// it's on, and never again — preserves the single-refresh-per-operation limit.
|
|
684
|
+
// A consumed or expired nonce is the exception: each fresh session may still
|
|
685
|
+
// pop a stale pooled nonce, so it refreshes on every attempt (bounded by the
|
|
686
|
+
// caller's maxAttempts).
|
|
676
687
|
let hasRefreshed = false;
|
|
677
688
|
return async (error, attempt)=>{
|
|
678
689
|
const status = getHttpStatus(error);
|
|
690
|
+
const reason = status === 400 || status === 403 ? classifyNonceFailure(readErrorBody(error)) : undefined;
|
|
679
691
|
// A 400 is a refreshable replay nonce; a 403 is a session-key
|
|
680
692
|
// mismatch the server returns to break the refresh loop (DYNT-1282) — logged only
|
|
681
693
|
// when the body classifies as a genuine session-signature failure so unrelated 403s
|
|
682
694
|
// (e.g. an exhausted add-signer grant) don't pollute the DYNT-1599 metric.
|
|
683
695
|
// Only the 400 can actually refresh.
|
|
684
|
-
const isSessionSignatureFailure = status === 403 && SESSION_SIGNATURE_FAILURE_REASONS.has(
|
|
696
|
+
const isSessionSignatureFailure = status === 403 && reason !== undefined && SESSION_SIGNATURE_FAILURE_REASONS.has(reason);
|
|
697
|
+
const isRefreshableNonceFailure = status === 400 && reason !== undefined && REFRESHABLE_NONCE_FAILURE_REASONS.has(reason);
|
|
698
|
+
const canRefreshNow = isRefreshableNonceFailure || !hasRefreshed;
|
|
685
699
|
const shouldInstrument = status === 400 || isSessionSignatureFailure;
|
|
686
700
|
if (shouldInstrument) {
|
|
687
|
-
const willRefresh = status === 400 &&
|
|
701
|
+
const willRefresh = status === 400 && canRefreshNow && this.canRefresh && this.supportsReverseChannel();
|
|
688
702
|
this.instrumentNonceFailure({
|
|
689
703
|
error,
|
|
690
704
|
attempt,
|
|
@@ -694,7 +708,7 @@ function meetsMinVersion(sdkVersion, { minVersionByNamespace, fallbackMinVersion
|
|
|
694
708
|
logContext
|
|
695
709
|
});
|
|
696
710
|
}
|
|
697
|
-
if (status === 400 &&
|
|
711
|
+
if (status === 400 && canRefreshNow && this.canRefresh) {
|
|
698
712
|
// canRefresh only proves a local callback exists, not that the host answers it.
|
|
699
713
|
if (!this.supportsReverseChannel()) {
|
|
700
714
|
this.logger.warn(`[${operationName}] host SDK version predates the signed-session reverse channel, declining nonce refresh`, _extends({
|
|
@@ -901,6 +915,8 @@ const SESSION_KEY_MISMATCH_REASONS = new Set([
|
|
|
901
915
|
]);
|
|
902
916
|
const NONCE_REASON_TO_BACKUP_REASON = {
|
|
903
917
|
nonce_already_used: 'nonce_already_used',
|
|
918
|
+
// Same dashboard bucket as before CKS split this code out of nonce_already_used.
|
|
919
|
+
nonce_not_found_or_expired: 'nonce_already_used',
|
|
904
920
|
invalid_nonce_signature: 'invalid_nonce_signature',
|
|
905
921
|
invalid_session_signature: 'invalid_session_signature',
|
|
906
922
|
other: 'signed_session'
|
|
@@ -3111,6 +3127,7 @@ const createDynamicOnlyDistribution = ({ allShares })=>({
|
|
|
3111
3127
|
// a reference to observe how it eventually settles.
|
|
3112
3128
|
let taskPromise;
|
|
3113
3129
|
let taskSettled = false;
|
|
3130
|
+
const holdState = {};
|
|
3114
3131
|
// Add to sign queue - allows concurrent signing
|
|
3115
3132
|
const resultPromise = signQueue.add(()=>{
|
|
3116
3133
|
this.walletsWithActiveSignOp.set(accountAddress, true);
|
|
@@ -3128,6 +3145,11 @@ const createDynamicOnlyDistribution = ({ allShares })=>({
|
|
|
3128
3145
|
delete signDetails.forwardMpcFailure;
|
|
3129
3146
|
}
|
|
3130
3147
|
Object.assign(signDetails, details);
|
|
3148
|
+
},
|
|
3149
|
+
readingShares: ()=>{
|
|
3150
|
+
var _holdState;
|
|
3151
|
+
var _release;
|
|
3152
|
+
(_release = (_holdState = holdState).release) != null ? _release : _holdState.release = this.holdShares(accountAddress);
|
|
3131
3153
|
}
|
|
3132
3154
|
};
|
|
3133
3155
|
taskPromise = (async ()=>{
|
|
@@ -3135,6 +3157,7 @@ const createDynamicOnlyDistribution = ({ allShares })=>({
|
|
|
3135
3157
|
return await callback(hooks);
|
|
3136
3158
|
} finally{
|
|
3137
3159
|
this.walletsWithActiveSignOp.delete(accountAddress);
|
|
3160
|
+
holdState.release == null ? void 0 : holdState.release.call(holdState);
|
|
3138
3161
|
taskSettled = true;
|
|
3139
3162
|
}
|
|
3140
3163
|
})();
|
|
@@ -3148,6 +3171,8 @@ const createDynamicOnlyDistribution = ({ allShares })=>({
|
|
|
3148
3171
|
if (!isQueueTimeout) {
|
|
3149
3172
|
return;
|
|
3150
3173
|
}
|
|
3174
|
+
// A timed-out sign no longer counts for the queue, so it stops counting as a share holder too.
|
|
3175
|
+
holdState.release == null ? void 0 : holdState.release.call(holdState);
|
|
3151
3176
|
logError({
|
|
3152
3177
|
message: '[WalletQueueManager] Sign queue timed out',
|
|
3153
3178
|
error: error,
|
|
@@ -3265,12 +3290,50 @@ const createDynamicOnlyDistribution = ({ allShares })=>({
|
|
|
3265
3290
|
return recoveryPromise;
|
|
3266
3291
|
}
|
|
3267
3292
|
/**
|
|
3293
|
+
* Waits for the wallet's in-flight recovery, if any.
|
|
3294
|
+
* Never rejects: recovery failures are logged where they happen.
|
|
3295
|
+
*
|
|
3296
|
+
* @returns `none` without a recovery, `settled` once it finished
|
|
3297
|
+
*/ static async waitForPendingRecovery(accountAddress) {
|
|
3298
|
+
const pending = this.pendingRecoveryPromises.get(normalizeAddress(accountAddress));
|
|
3299
|
+
if (!pending) {
|
|
3300
|
+
return 'none';
|
|
3301
|
+
}
|
|
3302
|
+
// `pending` may reject; allSettled waits for it to end either way.
|
|
3303
|
+
await Promise.allSettled([
|
|
3304
|
+
pending
|
|
3305
|
+
]);
|
|
3306
|
+
return 'settled';
|
|
3307
|
+
}
|
|
3308
|
+
static getShareHolderQueue(address) {
|
|
3309
|
+
const existing = this.shareHolderQueues.get(address);
|
|
3310
|
+
if (existing) {
|
|
3311
|
+
return existing;
|
|
3312
|
+
}
|
|
3313
|
+
const queue = new PQueue({
|
|
3314
|
+
concurrency: Infinity
|
|
3315
|
+
});
|
|
3316
|
+
this.shareHolderQueues.set(address, queue);
|
|
3317
|
+
return queue;
|
|
3318
|
+
}
|
|
3319
|
+
/** Registers a sign as holding its shares until the returned function is called. */ static holdShares(address) {
|
|
3320
|
+
const hold = {
|
|
3321
|
+
release: ()=>undefined
|
|
3322
|
+
};
|
|
3323
|
+
const held = new Promise((resolve)=>{
|
|
3324
|
+
hold.release = resolve;
|
|
3325
|
+
});
|
|
3326
|
+
void this.getShareHolderQueue(address).add(()=>held);
|
|
3327
|
+
return ()=>hold.release();
|
|
3328
|
+
}
|
|
3329
|
+
/**
|
|
3268
3330
|
* Reset static state for testing purposes.
|
|
3269
3331
|
* This clears all wallet queues and pending recovery promises.
|
|
3270
3332
|
*/ static resetForTesting() {
|
|
3271
3333
|
this.heavyOpQueues.clear();
|
|
3272
3334
|
this.signQueues.clear();
|
|
3273
3335
|
this.pendingRecoveryPromises.clear();
|
|
3336
|
+
this.shareHolderQueues.clear();
|
|
3274
3337
|
this.walletsWithActiveHeavyOp.clear();
|
|
3275
3338
|
this.walletsWithActiveSignOp.clear();
|
|
3276
3339
|
this.signQueueTimeoutMs = 20000;
|
|
@@ -3280,6 +3343,7 @@ const createDynamicOnlyDistribution = ({ allShares })=>({
|
|
|
3280
3343
|
/** Sign queues per wallet address. These allow concurrent operations. */ WalletQueueManager.signQueues = new Map();
|
|
3281
3344
|
/** Timeout applied to new sign queues. Mutable for tests via resetForTesting. */ WalletQueueManager.signQueueTimeoutMs = 20000;
|
|
3282
3345
|
/** Track pending recovery promises for deduplication */ WalletQueueManager.pendingRecoveryPromises = new Map();
|
|
3346
|
+
/** Per wallet, the signs that have loaded their key shares and not finished. */ WalletQueueManager.shareHolderQueues = new Map();
|
|
3283
3347
|
/** Track which wallets are currently executing inside a heavy operation.
|
|
3284
3348
|
* Used to prevent deadlocks when recovery is called from within a heavy op. */ WalletQueueManager.walletsWithActiveHeavyOp = new Map();
|
|
3285
3349
|
/** Track which wallets are currently executing inside a sign operation.
|
|
@@ -4039,13 +4103,7 @@ class DynamicWalletClient {
|
|
|
4039
4103
|
this.restoreWallets()
|
|
4040
4104
|
];
|
|
4041
4105
|
if (this.featureFlags[core.FEATURE_FLAGS.ENABLE_ROOM_CACHE_FLAG] === true) {
|
|
4042
|
-
|
|
4043
|
-
initializePromises.push(this.restoreRooms().then(()=>// assume two of two threshold signature scheme
|
|
4044
|
-
this.createRooms({
|
|
4045
|
-
roomType: RoomTypeEnum.RoomTypeEnum.Threshold,
|
|
4046
|
-
thresholdSignatureScheme: core.ThresholdSignatureScheme.TWO_OF_TWO,
|
|
4047
|
-
roomCount: ROOM_CACHE_COUNT
|
|
4048
|
-
})));
|
|
4106
|
+
initializePromises.push(this.prefetchRoomPool());
|
|
4049
4107
|
}
|
|
4050
4108
|
await Promise.all(initializePromises);
|
|
4051
4109
|
return {
|
|
@@ -4059,6 +4117,25 @@ class DynamicWalletClient {
|
|
|
4059
4117
|
};
|
|
4060
4118
|
}
|
|
4061
4119
|
}
|
|
4120
|
+
/**
|
|
4121
|
+
* Restores the cached rooms, then creates more if the pool is low. Best effort: the pool is only a
|
|
4122
|
+
* prefetch, and takeRooms() falls back and refills it later, so a failure (e.g. a dropped request)
|
|
4123
|
+
* must not fail init. createRooms already logs its own error.
|
|
4124
|
+
*/ async prefetchRoomPool() {
|
|
4125
|
+
try {
|
|
4126
|
+
await this.restoreRooms();
|
|
4127
|
+
// assume two of two threshold signature scheme
|
|
4128
|
+
await this.createRooms({
|
|
4129
|
+
roomType: RoomTypeEnum.RoomTypeEnum.Threshold,
|
|
4130
|
+
thresholdSignatureScheme: core.ThresholdSignatureScheme.TWO_OF_TWO,
|
|
4131
|
+
roomCount: ROOM_CACHE_COUNT
|
|
4132
|
+
});
|
|
4133
|
+
} catch (error) {
|
|
4134
|
+
this.logger.debug('[DynamicWaasWalletClient] Room cache prefetch failed during init', {
|
|
4135
|
+
error
|
|
4136
|
+
});
|
|
4137
|
+
}
|
|
4138
|
+
}
|
|
4062
4139
|
async serverInitializeKeyGen({ chainName, clientKeygenIds, dynamicRequestId, thresholdSignatureScheme, bitcoinConfig, businessAccountId, idempotencyKey, traceContext, onError, onCeremonyComplete }) {
|
|
4063
4140
|
// Initialize keygen, create room, and create the wallet account on the server.
|
|
4064
4141
|
// HTTP transport (flag on + idempotency key present) uses a single JSON
|
|
@@ -4783,14 +4860,14 @@ class DynamicWalletClient {
|
|
|
4783
4860
|
return data;
|
|
4784
4861
|
}
|
|
4785
4862
|
/**
|
|
4786
|
-
* The server half of `signAndBroadcast`: asks Dynamic to open the signing
|
|
4863
|
+
* The server half of `signAndBroadcast`: asks Dynamic to open the signing room and returns the transaction record.
|
|
4787
4864
|
*
|
|
4788
4865
|
* @param params.walletId - The wallet that signs.
|
|
4789
4866
|
* @param params.shareSetId - Which of the wallet's key sets to use. Defaults to its active one.
|
|
4790
|
-
* @param params.
|
|
4791
|
-
* @param params.
|
|
4867
|
+
* @param params.message - The transaction as the bytes to sign (RLP hex on EVM, message bytes on Solana). Not hashed.
|
|
4868
|
+
* @param params.context - What the message does in readable form (chain, recipient, amount). Dynamic checks it against the wallet and its policies.
|
|
4792
4869
|
* @param params.broadcastOptions - Solana send settings: other required signers' signatures, and skipping the pre-send dry run.
|
|
4793
|
-
* @param params.
|
|
4870
|
+
* @param params.roomId - A signing room you already opened; Dynamic opens one when omitted.
|
|
4794
4871
|
* @param params.requestedCertainty - How sure to be before calling it confirmed: `fast`, `safe` or `final`, or `chain:` plus a chain-specific value. Defaults to `safe`.
|
|
4795
4872
|
* @param params.externalReference - Your own label, returned on every event. Not unique.
|
|
4796
4873
|
* @param params.idempotencyKey - Makes a retry safe: the same key replays the stored answer instead of sending twice.
|
|
@@ -4800,15 +4877,15 @@ class DynamicWalletClient {
|
|
|
4800
4877
|
* @param params.onError - Called with the failure's message, never the raw error.
|
|
4801
4878
|
* @param params.dynamicRequestId - Id of this one request, to match client and server logs.
|
|
4802
4879
|
* @param params.traceContext - Ties this call's logs to the server's, across retries. Created when omitted.
|
|
4803
|
-
*/ async serverSignAndBroadcast({ walletId, shareSetId,
|
|
4804
|
-
// Opens
|
|
4880
|
+
*/ async serverSignAndBroadcast({ walletId, shareSetId, context, broadcastOptions, message, roomId, requestedCertainty, externalReference, idempotencyKey, mfaToken, elevatedAccessToken, approvalQuorum, onError, dynamicRequestId, traceContext }) {
|
|
4881
|
+
// Opens a signing room, unless `roomId` names one, and returns the transaction record.
|
|
4805
4882
|
return await this.apiClient.signAndBroadcast({
|
|
4806
4883
|
walletId,
|
|
4807
4884
|
shareSetId,
|
|
4808
|
-
|
|
4885
|
+
context: core.toJsonSafeContext(context),
|
|
4809
4886
|
broadcastOptions,
|
|
4810
|
-
|
|
4811
|
-
|
|
4887
|
+
message,
|
|
4888
|
+
roomId,
|
|
4812
4889
|
isFormatted: false,
|
|
4813
4890
|
requestedCertainty,
|
|
4814
4891
|
externalReference,
|
|
@@ -5246,7 +5323,7 @@ class DynamicWalletClient {
|
|
|
5246
5323
|
}
|
|
5247
5324
|
//todo: need to modify with imported flag
|
|
5248
5325
|
async sign({ accountAddress, message, chainName, password = undefined, isFormatted = false, signedSessionId, mfaToken, elevatedAccessToken, context, approvalQuorum, onError, traceContext, bitcoinConfig }) {
|
|
5249
|
-
return this.queueSign(accountAddress, ({ reportPhase, reportDetails })=>this.internalSign({
|
|
5326
|
+
return this.queueSign(accountAddress, ({ reportPhase, reportDetails, readingShares })=>this.internalSign({
|
|
5250
5327
|
accountAddress,
|
|
5251
5328
|
message,
|
|
5252
5329
|
chainName,
|
|
@@ -5261,16 +5338,17 @@ class DynamicWalletClient {
|
|
|
5261
5338
|
traceContext,
|
|
5262
5339
|
bitcoinConfig,
|
|
5263
5340
|
onSignPhase: reportPhase,
|
|
5264
|
-
onSignDetails: reportDetails
|
|
5341
|
+
onSignDetails: reportDetails,
|
|
5342
|
+
onReadingShares: readingShares
|
|
5265
5343
|
}));
|
|
5266
5344
|
}
|
|
5267
5345
|
/**
|
|
5268
|
-
* Signs a transaction for Dynamic to broadcast
|
|
5346
|
+
* Signs a transaction for Dynamic to broadcast. The signature stays on the server.
|
|
5269
5347
|
*
|
|
5270
5348
|
* @param params.accountAddress - The wallet's address.
|
|
5271
5349
|
* @param params.chainName - The wallet's chain, e.g. `EVM`.
|
|
5272
|
-
* @param params.
|
|
5273
|
-
* @param params.
|
|
5350
|
+
* @param params.message - The transaction as the bytes to sign (RLP hex on EVM, message bytes on Solana). Not hashed.
|
|
5351
|
+
* @param params.context - What the message does in readable form (chain, recipient, amount). Dynamic checks it against the wallet and its policies.
|
|
5274
5352
|
* @param params.broadcastOptions - Solana send settings: other required signers' signatures, and skipping the pre-send dry run.
|
|
5275
5353
|
* @param params.password - The wallet's backup password, if it has one.
|
|
5276
5354
|
* @param params.signedSessionId - The signed-in session, used to unlock the wallet's key share.
|
|
@@ -5283,18 +5361,13 @@ class DynamicWalletClient {
|
|
|
5283
5361
|
* @param params.onError - Called with the failure's message, never the raw error.
|
|
5284
5362
|
* @param params.traceContext - Ties this call's logs to the server's, across retries. Created when omitted.
|
|
5285
5363
|
* @returns The transaction record. A finished one comes back without signing again.
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
// Each fee attempt is one message and one context; a mismatch is the caller's, so it is named here, before any ceremony.
|
|
5289
|
-
if (messages.length === 0 || contexts.length !== messages.length) {
|
|
5290
|
-
throw new Error(`Sign and broadcast needs one signing context per message, got ${contexts.length} contexts for ${messages.length} messages`);
|
|
5291
|
-
}
|
|
5292
|
-
return this.queueSign(accountAddress, ({ reportPhase })=>this.internalSignAndBroadcast({
|
|
5364
|
+
*/ async signAndBroadcast({ accountAddress, chainName, context, broadcastOptions, message, password = undefined, signedSessionId, mfaToken, elevatedAccessToken, approvalQuorum, requestedCertainty, externalReference, idempotencyKey = uuid.v4(), onError, traceContext }) {
|
|
5365
|
+
return this.queueSign(accountAddress, ({ reportPhase, reportDetails, readingShares })=>this.internalSignAndBroadcast({
|
|
5293
5366
|
accountAddress,
|
|
5294
5367
|
chainName,
|
|
5295
|
-
|
|
5368
|
+
context,
|
|
5296
5369
|
broadcastOptions,
|
|
5297
|
-
|
|
5370
|
+
message,
|
|
5298
5371
|
password,
|
|
5299
5372
|
signedSessionId,
|
|
5300
5373
|
mfaToken,
|
|
@@ -5305,17 +5378,22 @@ class DynamicWalletClient {
|
|
|
5305
5378
|
idempotencyKey,
|
|
5306
5379
|
onError,
|
|
5307
5380
|
traceContext,
|
|
5308
|
-
onSignPhase: reportPhase
|
|
5381
|
+
onSignPhase: reportPhase,
|
|
5382
|
+
onSignDetails: reportDetails,
|
|
5383
|
+
onReadingShares: readingShares
|
|
5309
5384
|
}));
|
|
5310
5385
|
}
|
|
5311
5386
|
/**
|
|
5312
|
-
* Does the work of `signAndBroadcast` once it has its turn in the wallet queue: loads the wallet, asks Dynamic to open the
|
|
5387
|
+
* Does the work of `signAndBroadcast` once it has its turn in the wallet queue: loads the wallet, asks Dynamic to open the room, then signs in it.
|
|
5313
5388
|
*
|
|
5314
5389
|
* Takes the same fields as `signAndBroadcast`.
|
|
5315
5390
|
*
|
|
5316
5391
|
* @param params.onSignPhase - Reports which step it is on, so the queue can tell which step used up the time.
|
|
5317
|
-
*/ async internalSignAndBroadcast({ accountAddress, chainName,
|
|
5392
|
+
*/ async internalSignAndBroadcast({ accountAddress, chainName, context, broadcastOptions, message, password, signedSessionId, mfaToken, elevatedAccessToken, approvalQuorum, requestedCertainty, externalReference, idempotencyKey, onError, traceContext, onSignPhase, onSignDetails, onReadingShares, staleShareRetry = false, roomIdRetry = false }) {
|
|
5318
5393
|
const dynamicRequestId = uuid.v4();
|
|
5394
|
+
onSignDetails == null ? void 0 : onSignDetails({
|
|
5395
|
+
dynamicRequestId
|
|
5396
|
+
});
|
|
5319
5397
|
traceContext = core.withTraceId(traceContext);
|
|
5320
5398
|
let stage = 'prepareWallet';
|
|
5321
5399
|
let roomIdSource;
|
|
@@ -5325,7 +5403,6 @@ class DynamicWalletClient {
|
|
|
5325
5403
|
accountAddress,
|
|
5326
5404
|
chainName,
|
|
5327
5405
|
dynamicRequestId,
|
|
5328
|
-
attemptCount: messages.length,
|
|
5329
5406
|
staleShareRetry,
|
|
5330
5407
|
roomIdRetry
|
|
5331
5408
|
}, this.getTraceContext(traceContext)));
|
|
@@ -5334,12 +5411,20 @@ class DynamicWalletClient {
|
|
|
5334
5411
|
accountAddress,
|
|
5335
5412
|
password,
|
|
5336
5413
|
signedSessionId,
|
|
5337
|
-
onSignPhase
|
|
5414
|
+
onSignPhase,
|
|
5415
|
+
onReadingShares
|
|
5338
5416
|
});
|
|
5339
5417
|
stage = 'resolveCachedRoom';
|
|
5340
5418
|
onSignPhase == null ? void 0 : onSignPhase('resolveCachedRoom');
|
|
5341
|
-
const
|
|
5342
|
-
|
|
5419
|
+
const cachedRoomId = roomIdRetry ? undefined : await this.getCachedRoom({
|
|
5420
|
+
wallet,
|
|
5421
|
+
accountAddress,
|
|
5422
|
+
chainName,
|
|
5423
|
+
key: 'signAndBroadcast',
|
|
5424
|
+
dynamicRequestId,
|
|
5425
|
+
traceContext
|
|
5426
|
+
});
|
|
5427
|
+
roomIdSource = cachedRoomId ? 'cache' : 'server';
|
|
5343
5428
|
const roomsResolvedAt = Date.now();
|
|
5344
5429
|
this.logSlowSignPreflight({
|
|
5345
5430
|
accountAddress,
|
|
@@ -5357,10 +5442,10 @@ class DynamicWalletClient {
|
|
|
5357
5442
|
const record = await this.serverSignAndBroadcast({
|
|
5358
5443
|
walletId: wallet.walletId,
|
|
5359
5444
|
shareSetId: wallet.shareSetId,
|
|
5360
|
-
|
|
5445
|
+
context,
|
|
5361
5446
|
broadcastOptions,
|
|
5362
|
-
|
|
5363
|
-
|
|
5447
|
+
message,
|
|
5448
|
+
roomId: cachedRoomId,
|
|
5364
5449
|
requestedCertainty,
|
|
5365
5450
|
externalReference,
|
|
5366
5451
|
idempotencyKey,
|
|
@@ -5371,9 +5456,7 @@ class DynamicWalletClient {
|
|
|
5371
5456
|
dynamicRequestId,
|
|
5372
5457
|
traceContext
|
|
5373
5458
|
});
|
|
5374
|
-
|
|
5375
|
-
const rooms = (_record_rooms = record.rooms) != null ? _record_rooms : [];
|
|
5376
|
-
var _roomIds_length;
|
|
5459
|
+
const room = record.room;
|
|
5377
5460
|
this.logger.info('[DynamicWaasWalletClient] Sign and broadcast accepted', _extends({
|
|
5378
5461
|
key: 'signAndBroadcast',
|
|
5379
5462
|
operation: 'accepted',
|
|
@@ -5382,38 +5465,30 @@ class DynamicWalletClient {
|
|
|
5382
5465
|
dynamicRequestId,
|
|
5383
5466
|
transactionId: record.transactionId,
|
|
5384
5467
|
transactionStatus: record.status,
|
|
5385
|
-
|
|
5386
|
-
roomIdSource,
|
|
5387
|
-
roomsSent: (_roomIds_length = roomIds == null ? void 0 : roomIds.length) != null ? _roomIds_length : 0,
|
|
5388
|
-
roomsReturned: rooms.length,
|
|
5389
|
-
// A retry of an existing row names that row's rooms, not the ones just sent.
|
|
5390
|
-
returnedRoomsMatchSent: roomIds ? roomIds.every((roomId, index)=>{
|
|
5391
|
-
var _rooms_index;
|
|
5392
|
-
return ((_rooms_index = rooms[index]) == null ? void 0 : _rooms_index.roomId) === roomId;
|
|
5393
|
-
}) : undefined
|
|
5468
|
+
roomIdSource
|
|
5394
5469
|
}, this.getTraceContext(traceContext)));
|
|
5395
|
-
// A retry of a finished request returns the record without
|
|
5470
|
+
// A retry of a finished request returns the record without a room.
|
|
5396
5471
|
if (record.status !== 'pending_signature') {
|
|
5397
5472
|
return record;
|
|
5398
5473
|
}
|
|
5399
|
-
if (
|
|
5400
|
-
throw new Error(`Expected
|
|
5474
|
+
if (!room) {
|
|
5475
|
+
throw new Error(`Expected a signing room for transaction ${record.transactionId}`);
|
|
5401
5476
|
}
|
|
5402
5477
|
stage = 'ceremony';
|
|
5403
5478
|
onSignPhase == null ? void 0 : onSignPhase('ceremony');
|
|
5404
|
-
const ceremonies =
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5479
|
+
const ceremonies = this.clientSign({
|
|
5480
|
+
chainName,
|
|
5481
|
+
message,
|
|
5482
|
+
roomId: room.roomId,
|
|
5483
|
+
keyShare: clientKeyShares[0],
|
|
5484
|
+
derivationPath,
|
|
5485
|
+
isFormatted: false,
|
|
5486
|
+
dynamicRequestId,
|
|
5487
|
+
traceContext,
|
|
5488
|
+
variant: wallet.variant,
|
|
5489
|
+
// Nothing mints a replacement room for a broadcast.
|
|
5490
|
+
skipForwardMPC: true
|
|
5491
|
+
});
|
|
5417
5492
|
const getClientSignMismatch = this.trackClientSignMismatch(ceremonies, {
|
|
5418
5493
|
accountAddress,
|
|
5419
5494
|
chainName,
|
|
@@ -5444,7 +5519,7 @@ class DynamicWalletClient {
|
|
|
5444
5519
|
this.logger.info('[DynamicWaasWalletClient] Client sign completed', _extends({
|
|
5445
5520
|
accountAddress,
|
|
5446
5521
|
chainName,
|
|
5447
|
-
|
|
5522
|
+
roomId: room.roomId,
|
|
5448
5523
|
dynamicRequestId,
|
|
5449
5524
|
key: 'signAndBroadcast',
|
|
5450
5525
|
operation: 'clientSign'
|
|
@@ -5454,9 +5529,9 @@ class DynamicWalletClient {
|
|
|
5454
5529
|
const retryParams = {
|
|
5455
5530
|
accountAddress,
|
|
5456
5531
|
chainName,
|
|
5457
|
-
|
|
5532
|
+
context,
|
|
5458
5533
|
broadcastOptions,
|
|
5459
|
-
|
|
5534
|
+
message,
|
|
5460
5535
|
password,
|
|
5461
5536
|
signedSessionId,
|
|
5462
5537
|
mfaToken,
|
|
@@ -5467,7 +5542,9 @@ class DynamicWalletClient {
|
|
|
5467
5542
|
idempotencyKey,
|
|
5468
5543
|
onError,
|
|
5469
5544
|
traceContext,
|
|
5470
|
-
onSignPhase
|
|
5545
|
+
onSignPhase,
|
|
5546
|
+
onSignDetails,
|
|
5547
|
+
onReadingShares
|
|
5471
5548
|
};
|
|
5472
5549
|
// The server rejected the local shares as stale before opening any room. Heal them
|
|
5473
5550
|
// from backup and retry once with the same idempotency key, as plain signing does.
|
|
@@ -5498,9 +5575,9 @@ class DynamicWalletClient {
|
|
|
5498
5575
|
}));
|
|
5499
5576
|
}
|
|
5500
5577
|
// A cached room was already claimed by an earlier ceremony. Retry once with the same
|
|
5501
|
-
// idempotency key and no cached
|
|
5578
|
+
// idempotency key and no cached room, so the server opens a fresh one.
|
|
5502
5579
|
if (!roomIdRetry && isRoomIdAlreadyUsedError(error)) {
|
|
5503
|
-
this.logger.info('[RoomCache] retrying sign and broadcast without cached
|
|
5580
|
+
this.logger.info('[RoomCache] retrying sign and broadcast without a cached room after 409 ROOM_ID_ALREADY_USED', _extends({
|
|
5504
5581
|
key: 'signAndBroadcast',
|
|
5505
5582
|
operation: 'roomIdRetry',
|
|
5506
5583
|
accountAddress,
|
|
@@ -5523,7 +5600,6 @@ class DynamicWalletClient {
|
|
|
5523
5600
|
operation: 'signAndBroadcast',
|
|
5524
5601
|
details: {
|
|
5525
5602
|
stage,
|
|
5526
|
-
attemptCount: messages.length,
|
|
5527
5603
|
roomIdSource,
|
|
5528
5604
|
staleShareRetry,
|
|
5529
5605
|
roomIdRetry
|
|
@@ -5561,95 +5637,67 @@ class DynamicWalletClient {
|
|
|
5561
5637
|
}, this.getTraceContext(traceContext)));
|
|
5562
5638
|
}
|
|
5563
5639
|
/**
|
|
5564
|
-
* Takes one already-opened room
|
|
5640
|
+
* Takes one already-opened room from the local pool for a sign or a sign-and-broadcast, or none when the cache is off or empty, so the server opens one instead.
|
|
5565
5641
|
*
|
|
5566
|
-
* @param wallet - The wallet signing; its key scheme decides which
|
|
5567
|
-
* @param accountAddress - The wallet's address, for logs.
|
|
5568
|
-
* @param chainName - The wallet's chain, for logs.
|
|
5569
|
-
* @param
|
|
5570
|
-
* @param dynamicRequestId - Id of this request, so the pool's logs join the
|
|
5571
|
-
* @param traceContext - Trace ids for the same reason.
|
|
5572
|
-
* @returns The room
|
|
5573
|
-
*/ async
|
|
5642
|
+
* @param params.wallet - The wallet signing; its key scheme decides which room fits.
|
|
5643
|
+
* @param params.accountAddress - The wallet's address, for logs.
|
|
5644
|
+
* @param params.chainName - The wallet's chain, for logs.
|
|
5645
|
+
* @param params.key - Which operation is asking, for logs.
|
|
5646
|
+
* @param params.dynamicRequestId - Id of this request, so the pool's logs join the operation's.
|
|
5647
|
+
* @param params.traceContext - Trace ids for the same reason.
|
|
5648
|
+
* @returns The room id, or `undefined`.
|
|
5649
|
+
*/ async getCachedRoom({ wallet, accountAddress, chainName, key, dynamicRequestId, traceContext }) {
|
|
5574
5650
|
const logContext = _extends({
|
|
5575
5651
|
dynamicRequestId
|
|
5576
5652
|
}, this.getTraceContext(traceContext));
|
|
5577
5653
|
if (this.featureFlags[core.FEATURE_FLAGS.ENABLE_ROOM_CACHE_FLAG] !== true) {
|
|
5578
|
-
this.
|
|
5654
|
+
this.logCachedRoomUnavailable({
|
|
5579
5655
|
reason: 'cache_disabled',
|
|
5656
|
+
key,
|
|
5580
5657
|
accountAddress,
|
|
5581
5658
|
chainName,
|
|
5582
|
-
count,
|
|
5583
5659
|
logContext
|
|
5584
5660
|
});
|
|
5585
5661
|
return undefined;
|
|
5586
5662
|
}
|
|
5587
|
-
const
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
});
|
|
5593
|
-
if (rooms.length === 0) {
|
|
5594
|
-
this.logCachedRoomsUnavailable({
|
|
5595
|
-
reason: 'pool_too_small',
|
|
5663
|
+
const room = await this.getRoom(RoomTypeEnum.RoomTypeEnum.Threshold, wallet.thresholdSignatureScheme, logContext);
|
|
5664
|
+
if (!room) {
|
|
5665
|
+
this.logCachedRoomUnavailable({
|
|
5666
|
+
reason: 'pool_empty',
|
|
5667
|
+
key,
|
|
5596
5668
|
accountAddress,
|
|
5597
5669
|
chainName,
|
|
5598
|
-
count,
|
|
5599
5670
|
logContext
|
|
5600
5671
|
});
|
|
5601
5672
|
return undefined;
|
|
5602
5673
|
}
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
operation: 'cachedRoomsFound',
|
|
5674
|
+
this.logger.info('[DynamicWaasWalletClient] Using cached room', _extends({
|
|
5675
|
+
key,
|
|
5676
|
+
operation: 'cachedRoomFound',
|
|
5607
5677
|
accountAddress,
|
|
5608
5678
|
chainName,
|
|
5609
5679
|
walletId: wallet.walletId,
|
|
5610
|
-
|
|
5680
|
+
roomId: room.roomId
|
|
5611
5681
|
}, logContext));
|
|
5612
|
-
return
|
|
5682
|
+
return room.roomId;
|
|
5613
5683
|
}
|
|
5614
5684
|
/**
|
|
5615
|
-
* Logs why no cached
|
|
5685
|
+
* Logs why no cached room was sent, so how often the cache covers a request can be measured.
|
|
5616
5686
|
*
|
|
5617
|
-
* @param params.reason - `cache_disabled` or `
|
|
5687
|
+
* @param params.reason - `cache_disabled` or `pool_empty`.
|
|
5688
|
+
* @param params.key - Which operation asked.
|
|
5618
5689
|
* @param params.accountAddress - The wallet's address.
|
|
5619
5690
|
* @param params.chainName - The wallet's chain.
|
|
5620
|
-
* @param params.count - How many rooms were needed.
|
|
5621
5691
|
* @param params.logContext - Request and trace ids to attach.
|
|
5622
|
-
*/
|
|
5623
|
-
this.logger.info('[DynamicWaasWalletClient] Cached
|
|
5624
|
-
key
|
|
5625
|
-
operation: '
|
|
5692
|
+
*/ logCachedRoomUnavailable({ reason, key, accountAddress, chainName, logContext }) {
|
|
5693
|
+
this.logger.info('[DynamicWaasWalletClient] Cached room unavailable; the server opens one', _extends({
|
|
5694
|
+
key,
|
|
5695
|
+
operation: 'cachedRoomUnavailable',
|
|
5626
5696
|
reason,
|
|
5627
5697
|
accountAddress,
|
|
5628
|
-
chainName
|
|
5629
|
-
requestedCount: count
|
|
5698
|
+
chainName
|
|
5630
5699
|
}, logContext));
|
|
5631
5700
|
}
|
|
5632
|
-
async getCachedRoomForSign(wallet, accountAddress, chainName, dynamicRequestId, traceContext) {
|
|
5633
|
-
if (this.featureFlags[core.FEATURE_FLAGS.ENABLE_ROOM_CACHE_FLAG] !== true) {
|
|
5634
|
-
return undefined;
|
|
5635
|
-
}
|
|
5636
|
-
const logContext = _extends({
|
|
5637
|
-
dynamicRequestId
|
|
5638
|
-
}, this.getTraceContext(traceContext));
|
|
5639
|
-
const room = await this.getRoom(RoomTypeEnum.RoomTypeEnum.Threshold, wallet.thresholdSignatureScheme, logContext);
|
|
5640
|
-
const roomId = room == null ? void 0 : room.roomId;
|
|
5641
|
-
if (roomId) {
|
|
5642
|
-
this.logger.info('[DynamicWaasWalletClient] Using cached room', _extends({
|
|
5643
|
-
key: 'sign',
|
|
5644
|
-
operation: 'cachedRoomFound',
|
|
5645
|
-
accountAddress,
|
|
5646
|
-
chainName,
|
|
5647
|
-
walletId: wallet.walletId,
|
|
5648
|
-
roomId
|
|
5649
|
-
}, logContext));
|
|
5650
|
-
}
|
|
5651
|
-
return roomId;
|
|
5652
|
-
}
|
|
5653
5701
|
// Without a cached room the client depends on the server reporting one
|
|
5654
5702
|
// before it can join the ceremony.
|
|
5655
5703
|
async resolveSignRoom({ cachedRoomId, serverSignPromise, accountAddress, chainName, walletId, dynamicRequestId, traceContext }) {
|
|
@@ -5713,7 +5761,7 @@ class DynamicWalletClient {
|
|
|
5713
5761
|
clearTimeout(warningTimer);
|
|
5714
5762
|
}
|
|
5715
5763
|
}
|
|
5716
|
-
/** Verifies the password, loads the wallet and makes sure its client share exists: the start of every sign. */ async prepareWalletForSign({ accountAddress, password, signedSessionId, onSignPhase }) {
|
|
5764
|
+
/** Verifies the password, loads the wallet and makes sure its client share exists: the start of every sign. */ async prepareWalletForSign({ accountAddress, password, signedSessionId, onSignPhase, onReadingShares }) {
|
|
5717
5765
|
const startedAt = Date.now();
|
|
5718
5766
|
onSignPhase == null ? void 0 : onSignPhase('verifyPassword');
|
|
5719
5767
|
await this.verifyPassword({
|
|
@@ -5732,7 +5780,9 @@ class DynamicWalletClient {
|
|
|
5732
5780
|
const walletLoadedAt = Date.now();
|
|
5733
5781
|
const derivationPath = wallet.derivationPath && wallet.derivationPath != '' ? new Uint32Array(Object.values(JSON.parse(wallet.derivationPath))) : undefined;
|
|
5734
5782
|
onSignPhase == null ? void 0 : onSignPhase('ensureClientShare');
|
|
5735
|
-
const clientKeyShares = await this.ensureClientShare(accountAddress
|
|
5783
|
+
const clientKeyShares = await this.ensureClientShare(accountAddress, {
|
|
5784
|
+
onReadingShares
|
|
5785
|
+
});
|
|
5736
5786
|
const sharesReadyAt = Date.now();
|
|
5737
5787
|
return {
|
|
5738
5788
|
wallet,
|
|
@@ -5808,7 +5858,7 @@ class DynamicWalletClient {
|
|
|
5808
5858
|
}, describeErrorForLog(error), details, this.getTraceContext(traceContext))
|
|
5809
5859
|
});
|
|
5810
5860
|
}
|
|
5811
|
-
async internalSign({ accountAddress, message, chainName, password = undefined, isFormatted = false, signedSessionId, mfaToken, elevatedAccessToken, context, approvalQuorum, onError, traceContext, bitcoinConfig, onSignPhase, onSignDetails, staleShareRetry = false, roomIdRetry = false }) {
|
|
5861
|
+
async internalSign({ accountAddress, message, chainName, password = undefined, isFormatted = false, signedSessionId, mfaToken, elevatedAccessToken, context, approvalQuorum, onError, traceContext, bitcoinConfig, onSignPhase, onSignDetails, onReadingShares, staleShareRetry = false, roomIdRetry = false }) {
|
|
5812
5862
|
const dynamicRequestId = uuid.v4();
|
|
5813
5863
|
onSignDetails == null ? void 0 : onSignDetails({
|
|
5814
5864
|
dynamicRequestId
|
|
@@ -5833,10 +5883,18 @@ class DynamicWalletClient {
|
|
|
5833
5883
|
accountAddress,
|
|
5834
5884
|
password,
|
|
5835
5885
|
signedSessionId,
|
|
5836
|
-
onSignPhase
|
|
5886
|
+
onSignPhase,
|
|
5887
|
+
onReadingShares
|
|
5837
5888
|
});
|
|
5838
5889
|
onSignPhase == null ? void 0 : onSignPhase('resolveCachedRoom');
|
|
5839
|
-
const cachedRoomId = roomIdRetry ? undefined : await this.
|
|
5890
|
+
const cachedRoomId = roomIdRetry ? undefined : await this.getCachedRoom({
|
|
5891
|
+
wallet,
|
|
5892
|
+
accountAddress,
|
|
5893
|
+
chainName,
|
|
5894
|
+
key: 'sign',
|
|
5895
|
+
dynamicRequestId,
|
|
5896
|
+
traceContext
|
|
5897
|
+
});
|
|
5840
5898
|
const roomResolvedAt = Date.now();
|
|
5841
5899
|
this.logSlowSignPreflight({
|
|
5842
5900
|
accountAddress,
|
|
@@ -6079,6 +6137,7 @@ class DynamicWalletClient {
|
|
|
6079
6137
|
bitcoinConfig,
|
|
6080
6138
|
onSignPhase,
|
|
6081
6139
|
onSignDetails,
|
|
6140
|
+
onReadingShares,
|
|
6082
6141
|
staleShareRetry: true,
|
|
6083
6142
|
roomIdRetry
|
|
6084
6143
|
});
|
|
@@ -6108,6 +6167,7 @@ class DynamicWalletClient {
|
|
|
6108
6167
|
bitcoinConfig,
|
|
6109
6168
|
onSignPhase,
|
|
6110
6169
|
onSignDetails,
|
|
6170
|
+
onReadingShares,
|
|
6111
6171
|
staleShareRetry,
|
|
6112
6172
|
roomIdRetry: true
|
|
6113
6173
|
});
|
|
@@ -8291,17 +8351,15 @@ class DynamicWalletClient {
|
|
|
8291
8351
|
* Note: This method only checks for existing shares in storage.
|
|
8292
8352
|
* Auto-recovery logic has been removed in favor of the queue pattern.
|
|
8293
8353
|
* Callers should handle recovery explicitly if needed.
|
|
8294
|
-
*/ async ensureClientShare(accountAddress) {
|
|
8354
|
+
*/ async ensureClientShare(accountAddress, { onReadingShares } = {}) {
|
|
8295
8355
|
// A recovery may be in flight (e.g. a stale-share heal scheduled when a
|
|
8296
8356
|
// previous sign hit a public-key mismatch). Wait for it so this operation
|
|
8297
8357
|
// reads the recovered shares instead of the superseded ones. Recovery
|
|
8298
8358
|
// failures are logged where they happen; this operation should proceed
|
|
8299
8359
|
// with whatever shares exist rather than fail on the recovery.
|
|
8300
|
-
const
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
8304
|
-
await pendingRecovery.catch(()=>{});
|
|
8360
|
+
const waitStartedAt = Date.now();
|
|
8361
|
+
const recoveryWait = await WalletQueueManager.waitForPendingRecovery(accountAddress);
|
|
8362
|
+
if (recoveryWait === 'settled') {
|
|
8305
8363
|
// Every concurrent sign awaits this same promise, so it releases them all
|
|
8306
8364
|
// at once. One line per waiter: the count is the size of the herd and the
|
|
8307
8365
|
// shared finish timestamp is when it was released.
|
|
@@ -8311,6 +8369,8 @@ class DynamicWalletClient {
|
|
|
8311
8369
|
recoveryWaitMs: Date.now() - waitStartedAt
|
|
8312
8370
|
});
|
|
8313
8371
|
}
|
|
8372
|
+
// From here a recovery waits for this sign to finish, so it cannot replace the shares being read.
|
|
8373
|
+
onReadingShares == null ? void 0 : onReadingShares();
|
|
8314
8374
|
const shares = await this.getClientKeySharesFromStorage({
|
|
8315
8375
|
accountAddress
|
|
8316
8376
|
});
|
|
@@ -11151,6 +11211,17 @@ class DynamicWalletClient {
|
|
|
11151
11211
|
}
|
|
11152
11212
|
this.apiClient.syncAuthToken(authToken);
|
|
11153
11213
|
}
|
|
11214
|
+
/** Reads the persisted room pool. A corrupt value counts as empty, so the next write replaces it. */ async readStoredRooms() {
|
|
11215
|
+
const rawData = await this.storage.getItem(`${this.storageKey}-rooms`);
|
|
11216
|
+
if (!rawData) {
|
|
11217
|
+
return {};
|
|
11218
|
+
}
|
|
11219
|
+
try {
|
|
11220
|
+
return JSON.parse(rawData);
|
|
11221
|
+
} catch (e) {
|
|
11222
|
+
return {};
|
|
11223
|
+
}
|
|
11224
|
+
}
|
|
11154
11225
|
async createRooms({ roomType, thresholdSignatureScheme, roomCount = 5 }) {
|
|
11155
11226
|
const numberOfParties = this.getNumberOfParties(roomType, thresholdSignatureScheme);
|
|
11156
11227
|
const generation = DynamicWalletClient.roomsGeneration;
|
|
@@ -11192,8 +11263,7 @@ class DynamicWalletClient {
|
|
|
11192
11263
|
// Re-read from storage under the lock so we don't clobber rooms another
|
|
11193
11264
|
// tab just wrote, and so we don't lose rooms consumed by getRoom() calls
|
|
11194
11265
|
// that ran while our API request was in-flight.
|
|
11195
|
-
const
|
|
11196
|
-
const storedRooms = rawData ? JSON.parse(rawData) : {};
|
|
11266
|
+
const storedRooms = await this.readStoredRooms();
|
|
11197
11267
|
const currentRooms = storedRooms[numberOfParties] || [];
|
|
11198
11268
|
await this.setRooms(numberOfParties, [
|
|
11199
11269
|
...newRooms,
|
|
@@ -11310,8 +11380,7 @@ class DynamicWalletClient {
|
|
|
11310
11380
|
// already consumed by other tabs. Combined with the Web Locks API below, this
|
|
11311
11381
|
// makes room consumption atomic across tabs and prevents two tabs from both
|
|
11312
11382
|
// receiving the same cached room.
|
|
11313
|
-
const
|
|
11314
|
-
const storedRooms = rawData ? JSON.parse(rawData) : {};
|
|
11383
|
+
const storedRooms = await this.readStoredRooms();
|
|
11315
11384
|
const now = Date.now();
|
|
11316
11385
|
const schemeRooms = storedRooms[numberOfParties] || [];
|
|
11317
11386
|
// Filter out expired rooms and remove the next available room.
|