@dynamic-labs-wallet/browser 1.0.43 → 1.0.45
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 +514 -71
- package/index.esm.js +514 -72
- package/package.json +3 -3
- package/src/client.d.ts +56 -17
- package/src/client.d.ts.map +1 -1
- package/src/utils.d.ts +12 -0
- package/src/utils.d.ts.map +1 -1
package/index.esm.js
CHANGED
|
@@ -1266,6 +1266,22 @@ const downloadStringAsFile = ({ filename, content, mimeType = 'application/json'
|
|
|
1266
1266
|
}
|
|
1267
1267
|
return false;
|
|
1268
1268
|
};
|
|
1269
|
+
/**
|
|
1270
|
+
* Checks for the server's stale-share preflight rejection (HTTP 409 sent as
|
|
1271
|
+
* an SSE error event with `code: 'STALE_CLIENT_SHARES'`). The server compares
|
|
1272
|
+
* the `shareSetId` the SDK sends on sign requests against the wallet's active
|
|
1273
|
+
* share set and rejects BEFORE any room/ceremony work when they diverge
|
|
1274
|
+
* (e.g. the shares were rotated by a reshare/refresh on another device).
|
|
1275
|
+
* The SDK heals (re-syncs local shares from backup) and retries internally.
|
|
1276
|
+
*
|
|
1277
|
+
* The SSE event-stream layer copies the event's data fields onto the Error
|
|
1278
|
+
* object (see createErrorFromEventData), so `code` is available here.
|
|
1279
|
+
*/ const isStaleClientSharesError = (error)=>{
|
|
1280
|
+
if (!error || typeof error !== 'object') {
|
|
1281
|
+
return false;
|
|
1282
|
+
}
|
|
1283
|
+
return error.code === 'STALE_CLIENT_SHARES';
|
|
1284
|
+
};
|
|
1269
1285
|
/**
|
|
1270
1286
|
* Determines whether a reshare operation should reuse the wallet's existing backup locations.
|
|
1271
1287
|
*
|
|
@@ -2566,6 +2582,15 @@ const KNOWN_SHARE_SET_TYPES = new Set([
|
|
|
2566
2582
|
'delegated',
|
|
2567
2583
|
'server'
|
|
2568
2584
|
]);
|
|
2585
|
+
/**
|
|
2586
|
+
* keygenIds the server records for the wallet's Dynamic-backed-up client
|
|
2587
|
+
* shares — the reference generation that local shares are compared against.
|
|
2588
|
+
* Empty for first-time backups or legacy data without keygenIds.
|
|
2589
|
+
*/ const recordedDynamicKeygenIds = (backupInfo)=>{
|
|
2590
|
+
var _backupInfo_backups;
|
|
2591
|
+
var _backupInfo_backups_BackupLocation_DYNAMIC;
|
|
2592
|
+
return ((_backupInfo_backups_BackupLocation_DYNAMIC = backupInfo == null ? void 0 : (_backupInfo_backups = backupInfo.backups) == null ? void 0 : _backupInfo_backups[BackupLocation.DYNAMIC]) != null ? _backupInfo_backups_BackupLocation_DYNAMIC : []).map((backup)=>backup.keygenId).filter((keygenId)=>typeof keygenId === 'string' && keygenId.length > 0);
|
|
2593
|
+
};
|
|
2569
2594
|
class DynamicWalletClient {
|
|
2570
2595
|
/**
|
|
2571
2596
|
* Check if wallet has heavy operations in progress
|
|
@@ -3400,22 +3425,114 @@ class DynamicWalletClient {
|
|
|
3400
3425
|
}
|
|
3401
3426
|
}
|
|
3402
3427
|
/**
|
|
3403
|
-
*
|
|
3404
|
-
* the
|
|
3405
|
-
*
|
|
3406
|
-
*
|
|
3407
|
-
*
|
|
3408
|
-
*/ async
|
|
3409
|
-
|
|
3410
|
-
|
|
3428
|
+
* Best-effort heal for stale local shares. Runs verifyAndRecoverStaleShare
|
|
3429
|
+
* through the shared recovery queue: concurrent triggers join the in-flight
|
|
3430
|
+
* recovery (WalletQueueManager dedup), and execution is deadlock-safe when
|
|
3431
|
+
* called from inside a queued sign/heavy operation. Never throws — callers
|
|
3432
|
+
* are already on a failing path and rethrow their original error.
|
|
3433
|
+
*/ async healStaleShares(params) {
|
|
3434
|
+
const runCheck = ()=>WalletQueueManager.queueRecoverOperation(params.accountAddress, WalletOperation.RECOVER, ()=>this.verifyAndRecoverStaleShare(params));
|
|
3435
|
+
try {
|
|
3436
|
+
const result = await runCheck();
|
|
3437
|
+
// The queue dedup returns the in-flight recovery's promise, dropping
|
|
3438
|
+
// this caller's params. If that run skipped recovery but we carry a
|
|
3439
|
+
// confirmed mismatch, re-run once with our params. (A run we started
|
|
3440
|
+
// ourselves with confirmedMismatch=true always recovers, so this only
|
|
3441
|
+
// fires after joining a weaker concurrent check.)
|
|
3442
|
+
if (params.confirmedMismatch && (result == null ? void 0 : result.recovered) === false) {
|
|
3443
|
+
await runCheck();
|
|
3444
|
+
}
|
|
3445
|
+
} catch (error) {
|
|
3446
|
+
this.logger.error('[StaleShareCheck] share recovery failed', {
|
|
3447
|
+
accountAddress: params.accountAddress,
|
|
3448
|
+
dynamicRequestId: params.dynamicRequestId,
|
|
3449
|
+
trigger: params.trigger,
|
|
3450
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3451
|
+
});
|
|
3411
3452
|
}
|
|
3412
|
-
|
|
3453
|
+
}
|
|
3454
|
+
/**
|
|
3455
|
+
* Ground-truth staleness check: refreshes the wallet from the API, compares
|
|
3456
|
+
* local share keygenIds against the server's recorded Dynamic-backup
|
|
3457
|
+
* keygenIds, and restores from backup when the local generation is stale
|
|
3458
|
+
* (a reshare/refresh on another device/browser rotates the shares without
|
|
3459
|
+
* this client noticing). No-ops when shares are fresh, so callers may run
|
|
3460
|
+
* it on any ceremony failure regardless of the error message.
|
|
3461
|
+
*/ async verifyAndRecoverStaleShare({ accountAddress, chainName, password, signedSessionId, walletOperation, mfaToken, dynamicRequestId, traceContext, bitcoinConfig, trigger, confirmedMismatch }) {
|
|
3462
|
+
this.logger.info('[StaleShareCheck] verifying local share generation against server', _extends({
|
|
3413
3463
|
accountAddress,
|
|
3414
|
-
|
|
3464
|
+
chainName,
|
|
3465
|
+
trigger,
|
|
3466
|
+
confirmedMismatch,
|
|
3415
3467
|
dynamicRequestId
|
|
3416
3468
|
}, this.getTraceContext(traceContext)));
|
|
3417
|
-
// Refresh walletMap from API
|
|
3469
|
+
// Refresh walletMap from the API: the cached entry may predate a rotation
|
|
3470
|
+
// performed on another device, which would make this comparison a no-op.
|
|
3471
|
+
// Full getWallets (not just backup info) so shareSetId etc. are also
|
|
3472
|
+
// current for the caller's retry.
|
|
3418
3473
|
await this.getWallets();
|
|
3474
|
+
const wallet = this.getWalletFromMap(accountAddress);
|
|
3475
|
+
const recordedKeygenIds = recordedDynamicKeygenIds(wallet == null ? void 0 : wallet.clientKeySharesBackupInfo);
|
|
3476
|
+
let staleness;
|
|
3477
|
+
let localKeygenIds = [];
|
|
3478
|
+
if (recordedKeygenIds.length === 0) {
|
|
3479
|
+
// First-time backup or legacy data (server didn't populate keygenId) —
|
|
3480
|
+
// generation can't be compared. A confirmed mismatch still recovers
|
|
3481
|
+
// below (legacy recovery behavior); an unclassified
|
|
3482
|
+
// failure does not.
|
|
3483
|
+
staleness = 'unverifiable';
|
|
3484
|
+
} else {
|
|
3485
|
+
try {
|
|
3486
|
+
const localShares = await this.getClientKeySharesFromStorage({
|
|
3487
|
+
accountAddress
|
|
3488
|
+
});
|
|
3489
|
+
const comparison = await this.compareShareGenerations({
|
|
3490
|
+
accountAddress,
|
|
3491
|
+
chainName,
|
|
3492
|
+
localShares,
|
|
3493
|
+
recordedKeygenIds,
|
|
3494
|
+
bitcoinConfig
|
|
3495
|
+
});
|
|
3496
|
+
localKeygenIds = comparison.localKeygenIds;
|
|
3497
|
+
staleness = comparison.fresh ? 'fresh' : 'stale';
|
|
3498
|
+
} catch (error) {
|
|
3499
|
+
// Unreadable/corrupted local share — treat as stale and recover.
|
|
3500
|
+
this.logger.warn('[StaleShareCheck] could not derive keygenId from local shares — treating as stale', {
|
|
3501
|
+
accountAddress,
|
|
3502
|
+
trigger,
|
|
3503
|
+
dynamicRequestId,
|
|
3504
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3505
|
+
});
|
|
3506
|
+
staleness = 'stale';
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
// Stale generation always recovers. A confirmed mismatch also recovers
|
|
3510
|
+
// when the comparison is inconclusive or even reports fresh (possible
|
|
3511
|
+
// server-side inconsistency) — matching the legacy recovery behavior.
|
|
3512
|
+
const shouldRecover = staleness === 'stale' || confirmedMismatch;
|
|
3513
|
+
if (!shouldRecover) {
|
|
3514
|
+
this.logger.info('[StaleShareCheck] no recovery needed', {
|
|
3515
|
+
accountAddress,
|
|
3516
|
+
trigger,
|
|
3517
|
+
staleness,
|
|
3518
|
+
localKeygenIds,
|
|
3519
|
+
dynamicRequestId
|
|
3520
|
+
});
|
|
3521
|
+
return {
|
|
3522
|
+
recovered: false,
|
|
3523
|
+
localKeygenIds,
|
|
3524
|
+
recordedKeygenIds
|
|
3525
|
+
};
|
|
3526
|
+
}
|
|
3527
|
+
this.logger.warn('[StaleShareCheck] recovering current shares from backup', _extends({
|
|
3528
|
+
accountAddress,
|
|
3529
|
+
trigger,
|
|
3530
|
+
confirmedMismatch,
|
|
3531
|
+
staleness,
|
|
3532
|
+
localKeygenIds,
|
|
3533
|
+
recordedKeygenIds,
|
|
3534
|
+
dynamicRequestId
|
|
3535
|
+
}, this.getTraceContext(traceContext)));
|
|
3419
3536
|
await this.internalRecoverEncryptedBackupByWallet({
|
|
3420
3537
|
accountAddress,
|
|
3421
3538
|
password,
|
|
@@ -3424,12 +3541,35 @@ class DynamicWalletClient {
|
|
|
3424
3541
|
mfaToken,
|
|
3425
3542
|
storeRecoveredShares: true
|
|
3426
3543
|
});
|
|
3427
|
-
this.logger.info('[
|
|
3544
|
+
this.logger.info('[StaleShareCheck] recovery completed — next operation will use the recovered shares', {
|
|
3428
3545
|
accountAddress,
|
|
3429
|
-
|
|
3546
|
+
trigger,
|
|
3430
3547
|
dynamicRequestId
|
|
3431
|
-
}
|
|
3432
|
-
|
|
3548
|
+
});
|
|
3549
|
+
return {
|
|
3550
|
+
recovered: true,
|
|
3551
|
+
localKeygenIds,
|
|
3552
|
+
recordedKeygenIds
|
|
3553
|
+
};
|
|
3554
|
+
}
|
|
3555
|
+
/**
|
|
3556
|
+
* Set-compare local share keygenIds (MPC exportID) against the keygenIds
|
|
3557
|
+
* the server recorded for the wallet's Dynamic backups. Shared by the
|
|
3558
|
+
* stale-share heal (verifyAndRecoverStaleShare) and the backup preflight
|
|
3559
|
+
* (ensureLocalSharesAreFresh).
|
|
3560
|
+
*/ async compareShareGenerations({ accountAddress, chainName, localShares, recordedKeygenIds, bitcoinConfig }) {
|
|
3561
|
+
const mpcSigner = getMPCSigner({
|
|
3562
|
+
chainName,
|
|
3563
|
+
baseRelayUrl: this.baseMPCRelayApiUrl,
|
|
3564
|
+
bitcoinConfig: bitcoinConfig != null ? bitcoinConfig : this.getBitcoinConfigForChain(chainName, accountAddress)
|
|
3565
|
+
});
|
|
3566
|
+
const localKeygenIds = await Promise.all(localShares.map((share)=>this.getKeygenIdForShare(mpcSigner, share)));
|
|
3567
|
+
const recordedSet = new Set(recordedKeygenIds);
|
|
3568
|
+
const fresh = localKeygenIds.length === recordedSet.size && localKeygenIds.every((id)=>recordedSet.has(id));
|
|
3569
|
+
return {
|
|
3570
|
+
fresh,
|
|
3571
|
+
localKeygenIds
|
|
3572
|
+
};
|
|
3433
3573
|
}
|
|
3434
3574
|
//todo: need to modify with imported flag
|
|
3435
3575
|
async sign({ accountAddress, message, chainName, password = undefined, isFormatted = false, signedSessionId, mfaToken, elevatedAccessToken, context, onError, traceContext, bitcoinConfig }) {
|
|
@@ -3448,7 +3588,7 @@ class DynamicWalletClient {
|
|
|
3448
3588
|
bitcoinConfig
|
|
3449
3589
|
}));
|
|
3450
3590
|
}
|
|
3451
|
-
async internalSign({ accountAddress, message, chainName, password = undefined, isFormatted = false, signedSessionId, mfaToken, elevatedAccessToken, context, onError, traceContext, bitcoinConfig }) {
|
|
3591
|
+
async internalSign({ accountAddress, message, chainName, password = undefined, isFormatted = false, signedSessionId, mfaToken, elevatedAccessToken, context, onError, traceContext, bitcoinConfig, staleShareRetry = false }) {
|
|
3452
3592
|
const dynamicRequestId = v4();
|
|
3453
3593
|
try {
|
|
3454
3594
|
await this.verifyPassword({
|
|
@@ -3491,6 +3631,17 @@ class DynamicWalletClient {
|
|
|
3491
3631
|
// (sseErrorPromise would never reach Promise.race in that path)
|
|
3492
3632
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
3493
3633
|
sseErrorPromise.catch(()=>{});
|
|
3634
|
+
// Dedicated channel for the server's 409 stale-shares preflight: it must
|
|
3635
|
+
// abort the ceremony into the heal-and-retry path WITHOUT settling the
|
|
3636
|
+
// host request (so the transparent retry's outcome is what the host
|
|
3637
|
+
// sees). Needed as a race arm because in the cached-room flow
|
|
3638
|
+
// serverSignPromise is never awaited, so its rejection alone would float.
|
|
3639
|
+
let rejectOnStaleShares;
|
|
3640
|
+
const staleSharesPromise = new Promise((_, reject)=>{
|
|
3641
|
+
rejectOnStaleShares = reject;
|
|
3642
|
+
});
|
|
3643
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
3644
|
+
staleSharesPromise.catch(()=>{});
|
|
3494
3645
|
// Perform the server sign
|
|
3495
3646
|
const serverSignPromise = this.serverSign({
|
|
3496
3647
|
walletId: wallet.walletId,
|
|
@@ -3502,6 +3653,13 @@ class DynamicWalletClient {
|
|
|
3502
3653
|
roomId,
|
|
3503
3654
|
context,
|
|
3504
3655
|
onError: (error)=>{
|
|
3656
|
+
// The server's 409 stale-shares preflight rejection is healed and
|
|
3657
|
+
// retried internally (see the catch below) — don't settle the host
|
|
3658
|
+
// request or poison the SSE race with it.
|
|
3659
|
+
if (isStaleClientSharesError(error)) {
|
|
3660
|
+
rejectOnStaleShares(error);
|
|
3661
|
+
return;
|
|
3662
|
+
}
|
|
3505
3663
|
onError == null ? void 0 : onError(error);
|
|
3506
3664
|
rejectOnSSEError(error);
|
|
3507
3665
|
},
|
|
@@ -3509,6 +3667,11 @@ class DynamicWalletClient {
|
|
|
3509
3667
|
traceContext,
|
|
3510
3668
|
bitcoinConfig
|
|
3511
3669
|
});
|
|
3670
|
+
// In the cached-room flow the promise below is never awaited — attach a
|
|
3671
|
+
// handler so a rejection (e.g. the 409 preflight, which also arrives via
|
|
3672
|
+
// staleSharesPromise above) doesn't surface as an unhandled rejection.
|
|
3673
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
3674
|
+
serverSignPromise.catch(()=>{});
|
|
3512
3675
|
// Only await if roomId is not provided
|
|
3513
3676
|
roomId = roomId != null ? roomId : (await serverSignPromise).roomId;
|
|
3514
3677
|
this.logger.info('[DynamicWaasWalletClient] Server sign completed', _extends({
|
|
@@ -3521,6 +3684,9 @@ class DynamicWalletClient {
|
|
|
3521
3684
|
operation: 'serverSign'
|
|
3522
3685
|
}, this.getTraceContext(traceContext)));
|
|
3523
3686
|
let signature;
|
|
3687
|
+
// Set by the clientSign rejection handler below; read in the catch to
|
|
3688
|
+
// detect a client-side mismatch masked by a generic SSE race winner.
|
|
3689
|
+
let clientSignMismatchError;
|
|
3524
3690
|
try {
|
|
3525
3691
|
// Race clientSign against SSE errors so that server-side failures
|
|
3526
3692
|
// (e.g. policy violations) immediately reject instead of hanging
|
|
@@ -3548,6 +3714,12 @@ class DynamicWalletClient {
|
|
|
3548
3714
|
roomId: undefined,
|
|
3549
3715
|
context,
|
|
3550
3716
|
onError: (error)=>{
|
|
3717
|
+
// See the initial serverSign onError: 409 stale-shares is
|
|
3718
|
+
// healed + retried internally, never surfaced to the host.
|
|
3719
|
+
if (isStaleClientSharesError(error)) {
|
|
3720
|
+
rejectOnStaleShares(error);
|
|
3721
|
+
return;
|
|
3722
|
+
}
|
|
3551
3723
|
onError == null ? void 0 : onError(error);
|
|
3552
3724
|
rejectOnSSEError(error);
|
|
3553
3725
|
},
|
|
@@ -3558,26 +3730,63 @@ class DynamicWalletClient {
|
|
|
3558
3730
|
return freshRoomId;
|
|
3559
3731
|
}
|
|
3560
3732
|
});
|
|
3561
|
-
//
|
|
3562
|
-
//
|
|
3563
|
-
//
|
|
3564
|
-
//
|
|
3565
|
-
|
|
3733
|
+
// Handle the losing race promise (prevents an unhandled rejection) and
|
|
3734
|
+
// capture a client-side ERROR_PUBLIC_KEY_MISMATCH here — the only place
|
|
3735
|
+
// a stale-local-share failure surfaces, since the server's generic SSE
|
|
3736
|
+
// error wins the race and masks it. Schedule recovery regardless of
|
|
3737
|
+
// which promise wins or when this rejection settles.
|
|
3738
|
+
clientSignPromise.catch((error)=>{
|
|
3739
|
+
if (isPublicKeyMismatchError(error)) {
|
|
3740
|
+
clientSignMismatchError = error;
|
|
3741
|
+
this.logger.warn('[StaleShareCheck] clientSign rejected with public-key mismatch — scheduling share recovery', _extends({
|
|
3742
|
+
accountAddress,
|
|
3743
|
+
chainName,
|
|
3744
|
+
dynamicRequestId
|
|
3745
|
+
}, this.getTraceContext(traceContext)));
|
|
3746
|
+
void this.healStaleShares({
|
|
3747
|
+
accountAddress,
|
|
3748
|
+
chainName,
|
|
3749
|
+
password,
|
|
3750
|
+
signedSessionId,
|
|
3751
|
+
walletOperation: WalletOperation.SIGN_MESSAGE,
|
|
3752
|
+
mfaToken,
|
|
3753
|
+
dynamicRequestId,
|
|
3754
|
+
traceContext,
|
|
3755
|
+
bitcoinConfig,
|
|
3756
|
+
trigger: 'clientSign rejection',
|
|
3757
|
+
confirmedMismatch: true
|
|
3758
|
+
});
|
|
3759
|
+
}
|
|
3760
|
+
});
|
|
3566
3761
|
signature = await Promise.race([
|
|
3567
3762
|
clientSignPromise,
|
|
3568
|
-
sseErrorPromise
|
|
3763
|
+
sseErrorPromise,
|
|
3764
|
+
staleSharesPromise
|
|
3569
3765
|
]);
|
|
3570
3766
|
} catch (signError) {
|
|
3571
|
-
//
|
|
3572
|
-
|
|
3573
|
-
|
|
3767
|
+
// The 409 stale-shares preflight is handled exclusively by the outer
|
|
3768
|
+
// catch (heal + transparent retry) — skip the generic heal here so the
|
|
3769
|
+
// ground-truth check runs once, right before the retry decision.
|
|
3770
|
+
if (isStaleClientSharesError(signError)) {
|
|
3771
|
+
throw signError;
|
|
3772
|
+
}
|
|
3773
|
+
// Heal before rethrowing so the caller's retry succeeds. Run the
|
|
3774
|
+
// ground-truth check whatever the raced error was: a generic SSE
|
|
3775
|
+
// error frequently wins the race while the client-side mismatch
|
|
3776
|
+
// settles later, so error-string inspection alone is timing-dependent.
|
|
3777
|
+
// The check no-ops when shares are fresh.
|
|
3778
|
+
await this.healStaleShares({
|
|
3574
3779
|
accountAddress,
|
|
3780
|
+
chainName,
|
|
3575
3781
|
password,
|
|
3576
3782
|
signedSessionId,
|
|
3577
3783
|
walletOperation: WalletOperation.SIGN_MESSAGE,
|
|
3578
3784
|
mfaToken,
|
|
3785
|
+
dynamicRequestId,
|
|
3579
3786
|
traceContext,
|
|
3580
|
-
|
|
3787
|
+
bitcoinConfig,
|
|
3788
|
+
trigger: 'sign failure',
|
|
3789
|
+
confirmedMismatch: isPublicKeyMismatchError(signError) || clientSignMismatchError !== undefined
|
|
3581
3790
|
});
|
|
3582
3791
|
throw signError;
|
|
3583
3792
|
}
|
|
@@ -3593,6 +3802,51 @@ class DynamicWalletClient {
|
|
|
3593
3802
|
}, this.getTraceContext(traceContext)));
|
|
3594
3803
|
return signature;
|
|
3595
3804
|
} catch (error) {
|
|
3805
|
+
// Server-side stale-share preflight (409 STALE_CLIENT_SHARES): the
|
|
3806
|
+
// request was rejected BEFORE any room/ceremony work, and the onError
|
|
3807
|
+
// wrappers above kept it away from the host — so the host request is
|
|
3808
|
+
// still pending. Heal the local shares from backup and transparently
|
|
3809
|
+
// retry once; the host only ever sees the (successful) outcome.
|
|
3810
|
+
if (!staleShareRetry && isStaleClientSharesError(error)) {
|
|
3811
|
+
// Heal first (re-sync local shares + walletMap from the server), then
|
|
3812
|
+
// retry regardless of whether shares were replaced: even a "fresh"
|
|
3813
|
+
// verdict means the heal's getWallets() refreshed the stale
|
|
3814
|
+
// walletMap.shareSetId the server rejected. The retry is guarded to
|
|
3815
|
+
// run at most once; if it 409s again the error surfaces normally.
|
|
3816
|
+
await this.healStaleShares({
|
|
3817
|
+
accountAddress,
|
|
3818
|
+
chainName,
|
|
3819
|
+
password,
|
|
3820
|
+
signedSessionId,
|
|
3821
|
+
walletOperation: WalletOperation.SIGN_MESSAGE,
|
|
3822
|
+
mfaToken,
|
|
3823
|
+
dynamicRequestId,
|
|
3824
|
+
traceContext,
|
|
3825
|
+
bitcoinConfig,
|
|
3826
|
+
trigger: 'server 409 stale-shares preflight',
|
|
3827
|
+
confirmedMismatch: true
|
|
3828
|
+
});
|
|
3829
|
+
this.logger.info('[StaleShareCheck] retrying sign transparently after 409 stale-shares preflight', _extends({
|
|
3830
|
+
accountAddress,
|
|
3831
|
+
chainName,
|
|
3832
|
+
dynamicRequestId
|
|
3833
|
+
}, this.getTraceContext(traceContext)));
|
|
3834
|
+
return await this.internalSign({
|
|
3835
|
+
accountAddress,
|
|
3836
|
+
message,
|
|
3837
|
+
chainName,
|
|
3838
|
+
password,
|
|
3839
|
+
isFormatted,
|
|
3840
|
+
signedSessionId,
|
|
3841
|
+
mfaToken,
|
|
3842
|
+
elevatedAccessToken,
|
|
3843
|
+
context,
|
|
3844
|
+
onError,
|
|
3845
|
+
traceContext,
|
|
3846
|
+
bitcoinConfig,
|
|
3847
|
+
staleShareRetry: true
|
|
3848
|
+
});
|
|
3849
|
+
}
|
|
3596
3850
|
logError({
|
|
3597
3851
|
message: 'Error in sign',
|
|
3598
3852
|
error: error,
|
|
@@ -3606,12 +3860,13 @@ class DynamicWalletClient {
|
|
|
3606
3860
|
throw error;
|
|
3607
3861
|
}
|
|
3608
3862
|
}
|
|
3609
|
-
async refreshWalletAccountShares({ accountAddress, chainName, password = undefined, signedSessionId, mfaToken, elevatedAccessToken, traceContext }) {
|
|
3863
|
+
async refreshWalletAccountShares({ accountAddress, chainName, password = undefined, signedSessionId, sessionPublicKey, mfaToken, elevatedAccessToken, traceContext }) {
|
|
3610
3864
|
return WalletQueueManager.queueHeavyOperation(accountAddress, WalletOperation.REFRESH, ()=>this.internalRefreshWalletAccountShares({
|
|
3611
3865
|
accountAddress,
|
|
3612
3866
|
chainName,
|
|
3613
3867
|
password,
|
|
3614
3868
|
signedSessionId,
|
|
3869
|
+
sessionPublicKey,
|
|
3615
3870
|
mfaToken,
|
|
3616
3871
|
elevatedAccessToken,
|
|
3617
3872
|
traceContext
|
|
@@ -3678,7 +3933,7 @@ class DynamicWalletClient {
|
|
|
3678
3933
|
addressType: addressType
|
|
3679
3934
|
} : undefined;
|
|
3680
3935
|
}
|
|
3681
|
-
async internalRefreshWalletAccountShares({ accountAddress, chainName, password = undefined, signedSessionId, mfaToken, elevatedAccessToken, traceContext }) {
|
|
3936
|
+
async internalRefreshWalletAccountShares({ accountAddress, chainName, password = undefined, signedSessionId, sessionPublicKey, mfaToken, elevatedAccessToken, traceContext, staleShareRetry = false }) {
|
|
3682
3937
|
const dynamicRequestId = v4();
|
|
3683
3938
|
try {
|
|
3684
3939
|
this.assertPasswordRequired(password);
|
|
@@ -3770,16 +4025,19 @@ class DynamicWalletClient {
|
|
|
3770
4025
|
try {
|
|
3771
4026
|
refreshResults = await Promise.all(clientKeyShares.map((clientKeyShare)=>mpcSigner.refresh(roomId, clientKeyShare)));
|
|
3772
4027
|
} catch (refreshError) {
|
|
3773
|
-
//
|
|
3774
|
-
await this.
|
|
3775
|
-
error: refreshError,
|
|
4028
|
+
// Heal stale local shares before rethrowing so a retry succeeds.
|
|
4029
|
+
await this.healStaleShares({
|
|
3776
4030
|
accountAddress,
|
|
4031
|
+
chainName,
|
|
3777
4032
|
password,
|
|
3778
4033
|
signedSessionId,
|
|
3779
4034
|
walletOperation: WalletOperation.REFRESH,
|
|
3780
4035
|
mfaToken,
|
|
4036
|
+
dynamicRequestId,
|
|
3781
4037
|
traceContext,
|
|
3782
|
-
|
|
4038
|
+
bitcoinConfig,
|
|
4039
|
+
trigger: 'refresh failure',
|
|
4040
|
+
confirmedMismatch: isPublicKeyMismatchError(refreshError)
|
|
3783
4041
|
});
|
|
3784
4042
|
throw refreshError;
|
|
3785
4043
|
}
|
|
@@ -3805,7 +4063,8 @@ class DynamicWalletClient {
|
|
|
3805
4063
|
accountAddress,
|
|
3806
4064
|
clientKeyShares: refreshResults,
|
|
3807
4065
|
password: password != null ? password : this.environmentId,
|
|
3808
|
-
signedSessionId
|
|
4066
|
+
signedSessionId,
|
|
4067
|
+
sessionPublicKey
|
|
3809
4068
|
})
|
|
3810
4069
|
});
|
|
3811
4070
|
await this.setClientKeySharesToStorage({
|
|
@@ -3831,6 +4090,42 @@ class DynamicWalletClient {
|
|
|
3831
4090
|
});
|
|
3832
4091
|
throw error;
|
|
3833
4092
|
}
|
|
4093
|
+
// Server-side stale-share preflight (409 STALE_CLIENT_SHARES): rejected
|
|
4094
|
+
// BEFORE any room/ceremony work, so nothing was rotated. Heal the local
|
|
4095
|
+
// shares from backup and transparently retry once — even a "fresh" heal
|
|
4096
|
+
// verdict matters, because its getWallets() refreshed the stale
|
|
4097
|
+
// walletMap.shareSetId the server rejected.
|
|
4098
|
+
if (!staleShareRetry && isStaleClientSharesError(error)) {
|
|
4099
|
+
await this.healStaleShares({
|
|
4100
|
+
accountAddress,
|
|
4101
|
+
chainName,
|
|
4102
|
+
password,
|
|
4103
|
+
signedSessionId,
|
|
4104
|
+
walletOperation: WalletOperation.REFRESH,
|
|
4105
|
+
mfaToken,
|
|
4106
|
+
dynamicRequestId,
|
|
4107
|
+
traceContext,
|
|
4108
|
+
bitcoinConfig: this.getBitcoinConfigForChain(chainName, accountAddress),
|
|
4109
|
+
trigger: 'server 409 stale-shares preflight (refresh)',
|
|
4110
|
+
confirmedMismatch: true
|
|
4111
|
+
});
|
|
4112
|
+
this.logger.info('[StaleShareCheck] retrying refresh transparently after 409 stale-shares preflight', _extends({
|
|
4113
|
+
accountAddress,
|
|
4114
|
+
chainName,
|
|
4115
|
+
dynamicRequestId
|
|
4116
|
+
}, this.getTraceContext(traceContext)));
|
|
4117
|
+
return await this.internalRefreshWalletAccountShares({
|
|
4118
|
+
accountAddress,
|
|
4119
|
+
chainName,
|
|
4120
|
+
password,
|
|
4121
|
+
signedSessionId,
|
|
4122
|
+
sessionPublicKey,
|
|
4123
|
+
mfaToken,
|
|
4124
|
+
elevatedAccessToken,
|
|
4125
|
+
traceContext,
|
|
4126
|
+
staleShareRetry: true
|
|
4127
|
+
});
|
|
4128
|
+
}
|
|
3834
4129
|
logError({
|
|
3835
4130
|
message: 'Error in refreshWalletAccountShares',
|
|
3836
4131
|
error: error,
|
|
@@ -3922,7 +4217,7 @@ class DynamicWalletClient {
|
|
|
3922
4217
|
existingClientKeyShares
|
|
3923
4218
|
};
|
|
3924
4219
|
}
|
|
3925
|
-
async reshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined, signedSessionId, cloudProviders = [], delegateToProjectEnvironment = false, mfaToken, elevatedAccessToken, revokeDelegation = false, googleDriveAccessToken, googleDriveTokenSource }) {
|
|
4220
|
+
async reshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined, signedSessionId, sessionPublicKey, cloudProviders = [], delegateToProjectEnvironment = false, mfaToken, elevatedAccessToken, revokeDelegation = false, googleDriveAccessToken, googleDriveTokenSource }) {
|
|
3926
4221
|
return WalletQueueManager.queueHeavyOperation(accountAddress, WalletOperation.RESHARE, ()=>this.internalReshare({
|
|
3927
4222
|
chainName,
|
|
3928
4223
|
accountAddress,
|
|
@@ -3930,6 +4225,7 @@ class DynamicWalletClient {
|
|
|
3930
4225
|
newThresholdSignatureScheme,
|
|
3931
4226
|
password,
|
|
3932
4227
|
signedSessionId,
|
|
4228
|
+
sessionPublicKey,
|
|
3933
4229
|
cloudProviders,
|
|
3934
4230
|
delegateToProjectEnvironment,
|
|
3935
4231
|
mfaToken,
|
|
@@ -4414,7 +4710,7 @@ class DynamicWalletClient {
|
|
|
4414
4710
|
clientKeySharesBackupInfo: updatedBackupInfo
|
|
4415
4711
|
} : {}));
|
|
4416
4712
|
}
|
|
4417
|
-
async internalReshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined, signedSessionId, cloudProviders = [], delegateToProjectEnvironment = false, mfaToken, elevatedAccessToken, revokeDelegation = false, googleDriveAccessToken, googleDriveTokenSource }) {
|
|
4713
|
+
async internalReshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined, signedSessionId, sessionPublicKey, cloudProviders = [], delegateToProjectEnvironment = false, mfaToken, elevatedAccessToken, revokeDelegation = false, googleDriveAccessToken, googleDriveTokenSource, staleShareRetry = false }) {
|
|
4418
4714
|
const dynamicRequestId = v4();
|
|
4419
4715
|
// Password validation - wrapped in try-catch for consistent error handling
|
|
4420
4716
|
// This path should NOT wipe key shares on failure (shares remain valid)
|
|
@@ -4626,15 +4922,18 @@ class DynamicWalletClient {
|
|
|
4626
4922
|
existingReshareResults = existingSettled.map((r)=>r.value);
|
|
4627
4923
|
newReshareResults = newSettled.map((r)=>r.value);
|
|
4628
4924
|
} catch (reshareError) {
|
|
4629
|
-
//
|
|
4630
|
-
await this.
|
|
4631
|
-
error: reshareError,
|
|
4925
|
+
// Heal stale local shares before rethrowing so a retry succeeds.
|
|
4926
|
+
await this.healStaleShares({
|
|
4632
4927
|
accountAddress,
|
|
4928
|
+
chainName,
|
|
4633
4929
|
password,
|
|
4634
4930
|
signedSessionId,
|
|
4635
4931
|
walletOperation: WalletOperation.RESHARE,
|
|
4636
4932
|
mfaToken,
|
|
4637
|
-
dynamicRequestId
|
|
4933
|
+
dynamicRequestId,
|
|
4934
|
+
bitcoinConfig,
|
|
4935
|
+
trigger: 'reshare failure',
|
|
4936
|
+
confirmedMismatch: isPublicKeyMismatchError(reshareError)
|
|
4638
4937
|
});
|
|
4639
4938
|
throw reshareError;
|
|
4640
4939
|
}
|
|
@@ -4669,6 +4968,7 @@ class DynamicWalletClient {
|
|
|
4669
4968
|
accountAddress,
|
|
4670
4969
|
password,
|
|
4671
4970
|
signedSessionId,
|
|
4971
|
+
sessionPublicKey,
|
|
4672
4972
|
distribution,
|
|
4673
4973
|
googleDriveAccessToken,
|
|
4674
4974
|
googleDriveTokenSource
|
|
@@ -4706,6 +5006,47 @@ class DynamicWalletClient {
|
|
|
4706
5006
|
}
|
|
4707
5007
|
});
|
|
4708
5008
|
} catch (error) {
|
|
5009
|
+
// Server-side stale-share preflight (409 STALE_CLIENT_SHARES): rejected
|
|
5010
|
+
// BEFORE any room/ceremony work, so local shares are intact (nothing to
|
|
5011
|
+
// wipe). Heal from backup and transparently retry once — even a "fresh"
|
|
5012
|
+
// heal verdict matters, because its getWallets() refreshed the stale
|
|
5013
|
+
// walletMap.shareSetId the server rejected.
|
|
5014
|
+
if (!staleShareRetry && isStaleClientSharesError(error)) {
|
|
5015
|
+
await this.healStaleShares({
|
|
5016
|
+
accountAddress,
|
|
5017
|
+
chainName,
|
|
5018
|
+
password,
|
|
5019
|
+
signedSessionId,
|
|
5020
|
+
walletOperation: WalletOperation.RESHARE,
|
|
5021
|
+
mfaToken,
|
|
5022
|
+
dynamicRequestId,
|
|
5023
|
+
bitcoinConfig: this.getBitcoinConfigForChain(chainName, accountAddress),
|
|
5024
|
+
trigger: 'server 409 stale-shares preflight (reshare)',
|
|
5025
|
+
confirmedMismatch: true
|
|
5026
|
+
});
|
|
5027
|
+
this.logger.info('[StaleShareCheck] retrying reshare transparently after 409 stale-shares preflight', {
|
|
5028
|
+
accountAddress,
|
|
5029
|
+
chainName,
|
|
5030
|
+
dynamicRequestId
|
|
5031
|
+
});
|
|
5032
|
+
return await this.internalReshare({
|
|
5033
|
+
chainName,
|
|
5034
|
+
accountAddress,
|
|
5035
|
+
oldThresholdSignatureScheme,
|
|
5036
|
+
newThresholdSignatureScheme,
|
|
5037
|
+
password,
|
|
5038
|
+
signedSessionId,
|
|
5039
|
+
sessionPublicKey,
|
|
5040
|
+
cloudProviders,
|
|
5041
|
+
delegateToProjectEnvironment,
|
|
5042
|
+
mfaToken,
|
|
5043
|
+
elevatedAccessToken,
|
|
5044
|
+
revokeDelegation,
|
|
5045
|
+
googleDriveAccessToken,
|
|
5046
|
+
googleDriveTokenSource,
|
|
5047
|
+
staleShareRetry: true
|
|
5048
|
+
});
|
|
5049
|
+
}
|
|
4709
5050
|
logError({
|
|
4710
5051
|
message: 'Error in reshare, resetting wallet to previous state',
|
|
4711
5052
|
error: error,
|
|
@@ -4720,9 +5061,12 @@ class DynamicWalletClient {
|
|
|
4720
5061
|
dynamicRequestId
|
|
4721
5062
|
}
|
|
4722
5063
|
});
|
|
4723
|
-
// Skip the wipe on mismatch —
|
|
4724
|
-
//
|
|
4725
|
-
|
|
5064
|
+
// Skip the wipe on mismatch — healStaleShares just restored these
|
|
5065
|
+
// shares from backup, undoing that would break the caller's retry.
|
|
5066
|
+
// Same for a 409 stale-shares preflight (only reachable here on the
|
|
5067
|
+
// retried attempt): the request was rejected before any ceremony, so
|
|
5068
|
+
// local shares are consistent — wiping would strand the user.
|
|
5069
|
+
if (!isPublicKeyMismatchError(error) && !isStaleClientSharesError(error)) {
|
|
4726
5070
|
this.updateWalletMap(accountAddress, {
|
|
4727
5071
|
thresholdSignatureScheme: oldThresholdSignatureScheme
|
|
4728
5072
|
});
|
|
@@ -4734,7 +5078,7 @@ class DynamicWalletClient {
|
|
|
4734
5078
|
throw error;
|
|
4735
5079
|
}
|
|
4736
5080
|
}
|
|
4737
|
-
async performDelegationOperation({ accountAddress, password, signedSessionId, mfaToken, newThresholdSignatureScheme, revokeDelegation = false, useShareSetReshare = false, operationName }) {
|
|
5081
|
+
async performDelegationOperation({ accountAddress, password, signedSessionId, sessionPublicKey, mfaToken, newThresholdSignatureScheme, revokeDelegation = false, useShareSetReshare = false, operationName }) {
|
|
4738
5082
|
try {
|
|
4739
5083
|
const delegateToProjectEnvironment = this.featureFlags && this.featureFlags[FEATURE_FLAGS.ENABLE_DELEGATED_KEY_SHARES_FLAG] === true;
|
|
4740
5084
|
if (!delegateToProjectEnvironment) {
|
|
@@ -4763,6 +5107,7 @@ class DynamicWalletClient {
|
|
|
4763
5107
|
newThresholdSignatureScheme,
|
|
4764
5108
|
password,
|
|
4765
5109
|
signedSessionId,
|
|
5110
|
+
sessionPublicKey,
|
|
4766
5111
|
cloudProviders: activeProviders,
|
|
4767
5112
|
// Server soft-deletes the wallet API key on revoke; a delegated
|
|
4768
5113
|
// distribution would 400 on the follow-up delivery POST.
|
|
@@ -4781,7 +5126,7 @@ class DynamicWalletClient {
|
|
|
4781
5126
|
throw error;
|
|
4782
5127
|
}
|
|
4783
5128
|
}
|
|
4784
|
-
async delegateKeyShares({ accountAddress, password = undefined, signedSessionId, mfaToken }) {
|
|
5129
|
+
async delegateKeyShares({ accountAddress, password = undefined, signedSessionId, sessionPublicKey, mfaToken }) {
|
|
4785
5130
|
var _this_featureFlags;
|
|
4786
5131
|
const useShareSetReshare = ((_this_featureFlags = this.featureFlags) == null ? void 0 : _this_featureFlags[FEATURE_FLAGS.ENABLE_SHARE_SET_RESHARE]) === true;
|
|
4787
5132
|
if (useShareSetReshare) {
|
|
@@ -4806,6 +5151,7 @@ class DynamicWalletClient {
|
|
|
4806
5151
|
accountAddress,
|
|
4807
5152
|
password,
|
|
4808
5153
|
signedSessionId,
|
|
5154
|
+
sessionPublicKey,
|
|
4809
5155
|
mfaToken,
|
|
4810
5156
|
// FF on: reshare to 2/2 — the new delegated share set is always 2/2
|
|
4811
5157
|
// (server + delegated webhook). getReshareConfig now has a 2/2 → 2/2
|
|
@@ -4820,7 +5166,7 @@ class DynamicWalletClient {
|
|
|
4820
5166
|
const delegatedKeyShares = backupInfo.backups[BackupLocation.DELEGATED] || [];
|
|
4821
5167
|
return delegatedKeyShares;
|
|
4822
5168
|
}
|
|
4823
|
-
async revokeDelegation({ accountAddress, password = undefined, signedSessionId, mfaToken }) {
|
|
5169
|
+
async revokeDelegation({ accountAddress, password = undefined, signedSessionId, sessionPublicKey, mfaToken }) {
|
|
4824
5170
|
var _this_featureFlags;
|
|
4825
5171
|
const useShareSetReshare = ((_this_featureFlags = this.featureFlags) == null ? void 0 : _this_featureFlags[FEATURE_FLAGS.ENABLE_SHARE_SET_RESHARE]) === true;
|
|
4826
5172
|
if (useShareSetReshare) {
|
|
@@ -4866,6 +5212,7 @@ class DynamicWalletClient {
|
|
|
4866
5212
|
accountAddress,
|
|
4867
5213
|
password,
|
|
4868
5214
|
signedSessionId,
|
|
5215
|
+
sessionPublicKey,
|
|
4869
5216
|
mfaToken,
|
|
4870
5217
|
newThresholdSignatureScheme: ThresholdSignatureScheme.TWO_OF_TWO,
|
|
4871
5218
|
revokeDelegation: true,
|
|
@@ -4882,6 +5229,22 @@ class DynamicWalletClient {
|
|
|
4882
5229
|
return new BIP340KeygenResult(extractedPubkey, secretShare);
|
|
4883
5230
|
}
|
|
4884
5231
|
async exportKey({ accountAddress, chainName, bitcoinConfig, password = undefined, signedSessionId, mfaToken, elevatedAccessToken, traceContext }) {
|
|
5232
|
+
// Public entry point delegates to the private implementation. Keeping the
|
|
5233
|
+
// `staleShareRetry` flag off the public signature mirrors sign/refresh/
|
|
5234
|
+
// reshare and ensures the transparent 409 retry can't be suppressed by
|
|
5235
|
+
// external callers or intercepted by subclass overrides.
|
|
5236
|
+
return this.internalExportKey({
|
|
5237
|
+
accountAddress,
|
|
5238
|
+
chainName,
|
|
5239
|
+
bitcoinConfig,
|
|
5240
|
+
password,
|
|
5241
|
+
signedSessionId,
|
|
5242
|
+
mfaToken,
|
|
5243
|
+
elevatedAccessToken,
|
|
5244
|
+
traceContext
|
|
5245
|
+
});
|
|
5246
|
+
}
|
|
5247
|
+
async internalExportKey({ accountAddress, chainName, bitcoinConfig, password = undefined, signedSessionId, mfaToken, elevatedAccessToken, traceContext, staleShareRetry = false }) {
|
|
4885
5248
|
const dynamicRequestId = v4();
|
|
4886
5249
|
try {
|
|
4887
5250
|
const wallet = await this.getWallet({
|
|
@@ -4926,10 +5289,51 @@ class DynamicWalletClient {
|
|
|
4926
5289
|
roomId: data.roomId
|
|
4927
5290
|
}, this.getTraceContext(traceContext)));
|
|
4928
5291
|
const derivedPrivateKey = await this.derivePrivateKeyFromExport(mpcSigner, keyExportRaw, wallet);
|
|
5292
|
+
// derivePrivateKeyFromExport returns undefined for unsupported signer
|
|
5293
|
+
// types; fail loudly rather than returning undefined as a string.
|
|
5294
|
+
if (derivedPrivateKey === undefined) {
|
|
5295
|
+
throw new Error(`Private key export is not supported for chain ${chainName}`);
|
|
5296
|
+
}
|
|
4929
5297
|
return {
|
|
4930
5298
|
derivedPrivateKey
|
|
4931
5299
|
};
|
|
4932
5300
|
} catch (error) {
|
|
5301
|
+
// Server-side stale-share preflight (409 STALE_CLIENT_SHARES): rejected
|
|
5302
|
+
// BEFORE any room/ceremony work. Heal the local shares from backup and
|
|
5303
|
+
// transparently retry once — even a "fresh" heal verdict matters,
|
|
5304
|
+
// because its getWallets() refreshed the stale walletMap.shareSetId the
|
|
5305
|
+
// server rejected.
|
|
5306
|
+
if (!staleShareRetry && isStaleClientSharesError(error)) {
|
|
5307
|
+
await this.healStaleShares({
|
|
5308
|
+
accountAddress,
|
|
5309
|
+
chainName,
|
|
5310
|
+
password,
|
|
5311
|
+
signedSessionId,
|
|
5312
|
+
walletOperation: WalletOperation.EXPORT_PRIVATE_KEY,
|
|
5313
|
+
mfaToken,
|
|
5314
|
+
dynamicRequestId,
|
|
5315
|
+
traceContext,
|
|
5316
|
+
bitcoinConfig,
|
|
5317
|
+
trigger: 'server 409 stale-shares preflight (export)',
|
|
5318
|
+
confirmedMismatch: true
|
|
5319
|
+
});
|
|
5320
|
+
this.logger.info('[StaleShareCheck] retrying export transparently after 409 stale-shares preflight', _extends({
|
|
5321
|
+
accountAddress,
|
|
5322
|
+
chainName,
|
|
5323
|
+
dynamicRequestId
|
|
5324
|
+
}, this.getTraceContext(traceContext)));
|
|
5325
|
+
return await this.internalExportKey({
|
|
5326
|
+
accountAddress,
|
|
5327
|
+
chainName,
|
|
5328
|
+
bitcoinConfig,
|
|
5329
|
+
password,
|
|
5330
|
+
signedSessionId,
|
|
5331
|
+
mfaToken,
|
|
5332
|
+
elevatedAccessToken,
|
|
5333
|
+
traceContext,
|
|
5334
|
+
staleShareRetry: true
|
|
5335
|
+
});
|
|
5336
|
+
}
|
|
4933
5337
|
logError({
|
|
4934
5338
|
message: 'Error in exportKey',
|
|
4935
5339
|
error: error,
|
|
@@ -4939,6 +5343,22 @@ class DynamicWalletClient {
|
|
|
4939
5343
|
dynamicRequestId
|
|
4940
5344
|
}, this.getTraceContext(traceContext))
|
|
4941
5345
|
});
|
|
5346
|
+
// Heal stale local shares before rethrowing so a retry succeeds —
|
|
5347
|
+
// export reconstructs from the local share, so a generation rotated on
|
|
5348
|
+
// another device fails here too.
|
|
5349
|
+
await this.healStaleShares({
|
|
5350
|
+
accountAddress,
|
|
5351
|
+
chainName,
|
|
5352
|
+
password,
|
|
5353
|
+
signedSessionId,
|
|
5354
|
+
walletOperation: WalletOperation.EXPORT_PRIVATE_KEY,
|
|
5355
|
+
mfaToken,
|
|
5356
|
+
dynamicRequestId,
|
|
5357
|
+
traceContext,
|
|
5358
|
+
bitcoinConfig,
|
|
5359
|
+
trigger: 'export failure',
|
|
5360
|
+
confirmedMismatch: isPublicKeyMismatchError(error)
|
|
5361
|
+
});
|
|
4942
5362
|
throw error;
|
|
4943
5363
|
}
|
|
4944
5364
|
}
|
|
@@ -5308,6 +5728,14 @@ class DynamicWalletClient {
|
|
|
5308
5728
|
* Auto-recovery logic has been removed in favor of the queue pattern.
|
|
5309
5729
|
* Callers should handle recovery explicitly if needed.
|
|
5310
5730
|
*/ async ensureClientShare(accountAddress) {
|
|
5731
|
+
var _WalletQueueManager_getPendingRecoveryPromise;
|
|
5732
|
+
// A recovery may be in flight (e.g. a stale-share heal scheduled when a
|
|
5733
|
+
// previous sign hit a public-key mismatch). Wait for it so this operation
|
|
5734
|
+
// reads the recovered shares instead of the superseded ones. Recovery
|
|
5735
|
+
// failures are logged where they happen; this operation should proceed
|
|
5736
|
+
// with whatever shares exist rather than fail on the recovery.
|
|
5737
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
5738
|
+
await ((_WalletQueueManager_getPendingRecoveryPromise = WalletQueueManager.getPendingRecoveryPromise(accountAddress)) == null ? void 0 : _WalletQueueManager_getPendingRecoveryPromise.catch(()=>{}));
|
|
5311
5739
|
const shares = await this.getClientKeySharesFromStorage({
|
|
5312
5740
|
accountAddress
|
|
5313
5741
|
});
|
|
@@ -5316,13 +5744,14 @@ class DynamicWalletClient {
|
|
|
5316
5744
|
}
|
|
5317
5745
|
return shares;
|
|
5318
5746
|
}
|
|
5319
|
-
async backupToDynamicBackend({ walletId, clientShares, password, signedSessionId, dynamicRequestId, chainName, bitcoinConfig, isPasswordEncrypted, accountAddress, preEncryptedShares, passwordUpdateBatchId, encryptionVersion = DEFAULT_ENCRYPTION_VERSION }) {
|
|
5747
|
+
async backupToDynamicBackend({ walletId, clientShares, password, signedSessionId, sessionPublicKey, dynamicRequestId, chainName, bitcoinConfig, isPasswordEncrypted, accountAddress, preEncryptedShares, passwordUpdateBatchId, encryptionVersion = DEFAULT_ENCRYPTION_VERSION }) {
|
|
5320
5748
|
var _this_getWalletFromMap;
|
|
5321
5749
|
const encryptedDynamicShares = preEncryptedShares != null ? preEncryptedShares : await Promise.all(clientShares.map((keyShare)=>this.encryptKeyShare({
|
|
5322
5750
|
keyShare,
|
|
5323
5751
|
password,
|
|
5324
5752
|
version: encryptionVersion
|
|
5325
5753
|
})));
|
|
5754
|
+
const resolvedSessionPublicKey = sessionPublicKey != null ? sessionPublicKey : await (this.getSessionPublicKeyCallback == null ? void 0 : this.getSessionPublicKeyCallback.call(this));
|
|
5326
5755
|
const data = await this.apiClient.storeEncryptedBackupByWallet({
|
|
5327
5756
|
walletId,
|
|
5328
5757
|
shareSetId: (_this_getWalletFromMap = this.getWalletFromMap(accountAddress)) == null ? void 0 : _this_getWalletFromMap.shareSetId,
|
|
@@ -5330,6 +5759,7 @@ class DynamicWalletClient {
|
|
|
5330
5759
|
passwordEncrypted: isPasswordEncrypted,
|
|
5331
5760
|
encryptionVersion,
|
|
5332
5761
|
signedSessionId,
|
|
5762
|
+
sessionPublicKey: resolvedSessionPublicKey,
|
|
5333
5763
|
requiresSignedSessionId: this.requiresSignedSessionId(),
|
|
5334
5764
|
dynamicRequestId
|
|
5335
5765
|
});
|
|
@@ -5478,7 +5908,7 @@ class DynamicWalletClient {
|
|
|
5478
5908
|
}
|
|
5479
5909
|
return location;
|
|
5480
5910
|
}
|
|
5481
|
-
async backupSharesWithDistribution({ accountAddress, password, signedSessionId, distribution, preserveDelegatedLocation = false, passwordUpdateBatchId, googleDriveAccessToken, googleDriveTokenSource, businessAccountId, encryptionVersion }) {
|
|
5911
|
+
async backupSharesWithDistribution({ accountAddress, password, signedSessionId, sessionPublicKey, distribution, preserveDelegatedLocation = false, passwordUpdateBatchId, googleDriveAccessToken, googleDriveTokenSource, businessAccountId, encryptionVersion }) {
|
|
5482
5912
|
const dynamicRequestId = v4();
|
|
5483
5913
|
// Get wallet data early for logging context (also used in catch block)
|
|
5484
5914
|
const walletData = this.getWalletFromMap(accountAddress);
|
|
@@ -5557,6 +5987,7 @@ class DynamicWalletClient {
|
|
|
5557
5987
|
clientShares: distribution.clientShares,
|
|
5558
5988
|
password,
|
|
5559
5989
|
signedSessionId: resolvedSignedSessionId,
|
|
5990
|
+
sessionPublicKey,
|
|
5560
5991
|
dynamicRequestId,
|
|
5561
5992
|
chainName: walletData.chainName,
|
|
5562
5993
|
bitcoinConfig,
|
|
@@ -5753,29 +6184,23 @@ class DynamicWalletClient {
|
|
|
5753
6184
|
// re-enter the outer operation with correct context. Skipped on internal post-rotation
|
|
5754
6185
|
// flows that pass clientKeyShares explicitly (the refresh path).
|
|
5755
6186
|
async ensureLocalSharesAreFresh({ accountAddress, walletData, localShares, password, signedSessionId }) {
|
|
5756
|
-
var _freshBackupInfo_backups;
|
|
5757
6187
|
// Always round-trip to the server — walletMap is populated at wallet-load and is
|
|
5758
6188
|
// not invalidated when another tab rotates shares, so trusting it would
|
|
5759
6189
|
// false-positive on cross-tab refresh.
|
|
5760
6190
|
const { chainName } = walletData;
|
|
5761
|
-
const
|
|
5762
|
-
|
|
5763
|
-
baseRelayUrl: this.baseMPCRelayApiUrl,
|
|
5764
|
-
bitcoinConfig: this.getBitcoinConfigForChain(chainName, accountAddress)
|
|
6191
|
+
const freshBackupInfo = await this.fetchFreshWalletBackupInfo({
|
|
6192
|
+
accountAddress
|
|
5765
6193
|
});
|
|
5766
|
-
const
|
|
5767
|
-
this.fetchFreshWalletBackupInfo({
|
|
5768
|
-
accountAddress
|
|
5769
|
-
}),
|
|
5770
|
-
Promise.all(localShares.map((share)=>this.getKeygenIdForShare(mpcSigner, share)))
|
|
5771
|
-
]);
|
|
5772
|
-
var _freshBackupInfo_backups_BackupLocation_DYNAMIC;
|
|
5773
|
-
const recordedKeygenIds = ((_freshBackupInfo_backups_BackupLocation_DYNAMIC = freshBackupInfo == null ? void 0 : (_freshBackupInfo_backups = freshBackupInfo.backups) == null ? void 0 : _freshBackupInfo_backups[BackupLocation.DYNAMIC]) != null ? _freshBackupInfo_backups_BackupLocation_DYNAMIC : []).map((entry)=>entry.keygenId).filter((id)=>typeof id === 'string' && id.length > 0);
|
|
6194
|
+
const recordedKeygenIds = recordedDynamicKeygenIds(freshBackupInfo);
|
|
5774
6195
|
// First-time backup or legacy data (server didn't populate keygenId yet) — nothing to compare.
|
|
5775
6196
|
if (recordedKeygenIds.length === 0) return;
|
|
5776
|
-
const
|
|
5777
|
-
|
|
5778
|
-
|
|
6197
|
+
const { fresh, localKeygenIds } = await this.compareShareGenerations({
|
|
6198
|
+
accountAddress,
|
|
6199
|
+
chainName,
|
|
6200
|
+
localShares,
|
|
6201
|
+
recordedKeygenIds
|
|
6202
|
+
});
|
|
6203
|
+
if (fresh) return;
|
|
5779
6204
|
this.logger.warn('[storeEncryptedBackupByWallet] Stale local shares detected; refreshing local storage', {
|
|
5780
6205
|
accountAddress,
|
|
5781
6206
|
walletId: walletData.walletId,
|
|
@@ -5802,7 +6227,7 @@ class DynamicWalletClient {
|
|
|
5802
6227
|
const recoveryPassword = freshBackupInfo.passwordEncrypted ? password : undefined;
|
|
5803
6228
|
try {
|
|
5804
6229
|
// walletMap was already refreshed by fetchFreshWalletBackupInfo above, so we skip the
|
|
5805
|
-
// getWallets() refresh that
|
|
6230
|
+
// getWallets() refresh that verifyAndRecoverStaleShare performs for its own callers.
|
|
5806
6231
|
await this.internalRecoverEncryptedBackupByWallet({
|
|
5807
6232
|
accountAddress,
|
|
5808
6233
|
password: recoveryPassword,
|
|
@@ -5860,7 +6285,7 @@ class DynamicWalletClient {
|
|
|
5860
6285
|
* @param params.signedSessionId - Optional signed session ID for authentication
|
|
5861
6286
|
* @param params.backupToGoogleDrive - Whether to backup to Google Drive (defaults to false)
|
|
5862
6287
|
* @returns Promise with backup metadata including share locations and IDs
|
|
5863
|
-
*/ async storeEncryptedBackupByWallet({ accountAddress, clientKeyShares = undefined, password = undefined, signedSessionId, cloudProviders = [], delegatedKeyshare = undefined, passwordUpdateBatchId, googleDriveAccessToken, googleDriveTokenSource, businessAccountId, encryptionVersion }) {
|
|
6288
|
+
*/ async storeEncryptedBackupByWallet({ accountAddress, clientKeyShares = undefined, password = undefined, signedSessionId, sessionPublicKey, cloudProviders = [], delegatedKeyshare = undefined, passwordUpdateBatchId, googleDriveAccessToken, googleDriveTokenSource, businessAccountId, encryptionVersion }) {
|
|
5864
6289
|
var _backupData_locationsWithKeyShares, _backupData_locationsWithKeyShares1;
|
|
5865
6290
|
const hasProvidedShares = clientKeyShares !== undefined;
|
|
5866
6291
|
const keySharesToBackup = clientKeyShares != null ? clientKeyShares : await this.getClientKeySharesFromStorage({
|
|
@@ -5930,6 +6355,7 @@ class DynamicWalletClient {
|
|
|
5930
6355
|
accountAddress,
|
|
5931
6356
|
password,
|
|
5932
6357
|
signedSessionId,
|
|
6358
|
+
sessionPublicKey,
|
|
5933
6359
|
distribution,
|
|
5934
6360
|
preserveDelegatedLocation,
|
|
5935
6361
|
passwordUpdateBatchId,
|
|
@@ -6002,7 +6428,7 @@ class DynamicWalletClient {
|
|
|
6002
6428
|
errorContext
|
|
6003
6429
|
} : {}));
|
|
6004
6430
|
}
|
|
6005
|
-
async updatePassword({ accountAddress, existingPassword, newPassword, signedSessionId, passwordUpdateBatchId }) {
|
|
6431
|
+
async updatePassword({ accountAddress, existingPassword, newPassword, signedSessionId, sessionPublicKey, passwordUpdateBatchId }) {
|
|
6006
6432
|
const dynamicRequestId = v4();
|
|
6007
6433
|
try {
|
|
6008
6434
|
const wallet = this.getWalletFromMap(accountAddress);
|
|
@@ -6022,6 +6448,7 @@ class DynamicWalletClient {
|
|
|
6022
6448
|
accountAddress,
|
|
6023
6449
|
password: newPassword,
|
|
6024
6450
|
signedSessionId,
|
|
6451
|
+
sessionPublicKey,
|
|
6025
6452
|
passwordUpdateBatchId
|
|
6026
6453
|
});
|
|
6027
6454
|
this.logPasswordOperationSuccess('updatePassword', {
|
|
@@ -6051,7 +6478,7 @@ class DynamicWalletClient {
|
|
|
6051
6478
|
throw error;
|
|
6052
6479
|
}
|
|
6053
6480
|
}
|
|
6054
|
-
async setPassword({ accountAddress, newPassword, signedSessionId, passwordUpdateBatchId }) {
|
|
6481
|
+
async setPassword({ accountAddress, newPassword, signedSessionId, sessionPublicKey, passwordUpdateBatchId }) {
|
|
6055
6482
|
const dynamicRequestId = v4();
|
|
6056
6483
|
try {
|
|
6057
6484
|
const wallet = this.getWalletFromMap(accountAddress);
|
|
@@ -6062,6 +6489,7 @@ class DynamicWalletClient {
|
|
|
6062
6489
|
accountAddress,
|
|
6063
6490
|
password: newPassword,
|
|
6064
6491
|
signedSessionId,
|
|
6492
|
+
sessionPublicKey,
|
|
6065
6493
|
passwordUpdateBatchId
|
|
6066
6494
|
});
|
|
6067
6495
|
this.logPasswordOperationSuccess('setPassword', {
|
|
@@ -6482,7 +6910,7 @@ class DynamicWalletClient {
|
|
|
6482
6910
|
* Internal helper method that handles the complete flow for ensuring wallet key shares are backed up to a cloud provider.
|
|
6483
6911
|
* - For 2-of-2 wallets: Automatically reshares to 2-of-3 threshold, then distributes shares (1 to backend, 1 to cloud)
|
|
6484
6912
|
* - For 2-of-3 wallets: Call storeEncryptedBackupByWallet to backup for backend and cloud
|
|
6485
|
-
*/ async backupKeySharesToCloudProvider({ accountAddress, password, signedSessionId, backupLocation, googleDriveAccessToken, googleDriveTokenSource }) {
|
|
6913
|
+
*/ async backupKeySharesToCloudProvider({ accountAddress, password, signedSessionId, sessionPublicKey, backupLocation, googleDriveAccessToken, googleDriveTokenSource }) {
|
|
6486
6914
|
try {
|
|
6487
6915
|
await this.getWallet({
|
|
6488
6916
|
accountAddress,
|
|
@@ -6501,6 +6929,7 @@ class DynamicWalletClient {
|
|
|
6501
6929
|
newThresholdSignatureScheme: ThresholdSignatureScheme.TWO_OF_THREE,
|
|
6502
6930
|
password,
|
|
6503
6931
|
signedSessionId,
|
|
6932
|
+
sessionPublicKey,
|
|
6504
6933
|
cloudProviders: [
|
|
6505
6934
|
backupLocation
|
|
6506
6935
|
],
|
|
@@ -6512,6 +6941,7 @@ class DynamicWalletClient {
|
|
|
6512
6941
|
accountAddress,
|
|
6513
6942
|
password,
|
|
6514
6943
|
signedSessionId,
|
|
6944
|
+
sessionPublicKey,
|
|
6515
6945
|
cloudProviders: [
|
|
6516
6946
|
backupLocation
|
|
6517
6947
|
],
|
|
@@ -6542,7 +6972,7 @@ class DynamicWalletClient {
|
|
|
6542
6972
|
* @param params.signedSessionId - Optional signed session ID for authentication
|
|
6543
6973
|
* @param params.googleDriveAccessToken - Optional Google OAuth access token. When provided, the SDK uses it directly
|
|
6544
6974
|
* instead of looking up the user's linked Google account and exchanging it for a token via the Dynamic backend.
|
|
6545
|
-
*/ async backupKeySharesToGoogleDrive({ accountAddress, password, signedSessionId, googleDriveAccessToken }) {
|
|
6975
|
+
*/ async backupKeySharesToGoogleDrive({ accountAddress, password, signedSessionId, sessionPublicKey, googleDriveAccessToken }) {
|
|
6546
6976
|
// Track whether the Drive token was supplied by the caller (external) or
|
|
6547
6977
|
// will be resolved from the user's stored OAuth account (stored_oauth), so
|
|
6548
6978
|
// backups using a token we don't control are auditable. Enum only — the
|
|
@@ -6557,11 +6987,18 @@ class DynamicWalletClient {
|
|
|
6557
6987
|
googleDriveAccessToken
|
|
6558
6988
|
})
|
|
6559
6989
|
]);
|
|
6560
|
-
|
|
6990
|
+
// getBackupRelayToken can resolve to undefined; only sync when we got a
|
|
6991
|
+
// token (matches the other callers). Now that syncBackupServiceAuthToken
|
|
6992
|
+
// runs in HEADER mode too, an unconditional call would throw on an empty
|
|
6993
|
+
// token instead of the previous silent no-op.
|
|
6994
|
+
if (updatedBackupServiceAuthToken) {
|
|
6995
|
+
this.apiClient.syncBackupServiceAuthToken(updatedBackupServiceAuthToken);
|
|
6996
|
+
}
|
|
6561
6997
|
await this.backupKeySharesToCloudProvider({
|
|
6562
6998
|
accountAddress,
|
|
6563
6999
|
password,
|
|
6564
7000
|
signedSessionId,
|
|
7001
|
+
sessionPublicKey,
|
|
6565
7002
|
backupLocation: BackupLocation.GOOGLE_DRIVE,
|
|
6566
7003
|
// Forward the resolved token so the downstream upload path doesn't
|
|
6567
7004
|
// re-fetch it via fetchGoogleDriveAccessToken.
|
|
@@ -6633,11 +7070,12 @@ class DynamicWalletClient {
|
|
|
6633
7070
|
* @param params.accountAddress - The wallet account address to backup
|
|
6634
7071
|
* @param params.password - Optional password for encryption (uses environment ID if not provided)
|
|
6635
7072
|
* @param params.signedSessionId - Optional signed session ID for authentication
|
|
6636
|
-
*/ async backupKeySharesToICloud({ accountAddress, password, signedSessionId }) {
|
|
7073
|
+
*/ async backupKeySharesToICloud({ accountAddress, password, signedSessionId, sessionPublicKey }) {
|
|
6637
7074
|
return this.backupKeySharesToCloudProvider({
|
|
6638
7075
|
accountAddress,
|
|
6639
7076
|
password,
|
|
6640
7077
|
signedSessionId,
|
|
7078
|
+
sessionPublicKey,
|
|
6641
7079
|
backupLocation: BackupLocation.ICLOUD
|
|
6642
7080
|
});
|
|
6643
7081
|
}
|
|
@@ -7850,6 +8288,10 @@ class DynamicWalletClient {
|
|
|
7850
8288
|
if (internalOptions == null ? void 0 : internalOptions.getSignedSessionId) {
|
|
7851
8289
|
this.getSignedSessionIdCallback = internalOptions.getSignedSessionId;
|
|
7852
8290
|
}
|
|
8291
|
+
// Set session public key callback if provided (internal use only)
|
|
8292
|
+
if (internalOptions == null ? void 0 : internalOptions.getSessionPublicKey) {
|
|
8293
|
+
this.getSessionPublicKeyCallback = internalOptions.getSessionPublicKey;
|
|
8294
|
+
}
|
|
7853
8295
|
// Read the callback lazily so it tracks `getSignedSessionIdCallback` even
|
|
7854
8296
|
// when tests reassign it after construction.
|
|
7855
8297
|
this.signedSession = new SignedSessionManager({
|
|
@@ -7923,4 +8365,4 @@ DynamicWalletClient.roomsPersistChain = Promise.resolve();
|
|
|
7923
8365
|
// rooms back into the new user's storage (TOCTOU on the create→merge gap).
|
|
7924
8366
|
DynamicWalletClient.roomsGeneration = 0;
|
|
7925
8367
|
|
|
7926
|
-
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_INCORRECT_PASSWORD, ERROR_KEYGEN_FAILED, ERROR_MULTIPLE_WALLETS_PER_CHAIN, ERROR_NO_KEY_SHARES_BACKED_UP, ERROR_PASSWORD_MISMATCH, ERROR_PASSWORD_REQUIRED, 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, OperationSource, 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, StaleLocalSharesError, WalletBusyError, WalletNotReadyError, cancelICloudAuth, createAddCloudProviderToExistingDelegationDistribution, createBackupData, createCloudProviderDistribution, createDelegationOnlyDistribution, createDelegationWithCloudProviderDistribution, createDynamicOnlyDistribution, deleteICloudBackup, downloadStringAsFile, extractPubkey, formatEvmMessage, formatMessage, formatTronMessage, getActiveCloudProviders, getBitcoinAddressTypeFromDerivationPath, getClientKeyShareBackupInfo, getClientKeyShareExportFileName, getDelegatedShareSet, getGoogleOAuthAccountId, getHttpStatus, getICloudBackup, getMPCSignatureScheme, getMPCSigner, hasCloudProviderBackup, hasDelegatedBackup, hasEncryptedSharesCached, initializeCloudKit, isBrowser, isHeavyQueueOperation, isHexString, isICloudAuthenticated, isNonRetryableCeremonyError, isPublicKeyMismatchError, isRecoverQueueOperation, isSignQueueOperation, listICloudBackups, markCeremonyErrorNonRetryable, readEnvironmentSettings, retryPromise, shouldReshareToSameBackups, timeoutPromise };
|
|
8368
|
+
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_INCORRECT_PASSWORD, ERROR_KEYGEN_FAILED, ERROR_MULTIPLE_WALLETS_PER_CHAIN, ERROR_NO_KEY_SHARES_BACKED_UP, ERROR_PASSWORD_MISMATCH, ERROR_PASSWORD_REQUIRED, 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, OperationSource, 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, StaleLocalSharesError, WalletBusyError, WalletNotReadyError, cancelICloudAuth, createAddCloudProviderToExistingDelegationDistribution, createBackupData, createCloudProviderDistribution, createDelegationOnlyDistribution, createDelegationWithCloudProviderDistribution, createDynamicOnlyDistribution, deleteICloudBackup, downloadStringAsFile, extractPubkey, formatEvmMessage, formatMessage, formatTronMessage, getActiveCloudProviders, getBitcoinAddressTypeFromDerivationPath, getClientKeyShareBackupInfo, getClientKeyShareExportFileName, getDelegatedShareSet, getGoogleOAuthAccountId, getHttpStatus, getICloudBackup, getMPCSignatureScheme, getMPCSigner, hasCloudProviderBackup, hasDelegatedBackup, hasEncryptedSharesCached, initializeCloudKit, isBrowser, isHeavyQueueOperation, isHexString, isICloudAuthenticated, isNonRetryableCeremonyError, isPublicKeyMismatchError, isRecoverQueueOperation, isSignQueueOperation, isStaleClientSharesError, listICloudBackups, markCeremonyErrorNonRetryable, readEnvironmentSettings, retryPromise, shouldReshareToSameBackups, timeoutPromise };
|