@dynamic-labs-wallet/node 1.1.33 → 1.1.35

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 CHANGED
@@ -10,6 +10,13 @@ var crypto = require('node:crypto');
10
10
  var logger = require('@dynamic-labs/logger');
11
11
  var sdkApiCore = require('@dynamic-labs/sdk-api-core');
12
12
 
13
+ class ClientValidationError extends TypeError {
14
+ constructor(message, options){
15
+ super(message, options);
16
+ this.name = 'ClientValidationError';
17
+ }
18
+ }
19
+
13
20
  // Removed duplicate exports - these are already exported from #internal/core
14
21
  const getMPCSignatureScheme = ({ signingAlgorithm, baseRelayUrl = core$1.MPC_RELAY_PROD_API_URL, variant = core$1.DEFAULT_ED25519_VARIANT })=>{
15
22
  switch(signingAlgorithm){
@@ -81,6 +88,180 @@ function _object_without_properties_loose(source, excluded) {
81
88
  }
82
89
  }
83
90
 
91
+ var version = "1.1.35";
92
+
93
+ const MAX_CAUSE_CHAIN_LENGTH = 4;
94
+ const SAFE_ERROR_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/;
95
+ const TRANSPORT_ERROR_CODES = new Set([
96
+ 'EAI_AGAIN',
97
+ 'ECONNREFUSED',
98
+ 'ECONNRESET',
99
+ 'EHOSTUNREACH',
100
+ 'ENETUNREACH',
101
+ 'ETIMEDOUT',
102
+ 'UND_ERR_CONNECT_TIMEOUT'
103
+ ]);
104
+ const getSafeErrorName = (error)=>SAFE_ERROR_NAME_PATTERN.test(error.name) ? error.name : 'Error';
105
+ const normalizeContext = (context)=>{
106
+ const backendReached = context == null ? void 0 : context['backendReached'];
107
+ const dynamicRequestId = context == null ? void 0 : context['dynamicRequestId'];
108
+ const failurePhase = context == null ? void 0 : context['failurePhase'];
109
+ const traceId = context == null ? void 0 : context['traceId'];
110
+ return _extends({}, typeof backendReached === 'boolean' ? {
111
+ backendReached
112
+ } : {}, typeof dynamicRequestId === 'string' ? {
113
+ dynamicRequestId
114
+ } : {}, typeof failurePhase === 'string' ? {
115
+ failurePhase
116
+ } : {}, typeof traceId === 'string' ? {
117
+ traceId
118
+ } : {});
119
+ };
120
+ const getHeader = (error, name)=>{
121
+ var _error_config;
122
+ const headers = (_error_config = error.config) == null ? void 0 : _error_config.headers;
123
+ if (!headers) {
124
+ return undefined;
125
+ }
126
+ const get = headers.get;
127
+ let value;
128
+ if (typeof get === 'function') {
129
+ value = get.call(headers, name);
130
+ } else {
131
+ const headerRecord = headers;
132
+ const matchingKey = Object.keys(headerRecord).find((key)=>key.toLowerCase() === name.toLowerCase());
133
+ value = matchingKey ? headerRecord[matchingKey] : undefined;
134
+ }
135
+ return typeof value === 'string' ? value : undefined;
136
+ };
137
+ const getSafeRequestUrl = (error)=>{
138
+ var _error_config;
139
+ const requestUrl = (_error_config = error.config) == null ? void 0 : _error_config.url;
140
+ if (!requestUrl) {
141
+ return undefined;
142
+ }
143
+ try {
144
+ var _error_config1;
145
+ const url = new URL(requestUrl, (_error_config1 = error.config) == null ? void 0 : _error_config1.baseURL);
146
+ return url.protocol === 'https:' || url.protocol === 'http:' ? url.origin : undefined;
147
+ } catch (e) {
148
+ return undefined;
149
+ }
150
+ };
151
+ const getErrorCauseChain = (error)=>{
152
+ const chain = [];
153
+ const visited = new Set();
154
+ let current = error instanceof Error ? error : undefined;
155
+ while(current && chain.length < MAX_CAUSE_CHAIN_LENGTH && !visited.has(current)){
156
+ chain.push(current);
157
+ visited.add(current);
158
+ current = current.cause instanceof Error ? current.cause : undefined;
159
+ }
160
+ return chain;
161
+ };
162
+ const serializeError = (error)=>{
163
+ let serialized;
164
+ for (const current of getErrorCauseChain(error).reverse()){
165
+ serialized = _extends({
166
+ name: getSafeErrorName(current)
167
+ }, serialized ? {
168
+ cause: serialized
169
+ } : {});
170
+ }
171
+ return serialized != null ? serialized : {
172
+ name: 'Error'
173
+ };
174
+ };
175
+ const findErrorInCauseChain = (error, predicate)=>getErrorCauseChain(error).find(predicate);
176
+ const isWalletApiError = (error)=>error instanceof core$1.WalletApiError;
177
+ const isClientValidationErrorInstance = (error)=>error instanceof ClientValidationError;
178
+ const isAxiosErrorInstance = (error)=>core$1.isAxiosError(error);
179
+ const hasTransportErrorCode = (error)=>{
180
+ const code = error.code;
181
+ return typeof code === 'string' && TRANSPORT_ERROR_CODES.has(code);
182
+ };
183
+ const getHttpFailureCategory = (statusCode, isServerFailure)=>{
184
+ if (statusCode === undefined) {
185
+ return 'transport';
186
+ }
187
+ return isServerFailure ? 'backend' : 'unknown';
188
+ };
189
+ const getFailureCategory = (isClientValidation, isNetworkFailure, phase)=>{
190
+ if (isClientValidation) {
191
+ return 'client_validation';
192
+ }
193
+ if (isNetworkFailure) {
194
+ return 'transport';
195
+ }
196
+ return (phase == null ? void 0 : phase.startsWith('mpc_')) ? 'mpc_protocol' : 'unknown';
197
+ };
198
+ const classifyAxiosError = (error, context, outerError)=>{
199
+ var _error_response, _error_config_method, _error_config;
200
+ const statusCode = (_error_response = error.response) == null ? void 0 : _error_response.status;
201
+ var _context_backendReached;
202
+ const backendReached = (_context_backendReached = context.backendReached) != null ? _context_backendReached : statusCode !== undefined;
203
+ const isServerFailure = statusCode !== undefined && statusCode >= 500;
204
+ const category = getHttpFailureCategory(statusCode, isServerFailure);
205
+ const requestUrl = getSafeRequestUrl(error);
206
+ const requestMethod = (_error_config = error.config) == null ? void 0 : (_error_config_method = _error_config.method) == null ? void 0 : _error_config_method.toUpperCase();
207
+ const http = _extends({}, requestMethod ? {
208
+ method: requestMethod
209
+ } : {}, statusCode !== undefined ? {
210
+ status_code: statusCode
211
+ } : {}, requestUrl ? {
212
+ url: requestUrl
213
+ } : {});
214
+ var _getHeader, _getHeader1;
215
+ return _extends({
216
+ dynRequestId: (_getHeader = getHeader(error, 'x-dyn-request-id')) != null ? _getHeader : context.dynamicRequestId,
217
+ error: serializeError(outerError),
218
+ failure: {
219
+ actionable: statusCode === undefined || isServerFailure,
220
+ backendReached,
221
+ category,
222
+ phase: context.failurePhase,
223
+ retryable: statusCode === undefined || statusCode === 429 || isServerFailure
224
+ }
225
+ }, Object.keys(http).length > 0 ? {
226
+ http
227
+ } : {}, {
228
+ sdkVersion: version,
229
+ traceId: (_getHeader1 = getHeader(error, 'x-dyn-trace-id')) != null ? _getHeader1 : context.traceId
230
+ });
231
+ };
232
+ const buildForwardedErrorTelemetry = (error, context)=>{
233
+ const normalizedError = error instanceof Error ? error : undefined;
234
+ const telemetryContext = normalizeContext(context);
235
+ const axiosError = findErrorInCauseChain(normalizedError, isAxiosErrorInstance);
236
+ if (normalizedError && axiosError) {
237
+ return classifyAxiosError(axiosError, telemetryContext, normalizedError);
238
+ }
239
+ const { backendReached: contextBackendReached, dynamicRequestId, failurePhase: phase, traceId } = telemetryContext;
240
+ const isClientValidation = Boolean(findErrorInCauseChain(normalizedError, isClientValidationErrorInstance));
241
+ const walletApiError = findErrorInCauseChain(normalizedError, isWalletApiError);
242
+ const isNetworkFailure = (walletApiError == null ? void 0 : walletApiError.code) === core$1.NETWORK_ERROR_CODE || Boolean(findErrorInCauseChain(normalizedError, hasTransportErrorCode));
243
+ const category = getFailureCategory(isClientValidation, isNetworkFailure, phase);
244
+ return _extends({}, dynamicRequestId ? {
245
+ dynRequestId: dynamicRequestId
246
+ } : {}, normalizedError ? {
247
+ error: serializeError(normalizedError)
248
+ } : {}, {
249
+ failure: _extends({
250
+ actionable: !isClientValidation,
251
+ backendReached: isClientValidation ? false : contextBackendReached != null ? contextBackendReached : false,
252
+ category
253
+ }, phase ? {
254
+ phase
255
+ } : {}, {
256
+ retryable: isNetworkFailure
257
+ }),
258
+ sdkVersion: version
259
+ }, traceId ? {
260
+ traceId
261
+ } : {});
262
+ };
263
+ const isClientValidationError = (error)=>Boolean(findErrorInCauseChain(error instanceof Error ? error : undefined, isClientValidationErrorInstance));
264
+
84
265
  const createLogError = (clientTag)=>({ message, error, context })=>logError({
85
266
  message,
86
267
  error,
@@ -92,11 +273,18 @@ const logError = ({ message, error, context, clientTag = 'node' })=>{
92
273
  core$1.handleAxiosError(error, message, context);
93
274
  return;
94
275
  }
95
- core$1.Logger.error(`[DynamicWaasWalletClient] Error in ${clientTag} client`, {
276
+ const logMessage = `[DynamicWaasWalletClient] Error in ${clientTag} client`;
277
+ if (isClientValidationError(error)) {
278
+ core$1.Logger.warn(logMessage, _extends({
279
+ message,
280
+ context
281
+ }, buildForwardedErrorTelemetry(error, context)));
282
+ return;
283
+ }
284
+ core$1.Logger.error(logMessage, {
96
285
  message,
97
- error: error instanceof Error ? error.message : String(error),
98
286
  context
99
- });
287
+ }, error);
100
288
  };
101
289
  const bytesToBase64 = (arr)=>{
102
290
  return Buffer.from(arr).toString('base64');
@@ -409,24 +597,28 @@ const getKey = async ({ password, salt, encryptionConfig })=>{
409
597
  // walletId resolution server-side.
410
598
  const CEREMONY_COMPLETE_WAIT_MS = 5000;
411
599
 
412
- // Shared across all server-side WaaS SDKs (Node, Python, Rust, ...) — matches
413
- // cf-worker-log-ingestion's allowlisted `service` override (dynamic-labs/cf-worker-log-ingestion#13).
600
+ // Matches the ingestion worker's allowlisted server-SDK service override.
414
601
  const SERVICE_NAME = 'waas-server-sdk';
415
602
  const SDK_NAME = 'node';
416
- // A dedicated instance used only to reuse @dynamic-labs/logger's own HTTP
417
- // transport to logs.dynamicauth.com — never for console output. We call
418
- // `emitHttpLogs` directly rather than `.error()`, so the wrapped logger below
419
- // (the customer's own, or the default one) is the only thing that ever prints
420
- // to the local console; this instance's console output is simply unused.
421
- const forwardingLogger = new logger.Logger('waas-server-sdk');
603
+ const ScopedLogger = logger.Logger;
604
+ const forwardingLogger = new ScopedLogger(SERVICE_NAME, undefined, {
605
+ emitErrors: false
606
+ });
422
607
  const extractErrorAndContext = (args)=>{
423
608
  let error;
424
609
  let context;
425
- for (const arg of args){
610
+ for (const _ref of args){
611
+ let arg = _ref;
426
612
  if (arg instanceof Error) {
427
613
  error = arg;
428
614
  } else if (arg && typeof arg === 'object') {
429
- context = _extends({}, context, arg);
615
+ const { error: contextError } = arg, safeContext = _object_without_properties_loose(arg, [
616
+ "error"
617
+ ]);
618
+ if (contextError instanceof Error) {
619
+ error = contextError;
620
+ }
621
+ context = _extends({}, context, safeContext);
430
622
  }
431
623
  }
432
624
  return {
@@ -434,52 +626,39 @@ const extractErrorAndContext = (args)=>{
434
626
  context
435
627
  };
436
628
  };
437
- /**
438
- * Wraps an ILogger so `.error()` calls are also forwarded to Dynamic's
439
- * log-ingestion endpoint via `@dynamic-labs/logger`'s own HTTP transport
440
- * (dynamic-labs/dynamic-auth#12443 makes that transport Node-safe), in
441
- * addition to whatever the wrapped logger already does. Every other level
442
- * passes through untouched. Credential/key-material redaction is not
443
- * duplicated here — `emitHttpLogs` already runs every forwarded field through
444
- * `@dynamic-labs/redaction` before it leaves the process.
445
- *
446
- * Deliberately does NOT call `DynamicLogger.setEmitErrors(true)`: that flag is
447
- * static and process-global, so flipping it would make the *wrapped* logger's
448
- * own `.error()` call (two lines below) auto-forward too — double-sending
449
- * every error, and breaking a later client's `errorLogging: false` opt-out
450
- * for as long as any other client in the process has forwarding enabled.
451
- * `emitHttpLogs` below is a direct call and works regardless of that flag.
452
- *
453
- * IMPORTANT — process-global state: `environmentId` IS still static/shared
454
- * (`DynamicLogger.setEnvironmentId`), so a process that constructs multiple
455
- * `DynamicWalletClient`s for *different* `environmentId`s will have every one
456
- * of them forward under whichever environmentId was set last — there is no
457
- * per-instance scoping. This is a real regression versus the fetch-based
458
- * implementation, which threads `environmentId` through every call explicitly.
459
- */ const createErrorForwardingLogger = (logger$1, { environmentId })=>{
629
+ const disableAutomaticErrorForwarding = (logger)=>{
630
+ const configurableLogger = logger;
631
+ configurableLogger.setEmitErrors == null ? void 0 : configurableLogger.setEmitErrors.call(configurableLogger, false);
632
+ };
633
+ /** Adds one redacted server event per root error while preserving local logger output. */ const createErrorForwardingLogger = (logger$1, { environmentId })=>{
634
+ disableAutomaticErrorForwarding(logger$1);
635
+ const forwardedErrors = new WeakSet();
460
636
  return {
461
637
  debug: logger$1.debug.bind(logger$1),
462
638
  info: logger$1.info.bind(logger$1),
463
639
  warn: logger$1.warn.bind(logger$1),
464
640
  error: (message, ...args)=>{
465
- logger$1.error(message, ...args);
466
641
  const { error, context } = extractErrorAndContext(args);
467
- logger.Logger.setEnvironmentId(environmentId);
468
- // `service`/`sdk` are passed as call-level args (merged onto the
469
- // top-level forwarded body by emitHttpLogs), not via `.metaData.set()`
470
- // (which nests under `meta.*`) — cf-worker-log-ingestion's `service`
471
- // override only checks the top-level field. Spread after, not before,
472
- // so a caller-supplied context field named `service`/`sdk` can't
473
- // override the trusted tag the ingestion worker relies on.
642
+ const telemetry = buildForwardedErrorTelemetry(error, context);
643
+ if (telemetry.failure.category === 'client_validation') {
644
+ logger$1.warn(message, _extends({}, context, telemetry), error);
645
+ return;
646
+ }
647
+ logger$1.error(message, ...args);
648
+ const errorChain = getErrorCauseChain(error);
649
+ if (errorChain.some((candidate)=>forwardedErrors.has(candidate))) {
650
+ return;
651
+ }
652
+ errorChain.forEach((candidate)=>forwardedErrors.add(candidate));
474
653
  forwardingLogger.emitHttpLogs(logger.LogLevel.ERROR, message, {
475
654
  args: [
476
655
  _extends({}, context, {
477
656
  service: SERVICE_NAME,
478
657
  sdk: SDK_NAME
479
- }, (error == null ? void 0 : error.stack) ? {
480
- originalStack: error.stack
481
- } : {})
482
- ]
658
+ }, telemetry)
659
+ ],
660
+ environmentId,
661
+ stackField: 'callsiteStack'
483
662
  });
484
663
  }
485
664
  };
@@ -843,11 +1022,18 @@ class DynamicWalletClient {
843
1022
  }));
844
1023
  return data;
845
1024
  } catch (error) {
846
- this.logger.error('Error in dynamicServerInitializeKeyGen', error);
847
- throw new Error('Error creating wallet account in dynamicServerInitializeKeyGen');
1025
+ this.logger.error('Error in dynamicServerInitializeKeyGen', {
1026
+ backendReached: true,
1027
+ dynamicRequestId: createWalletAccountRequest.dynamicRequestId,
1028
+ failurePhase: 'backend_keygen',
1029
+ operation: 'keygen'
1030
+ }, error);
1031
+ throw new Error('Error creating wallet account in dynamicServerInitializeKeyGen', {
1032
+ cause: error
1033
+ });
848
1034
  }
849
1035
  }
850
- async externalServerInitializeKeyGen({ chainName, thresholdSignatureScheme, bitcoinConfig }) {
1036
+ async externalServerInitializeKeyGen({ chainName, dynamicRequestId, thresholdSignatureScheme, bitcoinConfig }) {
851
1037
  try {
852
1038
  // Get the mpc signer
853
1039
  const mpcSigner = getMPCSigner({
@@ -859,11 +1045,19 @@ class DynamicWalletClient {
859
1045
  const keygenInitResults = await Promise.all(new Array(clientThreshold).fill(null).map(()=>mpcSigner.initKeygen()));
860
1046
  return keygenInitResults;
861
1047
  } catch (error) {
862
- this.logger.error('Error in externalServerInitializeKeyGen', error);
863
- throw new Error('Error initializing keygen in externalServerInitializeKeyGen');
1048
+ this.logger.error('Error in externalServerInitializeKeyGen', {
1049
+ backendReached: false,
1050
+ chain: chainName,
1051
+ dynamicRequestId,
1052
+ failurePhase: 'mpc_keygen',
1053
+ operation: 'keygen'
1054
+ }, error);
1055
+ throw new Error('Error initializing keygen in externalServerInitializeKeyGen', {
1056
+ cause: error
1057
+ });
864
1058
  }
865
1059
  }
866
- async derivePublicKey({ chainName, keyShare, derivationPath, bitcoinConfig }) {
1060
+ async derivePublicKey({ chainName, keyShare, derivationPath, bitcoinConfig, dynamicRequestId }) {
867
1061
  try {
868
1062
  const mpcSigner = getMPCSigner({
869
1063
  chainName,
@@ -880,11 +1074,19 @@ class DynamicWalletClient {
880
1074
  }
881
1075
  return publicKey;
882
1076
  } catch (error) {
883
- this.logger.error('Error in derivePublicKey', error);
884
- throw new Error('Error deriving public key in derivePublicKey');
1077
+ this.logger.error('Error in derivePublicKey', {
1078
+ backendReached: Boolean(dynamicRequestId),
1079
+ chain: chainName,
1080
+ dynamicRequestId,
1081
+ failurePhase: 'mpc_keygen',
1082
+ operation: 'keygen'
1083
+ }, error);
1084
+ throw new Error('Error deriving public key in derivePublicKey', {
1085
+ cause: error
1086
+ });
885
1087
  }
886
1088
  }
887
- async forwardMPCExternalServerKeyGen({ chainName, roomId, dynamicServerKeygenIds, externalServerInitKeygenResults, thresholdSignatureScheme, bitcoinConfig }) {
1089
+ async forwardMPCExternalServerKeyGen({ chainName, roomId, dynamicServerKeygenIds, externalServerInitKeygenResults, dynamicRequestId, thresholdSignatureScheme, bitcoinConfig }) {
888
1090
  try {
889
1091
  const chainConfig = core$1.getMPCChainConfig(chainName, bitcoinConfig);
890
1092
  const signingAlgo = chainConfig.signingAlgorithm;
@@ -918,7 +1120,8 @@ class DynamicWalletClient {
918
1120
  chainName,
919
1121
  keyShare: serverKeygenResult,
920
1122
  derivationPath,
921
- bitcoinConfig
1123
+ bitcoinConfig,
1124
+ dynamicRequestId
922
1125
  });
923
1126
  return {
924
1127
  rawPublicKey,
@@ -927,6 +1130,7 @@ class DynamicWalletClient {
927
1130
  } catch (error) {
928
1131
  this.logger.error('Error during forward MPC external server keygen', error, {
929
1132
  chainName,
1133
+ dynamicRequestId,
930
1134
  environmentId: this.environmentId
931
1135
  });
932
1136
  throw error;
@@ -934,7 +1138,7 @@ class DynamicWalletClient {
934
1138
  this.apiClient.forwardMPCClient.disconnect();
935
1139
  }
936
1140
  }
937
- async externalServerKeyGen({ chainName, roomId, dynamicServerKeygenIds, externalServerInitKeygenResults, thresholdSignatureScheme, bitcoinConfig }) {
1141
+ async externalServerKeyGen({ chainName, roomId, dynamicRequestId, dynamicServerKeygenIds, externalServerInitKeygenResults, thresholdSignatureScheme, bitcoinConfig }) {
938
1142
  // Try forward MPC first if enabled
939
1143
  if (this.forwardMPCEnabled && !this.shouldSkipForwardMPC(chainName)) {
940
1144
  try {
@@ -943,6 +1147,7 @@ class DynamicWalletClient {
943
1147
  roomId,
944
1148
  dynamicServerKeygenIds,
945
1149
  externalServerInitKeygenResults,
1150
+ dynamicRequestId,
946
1151
  thresholdSignatureScheme,
947
1152
  bitcoinConfig
948
1153
  });
@@ -992,15 +1197,24 @@ class DynamicWalletClient {
992
1197
  chainName,
993
1198
  keyShare: serverKeygenResult,
994
1199
  derivationPath,
995
- bitcoinConfig
1200
+ bitcoinConfig,
1201
+ dynamicRequestId
996
1202
  });
997
1203
  return {
998
1204
  rawPublicKey,
999
1205
  externalServerKeyGenResults: serverKeygenResults
1000
1206
  };
1001
1207
  } catch (error) {
1002
- this.logger.error('Error in externalServerKeyGen', error);
1003
- throw new Error('Error deriving public key in externalServerKeyGen');
1208
+ this.logger.error('Error in externalServerKeyGen', {
1209
+ backendReached: true,
1210
+ chain: chainName,
1211
+ dynamicRequestId,
1212
+ failurePhase: 'mpc_keygen',
1213
+ operation: 'keygen'
1214
+ }, error);
1215
+ throw new Error('Error deriving public key in externalServerKeyGen', {
1216
+ cause: error
1217
+ });
1004
1218
  }
1005
1219
  }
1006
1220
  validatePasswordForBackup({ password, backUpToDynamic }) {
@@ -1038,9 +1252,11 @@ class DynamicWalletClient {
1038
1252
  backUpToDynamic
1039
1253
  });
1040
1254
  const dynamicRequestId = uuid.v4();
1255
+ let backendReached = false;
1041
1256
  try {
1042
1257
  const externalServerInitKeygenResults = await this.externalServerInitializeKeyGen({
1043
1258
  chainName,
1259
+ dynamicRequestId,
1044
1260
  thresholdSignatureScheme,
1045
1261
  bitcoinConfig
1046
1262
  });
@@ -1053,12 +1269,14 @@ class DynamicWalletClient {
1053
1269
  skipLock,
1054
1270
  bitcoinConfig
1055
1271
  };
1272
+ backendReached = true;
1056
1273
  const { roomId, serverKeygenIds: dynamicServerKeygenIds } = await this.dynamicServerInitializeKeyGen(_extends({}, createWalletAccountRequest, {
1057
1274
  onCeremonyComplete
1058
1275
  }));
1059
1276
  const { rawPublicKey, externalServerKeyGenResults } = await this.externalServerKeyGen({
1060
1277
  chainName,
1061
1278
  roomId,
1279
+ dynamicRequestId,
1062
1280
  dynamicServerKeygenIds,
1063
1281
  externalServerInitKeygenResults,
1064
1282
  thresholdSignatureScheme,
@@ -1069,8 +1287,16 @@ class DynamicWalletClient {
1069
1287
  externalServerKeyShares: externalServerKeyGenResults
1070
1288
  };
1071
1289
  } catch (error) {
1072
- this.logger.error('Error in keyGen', error);
1073
- throw new Error('Error creating wallet account in keyGen');
1290
+ this.logger.error('Error in keyGen', {
1291
+ backendReached,
1292
+ chain: chainName,
1293
+ dynamicRequestId,
1294
+ failurePhase: 'keygen',
1295
+ operation: 'keygen'
1296
+ }, error);
1297
+ throw new Error('Error creating wallet account in keyGen', {
1298
+ cause: error
1299
+ });
1074
1300
  }
1075
1301
  }
1076
1302
  async importRawPrivateKey({ chainName, privateKey, thresholdSignatureScheme, bitcoinConfig, password, backUpToDynamic, onError, onCeremonyComplete }) {
@@ -1093,6 +1319,7 @@ class DynamicWalletClient {
1093
1319
  });
1094
1320
  const externalServerKeygenInitResults = await this.externalServerInitializeKeyGen({
1095
1321
  chainName,
1322
+ dynamicRequestId,
1096
1323
  thresholdSignatureScheme,
1097
1324
  bitcoinConfig
1098
1325
  });
@@ -1129,7 +1356,8 @@ class DynamicWalletClient {
1129
1356
  chainName,
1130
1357
  keyShare: externalServerKeygenResult,
1131
1358
  derivationPath: undefined,
1132
- bitcoinConfig
1359
+ bitcoinConfig,
1360
+ dynamicRequestId
1133
1361
  });
1134
1362
  return {
1135
1363
  rawPublicKey,
@@ -1158,7 +1386,7 @@ class DynamicWalletClient {
1158
1386
  dynamicRequestId
1159
1387
  });
1160
1388
  }
1161
- async externalServerSign({ chainName, message, roomId, keyShare, derivationPath, isFormatted, bitcoinConfig }) {
1389
+ async externalServerSign({ chainName, message, roomId, keyShare, derivationPath, isFormatted, bitcoinConfig, dynamicRequestId }) {
1162
1390
  try {
1163
1391
  const mpcSigner = getMPCSigner({
1164
1392
  chainName,
@@ -1170,7 +1398,13 @@ class DynamicWalletClient {
1170
1398
  const signature = await mpcSigner.sign(roomId, keyShare, formattedMessage, derivationPath, tweak);
1171
1399
  return signature;
1172
1400
  } catch (error) {
1173
- this.logger.error('Error in externalServerSign', error);
1401
+ this.logger.error('Error in externalServerSign', {
1402
+ backendReached: true,
1403
+ chain: chainName,
1404
+ dynamicRequestId,
1405
+ failurePhase: 'mpc_sign',
1406
+ operation: 'sign'
1407
+ }, error);
1174
1408
  throw error;
1175
1409
  }
1176
1410
  }
@@ -1276,6 +1510,8 @@ class DynamicWalletClient {
1276
1510
  }
1277
1511
  }
1278
1512
  async sign({ accountAddress, externalServerKeyShares, message, chainName, password = undefined, isFormatted = false, context, onError, bitcoinConfig, walletMetadata, walletOperation = core$1.WalletOperation.SIGN_MESSAGE, passwordPreVerified = false }) {
1513
+ const dynamicRequestId = uuid.v4();
1514
+ let backendReached = false;
1279
1515
  try {
1280
1516
  this.assertAddressMatchesMetadata(accountAddress, walletMetadata);
1281
1517
  const backupInfo = walletMetadata.externalServerKeySharesBackupInfo;
@@ -1316,6 +1552,7 @@ class DynamicWalletClient {
1316
1552
  // timeout.
1317
1553
  const sseErrorGate = core$1.createRejectablePromise();
1318
1554
  // Perform the dynamic server sign
1555
+ backendReached = true;
1319
1556
  const data = await this.dynamicServerSign({
1320
1557
  walletId,
1321
1558
  message,
@@ -1326,7 +1563,8 @@ class DynamicWalletClient {
1326
1563
  sseErrorGate.reject(error);
1327
1564
  onError == null ? void 0 : onError(error);
1328
1565
  },
1329
- shareSetId: walletMetadata.shareSetId
1566
+ shareSetId: walletMetadata.shareSetId,
1567
+ dynamicRequestId
1330
1568
  });
1331
1569
  const derivationPath = derivationPathStr && derivationPathStr != '' ? new Uint32Array(Object.values(JSON.parse(derivationPathStr))) : undefined;
1332
1570
  // Try forward MPC first if enabled, with fallback to relay-based signing on attestation failure
@@ -1353,7 +1591,8 @@ class DynamicWalletClient {
1353
1591
  keyShare: resolvedShares[0],
1354
1592
  derivationPath,
1355
1593
  isFormatted,
1356
- bitcoinConfig
1594
+ bitcoinConfig,
1595
+ dynamicRequestId
1357
1596
  });
1358
1597
  })();
1359
1598
  // The losing branch's rejection (e.g. the relay timeout after a policy
@@ -1366,7 +1605,13 @@ class DynamicWalletClient {
1366
1605
  ]);
1367
1606
  } catch (error) {
1368
1607
  if (!(error instanceof Error && error.message === 'External server key shares are required to sign a message')) {
1369
- this.logger.error('Error in sign', error);
1608
+ this.logger.error('Error in sign', {
1609
+ backendReached,
1610
+ chain: chainName,
1611
+ dynamicRequestId,
1612
+ failurePhase: 'sign',
1613
+ operation: 'sign'
1614
+ }, error);
1370
1615
  }
1371
1616
  throw error;
1372
1617
  }
@@ -2197,7 +2442,9 @@ class DynamicWalletClient {
2197
2442
  // We can't distinguish wrong-password from stale-metadata without a
2198
2443
  // server-side backupInfo lookup (fetchWalletMetadata returns identity
2199
2444
  // only). Surface both possibilities so customers know where to look.
2200
- throw new Error('Failed to verify password. Either the password is incorrect, or the cached `walletMetadata.externalServerKeySharesBackupInfo` is stale ' + '(merge the `backupInfo` returned from updatePassword/refreshWalletAccountShares/reshare into your cached walletMetadata).');
2445
+ throw new Error('Failed to verify password. Either the password is incorrect, or the cached `walletMetadata.externalServerKeySharesBackupInfo` is stale ' + '(merge the `backupInfo` returned from updatePassword/refreshWalletAccountShares/reshare into your cached walletMetadata).', {
2446
+ cause: error
2447
+ });
2201
2448
  }
2202
2449
  }
2203
2450
  async isPasswordEncrypted({ accountAddress, walletMetadata, backupInfo }) {
@@ -2979,6 +3226,7 @@ Object.defineProperty(exports, "SignMessageRawSignHashFunctionEnum", {
2979
3226
  enumerable: true,
2980
3227
  get: function () { return sdkApiCore.SignMessageRawSignHashFunctionEnum; }
2981
3228
  });
3229
+ exports.ClientValidationError = ClientValidationError;
2982
3230
  exports.DynamicServerApiClient = DynamicServerApiClient;
2983
3231
  exports.DynamicWalletClient = DynamicWalletClient;
2984
3232
  exports.ERROR_RAW_SIGN_CONTEXT_REQUIRED = ERROR_RAW_SIGN_CONTEXT_REQUIRED;