@dynamic-labs-wallet/browser 1.0.97 → 1.0.99

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.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- import { BitcoinAddressType, MPC_RELAY_PROD_API_URL, DEFAULT_ED25519_VARIANT, getMPCChainConfig, parseNamespacedVersion, WalletApiError, BackupLocation, ENCRYPTED_SHARES_STORAGE_SUFFIX, Logger, NETWORK_ERROR_STATUS, handleAxiosError, WalletOperation, WalletReadyState, classifyForwardMpcError, isRecoveredForwardMpcConnectionChurn, FEATURE_FLAGS, ThresholdSignatureScheme, getClientThreshold, MPC_CONFIG, getTSSConfig, serializeMessageForForwardMPC, isEvmCompatibleChain, serializeError, getReshareConfig, getRequiredExternalKeyShareId, verifiedCredentialNameToChainEnum, AuthMode, NoopLogger, DynamicApiClient, BusinessAccountClient, getEnvironmentFromUrl, IFRAME_DOMAIN_MAP } from '@dynamic-labs-wallet/core';
1
+ import { BitcoinAddressType, MPC_RELAY_PROD_API_URL, DEFAULT_ED25519_VARIANT, getMPCChainConfig, parseNamespacedVersion, WalletApiError, BackupLocation, ENCRYPTED_SHARES_STORAGE_SUFFIX, Logger, NETWORK_ERROR_STATUS, handleAxiosError, WalletOperation, ThresholdSignatureScheme, WalletReadyState, classifyForwardMpcError, isRecoveredForwardMpcConnectionChurn, FEATURE_FLAGS, getClientThreshold, MPC_CONFIG, getTSSConfig, serializeMessageForForwardMPC, isEvmCompatibleChain, serializeError, getReshareConfig, getRequiredExternalKeyShareId, verifiedCredentialNameToChainEnum, AuthMode, NoopLogger, DynamicApiClient, BusinessAccountClient, getEnvironmentFromUrl, IFRAME_DOMAIN_MAP } from '@dynamic-labs-wallet/core';
2
2
  export * from '@dynamic-labs-wallet/core';
3
3
  export { Logger } from '@dynamic-labs-wallet/core';
4
4
  import { EdBls12377, BIP340, Ed25519, ExportableEd25519, Ecdsa, MessageHash, EcdsaSignature, EcdsaKeygenResult, ExportableEd25519KeygenResult, Ed25519KeygenResult, BIP340KeygenResult } from '#internal/web';
@@ -1275,6 +1275,19 @@ const downloadFileFromGoogleDrive = async ({ accessToken, fileName })=>{
1275
1275
  }
1276
1276
  };
1277
1277
 
1278
+ function _object_without_properties_loose(source, excluded) {
1279
+ if (source == null) return {};
1280
+ var target = {};
1281
+ var sourceKeys = Object.keys(source);
1282
+ var key, i;
1283
+ for(i = 0; i < sourceKeys.length; i++){
1284
+ key = sourceKeys[i];
1285
+ if (excluded.indexOf(key) >= 0) continue;
1286
+ target[key] = source[key];
1287
+ }
1288
+ return target;
1289
+ }
1290
+
1278
1291
  /**
1279
1292
  * Normalizes an address to lowercase for consistent map key lookups.
1280
1293
  * This ensures that addresses with different casing (e.g., EIP-55 checksummed vs lowercase)
@@ -1302,9 +1315,17 @@ const isBrowser = ()=>globalThis.window !== undefined;
1302
1315
  }
1303
1316
  return actualPubkey;
1304
1317
  };
1305
- const getClientKeyShareExportFileName = ({ thresholdSignatureScheme, accountAddress, isGoogleDrive = false })=>{
1306
- const suffix = isGoogleDrive ? '-google-drive' : '';
1307
- return `${CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX}-${thresholdSignatureScheme}-${accountAddress}${suffix}.json`;
1318
+ /**
1319
+ * Keeps a variant's Drive file from colliding with — and so shadowing — the
1320
+ * wallet's own (rootUser) backup, which can carry the same
1321
+ * `thresholdSignatureScheme` and address.
1322
+ */ const getExportFileNameSuffix = ({ isGoogleDrive = false, isOfflineRecovery = false })=>`${isGoogleDrive ? '-google-drive' : ''}${isOfflineRecovery ? '-offline-recovery' : ''}`;
1323
+ const getClientKeyShareExportFileName = (_param)=>{
1324
+ var { thresholdSignatureScheme, accountAddress } = _param, variant = _object_without_properties_loose(_param, [
1325
+ "thresholdSignatureScheme",
1326
+ "accountAddress"
1327
+ ]);
1328
+ return `${CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX}-${thresholdSignatureScheme}-${accountAddress}${getExportFileNameSuffix(variant)}.json`;
1308
1329
  };
