@dynamic-labs-wallet/browser 1.0.89 → 1.0.91

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, getMPCChainConfig, parseNamespacedVersion, WalletApiError, BackupLocation, ENCRYPTED_SHARES_STORAGE_SUFFIX, Logger, 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, 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';
2
2
  export * from '@dynamic-labs-wallet/core';
3
3
  export { Logger } from '@dynamic-labs-wallet/core';
4
4
  import { EdBls12377, BIP340, ExportableEd25519, Ecdsa, MessageHash, EcdsaSignature, EcdsaKeygenResult, ExportableEd25519KeygenResult, BIP340KeygenResult } from '#internal/web';
@@ -653,13 +653,17 @@ function meetsMinVersion(sdkVersion, { minVersionByNamespace, fallbackMinVersion
653
653
  // canRefresh only proves a local callback exists, not that the host answers it.
654
654
  if (!this.supportsReverseChannel()) {
655
655
  this.logger.warn(`[${operationName}] host SDK version predates the signed-session reverse channel, declining nonce refresh`, _extends({
656
- status
656
+ http: {
657
+ status_code: status
658
+ }
657
659
  }, logContext));
658
660
  return alsoRetry ? alsoRetry(status) : false;
659
661
  }
660
662
  onRefreshed(await this.resolve());
661
663
  this.logger.info(`[${operationName}] refreshed signed session, retrying`, _extends({
662
- status
664
+ http: {
665
+ status_code: status
666
+ }
663
667
  }, logContext));
664
668
  return true;
665
669
  }
@@ -675,7 +679,9 @@ function meetsMinVersion(sdkVersion, { minVersionByNamespace, fallbackMinVersion
675
679
  this.logger.info(NONCE_FAILURE_EVENT, _extends({}, logContext, {
676
680
  operationName,
677
681
  attempt,
678
- status,
682
+ http: {
683
+ status_code: status
684
+ },
679
685
  reason: classifyNonceFailure(body),
680
686
  errorCode: body.code,
681
687
  errorMessage: body.message,
@@ -2115,17 +2121,21 @@ const initializeCloudKit = async (config, signInButtonId, onSignInRequired, onSi
2115
2121
  // Downgrade them to warn so monitors can filter on status:error and
2116
2122
  // still catch real system-side failures (5xx/network/unknown) without
2117
2123
  // the user-side noise drowning out the signal.
2118
- const logFn = isUserActionable ? logger.warn : logger.error;
2119
2124
  // When every location failed for the same reason (the dominant case)
2120
2125
  // surface a single string; otherwise emit the array so consumers can
2121
2126
  // see the mix.
2122
2127
  const logErrorReason = uniqueReasons.length === 1 ? uniqueReasons[0] : uniqueReasons;
2123
- logFn.call(logger, '[DynamicWaasWalletClient] Google Drive backup failed', _extends({}, logContext, {
2128
+ const logPayload = _extends({}, logContext, {
2124
2129
  errorCount: failures.length,
2125
2130
  errorReason: logErrorReason,
2126
2131
  isUserActionable,
2127
2132
  errors: failures.map((f)=>`Failed to backup keyshares to ${f.locationName}: ${f.message}`)
2128
- }));
2133
+ });
2134
+ if (isUserActionable) {
2135
+ logger.warn('[DynamicWaasWalletClient] Google Drive backup failed', logPayload);
2136
+ } else {
2137
+ logger.error('[DynamicWaasWalletClient] Google Drive backup failed', logPayload);
2138
+ }
2129
2139
  // Both upload destinations (appDataFolder + personal) commonly fail with
2130
2140
  // the same underlying error (e.g., missing OAuth scope). When all failures
2131
2141
  // share one message, surface it once without per-location wrapping;
@@ -2172,7 +2182,12 @@ const classifyWaasErrorKind = (error)=>{
2172
2182
  // only if the deadline passes; per-attempt we just tag the kind.
2173
2183
  const classifyTransientPollError = (error)=>{
2174
2184
  const status = readHttpStatus(error);
2175
- if (status === 429 || status !== undefined && status >= 500) return 'transient_exhausted';
2185
+ // `NETWORK_ERROR_STATUS` (0) is "no response received" as transient as a 5xx.
2186
+ // It previously arrived here as 500 because handleAxiosError coerced every
2187
+ // unmapped failure to that, so it must stay classified as transient.
2188
+ if (status === 429 || status === NETWORK_ERROR_STATUS || status !== undefined && status >= 500) {
2189
+ return 'transient_exhausted';
2190
+ }
2176
2191
  return classifyWaasErrorKind(error);
2177
2192
  };
2178
2193
  // Builds a structured log payload tagged with the HTTPS transport, dropping
@@ -2188,18 +2203,35 @@ const buildHttpCeremonyMeta = (fields)=>_extends({
2188
2203
  * so they don't pollute error dashboards with user mistakes.
2189
2204
  */ const isExpectedUserError = (error)=>error instanceof InvalidPasswordError;
2190
2205
  /**
2191
- * Returns true for transient API errors that are already logged at the
2192
- * appropriate level by `handleAxiosError` but may be re-thrown as
2193
- * `WalletApiError` and caught again (e.g. by queue error handlers).
2194
- * Logging these at `warn` prevents double-counting them as real errors.
2195
- */ const isTransientApiError = (error)=>error instanceof WalletApiError && error.status === 429;
2206
+ * Reads the HTTP status off a `WalletApiError`.
2207
+ *
2208
+ * Matches on `name` as well as `instanceof` because core is inlined into more
2209
+ * than one bundle, and a second copy of the class makes `instanceof` fail
2210
+ * against the first which would silently defeat every downgrade below. An
2211
+ * error without a numeric status is treated as unrecognised (fail safe: stays
2212
+ * at the caller's fallback level).
2213
+ */ const getWalletApiErrorStatus = (error)=>{
2214
+ const isWalletApiError = error instanceof WalletApiError || error instanceof Error && error.name === 'WalletApiError';
2215
+ if (!isWalletApiError) return undefined;
2216
+ const { status } = error;
2217
+ return typeof status === 'number' ? status : undefined;
2218
+ };
2196
2219
  /**
2197
- * Returns true for missing/expired end-user auth (401). This is a normal
2198
- * session-lifecycle event (cookie/JWT expired, user must re-authenticate),
2199
- * not an actionable server error retrying cannot add credentials. Logged
2200
- * at `warn` so it stays queryable without firing error-level monitors. It was
2201
- * the dominant `Error in getWallet` catch-all contributor.
2202
- */ const isExpiredAuthError = (error)=>error instanceof WalletApiError && error.status === 401;
2220
+ * Returns true for client-class API errors (4xx, including 429 and 401)
2221
+ * surfaced as `WalletApiError`. A bad request, missing/invalid auth, forbidden,
2222
+ * or rate-limit is caused by the caller or is transient, so none of them are
2223
+ * actionable as backend errors the catch-site `logError` logs them at `warn`
2224
+ * to keep them out of error-level dashboards. 401 in particular was the
2225
+ * dominant `Error in getWallet` catch-all contributor.
2226
+ *
2227
+ * 5xx is deliberately NOT downgraded: those are genuine backend failures and
2228
+ * the 500 monitors depend on them staying at `error`. The `handleAxiosError`
2229
+ * HTTP-layer log also records them with fuller context, now that it no longer
2230
+ * collides with Datadog's reserved `status` field.
2231
+ */ const isDowngradableApiError = (error)=>{
2232
+ const status = getWalletApiErrorStatus(error);
2233
+ return status !== undefined && status < 500;
2234
+ };
2203
2235
  /**
2204
2236
  * Returns true for p-queue TimeoutError. These are transient and
2205
2237
  * retryable client-side, so logging at `warn` prevents them from
@@ -2219,15 +2251,93 @@ const CLIENT_NETWORK_ERROR_MESSAGES = new Set([
2219
2251
  const msg = error.message.toLowerCase();
2220
2252
  return CLIENT_NETWORK_ERROR_MESSAGES.has(msg) || msg.startsWith('webassembly compilation aborted: network error');
2221
2253
  };
2222
- const resolveLogLevel = (error, fallback)=>{
2223
- if (isExpectedUserError(error)) {
2224
- return 'info';
2225
- }
2226
- if (isTransientApiError(error) || isExpiredAuthError(error) || isQueueTimeoutError(error) || isClientNetworkError(error)) {
2227
- return 'warn';
2254
+ const classifyOne = (error)=>{
2255
+ if (isExpectedUserError(error)) return 'info';
2256
+ if (isDowngradableApiError(error) || isQueueTimeoutError(error) || isClientNetworkError(error)) return 'warn';
2257
+ return undefined;
2258
+ };
2259
+ /** Bounded so a self-referencing or cyclic `cause` chain cannot spin. */ const MAX_CAUSE_DEPTH = 5;
2260
+ /**
2261
+ * Single source of truth for "how bad is this error". Exported so callers
2262
+ * classify identically instead of hard-coding `logger.error` for every failure.
2263
+ *
2264
+ * Walks the `cause` chain, because the outermost error is frequently a wrapper
2265
+ * that carries no information about severity — a chain client throws
2266
+ * `new Error(ERROR_SIGN_MESSAGE, { cause })`, so classifying only the top link
2267
+ * sees a bare `Error` and falls through to `error`. That is how a Cloudflare WAF
2268
+ * 429 kept being reported as a hard SDK failure even after the direct call sites
2269
+ * were fixed: the wrapper, not the cause, was being classified.
2270
+ *
2271
+ * The first classifiable link wins, so the most specific known failure in the
2272
+ * chain decides.
2273
+ */ const resolveLogLevel = (error, fallback)=>{
2274
+ let current = error;
2275
+ for(let depth = 0; current !== undefined && current !== null && depth < MAX_CAUSE_DEPTH; depth += 1){
2276
+ const level = classifyOne(current);
2277
+ if (level) return level;
2278
+ const next = current instanceof Error ? current.cause : undefined;
2279
+ if (next === current) break;
2280
+ current = next;
2228
2281
  }
2229
2282
  return fallback;
2230
2283
  };
2284
+ const MAX_DESCRIBED_LENGTH = 200;
2285
+ /**
2286
+ * Builds a human-readable message for a thrown value that is not an `Error` —
2287
+ * most often an API response body, e.g.
2288
+ * `{ existingWalletId, error: 'Multiple wallets per chain not allowed' }`.
2289
+ *
2290
+ * `String(value)` would collapse that to `[object Object]`, losing the only
2291
+ * diagnostic content it had, so prefer a recognisable message field and fall
2292
+ * back to bounded JSON.
2293
+ */ const describeNonError = (value)=>{
2294
+ if (typeof value === 'string') return value.slice(0, MAX_DESCRIBED_LENGTH);
2295
+ if (value !== null && typeof value === 'object') {
2296
+ const record = value;
2297
+ const named = [
2298
+ record['error'],
2299
+ record['message'],
2300
+ record['detail']
2301
+ ].find((candidate)=>typeof candidate === 'string' && candidate.length > 0);
2302
+ if (named) return named.slice(0, MAX_DESCRIBED_LENGTH);
2303
+ try {
2304
+ return JSON.stringify(value).slice(0, MAX_DESCRIBED_LENGTH);
2305
+ } catch (e) {
2306
+ // Circular or non-serialisable — fall through to String().
2307
+ }
2308
+ }
2309
+ return String(value).slice(0, MAX_DESCRIBED_LENGTH);
2310
+ };
2311
+ /**
2312
+ * Decides how an operation failure reaches the Datadog logger, whose signature
2313
+ * is `(message, context, error)`.
2314
+ *
2315
+ * The rule that matters: a non-`Error` value must never be passed as `context`.
2316
+ * Datadog spreads a context object into top-level log attributes, so a thrown
2317
+ * API response body carrying `status: 500` lands on Datadog's reserved `status`
2318
+ * field and silently rewrites the log's own severity to `info` — the failure
2319
+ * then appears in neither the error nor the warn stream. Non-Errors are nested
2320
+ * under `operationError` instead, and a real `Error` is synthesised so the log
2321
+ * still gets a stack and an `error.kind`.
2322
+ *
2323
+ * Returned as a plain object so this is unit-testable without constructing a
2324
+ * wallet client (which pulls the MPC WASM bundle).
2325
+ */ const buildOperationFailureLog = (error)=>{
2326
+ // Classify the original value: an Error's `cause` chain carries the severity,
2327
+ // and normalising first would discard it.
2328
+ const level = resolveLogLevel(error, 'error');
2329
+ return error instanceof Error ? {
2330
+ level,
2331
+ context: undefined,
2332
+ error
2333
+ } : {
2334
+ level,
2335
+ context: {
2336
+ operationError: error
2337
+ },
2338
+ error: new Error(describeNonError(error))
2339
+ };
2340
+ };
2231
2341
  const logError = ({ message, error, context, level = 'error' })=>{
2232
2342
  if (error instanceof AxiosError) {
2233
2343
  handleAxiosError(error, message, context);
@@ -2991,6 +3101,17 @@ class DynamicWalletClient {
2991
3101
  return this.walletMap[normalizeAddress(accountAddress)];
2992
3102
  }
2993
3103
  /**
3104
+ * Sync variant of {@link requireWalletFromMap} (no refetch). Replaces the
3105
+ * `getWalletFromMap(...)!` non-null assertions that surfaced a missing
3106
+ * wallet as an opaque property-access TypeError at some later line.
3107
+ */ getRequiredWalletFromMap(accountAddress) {
3108
+ const walletData = this.getWalletFromMap(accountAddress);
3109
+ if (!walletData) {
3110
+ throw new Error(`Wallet not found for address: ${accountAddress}`);
3111
+ }
3112
+ return walletData;
3113
+ }
3114
+ /**
2994
3115
  * Get wallet properties from the map, refetching once if not found.
2995
3116
  * Uses getWallet (with NO_OPERATION) when signedSessionId is available for a
2996
3117
  * more robust fetch, otherwise falls back to getWallets().
@@ -4883,7 +5004,7 @@ class DynamicWalletClient {
4883
5004
  signedSessionId
4884
5005
  });
4885
5006
  const walletId = wallet.walletId;
4886
- const currentScheme = this.getWalletFromMap(accountAddress).thresholdSignatureScheme;
5007
+ const currentScheme = this.getRequiredWalletFromMap(accountAddress).thresholdSignatureScheme;
4887
5008
  const bitcoinConfig = this.getBitcoinConfigForChain(chainName, accountAddress);
4888
5009
  // Same-parties reshare — only the caller's share participates.
4889
5010
  const [existingClientShare] = await this.ensureClientShare(accountAddress);
@@ -5798,7 +5919,7 @@ class DynamicWalletClient {
5798
5919
  useShareSetReshare,
5799
5920
  initialSignerRules
5800
5921
  });
5801
- const backupInfo = this.getWalletFromMap(accountAddress).clientKeySharesBackupInfo;
5922
+ const backupInfo = this.getRequiredWalletFromMap(accountAddress).clientKeySharesBackupInfo;
5802
5923
  const delegatedKeyShares = backupInfo.backups[BackupLocation.DELEGATED] || [];
5803
5924
  return delegatedKeyShares;
5804
5925
  }
@@ -6261,7 +6382,7 @@ class DynamicWalletClient {
6261
6382
  if (shares.length > 1) {
6262
6383
  this.logger.warn('[DynamicWaasWalletClient] Multiple client key shares found in storage', {
6263
6384
  accountAddress,
6264
- source,
6385
+ shareSource: source,
6265
6386
  count: shares.length
6266
6387
  });
6267
6388
  }
@@ -6312,7 +6433,7 @@ class DynamicWalletClient {
6312
6433
  */ logSharePersistence({ accountAddress, clientKeyShares, source }) {
6313
6434
  this.logger.info('[DynamicWaasWalletClient] Persisting client key shares', {
6314
6435
  accountAddress,
6315
- source,
6436
+ shareSource: source,
6316
6437
  inputCount: clientKeyShares.length
6317
6438
  });
6318
6439
  }
@@ -6819,8 +6940,8 @@ class DynamicWalletClient {
6819
6940
  } catch (error) {
6820
6941
  var _error_cause_context;
6821
6942
  const errorReason = classifyPasswordBackupError(error);
6822
- const logFn = isUserActionablePasswordBackupErrorReason(errorReason) ? this.logger.warn : this.logger.error;
6823
- logFn.call(this.logger, '[backupSharesWithDistribution] failed', _extends({}, logContext, {
6943
+ const isUserActionable = isUserActionablePasswordBackupErrorReason(errorReason);
6944
+ const logPayload = _extends({}, logContext, {
6824
6945
  chainName: walletData == null ? void 0 : walletData.chainName,
6825
6946
  errorReason,
6826
6947
  errorName: error instanceof Error ? error.name : undefined,
@@ -6830,7 +6951,12 @@ class DynamicWalletClient {
6830
6951
  // failures show the underlying KeyShareDecryptionError / kdfVersion.
6831
6952
  errorCauseName: error instanceof Error && error.cause instanceof Error ? error.cause.name : undefined,
6832
6953
  kdfVersion: error instanceof Error && error.cause instanceof KeyShareDecryptionError ? (_error_cause_context = error.cause.context) == null ? void 0 : _error_cause_context.kdfVersion : undefined
6833
- }));
6954
+ });
6955
+ if (isUserActionable) {
6956
+ this.logger.warn('[backupSharesWithDistribution] failed', logPayload);
6957
+ } else {
6958
+ this.logger.error('[backupSharesWithDistribution] failed', logPayload);
6959
+ }
6834
6960
  logError({
6835
6961
  message: 'Error in backupSharesWithDistribution',
6836
6962
  error: error,
@@ -7081,9 +7207,9 @@ class DynamicWalletClient {
7081
7207
  // they don't count against the dashboard's System-side Success Rate metric.
7082
7208
  logPasswordOperationFailure(passwordOperation, error, context) {
7083
7209
  const errorReason = classifyPasswordBackupError(error);
7084
- const logFn = isUserActionablePasswordBackupErrorReason(errorReason) ? this.logger.warn : this.logger.error;
7210
+ const isUserActionable = isUserActionablePasswordBackupErrorReason(errorReason);
7085
7211
  const errorContext = error instanceof KeyShareDecryptionError ? error.context : undefined;
7086
- logFn.call(this.logger, `[${passwordOperation}] failed`, _extends({}, context, {
7212
+ const logPayload = _extends({}, context, {
7087
7213
  passwordOperation,
7088
7214
  errorReason,
7089
7215
  environmentId: this.environmentId,
@@ -7093,7 +7219,12 @@ class DynamicWalletClient {
7093
7219
  errorStack: error instanceof Error ? error.stack : undefined
7094
7220
  }, errorContext ? {
7095
7221
  errorContext
7096
- } : {}));
7222
+ } : {});
7223
+ if (isUserActionable) {
7224
+ this.logger.warn(`[${passwordOperation}] failed`, logPayload);
7225
+ } else {
7226
+ this.logger.error(`[${passwordOperation}] failed`, logPayload);
7227
+ }
7097
7228
  }
7098
7229
  async updatePassword({ accountAddress, existingPassword, newPassword, signedSessionId, sessionPublicKey, passwordUpdateBatchId }) {
7099
7230
  const dynamicRequestId = v4();
@@ -7408,7 +7539,14 @@ class DynamicWalletClient {
7408
7539
  */ recoverStrategy({ clientKeyShareBackupInfo, thresholdSignatureScheme, walletOperation, shareCount = undefined }) {
7409
7540
  var _clientKeyShareBackupInfo_backups_BackupLocation_DYNAMIC;
7410
7541
  const { backups } = clientKeyShareBackupInfo;
7411
- const { clientThreshold } = MPC_CONFIG[thresholdSignatureScheme];
7542
+ // Wallet metadata can carry a missing/unknown scheme (e.g. legacy or
7543
+ // partially-created wallets); indexing MPC_CONFIG with it used to throw a
7544
+ // bare destructuring TypeError that hid the actual problem.
7545
+ const mpcConfig = MPC_CONFIG[thresholdSignatureScheme];
7546
+ if (!mpcConfig) {
7547
+ throw new Error(`Unsupported or missing thresholdSignatureScheme: ${String(thresholdSignatureScheme)}`);
7548
+ }
7549
+ const { clientThreshold } = mpcConfig;
7412
7550
  let requiredShareCount = walletOperation === WalletOperation.REFRESH || walletOperation === WalletOperation.REACH_ALL_PARTIES || walletOperation === WalletOperation.RESHARE ? clientThreshold : 1;
7413
7551
  // Override requiredShareCount if shareCount is provided
7414
7552
  if (shareCount !== undefined) {
@@ -7477,7 +7615,7 @@ class DynamicWalletClient {
7477
7615
  }
7478
7616
  async internalRecoverEncryptedBackupByWallet({ accountAddress, password, walletOperation, signedSessionId, shareCount = undefined, storeRecoveredShares = true, mfaToken }) {
7479
7617
  try {
7480
- const wallet = this.getWalletFromMap(accountAddress);
7618
+ const wallet = this.getRequiredWalletFromMap(accountAddress);
7481
7619
  this.logger.debug(`recoverEncryptedBackupByWallet wallet: ${walletOperation}`, wallet);
7482
7620
  const { shares } = this.recoverStrategy({
7483
7621
  clientKeyShareBackupInfo: wallet.clientKeySharesBackupInfo,
@@ -7586,7 +7724,7 @@ class DynamicWalletClient {
7586
7724
  password,
7587
7725
  signedSessionId
7588
7726
  });
7589
- const walletData = this.getWalletFromMap(accountAddress);
7727
+ const walletData = this.getRequiredWalletFromMap(accountAddress);
7590
7728
  const currentThresholdSignatureScheme = walletData.thresholdSignatureScheme;
7591
7729
  if (currentThresholdSignatureScheme === ThresholdSignatureScheme.TWO_OF_TWO) {
7592
7730
  // Reshare to 2-of-3, which will automatically handle the backup distribution
@@ -7645,7 +7783,7 @@ class DynamicWalletClient {
7645
7783
  * every provider already holding a backup plus the requested one,
7646
7784
  * restricted to providers the reshare can deliver to.
7647
7785
  */ async reshareNonDelegatedTwoOfThree({ accountAddress, backupLocation, password, signedSessionId, sessionPublicKey, googleDriveAccessToken, googleDriveTokenSource }) {
7648
- const walletData = this.getWalletFromMap(accountAddress);
7786
+ const walletData = this.getRequiredWalletFromMap(accountAddress);
7649
7787
  const cloudProviders = new Set([
7650
7788
  ...getActiveCloudProviders(walletData.clientKeySharesBackupInfo).filter((provider)=>provider === BackupLocation.GOOGLE_DRIVE || provider === BackupLocation.ICLOUD),
7651
7789
  backupLocation
@@ -7784,14 +7922,18 @@ class DynamicWalletClient {
7784
7922
  // misuse of an externally-supplied token (e.g. repeated failures) is
7785
7923
  // traceable, not just completed backups. Enum only; the original error
7786
7924
  // still propagates after this. Failures log at warn to surface them.
7787
- const logFn = success ? this.logger.info : this.logger.warn;
7788
- logFn.call(this.logger, '[DynamicWaasWalletClient] Google Drive backup token source', {
7925
+ const logPayload = {
7789
7926
  accountAddress,
7790
7927
  environmentId: this.environmentId,
7791
7928
  userId: this.userId,
7792
7929
  tokenSource,
7793
7930
  success
7794
- });
7931
+ };
7932
+ if (success) {
7933
+ this.logger.info('[DynamicWaasWalletClient] Google Drive backup token source', logPayload);
7934
+ } else {
7935
+ this.logger.warn('[DynamicWaasWalletClient] Google Drive backup token source', logPayload);
7936
+ }
7795
7937
  }
7796
7938
  }
7797
7939
  /**
@@ -7810,8 +7952,7 @@ class DynamicWalletClient {
7810
7952
  ]);
7811
7953
  const { blocking } = preflightResult;
7812
7954
  if (blocking) {
7813
- const logFn = blocking.isUserActionable ? this.logger.warn : this.logger.error;
7814
- logFn.call(this.logger, '[DynamicWaasWalletClient] Google Drive backup failed (preflight)', {
7955
+ const logPayload = {
7815
7956
  accountAddress,
7816
7957
  environmentId: this.environmentId,
7817
7958
  userId: this.userId,
@@ -7822,8 +7963,13 @@ class DynamicWalletClient {
7822
7963
  `Preflight (${blocking.source}): ${blocking.message}`
7823
7964
  ],
7824
7965
  preflight: true,
7825
- source: blocking.source
7826
- });
7966
+ preflightSource: blocking.source
7967
+ };
7968
+ if (blocking.isUserActionable) {
7969
+ this.logger.warn('[DynamicWaasWalletClient] Google Drive backup failed (preflight)', logPayload);
7970
+ } else {
7971
+ this.logger.error('[DynamicWaasWalletClient] Google Drive backup failed (preflight)', logPayload);
7972
+ }
7827
7973
  throw createGoogleDriveError({
7828
7974
  message: blocking.message,
7829
7975
  isRetryable: false,
@@ -7939,7 +8085,7 @@ class DynamicWalletClient {
7939
8085
  if (encryptedKeyShares.length === 0) {
7940
8086
  throw new Error('No key shares found');
7941
8087
  }
7942
- const thresholdSignatureScheme = this.getWalletFromMap(accountAddress).thresholdSignatureScheme;
8088
+ const thresholdSignatureScheme = this.getRequiredWalletFromMap(accountAddress).thresholdSignatureScheme;
7943
8089
  const backupData = createBackupData({
7944
8090
  encryptedKeyShares,
7945
8091
  accountAddress,
@@ -7966,7 +8112,7 @@ class DynamicWalletClient {
7966
8112
  signedSessionId
7967
8113
  });
7968
8114
  const accessToken = googleDriveAccessToken != null ? googleDriveAccessToken : await this.fetchGoogleDriveAccessToken(accountAddress);
7969
- const thresholdSignatureScheme = this.getWalletFromMap(accountAddress).thresholdSignatureScheme;
8115
+ const thresholdSignatureScheme = this.getRequiredWalletFromMap(accountAddress).thresholdSignatureScheme;
7970
8116
  const backupFileName = getClientKeyShareExportFileName({
7971
8117
  thresholdSignatureScheme,
7972
8118
  accountAddress,
@@ -8186,7 +8332,7 @@ class DynamicWalletClient {
8186
8332
  });
8187
8333
  const { requiredShareCount } = this.recoverStrategy({
8188
8334
  clientKeyShareBackupInfo: clientKeySharesBackupInfo,
8189
- thresholdSignatureScheme: this.getWalletFromMap(accountAddress).thresholdSignatureScheme,
8335
+ thresholdSignatureScheme: this.getRequiredWalletFromMap(accountAddress).thresholdSignatureScheme,
8190
8336
  walletOperation
8191
8337
  });
8192
8338
  if (clientKeyShares.length >= requiredShareCount) {
@@ -8291,7 +8437,7 @@ class DynamicWalletClient {
8291
8437
  accountAddress,
8292
8438
  walletOperation
8293
8439
  })) {
8294
- const walletData = this.getWalletFromMap(accountAddress);
8440
+ const walletData = this.getRequiredWalletFromMap(accountAddress);
8295
8441
  const isPasswordEncrypted = isWalletPasswordEncrypted(walletData);
8296
8442
  // Locked wallet (password-encrypted, no password supplied): its shares need the
8297
8443
  // user's password, not the environmentId. Return it ENCRYPTED for any operation
@@ -8596,7 +8742,7 @@ class DynamicWalletClient {
8596
8742
  additionalWalletsUnlocked,
8597
8743
  additionalWalletsFailed
8598
8744
  });
8599
- return this.getWalletFromMap(accountAddress);
8745
+ return this.getRequiredWalletFromMap(accountAddress);
8600
8746
  } catch (error) {
8601
8747
  this.logPasswordOperationFailure('unlockWallet', error, {
8602
8748
  accountAddress
@@ -9043,6 +9189,16 @@ class DynamicWalletClient {
9043
9189
  time: (traceContext == null ? void 0 : traceContext.startTime) ? now - traceContext.startTime : 0
9044
9190
  }, traceContext);
9045
9191
  }
9192
+ /**
9193
+ * Log a chain-client operation failure at a severity that matches the
9194
+ * underlying cause. Logs through `this.logger` (not `logError`) so the
9195
+ * structured `error.causes` chain survives — that chain is what makes these
9196
+ * failures diagnosable — while still honouring the shared level policy, so a
9197
+ * transient 429/401/network blip does not read as a hard SDK error.
9198
+ */ logOperationFailure(message, error) {
9199
+ const { level, context, error: normalizedError } = buildOperationFailureLog(error);
9200
+ this.logger[level](message, context, normalizedError);
9201
+ }
9046
9202
  constructor({ environmentId, baseApiUrl, baseMPCRelayApiUrl, storageKey, debug, featureFlags, argon2idRebackupTargets, authMode = AuthMode.HEADER, authToken = undefined, backupServiceAuthToken, // Represents the version of the client SDK used by developer
9047
9203
  sdkVersion, forwardMPCClient, baseClientKeysharesRelayApiUrl, iCloudConfig, logger }, internalOptions){
9048
9204
  this.userId = undefined;
@@ -9146,4 +9302,4 @@ DynamicWalletClient.roomsPersistDirty = false;
9146
9302
  // rooms back into the new user's storage (TOCTOU on the create→merge gap).
9147
9303
  DynamicWalletClient.roomsGeneration = 0;
9148
9304
 
9149
- 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, getGoogleOAuthAccountId, getHttpStatus, getICloudBackup, getMPCSignatureScheme, getMPCSigner, hasCloudProviderBackup, hasDelegatedBackup, hasEncryptedSharesCached, initializeCloudKit, isBrowser, isHeavyQueueOperation, isHexString, isICloudAuthenticated, isNonRetryableCeremonyError, isPublicKeyMismatchError, isRecoverQueueOperation, isRoomIdAlreadyUsedError, isSignQueueOperation, isStaleClientSharesError, listICloudBackups, markCeremonyErrorNonRetryable, readEnvironmentSettings, retryPromise, shouldReshareToSameBackups, timeoutPromise };
9305
+ 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, getGoogleOAuthAccountId, getHttpStatus, getICloudBackup, getMPCSignatureScheme, getMPCSigner, hasCloudProviderBackup, hasDelegatedBackup, hasEncryptedSharesCached, initializeCloudKit, isBrowser, isHeavyQueueOperation, isHexString, isICloudAuthenticated, isNonRetryableCeremonyError, isPublicKeyMismatchError, isRecoverQueueOperation, isRoomIdAlreadyUsedError, isSignQueueOperation, isStaleClientSharesError, listICloudBackups, markCeremonyErrorNonRetryable, readEnvironmentSettings, resolveLogLevel, retryPromise, shouldReshareToSameBackups, timeoutPromise };