1309
1330
  const getClientKeyShareBackupInfo = (params)=>{
1310
1331
  var _params_walletProperties, _params_walletProperties_keyShares_;
@@ -1314,7 +1335,8 @@ const getClientKeyShareBackupInfo = (params)=>{
1314
1335
  [BackupLocation.ICLOUD]: [],
1315
1336
  [BackupLocation.USER]: [],
1316
1337
  [BackupLocation.EXTERNAL]: [],
1317
- [BackupLocation.DELEGATED]: []
1338
+ [BackupLocation.DELEGATED]: [],
1339
+ [BackupLocation.OFFLINE_RECOVERY]: []
1318
1340
  };
1319
1341
  if (!(params == null ? void 0 : (_params_walletProperties = params.walletProperties) == null ? void 0 : _params_walletProperties.keyShares)) {
1320
1342
  return {
@@ -1338,6 +1360,25 @@ const getClientKeyShareBackupInfo = (params)=>{
1338
1360
  passwordEncrypted
1339
1361
  };
1340
1362
  };
1363
+ /**
1364
+ * Records the just-activated offlineRecovery share set against the wallet's
1365
+ * own (rootUser) backup info, which it must leave otherwise untouched — the
1366
+ * exported set is a separate share set, not rootUser's.
1367
+ *
1368
+ * Only the `offlineRecovery` location is taken from `activated`. The exported
1369
+ * share is deliberately also reported under `googleDrive` so the server can
1370
+ * activate it, but folding that tag in here would make the wallet look like
1371
+ * rootUser itself is backed up to Drive — see `getActiveCloudProviders`, whose
1372
+ * result drives later reshare/delegation decisions. The location is replaced
1373
+ * rather than appended so repeated offline recoveries stay idempotent.
1374
+ */ const recordOfflineRecoveryBackupLocations = (existing, activated)=>{
1375
+ var _activated_backups_BackupLocation_OFFLINE_RECOVERY;
1376
+ return _extends({}, existing, {
1377
+ backups: _extends({}, existing.backups, {
1378
+ [BackupLocation.OFFLINE_RECOVERY]: (_activated_backups_BackupLocation_OFFLINE_RECOVERY = activated.backups[BackupLocation.OFFLINE_RECOVERY]) != null ? _activated_backups_BackupLocation_OFFLINE_RECOVERY : []
1379
+ })
1380
+ });
1381
+ };
1341
1382
  const timeoutPromise = ({ timeInMs, activity = 'Ceremony' })=>{
1342
1383
  return new Promise((_, reject)=>setTimeout(()=>reject(new Error(`${activity} did not complete in ${timeInMs}ms`)), timeInMs));
1343
1384
  };
@@ -2472,6 +2513,32 @@ class WalletBusyError extends Error {
2472
2513
  this.accountAddress = accountAddress;
2473
2514
  }
2474
2515
  }
2516
+ /**
2517
+ * Thrown when a Google Drive operation fails because the user's OAuth access
2518
+ * token has expired or is otherwise unusable. Distinct from a generic error so
2519
+ * the caller can prompt the user to re-link Google before retrying.
2520
+ */ class ExpiredGoogleDriveTokenError extends Error {
2521
+ constructor(message, options){
2522
+ super(message, (options == null ? void 0 : options.cause) !== undefined ? {
2523
+ cause: options.cause
2524
+ } : undefined);
2525
+ this.name = 'ExpiredGoogleDriveTokenError';
2526
+ this.accountAddress = options == null ? void 0 : options.accountAddress;
2527
+ }
2528
+ }
2529
+ /**
2530
+ * Thrown when a Google Drive backup preflight blocks for a reason OTHER than
2531
+ * an expired/scope-insufficient token (e.g. storage quota, rate limit,
2532
+ * network). Distinct from {@link ExpiredGoogleDriveTokenError} so callers
2533
+ * don't prompt a Google re-link for a problem re-linking can't fix.
2534
+ */ class GoogleDriveBackupBlockedError extends Error {
2535
+ constructor(message, errorReason, options){
2536
+ super(message);
2537
+ this.name = 'GoogleDriveBackupBlockedError';
2538
+ this.errorReason = errorReason;
2539
+ this.accountAddress = options == null ? void 0 : options.accountAddress;
2540
+ }
2541
+ }
2475
2542
  /**
2476
2543
  * Creates distribution where shares go to specified cloud providers
2477
2544
  * Last share goes to cloud providers, rest to Dynamic backend
@@ -2528,11 +2595,22 @@ class WalletBusyError extends Error {
2528
2595
  return ((_backupInfo_backups_provider_length = (_backupInfo_backups_provider = backupInfo.backups[provider]) == null ? void 0 : _backupInfo_backups_provider.length) != null ? _backupInfo_backups_provider_length : 0) > 0;
2529
2596
  });
2530
2597
  };
2598
+ /**
2599
+ * Backup locations that are NOT uploadable cloud providers. Anything returned by
2600
+ * {@link getActiveCloudProviders} is eventually dispatched through
2601
+ * `uploadToCloudProvider`, whose `default` branch throws — so a non-cloud
2602
+ * location leaking into that list turns the next backup/reshare into a hard
2603
+ * failure.
2604
+ */ const NON_CLOUD_BACKUP_LOCATIONS = new Set([
2605
+ BackupLocation.DYNAMIC,
2606
+ BackupLocation.DELEGATED,
2607
+ BackupLocation.OFFLINE_RECOVERY
2608
+ ]);
2531
2609
  /**
2532
2610
  * Gets all cloud providers that have backups for this wallet
2533
2611
  */ const getActiveCloudProviders = (backupInfo)=>{
2534
2612
  if (!(backupInfo == null ? void 0 : backupInfo.backups)) return [];
2535
- return Object.entries(backupInfo.backups).filter(([location, backups])=>location !== BackupLocation.DYNAMIC && location !== BackupLocation.DELEGATED && backups.length > 0).map(([location])=>location);
2613
+ return Object.entries(backupInfo.backups).filter(([location, backups])=>!NON_CLOUD_BACKUP_LOCATIONS.has(location) && backups.length > 0).map(([location])=>location);
2536
2614
  };
2537
2615
  const createDelegationOnlyDistribution = ({ existingShares, delegatedShare })=>({
2538
2616
  clientShares: existingShares,
@@ -2544,24 +2622,41 @@ const createDynamicOnlyDistribution = ({ allShares })=>({
2544
2622
  cloudProviderShares: {}
2545
2623
  });
2546
2624
  /**
2547
- * Returns the delegated share set from a wallet's `otherShareSets` array, if
2548
- * one exists. The new revoke flow targets the share set directly via this id
2549
- * rather than running a reshare ceremony.
2625
+ * Offline recovery's exported set is minted via the same same-parties 2/2
2626
+ * reshare as delegation. The client only ever sees its own reshared share
2627
+ * upload it to Google Drive (mandatory, no device-export variant). The
2628
+ * server's own reshared share is delivered to the developer's webhook
2629
+ * entirely server-side (see redcoast); the browser SDK never touches it.
2630
+ * clientShares: [] skips the local-storage rewrite — root_user is untouched.
2631
+ */ const createOfflineRecoveryDistribution = ({ exportedClientShare })=>({
2632
+ clientShares: [],
2633
+ cloudProviderShares: {
2634
+ [BackupLocation.GOOGLE_DRIVE]: [
2635
+ exportedClientShare
2636
+ ]
2637
+ },
2638
+ isOfflineRecovery: true
2639
+ });
2640
+ /**
2641
+ * Returns the share set of the given `shareSetType` from a wallet's
2642
+ * `otherShareSets` array, if one exists.
2550
2643
  *
2551
- * Invariant: at most one delegated share set per wallet. If the server ever
2552
- * returns more than one, the most recently created entry is returned and a
2553
- * warning is logged once on the call site (caller's responsibility — keep this
2554
- * helper pure).
2555
- */ const getDelegatedShareSet = (otherShareSets)=>{
2644
+ * Invariant: at most one entry per `shareSetType` per wallet. If the server
2645
+ * ever returns more than one, the most recently created entry is returned and
2646
+ * a warning is logged once on the call site (caller's responsibility — keep
2647
+ * this helper pure).
2648
+ */ const getShareSet = (otherShareSets, shareSetType)=>{
2556
2649
  var _otherShareSets_filter;
2557
- const delegated = (_otherShareSets_filter = otherShareSets == null ? void 0 : otherShareSets.filter((shareSet)=>shareSet.shareSetType === 'delegated')) != null ? _otherShareSets_filter : [];
2558
- if (delegated.length <= 1) return delegated[0];
2559
- // Multiple delegated entries shouldn't happen today. Pick the newest by
2560
- // createdAt so behavior is deterministic if the invariant ever breaks.
2650
+ const matches = (_otherShareSets_filter = otherShareSets == null ? void 0 : otherShareSets.filter((shareSet)=>shareSet.shareSetType === shareSetType)) != null ? _otherShareSets_filter : [];
2651
+ if (matches.length <= 1) return matches[0];
2652
+ // Multiple entries of the same type shouldn't happen today. Pick the newest
2653
+ // by createdAt so behavior is deterministic if the invariant ever breaks.
2561
2654
  return [
2562
- ...delegated
2655
+ ...matches
2563
2656
  ].sort((a, b)=>a.createdAt < b.createdAt ? 1 : -1)[0];
2564
2657
  };
2658
+ /** Convenience wrapper around {@link getShareSet} for `'delegated'`. */ const getDelegatedShareSet = (otherShareSets)=>getShareSet(otherShareSets, 'delegated');
2659
+ /** Convenience wrapper around {@link getShareSet} for `'offlineRecovery'`. */ const getOfflineRecoveryShareSet = (otherShareSets)=>getShareSet(otherShareSets, 'offlineRecovery');
2565
2660
  /**
2566
2661
  * Checks whether a wallet has delegation set up. Two sources are checked
2567
2662
  * because the SDK is in a transitional state:
@@ -2851,7 +2946,10 @@ const collectDynamicKeyShareIds = (locations)=>{
2851
2946
  // ShareDistribution that storage/publish should follow. Lives outside the
2852
2947
  // client class so it's straightforward to reason about and unit-test
2853
2948
  // without spinning up DynamicWalletClient.
2854
- const selectReshareDistribution = ({ resolvedDelegation, resolvedCloudProviders, existingReshareResults, newReshareResults })=>{
2949
+ const selectReshareDistribution = ({ resolvedDelegation, resolvedOfflineRecovery = false, resolvedCloudProviders, existingReshareResults, newReshareResults })=>{
2950
+ if (resolvedDelegation && resolvedOfflineRecovery) {
2951
+ throw new Error('selectReshareDistribution: resolvedDelegation and resolvedOfflineRecovery are mutually exclusive on a single reshare');
2952
+ }
2855
2953
  const allClientShares = [
2856
2954
  ...existingReshareResults,
2857
2955
  ...newReshareResults
@@ -2880,6 +2978,19 @@ const selectReshareDistribution = ({ resolvedDelegation, resolvedCloudProviders,
2880
2978
  delegatedShare: newReshareResults[0]
2881
2979
  });
2882
2980
  }
2981
+ // Offline recovery mints its exported share set via the same same-parties
2982
+ // reshare signal as delegation (1 existing, 0 new). The client's reshared
2983
+ // share routes to Google Drive (mandatory — no device-export variant); the
2984
+ // server's own reshared share is delivered to the developer entirely
2985
+ // server-side (redcoast) and never appears here.
2986
+ if (resolvedOfflineRecovery && newReshareResults.length === 0 && existingReshareResults.length === 1) {
2987
+ if (!resolvedCloudProviders.includes(BackupLocation.GOOGLE_DRIVE)) {
2988
+ throw new Error('selectReshareDistribution: offline recovery requires Google Drive');
2989
+ }
2990
+ return createOfflineRecoveryDistribution({
2991
+ exportedClientShare: existingReshareResults[0]
2992
+ });
2993
+ }
2883
2994
  if (resolvedCloudProviders.length > 0) {
2884
2995
  return createCloudProviderDistribution({
2885
2996
  providers: resolvedCloudProviders,
@@ -3047,6 +3158,123 @@ const readEnvironmentSettings = ()=>{
3047
3158
  }
3048
3159
  });
3049
3160
 
3161
+ const DEFAULT_OFFLINE_RECOVERY_CLOUD_PROVIDER = BackupLocation.GOOGLE_DRIVE;
3162
+ /**
3163
+ * Resolve a usable Google Drive access token. Unlike an opportunistic
3164
+ * multi-provider reshare (which silently drops Drive on failure), Drive is
3165
+ * mandatory here — resolution/preflight failures throw before the ceremony
3166
+ * starts. Only an actual token/scope problem throws `ExpiredGoogleDriveTokenError`;
3167
+ * quota, rate-limit, and network blocks throw `GoogleDriveBackupBlockedError`
3168
+ * instead, since re-linking Google fixes none of those.
3169
+ */ const resolveGoogleDriveAccessToken = async (client, accountAddress, googleDriveAccessToken)=>{
3170
+ let token;
3171
+ try {
3172
+ token = googleDriveAccessToken != null ? googleDriveAccessToken : await client.fetchGoogleDriveAccessToken(accountAddress);
3173
+ } catch (error) {
3174
+ throw new ExpiredGoogleDriveTokenError('Offline recovery requires a valid Google Drive access token', {
3175
+ cause: error,
3176
+ accountAddress
3177
+ });
3178
+ }
3179
+ const { blocking } = await runGoogleDriveBackupPreflight(token);
3180
+ if (!blocking) return token;
3181
+ if (blocking.errorReason === 'auth_denied') {
3182
+ throw new ExpiredGoogleDriveTokenError('Offline recovery requires a valid Google Drive access token', {
3183
+ accountAddress
3184
+ });
3185
+ }
3186
+ throw new GoogleDriveBackupBlockedError(blocking.message, blocking.errorReason, {
3187
+ accountAddress
3188
+ });
3189
+ };
3190
+ const googleDriveStrategy = {
3191
+ location: BackupLocation.GOOGLE_DRIVE,
3192
+ prepareReshareArgs: async (client, params)=>({
3193
+ googleDriveAccessToken: await resolveGoogleDriveAccessToken(client, params.accountAddress, params.cloudProviderAccessToken)
3194
+ })
3195
+ };
3196
+ // Registry of supported providers. iCloud and other third-party clouds slot in
3197
+ // here (each supplying its own readiness check in `prepareReshareArgs`) without
3198
+ // changing `performOfflineRecoveryOperation`. Requesting an unregistered
3199
+ // provider throws below.
3200
+ const CLOUD_PROVIDER_STRATEGIES = {
3201
+ [BackupLocation.GOOGLE_DRIVE]: googleDriveStrategy
3202
+ };
3203
+ const getCloudProviderStrategy = (provider)=>{
3204
+ const strategy = CLOUD_PROVIDER_STRATEGIES[provider];
3205
+ if (!strategy) {
3206
+ throw new Error(`Offline recovery is not supported for cloud provider "${provider}"`);
3207
+ }
3208
+ return strategy;
3209
+ };
3210
+ // Generous upper bound on an OAuth bearer token — not chain-address validation
3211
+ // (accountAddress spans EVM/BTC/SVM/TON/SUI formats, so no shared regex fits).
3212
+ const MAX_CLOUD_PROVIDER_ACCESS_TOKEN_LENGTH = 4096;
3213
+ const validateOfflineRecoveryParams = ({ accountAddress, cloudProviderAccessToken })=>{
3214
+ if (typeof accountAddress !== 'string' || accountAddress.length === 0) {
3215
+ throw new Error('offlineRecovery requires a non-empty accountAddress');
3216
+ }
3217
+ if (cloudProviderAccessToken !== undefined && (typeof cloudProviderAccessToken !== 'string' || cloudProviderAccessToken.length === 0 || cloudProviderAccessToken.length > MAX_CLOUD_PROVIDER_ACCESS_TOKEN_LENGTH)) {
3218
+ throw new Error('offlineRecovery: cloudProviderAccessToken must be a non-empty string');
3219
+ }
3220
+ };
3221
+ /**
3222
+ * Mints the dedicated 2/2 `offlineRecovery` share set: the same
3223
+ * same-parties reshare mechanism delegation uses, routed to the chosen cloud
3224
+ * provider instead of the delegation webhook (see `selectReshareDistribution`).
3225
+ * The server's own reshared share is delivered to the developer entirely
3226
+ * server-side — this flow never touches it.
3227
+ */ const performOfflineRecoveryOperation = async (client, params)=>{
3228
+ validateOfflineRecoveryParams(params);
3229
+ const { accountAddress, password, signedSessionId, sessionPublicKey, mfaToken } = params;
3230
+ var _params_cloudProvider;
3231
+ const provider = (_params_cloudProvider = params.cloudProvider) != null ? _params_cloudProvider : DEFAULT_OFFLINE_RECOVERY_CLOUD_PROVIDER;
3232
+ try {
3233
+ const wallet = await client.getWallet({
3234
+ accountAddress,
3235
+ walletOperation: WalletOperation.REACH_ALL_PARTIES,
3236
+ password,
3237
+ signedSessionId
3238
+ });
3239
+ if (wallet.chainName === 'SUI') {
3240
+ throw new Error('Offline recovery is not allowed for SUI');
3241
+ }
3242
+ const strategy = getCloudProviderStrategy(provider);
3243
+ const providerReshareArgs = await strategy.prepareReshareArgs(client, params);
3244
+ const walletData = client.getWalletFromMap(accountAddress);
3245
+ if (!walletData) {
3246
+ throw new Error(`Wallet not found for address: ${accountAddress}`);
3247
+ }
3248
+ await client.reshare(_extends({
3249
+ chainName: walletData.chainName,
3250
+ accountAddress,
3251
+ oldThresholdSignatureScheme: walletData.thresholdSignatureScheme,
3252
+ // Pinning 2/2 (the exported set's real shape) is what makes getReshareConfig
3253
+ // yield 1 existing / 0 new — the same-parties shape selectReshareDistribution's
3254
+ // offline-recovery branch requires. The wallet's own scheme would break
3255
+ // 2-of-3 wallets: 1/1, missing the branch, falling through to a plain reshare.
3256
+ newThresholdSignatureScheme: ThresholdSignatureScheme.TWO_OF_TWO,
3257
+ password,
3258
+ signedSessionId,
3259
+ sessionPublicKey,
3260
+ cloudProviders: [
3261
+ strategy.location
3262
+ ],
3263
+ offlineRecovery: true,
3264
+ mfaToken
3265
+ }, providerReshareArgs));
3266
+ } catch (error) {
3267
+ logError({
3268
+ message: 'Error in offlineRecovery',
3269
+ error: error,
3270
+ context: {
3271
+ accountAddress
3272
+ }
3273
+ });
3274
+ throw error;
3275
+ }
3276
+ };
3277
+
3050
3278
  /**
3051
3279
  * Determines the recovery state of a wallet based on backup info and local shares.
3052
3280
  *
@@ -3092,7 +3320,8 @@ const BACKUP_OPERATION = {
3092
3320
  const KNOWN_SHARE_SET_TYPES = new Set([
3093
3321
  'rootUser',
3094
3322
  'delegated',
3095
- 'server'
3323
+ 'server',
3324
+ 'offlineRecovery'
3096
3325
  ]);
3097
3326
  /**
3098
3327
  * keygenIds the server records for the wallet's Dynamic-backed-up client
@@ -3178,7 +3407,9 @@ class DynamicWalletClient {
3178
3407
  /**
3179
3408
  * Get wallet properties from the wallet map using normalized address.
3180
3409
  * Normalizes the address to lowercase for consistent lookups regardless of input casing.
3181
- */ getWalletFromMap(accountAddress) {
3410
+ */ // Public (was protected) so the extracted offlineRecovery flow can read
3411
+ // wallet state off the client instance — see ./offlineRecovery.ts.
3412
+ getWalletFromMap(accountAddress) {
3182
3413
  return this.walletMap[normalizeAddress(accountAddress)];
3183
3414
  }
3184
3415
  /**
@@ -5089,7 +5320,10 @@ class DynamicWalletClient {
5089
5320
  existingClientKeyShares
5090
5321
  };
5091
5322
  }
5092
- async reshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined, signedSessionId, sessionPublicKey, cloudProviders = [], delegateToProjectEnvironment = false, mfaToken, elevatedAccessToken, revokeDelegation = false, googleDriveAccessToken, googleDriveTokenSource, initialSignerRules }) {
5323
+ async reshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined, signedSessionId, sessionPublicKey, cloudProviders = [], delegateToProjectEnvironment = false, offlineRecovery = false, mfaToken, elevatedAccessToken, revokeDelegation = false, googleDriveAccessToken, googleDriveTokenSource, initialSignerRules }) {
5324
+ if (delegateToProjectEnvironment && offlineRecovery) {
5325
+ throw new Error('reshare: delegateToProjectEnvironment and offlineRecovery are mutually exclusive');
5326
+ }
5093
5327
  // Fail-closed choke point: rules are only mintable on a share-set delegation
5094
5328
  // reshare, never on the legacy 2/2 -> 2/3 path or revoke.
5095
5329
  if (initialSignerRules && initialSignerRules.length > 0) {
@@ -5109,6 +5343,7 @@ class DynamicWalletClient {
5109
5343
  sessionPublicKey,
5110
5344
  cloudProviders,
5111
5345
  delegateToProjectEnvironment,
5346
+ offlineRecovery,
5112
5347
  mfaToken,
5113
5348
  elevatedAccessToken,
5114
5349
  revokeDelegation,
@@ -5503,6 +5738,19 @@ class DynamicWalletClient {
5503
5738
  });
5504
5739
  return;
5505
5740
  }
5741
+ if (shareSetType === 'offlineRecovery' && newThresholdSignatureScheme) {
5742
+ // offlineRecovery activates its own share set, not rootUser: rootUser
5743
+ // stays the wallet's primary identity, so walletMap.shareSetId is never
5744
+ // touched here (unlike the rootUser branch below).
5745
+ this.recordOtherShareSet({
5746
+ accountAddress,
5747
+ wallet,
5748
+ newShareSetId: shareSetId,
5749
+ newThresholdSignatureScheme,
5750
+ shareSetType: 'offlineRecovery'
5751
+ });
5752
+ return;
5753
+ }
5506
5754
  if (shareSetType !== 'rootUser') {
5507
5755
  // 'delegated' (no newThresholdSignatureScheme, e.g. refresh) → SDK doesn't
5508
5756
  // own the otherShareSets[] write from refresh.
@@ -5534,38 +5782,81 @@ class DynamicWalletClient {
5534
5782
  shareSetId
5535
5783
  });
5536
5784
  }
5537
- // Records a new `delegated` share set under wallet.otherShareSets[],
5538
- // replacing an existing delegated entry if one is already there (rather
5539
- // than appending duplicates on retry). Only reachable from the reshare
5540
- // delegation flow refresh doesn't thread newThresholdSignatureScheme
5541
- // so it falls through to the skip path before reaching here.
5542
- recordDelegatedShareSet({ accountAddress, wallet, newShareSetId, newThresholdSignatureScheme }) {
5785
+ /**
5786
+ * Records a share set of `shareSetType` under `wallet.otherShareSets[]`,
5787
+ * replacing an existing entry of that type rather than appending a duplicate
5788
+ * on retry. Only reachable from ceremony flows that thread
5789
+ * `newThresholdSignatureScheme`; refresh doesn't, so it skips before here.
5790
+ */ recordOtherShareSet({ accountAddress, wallet, newShareSetId, newThresholdSignatureScheme, shareSetType }) {
5543
5791
  const walletProps = this.getWalletFromMap(accountAddress);
5544
5792
  var _walletProps_otherShareSets;
5545
5793
  const existing = (_walletProps_otherShareSets = walletProps == null ? void 0 : walletProps.otherShareSets) != null ? _walletProps_otherShareSets : [];
5546
5794
  const newEntry = {
5547
5795
  shareSetId: newShareSetId,
5548
- shareSetType: 'delegated',
5796
+ shareSetType,
5549
5797
  thresholdSignatureScheme: newThresholdSignatureScheme,
5550
5798
  createdAt: new Date().toISOString()
5551
5799
  };
5552
- const delegatedIdx = existing.findIndex((s)=>s.shareSetType === 'delegated');
5553
- const updated = delegatedIdx >= 0 ? existing.map((s, i)=>i === delegatedIdx ? newEntry : s) : [
5800
+ const existingIdx = existing.findIndex((s)=>s.shareSetType === shareSetType);
5801
+ const updated = existingIdx >= 0 ? existing.map((s, i)=>i === existingIdx ? newEntry : s) : [
5554
5802
  ...existing,
5555
5803
  newEntry
5556
5804
  ];
5557
- this.logger.info('[WaasShareSet] delegated shareSetId added by reshare ceremony', {
5805
+ this.logger.info(`[WaasShareSet] ${shareSetType} shareSetId added by reshare ceremony`, {
5558
5806
  context: {
5559
5807
  walletId: wallet.walletId,
5560
5808
  rootUserShareSetId: wallet.shareSetId,
5561
- delegatedShareSetId: newShareSetId,
5562
- replacedExistingDelegated: delegatedIdx >= 0
5809
+ shareSetType,
5810
+ newShareSetId,
5811
+ replacedExisting: existingIdx >= 0
5563
5812
  }
5564
5813
  });
5565
5814
  this.updateWalletMap(accountAddress, {
5566
5815
  otherShareSets: updated
5567
5816
  });
5568
5817
  }
5818
+ /** Delegation-path wrapper over {@link recordOtherShareSet}, so the delegation call sites are unchanged. */ recordDelegatedShareSet({ accountAddress, wallet, newShareSetId, newThresholdSignatureScheme }) {
5819
+ this.recordOtherShareSet({
5820
+ accountAddress,
5821
+ wallet,
5822
+ newShareSetId,
5823
+ newThresholdSignatureScheme,
5824
+ shareSetType: 'delegated'
5825
+ });
5826
+ }
5827
+ /**
5828
+ * Advances the wallet's recorded scheme after a reshare. Offline recovery
5829
+ * mints a separate 2/2 set and leaves rootUser alone, so it must not write
5830
+ * here — same invariant {@link rotateRootUserShareSetIdIfChanged} applies to
5831
+ * `shareSetId`.
5832
+ */ recordReshareThresholdScheme({ accountAddress, newThresholdSignatureScheme, offlineRecovery }) {
5833
+ if (offlineRecovery) return;
5834
+ this.updateWalletMap(accountAddress, {
5835
+ thresholdSignatureScheme: newThresholdSignatureScheme
5836
+ });
5837
+ }
5838
+ /**
5839
+ * Reverts the local state a failed reshare may have half-written: the
5840
+ * walletMap scheme bump, and the local client shares (wiped so the next
5841
+ * operation refetches a consistent set from backup).
5842
+ *
5843
+ * Skipped whenever the failed ceremony left rootUser's local state alone:
5844
+ * - public-key mismatch / 409 stale-shares preflight: `healStaleShares` just
5845
+ * restored these shares, and undoing that would break the caller's retry.
5846
+ * - offlineRecovery: mints a separate share set (`clientShares: []`) and
5847
+ * never rotates rootUser's shares or scheme — the mirror image of the
5848
+ * {@link recordReshareThresholdScheme} guard on the success path. Wiping
5849
+ * here would strand a wallet whose local keys are still valid.
5850
+ */ async rollbackFailedReshareLocalState({ accountAddress, oldThresholdSignatureScheme, offlineRecovery, error }) {
5851
+ if (offlineRecovery || isPublicKeyMismatchError(error) || isStaleClientSharesError(error)) return;
5852
+ this.updateWalletMap(accountAddress, {
5853
+ thresholdSignatureScheme: oldThresholdSignatureScheme
5854
+ });
5855
+ await this.setClientKeySharesToStorage({
5856
+ accountAddress,
5857
+ clientKeyShares: []
5858
+ });
5859
+ }
5569
5860
  // Inverse of `recordDelegatedShareSet`. The next delegateKeyShares no-op
5570
5861
  // guard reads `otherShareSets`, so we have to clear it locally — `getWallet`
5571
5862
  // short-circuits on cache hit and won't refresh from the server.
@@ -5594,7 +5885,7 @@ class DynamicWalletClient {
5594
5885
  clientKeySharesBackupInfo: updatedBackupInfo
5595
5886
  } : {}));
5596
5887
  }
5597
- async internalReshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined, signedSessionId, sessionPublicKey, cloudProviders = [], delegateToProjectEnvironment = false, mfaToken, elevatedAccessToken, revokeDelegation = false, googleDriveAccessToken, googleDriveTokenSource, staleShareRetry = false, initialSignerRules }) {
5888
+ async internalReshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined, signedSessionId, sessionPublicKey, cloudProviders = [], delegateToProjectEnvironment = false, offlineRecovery = false, mfaToken, elevatedAccessToken, revokeDelegation = false, googleDriveAccessToken, googleDriveTokenSource, staleShareRetry = false, initialSignerRules }) {
5598
5889
  const dynamicRequestId = v4();
5599
5890
  // Password validation - wrapped in try-catch for consistent error handling
5600
5891
  // This path should NOT wipe key shares on failure (shares remain valid)
@@ -5709,7 +6000,7 @@ class DynamicWalletClient {
5709
6000
  dynamicRequestId
5710
6001
  }
5711
6002
  });
5712
- const data = await this.apiClient.reshare({
6003
+ const data = await this.apiClient.reshare(_extends({
5713
6004
  walletId: wallet.walletId,
5714
6005
  shareSetId: wallet.shareSetId,
5715
6006
  clientKeygenIds: clientKeygenIds,
@@ -5720,10 +6011,13 @@ class DynamicWalletClient {
5720
6011
  mfaToken,
5721
6012
  elevatedAccessToken,
5722
6013
  revokeDelegation,
5723
- initialSignerRules,
6014
+ initialSignerRules
6015
+ }, offlineRecovery ? {
6016
+ reshareKind: 'offlineRecovery'
6017
+ } : {}, {
5724
6018
  onError: ceremonyGate.onError,
5725
6019
  onCeremonyComplete: ceremonyGate.onCeremonyComplete
5726
- });
6020
+ }));
5727
6021
  this.logger.info('[WaasReshare] room_created received, starting local MPC', {
5728
6022
  context: {
5729
6023
  walletId: wallet.walletId,
@@ -5828,12 +6122,15 @@ class DynamicWalletClient {
5828
6122
  }
5829
6123
  const distribution = selectReshareDistribution({
5830
6124
  resolvedDelegation,
6125
+ resolvedOfflineRecovery: offlineRecovery,
5831
6126
  resolvedCloudProviders,
5832
6127
  existingReshareResults,
5833
6128
  newReshareResults
5834
6129
  });
5835
- this.updateWalletMap(accountAddress, {
5836
- thresholdSignatureScheme: newThresholdSignatureScheme
6130
+ this.recordReshareThresholdScheme({
6131
+ accountAddress,
6132
+ newThresholdSignatureScheme,
6133
+ offlineRecovery
5837
6134
  });
5838
6135
  this.logger.info('[WaasReshare] local MPC done, awaiting ceremony_complete before backup', {
5839
6136
  context: {
@@ -5928,6 +6225,7 @@ class DynamicWalletClient {
5928
6225
  sessionPublicKey,
5929
6226
  cloudProviders,
5930
6227
  delegateToProjectEnvironment,
6228
+ offlineRecovery,
5931
6229
  mfaToken,
5932
6230
  elevatedAccessToken,
5933
6231
  revokeDelegation,
@@ -5951,20 +6249,12 @@ class DynamicWalletClient {
5951
6249
  dynamicRequestId
5952
6250
  }
5953
6251
  });
5954
- // Skip the wipe on mismatch — healStaleShares just restored these
5955
- // shares from backup, undoing that would break the caller's retry.
5956
- // Same for a 409 stale-shares preflight (only reachable here on the
5957
- // retried attempt): the request was rejected before any ceremony, so
5958
- // local shares are consistent — wiping would strand the user.
5959
- if (!isPublicKeyMismatchError(error) && !isStaleClientSharesError(error)) {
5960
- this.updateWalletMap(accountAddress, {
5961
- thresholdSignatureScheme: oldThresholdSignatureScheme
5962
- });
5963
- await this.setClientKeySharesToStorage({
5964
- accountAddress,
5965
- clientKeyShares: []
5966
- });
5967
- }
6252
+ await this.rollbackFailedReshareLocalState({
6253
+ accountAddress,
6254
+ oldThresholdSignatureScheme,
6255
+ offlineRecovery,
6256
+ error
6257
+ });
5968
6258
  throw error;
5969
6259
  }
5970
6260
  }
@@ -6120,6 +6410,27 @@ class DynamicWalletClient {
6120
6410
  operationName: 'revokeDelegation'
6121
6411
  });
6122
6412
  }
6413
+ /**
6414
+ * Mints the dedicated 2/2 `offlineRecovery` share set. The
6415
+ * operation logic lives in `./offlineRecovery.ts` (cloud-provider-agnostic,
6416
+ * so iCloud / other providers slot in there); this method just drives it
6417
+ * with the client instance and returns the exported set's own backups.
6418
+ */ async offlineRecovery({ accountAddress, password = undefined, signedSessionId, sessionPublicKey, mfaToken, cloudProvider = DEFAULT_OFFLINE_RECOVERY_CLOUD_PROVIDER, cloudProviderAccessToken }) {
6419
+ await performOfflineRecoveryOperation(this, {
6420
+ accountAddress,
6421
+ password,
6422
+ signedSessionId,
6423
+ sessionPublicKey,
6424
+ mfaToken,
6425
+ cloudProvider,
6426
+ cloudProviderAccessToken
6427
+ });
6428
+ // The exported set's own locations. Not `backups[cloudProvider]`: that is
6429
+ // rootUser's record for the provider, which this operation never writes.
6430
+ const backupInfo = this.getRequiredWalletFromMap(accountAddress).clientKeySharesBackupInfo;
6431
+ var _backupInfo_backups_BackupLocation_OFFLINE_RECOVERY;
6432
+ return (_backupInfo_backups_BackupLocation_OFFLINE_RECOVERY = backupInfo.backups[BackupLocation.OFFLINE_RECOVERY]) != null ? _backupInfo_backups_BackupLocation_OFFLINE_RECOVERY : [];
6433
+ }
6123
6434
  createKeygenResult(mpcSigner, extractedPubkey, secretShare) {
6124
6435
  if (mpcSigner instanceof Ecdsa) {
6125
6436
  return new EcdsaKeygenResult(extractedPubkey, secretShare);
@@ -6776,7 +7087,7 @@ class DynamicWalletClient {
6776
7087
  ]);
6777
7088
  }));
6778
7089
  }
6779
- async backupToCloudProvider({ provider, shares, accountAddress, password, chainName, bitcoinConfig, isPasswordEncrypted, preEncryptedShares, googleDriveAccessToken, googleDriveTokenSource, encryptionVersion }) {
7090
+ async backupToCloudProvider({ provider, shares, accountAddress, password, chainName, bitcoinConfig, isPasswordEncrypted, preEncryptedShares, googleDriveAccessToken, googleDriveTokenSource, encryptionVersion, isOfflineRecovery }) {
6780
7091
  const encryptedCloudShares = preEncryptedShares != null ? preEncryptedShares : await Promise.all(shares.map((keyShare)=>this.encryptKeyShare({
6781
7092
  keyShare,
6782
7093
  password,
@@ -6786,7 +7097,8 @@ class DynamicWalletClient {
6786
7097
  provider,
6787
7098
  accountAddress,
6788
7099
  encryptedKeyShares: encryptedCloudShares,
6789
- googleDriveAccessToken
7100
+ googleDriveAccessToken,
7101
+ isOfflineRecovery
6790
7102
  });
6791
7103
  const keygenId = await this.computeBackupKeygenId({
6792
7104
  chainName,
@@ -6969,7 +7281,8 @@ class DynamicWalletClient {
6969
7281
  preEncryptedShares: encrypted,
6970
7282
  googleDriveAccessToken,
6971
7283
  googleDriveTokenSource,
6972
- encryptionVersion
7284
+ encryptionVersion,
7285
+ isOfflineRecovery: distribution.isOfflineRecovery
6973
7286
  }));
6974
7287
  }
6975
7288
  // When this ceremony minted a separate `delegated` share set (FF=on
@@ -6984,8 +7297,12 @@ class DynamicWalletClient {
6984
7297
  // when `walletData` was captured at the top of this method and now.
6985
7298
  const freshOtherShareSets = (_this_getWalletFromMap = this.getWalletFromMap(accountAddress)) == null ? void 0 : _this_getWalletFromMap.otherShareSets;
6986
7299
  const delegatedShareSet = distribution.delegatedShare ? getDelegatedShareSet(freshOtherShareSets) : undefined;
6987
- var _delegatedShareSet_shareSetId;
6988
- const targetShareSetId = (_delegatedShareSet_shareSetId = delegatedShareSet == null ? void 0 : delegatedShareSet.shareSetId) != null ? _delegatedShareSet_shareSetId : walletData.shareSetId;
7300
+ // Offline recovery activates its own share set, not rootUser: the
7301
+ // pending set must be the backup target or it is never activated and
7302
+ // the Drive location lands on rootUser instead.
7303
+ const offlineRecoveryShareSet = distribution.isOfflineRecovery ? getOfflineRecoveryShareSet(freshOtherShareSets) : undefined;
7304
+ var _delegatedShareSet_shareSetId, _ref;
7305
+ const targetShareSetId = (_ref = (_delegatedShareSet_shareSetId = delegatedShareSet == null ? void 0 : delegatedShareSet.shareSetId) != null ? _delegatedShareSet_shareSetId : offlineRecoveryShareSet == null ? void 0 : offlineRecoveryShareSet.shareSetId) != null ? _ref : walletData.shareSetId;
6989
7306
  if (distribution.delegatedShare) {
6990
7307
  uploadPromises.push(retryPromise(()=>this.publishDelegatedShare({
6991
7308
  walletId: walletData.walletId,
@@ -7003,6 +7320,17 @@ class DynamicWalletClient {
7003
7320
  }
7004
7321
  const uploadResults = await Promise.all(uploadPromises);
7005
7322
  const locations = uploadResults.filter((loc)=>loc !== undefined);
7323
+ // One physical Drive share, reported under two tags: `googleDrive` for
7324
+ // legacy backup-info reads and `offlineRecovery` for redcoast's
7325
+ // exported-set activation. Same keygenId/externalKeyShareId.
7326
+ if (distribution.isOfflineRecovery) {
7327
+ const googleDriveLocation = locations.find((location)=>location.location === BackupLocation.GOOGLE_DRIVE);
7328
+ if (googleDriveLocation) {
7329
+ locations.push(_extends({}, googleDriveLocation, {
7330
+ location: BackupLocation.OFFLINE_RECOVERY
7331
+ }));
7332
+ }
7333
+ }
7006
7334
  this.logger.info('[backupSharesWithDistribution] Uploads complete, activating shares on server', _extends({}, logContext, {
7007
7335
  locationCount: locations.length,
7008
7336
  locations: locations.map((l)=>l.location)
@@ -7086,7 +7414,7 @@ class DynamicWalletClient {
7086
7414
  });
7087
7415
  }
7088
7416
  var _backupData_locationsWithKeyShares2;
7089
- const updatedBackupInfo = getClientKeyShareBackupInfo({
7417
+ const activatedBackupInfo = getClientKeyShareBackupInfo({
7090
7418
  walletProperties: {
7091
7419
  derivationPath: walletData.derivationPath,
7092
7420
  keyShares: ((_backupData_locationsWithKeyShares2 = backupData.locationsWithKeyShares) != null ? _backupData_locationsWithKeyShares2 : []).map((ks)=>({
@@ -7099,8 +7427,10 @@ class DynamicWalletClient {
7099
7427
  thresholdSignatureScheme: walletData.thresholdSignatureScheme
7100
7428
  }
7101
7429
  });
7430
+ // offlineRecovery activates a separate share set, not rootUser's — record
7431
+ // it alongside rootUser's own backup info instead of replacing it.
7102
7432
  this.updateWalletMap(accountAddress, {
7103
- clientKeySharesBackupInfo: updatedBackupInfo
7433
+ clientKeySharesBackupInfo: distribution.isOfflineRecovery ? recordOfflineRecoveryBackupLocations(walletData.clientKeySharesBackupInfo, activatedBackupInfo) : activatedBackupInfo
7104
7434
  });
7105
7435
  await this.storage.setItem(this.storageKey, JSON.stringify(this.walletMap));
7106
7436
  var _backupData_locationsWithKeyShares_length, _backupData_locationsWithKeyShares_map;
@@ -7692,6 +8022,8 @@ class DynamicWalletClient {
7692
8022
  throw error;
7693
8023
  }
7694
8024
  }
8025
+ // Public (was private) so the extracted offlineRecovery flow can resolve a
8026
+ // Google Drive token off the client instance — see ./offlineRecovery.ts.
7695
8027
  async fetchGoogleDriveAccessToken(accountAddress) {
7696
8028
  const oauthAccountId = await this.getGoogleOauthAccountIdOrThrow(accountAddress);
7697
8029
  return this.apiClient.getAccessToken({
@@ -8176,13 +8508,14 @@ class DynamicWalletClient {
8176
8508
  * @param encryptedKeyShares - Already encrypted key shares to upload
8177
8509
  * @param googleDriveAccessToken - Optional Google OAuth access token used when provider is GOOGLE_DRIVE
8178
8510
  * @returns Promise<void>
8179
- */ async uploadToCloudProvider({ provider, accountAddress, encryptedKeyShares, googleDriveAccessToken }) {
8511
+ */ async uploadToCloudProvider({ provider, accountAddress, encryptedKeyShares, googleDriveAccessToken, isOfflineRecovery }) {
8180
8512
  switch(provider){
8181
8513
  case BackupLocation.GOOGLE_DRIVE:
8182
8514
  return this.uploadKeySharesToGoogleDrive({
8183
8515
  accountAddress,
8184
8516
  encryptedKeyShares,
8185
- googleDriveAccessToken
8517
+ googleDriveAccessToken,
8518
+ isOfflineRecovery
8186
8519
  });
8187
8520
  case BackupLocation.ICLOUD:
8188
8521
  return this.uploadKeySharesToICloud({
@@ -8205,7 +8538,7 @@ class DynamicWalletClient {
8205
8538
  * @param params.encryptedKeyShares - The specific key shares to upload to Google Drive
8206
8539
  * @param params.googleDriveAccessToken - Google OAuth access token. Required; resolved upstream by `backupKeySharesToGoogleDrive` so the preflight can run on it.
8207
8540
  * @returns Promise<string[]> - Array of Google Drive key share IDs that were uploaded
8208
- */ async uploadKeySharesToGoogleDrive({ accountAddress, encryptedKeyShares, googleDriveAccessToken }) {
8541
+ */ async uploadKeySharesToGoogleDrive({ accountAddress, encryptedKeyShares, googleDriveAccessToken, isOfflineRecovery }) {
8209
8542
  try {
8210
8543
  if (encryptedKeyShares.length === 0) {
8211
8544
  throw new Error('No key shares found');
@@ -8213,12 +8546,13 @@ class DynamicWalletClient {
8213
8546
  if (!googleDriveAccessToken) {
8214
8547
  throw new Error('googleDriveAccessToken is required for Google Drive upload');
8215
8548
  }
8216
- const walletData = this.getWalletFromMap(accountAddress);
8549
+ const walletData = this.getRequiredWalletFromMap(accountAddress);
8217
8550
  const thresholdSignatureScheme = walletData.thresholdSignatureScheme;
8218
8551
  const fileName = getClientKeyShareExportFileName({
8219
8552
  thresholdSignatureScheme,
8220
8553
  accountAddress,
8221
- isGoogleDrive: true
8554
+ isGoogleDrive: true,
8555
+ isOfflineRecovery
8222
8556
  });
8223
8557
  const backupData = createBackupData({
8224
8558
  encryptedKeyShares,
@@ -8230,9 +8564,9 @@ class DynamicWalletClient {
8230
8564
  fileName,
8231
8565
  backupData,
8232
8566
  accountAddress,
8233
- walletId: walletData == null ? void 0 : walletData.walletId,
8567
+ walletId: walletData.walletId,
8234
8568
  environmentId: this.environmentId,
8235
- chainName: walletData == null ? void 0 : walletData.chainName,
8569
+ chainName: walletData.chainName,
8236
8570
  logger: this.logger
8237
8571
  });
8238
8572
  return;
@@ -9531,4 +9865,4 @@ DynamicWalletClient.roomsGeneration = 0;
9531
9865
  return ed25519.ExtendedPoint.BASE.multiply(reduced).toRawBytes();
9532
9866
  };
9533
9867
 
9534
- 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, SIGNED_SESSION_REVERSE_CHANNEL_MIN_VERSION, SIGNED_SESSION_REVERSE_CHANNEL_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, getBitcoinAddressTypeFromAddress, getBitcoinAddressTypeFromDerivationPath, getClientKeyShareBackupInfo, getClientKeyShareExportFileName, getDelegatedShareSet, getEd25519PublicKeyFromRawScalar, getGoogleOAuthAccountId, getHttpStatus, getICloudBackup, getMPCSignatureScheme, getMPCSigner, hasCloudProviderBackup, hasDelegatedBackup, hasEncryptedSharesCached, initializeCloudKit, isBrowser, isHeavyQueueOperation, isHexString, isICloudAuthenticated, isMultipleWalletsPerChainRejection, isNonRetryableCeremonyError, isPublicKeyMismatchError, isRecoverQueueOperation, isRoomIdAlreadyUsedError, isSignQueueOperation, isStaleClientSharesError, listICloudBackups, markCeremonyErrorNonRetryable, readEnvironmentSettings, resolveLogLevel, retryPromise, shouldReshareToSameBackups, timeoutPromise };
9868
+ 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, ExpiredGoogleDriveTokenError, GoogleDriveBackupBlockedError, 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, SIGNED_SESSION_REVERSE_CHANNEL_MIN_VERSION, SIGNED_SESSION_REVERSE_CHANNEL_MIN_VERSION_BY_NAMESPACE, SIGN_QUEUE_OPERATIONS, STORAGE_KEY, StaleLocalSharesError, WalletBusyError, WalletNotReadyError, cancelICloudAuth, createAddCloudProviderToExistingDelegationDistribution, createBackupData, createCloudProviderDistribution, createDelegationOnlyDistribution, createDelegationWithCloudProviderDistribution, createDynamicOnlyDistribution, createOfflineRecoveryDistribution, deleteICloudBackup, downloadStringAsFile, extractPubkey, formatEvmMessage, formatMessage, formatTronMessage, getActiveCloudProviders, getBitcoinAddressTypeFromAddress, getBitcoinAddressTypeFromDerivationPath, getClientKeyShareBackupInfo, getClientKeyShareExportFileName, getDelegatedShareSet, getEd25519PublicKeyFromRawScalar, getGoogleOAuthAccountId, getHttpStatus, getICloudBackup, getMPCSignatureScheme, getMPCSigner, getOfflineRecoveryShareSet, getShareSet, hasCloudProviderBackup, hasDelegatedBackup, hasEncryptedSharesCached, initializeCloudKit, isBrowser, isHeavyQueueOperation, isHexString, isICloudAuthenticated, isMultipleWalletsPerChainRejection, isNonRetryableCeremonyError, isPublicKeyMismatchError, isRecoverQueueOperation, isRoomIdAlreadyUsedError, isSignQueueOperation, isStaleClientSharesError, listICloudBackups, markCeremonyErrorNonRetryable, readEnvironmentSettings, recordOfflineRecoveryBackupLocations, resolveLogLevel, retryPromise, shouldReshareToSameBackups, timeoutPromise };