@dynamic-labs-wallet/core 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.esm.js CHANGED
@@ -5,7 +5,7 @@ import { WaasChainEnum } from '@dynamic-labs/sdk-api-core/models/WaasChainEnum';
5
5
  export { WaasChainEnum } from '@dynamic-labs/sdk-api-core/models/WaasChainEnum';
6
6
  export { WaasPolicyConstraintCondition } from '@dynamic-labs/sdk-api-core/models/WaasPolicyConstraintCondition';
7
7
  export { WaasPolicyRuleType } from '@dynamic-labs/sdk-api-core/models/WaasPolicyRuleType';
8
- import axios from 'axios';
8
+ import axios, { isAxiosError } from 'axios';
9
9
  export { isAxiosError } from 'axios';
10
10
  export { classifyForwardMpcError, isAttestationError, isForwardMpcError } from '@dynamic-labs-wallet/forward-mpc-client/utils';
11
11
 
@@ -153,6 +153,7 @@ const FEATURE_FLAGS = {
153
153
  ENABLE_FORWARD_MPC_CLIENT_FLAG: 'enable-forward-mpc-client',
154
154
  ENABLE_HTTP_WAAS_TRANSPORT: 'enable-http-waas-transport',
155
155
  ENABLE_HTTP_WAAS_TRANSPORT_SIGNMESSAGE: 'enable-http-waas-transport-signmessage',
156
+ ENABLE_HTTP_WAAS_TRANSPORT_EXPORT: 'enable-http-waas-transport-export',
156
157
  ENABLE_ROOM_CACHE_FLAG: 'enable-mpc-room-cache',
157
158
  ENABLE_SHARE_SET_RESHARE: 'enable-share-set-reshare',
158
159
  ENABLE_TRON_MPC_FLAG: 'enable-tron-mpc',
@@ -1279,6 +1280,155 @@ const getElapsedTime = (startTime)=>{
1279
1280
  };
1280
1281
  };
1281
1282
 
1283
+ /**
1284
+ * Thrown when sign-and-broadcast answers 413: the request body is over the server's 640kb limit,
1285
+ * which multiple large messages plus calldata can reach.
1286
+ */ class SignAndBroadcastRequestTooLargeError extends Error {
1287
+ constructor(){
1288
+ super('The sign-and-broadcast request is too large. Send less calldata or fewer fee attempts.');
1289
+ this.name = 'SignAndBroadcastRequestTooLargeError';
1290
+ }
1291
+ }
1292
+
1293
+ class WalletApiError extends Error {
1294
+ constructor(status, message, code){
1295
+ super(message);
1296
+ this.name = 'WalletApiError';
1297
+ this.status = status;
1298
+ this.code = code;
1299
+ }
1300
+ }
1301
+ const getResponseRecord = (error)=>{
1302
+ var _error_response;
1303
+ const data = (_error_response = error.response) == null ? void 0 : _error_response.data;
1304
+ return data && typeof data === 'object' ? data : undefined;
1305
+ };
1306
+ // The Key Share Service returns its machine-readable cause under `error_code`; other
1307
+ // backends use `code` or a bare `error`. Exported for callers that must skip handleAxiosError's own logging.
1308
+ const extractServerErrorCode = (error)=>{
1309
+ const record = getResponseRecord(error);
1310
+ const candidates = [
1311
+ record == null ? void 0 : record['code'],
1312
+ record == null ? void 0 : record['error_code'],
1313
+ record == null ? void 0 : record['error']
1314
+ ];
1315
+ return candidates.find((value)=>typeof value === 'string');
1316
+ };
1317
+ const INTERNAL_SERVER_ERROR_MESSAGE = 'Internal server error';
1318
+ /**
1319
+ * `status` for a request that never received an HTTP response at all — the
1320
+ * client lost connectivity, the request was aborted, DNS/TLS failed, or CORS
1321
+ * blocked it. 0 matches the XHR/`fetch` convention for "no response".
1322
+ *
1323
+ * This used to be reported as `500 Internal server error`, which made a
1324
+ * client-side network failure indistinguishable from a backend fault: the
1325
+ * "Backend 500s" monitor counted 1,395 events on a day when the API returned
1326
+ * only 33 real 5xx responses.
1327
+ */ const NETWORK_ERROR_STATUS = 0;
1328
+ const NETWORK_ERROR_CODE = 'NETWORK_ERROR';
1329
+ const NETWORK_ERROR_MESSAGE = 'Network error - no response received';
1330
+ const STATUS_MESSAGES = {
1331
+ 400: 'Invalid request',
1332
+ 403: 'Forbidden',
1333
+ 422: 'Unprocessable content',
1334
+ 429: 'Rate limited',
1335
+ 500: INTERNAL_SERVER_ERROR_MESSAGE
1336
+ };
1337
+ const MISSING_AUTH_MESSAGE = 'Authorization header or cookie missing';
1338
+ // A 401 can mean the request carried no credentials, or carried credentials the
1339
+ // backend rejected — the two need different messages. The Authorization header
1340
+ // is readable so we can confirm its presence; the auth cookie is httpOnly, so we
1341
+ // can only report whether cookie mode was enabled (`withCredentials`), not that a
1342
+ // cookie was actually attached.
1343
+ const getRequestCredentials = (error)=>{
1344
+ var _error_config, _error_config1;
1345
+ const requestHeaders = (_error_config = error.config) == null ? void 0 : _error_config.headers;
1346
+ var _requestHeaders_Authorization;
1347
+ const hasAuthorizationHeader = Boolean((_requestHeaders_Authorization = requestHeaders == null ? void 0 : requestHeaders['Authorization']) != null ? _requestHeaders_Authorization : requestHeaders == null ? void 0 : requestHeaders['authorization']);
1348
+ const usesCookieAuth = Boolean((_error_config1 = error.config) == null ? void 0 : _error_config1.withCredentials);
1349
+ return {
1350
+ hasAuthorizationHeader,
1351
+ usesCookieAuth
1352
+ };
1353
+ };
1354
+ const resolveUnauthorizedMessage = (error)=>{
1355
+ const { hasAuthorizationHeader, usesCookieAuth } = getRequestCredentials(error);
1356
+ if (!hasAuthorizationHeader && !usesCookieAuth) {
1357
+ return MISSING_AUTH_MESSAGE;
1358
+ }
1359
+ const credentialDescriptions = [];
1360
+ if (hasAuthorizationHeader) {
1361
+ credentialDescriptions.push('Authorization header present');
1362
+ }
1363
+ if (usesCookieAuth) {
1364
+ credentialDescriptions.push('withCredentials enabled (cookie auth)');
1365
+ }
1366
+ return `Unauthorized (${credentialDescriptions.join(', ')})`;
1367
+ };
1368
+ const resolveStatusMessage = (status, error)=>{
1369
+ if (status === 401) {
1370
+ return resolveUnauthorizedMessage(error);
1371
+ }
1372
+ if (status && STATUS_MESSAGES[status]) {
1373
+ return STATUS_MESSAGES[status];
1374
+ }
1375
+ return undefined;
1376
+ };
1377
+ const handleAxiosError = (error, message, context, /**
1378
+ * Optional severity hint from the caller, for failures the caller knows are
1379
+ * expected (e.g. a business-rule rejection). It can only *lower* severity to
1380
+ * `warn` — a caller must not be able to escalate, and a genuine 5xx has to
1381
+ * stay at `error` regardless of what the call site believes.
1382
+ *
1383
+ * Without this the hint that `logError` accepts was silently discarded for
1384
+ * every AxiosError, which is the most common error type in the SDK.
1385
+ */ options)=>{
1386
+ var _error_config, _error_config1, _error_response, _error_response1, _error_response2;
1387
+ const requestUrl = (_error_config = error.config) == null ? void 0 : _error_config.url;
1388
+ const requestBaseUrl = (_error_config1 = error.config) == null ? void 0 : _error_config1.baseURL;
1389
+ // The HTTP status MUST NOT be logged as a top-level `status` field: Datadog
1390
+ // reserves `status` for the log level and its remapper turns a numeric value
1391
+ // into `info`, silently burying every 4xx/5xx we emit here. `http.status_code`
1392
+ // is Datadog's standard attribute, so the level survives and these logs join
1393
+ // against edge/CDN logs on the same field.
1394
+ const logPayload = _extends({
1395
+ message,
1396
+ error: (_error_response = error.response) == null ? void 0 : _error_response.data,
1397
+ http: {
1398
+ status_code: (_error_response1 = error.response) == null ? void 0 : _error_response1.status
1399
+ }
1400
+ }, requestBaseUrl && {
1401
+ requestBaseUrl
1402
+ }, requestUrl && {
1403
+ requestUrl
1404
+ }, {
1405
+ context
1406
+ });
1407
+ const status = (_error_response2 = error.response) == null ? void 0 : _error_response2.status;
1408
+ // A 429 (Too Many Requests) is a transient rate-limit signal, not a code or
1409
+ // logic failure — and a request that never got a response is a client-side
1410
+ // connectivity problem, not a server fault. Both are logged at `warn` so they
1411
+ // don't pollute error dashboards or trigger PagerDuty. The 429 case covers
1412
+ // both Cloudflare WAF blocks and application-level rate limiting.
1413
+ const inferredLevel = status === 429 || status === undefined ? 'warn' : 'error';
1414
+ // A 5xx is a server fault: no call site is entitled to downgrade it, however
1415
+ // expected it believes the failure to be. The hint only applies below 500.
1416
+ const isServerFault = status !== undefined && status >= 500;
1417
+ const logLevel = (options == null ? void 0 : options.level) === 'warn' && !isServerFault ? 'warn' : inferredLevel;
1418
+ Logger[logLevel]('[DynamicWaasWalletClient] Axios error: ', logPayload);
1419
+ // No response at all: report it as such rather than laundering it into a 500.
1420
+ if (status === undefined) {
1421
+ throw new WalletApiError(NETWORK_ERROR_STATUS, NETWORK_ERROR_MESSAGE, NETWORK_ERROR_CODE);
1422
+ }
1423
+ var _resolveStatusMessage;
1424
+ // Preserve the real status. Collapsing every unmapped code to 500 made a 404
1425
+ // (backup not found) and a 504 (gateway timeout) both surface as
1426
+ // "Internal server error", so neither the caller nor a monitor could tell a
1427
+ // missing resource from an outage.
1428
+ const resolvedMessage = (_resolveStatusMessage = resolveStatusMessage(status, error)) != null ? _resolveStatusMessage : `Request failed with status ${status}`;
1429
+ throw new WalletApiError(status, resolvedMessage, extractServerErrorCode(error));
1430
+ };
1431
+
1282
1432
  const getRequiredExternalKeyShareId = (ks)=>{
1283
1433
  if (!ks.externalKeyShareId) {
1284
1434
  throw new Error(`Missing externalKeyShareId for key share (id: ${ks.id}, keygenId: ${ks.keygenId})`);
@@ -1293,7 +1443,7 @@ const getRequiredExternalKeyShareId = (ks)=>{
1293
1443
  * before it is posted and surfaces in Datadog exactly as the recovery path does.
1294
1444
  */ const assertDynamicLocationsHaveExternalKeyShareId = (locations)=>locations.filter((location)=>location.location === BackupLocation.DYNAMIC).forEach((location)=>getRequiredExternalKeyShareId(location));
1295
1445
 
1296
- var version = "1.1.33";
1446
+ var version = "1.1.35";
1297
1447
 
1298
1448
  /**
1299
1449
  * Reads the Authorization header off an axios request config, which stores
@@ -1458,6 +1608,29 @@ const createWalletAccountRequestBody = ({ chainName, clientKeygenIds, thresholdS
1458
1608
  businessAccountId
1459
1609
  } : {});
1460
1610
  };
1611
+ const MAX_SERVER_MESSAGE_LENGTH = 200;
1612
+ /**
1613
+ * A rejected sign-and-broadcast as a `WalletApiError` that names the server's reason and code, since the generic status text says nothing about which check failed. A failure with no response (timeout, no connection) passes through.
1614
+ *
1615
+ * @param error - What the request threw.
1616
+ */ const toSignAndBroadcastApiError = (error)=>{
1617
+ if (!isAxiosError(error) || !error.response) {
1618
+ return error;
1619
+ }
1620
+ const { status, data } = error.response;
1621
+ const code = extractServerErrorCode(error);
1622
+ const record = data && typeof data === 'object' ? data : undefined;
1623
+ const serverMessage = [
1624
+ record == null ? void 0 : record['error'],
1625
+ record == null ? void 0 : record['message']
1626
+ ].find((candidate)=>typeof candidate === 'string' && candidate.length > 0 && candidate !== code);
1627
+ const detail = [
1628
+ serverMessage == null ? void 0 : serverMessage.slice(0, MAX_SERVER_MESSAGE_LENGTH),
1629
+ code && `code: ${code}`
1630
+ ].filter(Boolean).join(', ');
1631
+ const suffix = detail ? ` (${detail})` : '';
1632
+ return new WalletApiError(status, `Sign and broadcast failed with status ${status}${suffix}`, code);
1633
+ };
1461
1634
  class DynamicApiClient extends BaseClient {
1462
1635
  async getBackupRelayToken({ traceContext }) {
1463
1636
  const { data } = await this.apiClient.get(`${this.waasRoutePrefix}/keyShares/backup/token`, {
@@ -1586,6 +1759,74 @@ class DynamicApiClient extends BaseClient {
1586
1759
  traceContext
1587
1760
  });
1588
1761
  }
1762
+ /**
1763
+ * Starts a sign-and-broadcast: Dynamic opens one signing room per message, you sign in each, and Dynamic then sends the transaction. The signature never comes back.
1764
+ *
1765
+ * @param params.walletId - The wallet that signs.
1766
+ * @param params.shareSetId - Which of the wallet's key sets to use. Defaults to its active one.
1767
+ * @param params.messages - The transaction once per fee attempt, in fee order, as the bytes to sign (RLP hex on EVM, message bytes on Solana). Not hashed.
1768
+ * @param params.contexts - What each message does in readable form (chain, recipient, amount), one per message in the same order. Dynamic checks each against the wallet and its policies.
1769
+ * @param params.broadcastOptions - Solana send settings: other required signers' signatures, and skipping the pre-send dry run.
1770
+ * @param params.isFormatted - `true` if each message is already a hash. `false` for a serialized transaction.
1771
+ * @param params.requestedCertainty - How sure to be before calling it confirmed: `fast`, `safe` or `final`, or `chain:` plus a chain-specific value. Defaults to `safe`.
1772
+ * @param params.externalReference - Your own label, returned on every event. Not unique.
1773
+ * @param params.roomIds - Signing rooms you already opened, one per message. Send all or none; Dynamic opens them when omitted.
1774
+ * @param params.approvalQuorum - Approvers' consent for a governed wallet, sent on the retry after `intent_required`.
1775
+ * @param params.idempotencyKey - Makes a retry safe: the same key replays the stored answer instead of sending twice.
1776
+ * @param params.dynamicRequestId - Id of this one request, to match client and server logs.
1777
+ * @param params.traceContext - Ties this call's logs to the server's, across retries. Created when omitted.
1778
+ * @param params.mfaToken - Proof of the user's multi-factor check, when the wallet needs one.
1779
+ * @param params.elevatedAccessToken - Proof of a recent re-login, when the wallet needs one.
1780
+ * @param params.onError - Called with the failure's message, never the raw error.
1781
+ * @returns The transaction record: its id, its status, and the signing rooms while signing is pending.
1782
+ * @throws SignAndBroadcastRequestTooLargeError when the body is over the server limit.
1783
+ * @throws SigningConsentRequiredError when a governed wallet needs approvers' consent first.
1784
+ * @throws WalletApiError when the server rejects it, with its reason and code.
1785
+ */ async signAndBroadcast({ dynamicRequestId, walletId, shareSetId, contexts, broadcastOptions, messages, isFormatted, requestedCertainty, externalReference, roomIds, approvalQuorum, idempotencyKey, onError, mfaToken, elevatedAccessToken, traceContext }) {
1786
+ try {
1787
+ return await requestJson({
1788
+ apiClient: this.apiClient,
1789
+ dynamicRequestId,
1790
+ idempotencyKey,
1791
+ endpoint: `${this.waasRoutePrefix}/${walletId}/transactions/signAndBroadcast`,
1792
+ body: _extends({
1793
+ messages,
1794
+ contexts
1795
+ }, broadcastOptions ? {
1796
+ broadcastOptions
1797
+ } : {}, {
1798
+ isFormatted
1799
+ }, requestedCertainty ? {
1800
+ requestedCertainty
1801
+ } : {}, externalReference ? {
1802
+ externalReference
1803
+ } : {}, roomIds ? {
1804
+ roomIds
1805
+ } : {}, approvalQuorum ? {
1806
+ approvalQuorum
1807
+ } : {}, shareSetId ? {
1808
+ shareSetId
1809
+ } : {}),
1810
+ timeoutMs: 20000,
1811
+ timeoutMessage: 'Sign and broadcast timed out',
1812
+ onError,
1813
+ mfaToken,
1814
+ elevatedAccessToken,
1815
+ traceContext
1816
+ });
1817
+ } catch (error) {
1818
+ const response = error.response;
1819
+ if ((response == null ? void 0 : response.status) === 413) {
1820
+ throw new SignAndBroadcastRequestTooLargeError();
1821
+ }
1822
+ // A governed wallet with nothing signed yet answers 422 intent_required, as plain signing's stream does.
1823
+ const intentRequired = (response == null ? void 0 : response.status) === 422 ? asIntentRequiredFrom422Body(response.data) : undefined;
1824
+ if (intentRequired) {
1825
+ throw new SigningConsentRequiredError(intentRequired);
1826
+ }
1827
+ throw toSignAndBroadcastApiError(error);
1828
+ }
1829
+ }
1589
1830
  async refreshWalletAccountShares({ dynamicRequestId, walletId, shareSetId, onError, onCeremonyComplete, mfaToken, elevatedAccessToken, traceContext }) {
1590
1831
  return createEventStreamPromise({
1591
1832
  apiClient: this.apiClient,
@@ -1653,6 +1894,35 @@ class DynamicApiClient extends BaseClient {
1653
1894
  });
1654
1895
  }
1655
1896
  /**
1897
+ * Plain-JSON (non-SSE) variant of `exportKey`. Hits the same
1898
+ * `/privateKey/export` endpoint; the `x-idempotency-key` header tells the
1899
+ * backend (when `enable-http-waas-transport-export` is on) to respond with
1900
+ * the OpenRoomResponse synchronously instead of streaming `room_created`
1901
+ * over SSE.
1902
+ */ async requestExportKeyJson({ mfaToken, elevatedAccessToken, dynamicRequestId, walletId, shareSetId, exportId, bitcoinConfig, onError, traceContext, idempotencyKey, timeoutMs }) {
1903
+ const { addressType } = bitcoinConfig || {};
1904
+ const requestBody = _extends({
1905
+ exportId
1906
+ }, addressType ? {
1907
+ addressType
1908
+ } : {}, shareSetId ? {
1909
+ shareSetId
1910
+ } : {});
1911
+ return requestJson({
1912
+ apiClient: this.apiClient,
1913
+ dynamicRequestId,
1914
+ idempotencyKey,
1915
+ endpoint: `${this.waasRoutePrefix}/${walletId}/privateKey/export`,
1916
+ body: requestBody,
1917
+ timeoutMs: timeoutMs != null ? timeoutMs : 20000,
1918
+ timeoutMessage: 'Key export timed out',
1919
+ onError,
1920
+ mfaToken,
1921
+ elevatedAccessToken,
1922
+ traceContext
1923
+ });
1924
+ }
1925
+ /**
1656
1926
  * Kick off the Aleo view-key export MPC ceremony. Returns the MPC room once
1657
1927
  * redcoast + wallet-service have joined; the client then joins the same
1658
1928
  * room via Sodot's `exportViewKey` to receive the view key.
@@ -2808,143 +3078,37 @@ const prepareRawMessageSigningPayload = (chainName, message)=>{
2808
3078
  return typeof formatted === 'string' ? formatted : Buffer.from(formatted).toString('hex');
2809
3079
  };
2810
3080
 
2811
- class WalletApiError extends Error {
2812
- constructor(status, message, code){
2813
- super(message);
2814
- this.name = 'WalletApiError';
2815
- this.status = status;
2816
- this.code = code;
3081
+ /** Returns the context as plain JSON, with each bigint (which `JSON.stringify` throws on) turned into a string. */ const toJsonSafeContext = (context)=>{
3082
+ try {
3083
+ return JSON.parse(JSON.stringify(context, (_key, value)=>typeof value === 'bigint' ? String(value) : value));
3084
+ } catch (error) {
3085
+ throw new Error(`Could not serialize the signing context: ${error instanceof Error ? error.message : String(error)}`, {
3086
+ cause: error
3087
+ });
2817
3088
  }
2818
- }
2819
- const getResponseRecord = (error)=>{
2820
- var _error_response;
2821
- const data = (_error_response = error.response) == null ? void 0 : _error_response.data;
2822
- return data && typeof data === 'object' ? data : undefined;
2823
- };
2824
- // The Key Share Service returns its machine-readable cause under `error_code`; other
2825
- // backends use `code` or a bare `error`. Exported for callers that must skip handleAxiosError's own logging.
2826
- const extractServerErrorCode = (error)=>{
2827
- const record = getResponseRecord(error);
2828
- const candidates = [
2829
- record == null ? void 0 : record['code'],
2830
- record == null ? void 0 : record['error_code'],
2831
- record == null ? void 0 : record['error']
2832
- ];
2833
- return candidates.find((value)=>typeof value === 'string');
2834
3089
  };
2835
- const INTERNAL_SERVER_ERROR_MESSAGE = 'Internal server error';
3090
+
2836
3091
  /**
2837
- * `status` for a request that never received an HTTP response at all — the
2838
- * client lost connectivity, the request was aborted, DNS/TLS failed, or CORS
2839
- * blocked it. 0 matches the XHR/`fetch` convention for "no response".
2840
- *
2841
- * This used to be reported as `500 Internal server error`, which made a
2842
- * client-side network failure indistinguishable from a backend fault: the
2843
- * "Backend 500s" monitor counted 1,395 events on a day when the API returned
2844
- * only 33 real 5xx responses.
2845
- */ const NETWORK_ERROR_STATUS = 0;
2846
- const NETWORK_ERROR_CODE = 'NETWORK_ERROR';
2847
- const NETWORK_ERROR_MESSAGE = 'Network error - no response received';
2848
- const STATUS_MESSAGES = {
2849
- 400: 'Invalid request',
2850
- 403: 'Forbidden',
2851
- 422: 'Unprocessable content',
2852
- 429: 'Rate limited',
2853
- 500: INTERNAL_SERVER_ERROR_MESSAGE
2854
- };
2855
- const MISSING_AUTH_MESSAGE = 'Authorization header or cookie missing';
2856
- // A 401 can mean the request carried no credentials, or carried credentials the
2857
- // backend rejected — the two need different messages. The Authorization header
2858
- // is readable so we can confirm its presence; the auth cookie is httpOnly, so we
2859
- // can only report whether cookie mode was enabled (`withCredentials`), not that a
2860
- // cookie was actually attached.
2861
- const getRequestCredentials = (error)=>{
2862
- var _error_config, _error_config1;
2863
- const requestHeaders = (_error_config = error.config) == null ? void 0 : _error_config.headers;
2864
- var _requestHeaders_Authorization;
2865
- const hasAuthorizationHeader = Boolean((_requestHeaders_Authorization = requestHeaders == null ? void 0 : requestHeaders['Authorization']) != null ? _requestHeaders_Authorization : requestHeaders == null ? void 0 : requestHeaders['authorization']);
2866
- const usesCookieAuth = Boolean((_error_config1 = error.config) == null ? void 0 : _error_config1.withCredentials);
2867
- return {
2868
- hasAuthorizationHeader,
2869
- usesCookieAuth
2870
- };
2871
- };
2872
- const resolveUnauthorizedMessage = (error)=>{
2873
- const { hasAuthorizationHeader, usesCookieAuth } = getRequestCredentials(error);
2874
- if (!hasAuthorizationHeader && !usesCookieAuth) {
2875
- return MISSING_AUTH_MESSAGE;
2876
- }
2877
- const credentialDescriptions = [];
2878
- if (hasAuthorizationHeader) {
2879
- credentialDescriptions.push('Authorization header present');
2880
- }
2881
- if (usesCookieAuth) {
2882
- credentialDescriptions.push('withCredentials enabled (cookie auth)');
2883
- }
2884
- return `Unauthorized (${credentialDescriptions.join(', ')})`;
2885
- };
2886
- const resolveStatusMessage = (status, error)=>{
2887
- if (status === 401) {
2888
- return resolveUnauthorizedMessage(error);
2889
- }
2890
- if (status && STATUS_MESSAGES[status]) {
2891
- return STATUS_MESSAGES[status];
2892
- }
2893
- return undefined;
2894
- };
2895
- const handleAxiosError = (error, message, context, /**
2896
- * Optional severity hint from the caller, for failures the caller knows are
2897
- * expected (e.g. a business-rule rejection). It can only *lower* severity to
2898
- * `warn` — a caller must not be able to escalate, and a genuine 5xx has to
2899
- * stay at `error` regardless of what the call site believes.
2900
- *
2901
- * Without this the hint that `logError` accepts was silently discarded for
2902
- * every AxiosError, which is the most common error type in the SDK.
2903
- */ options)=>{
2904
- var _error_config, _error_config1, _error_response, _error_response1, _error_response2;
2905
- const requestUrl = (_error_config = error.config) == null ? void 0 : _error_config.url;
2906
- const requestBaseUrl = (_error_config1 = error.config) == null ? void 0 : _error_config1.baseURL;
2907
- // The HTTP status MUST NOT be logged as a top-level `status` field: Datadog
2908
- // reserves `status` for the log level and its remapper turns a numeric value
2909
- // into `info`, silently burying every 4xx/5xx we emit here. `http.status_code`
2910
- // is Datadog's standard attribute, so the level survives and these logs join
2911
- // against edge/CDN logs on the same field.
2912
- const logPayload = _extends({
2913
- message,
2914
- error: (_error_response = error.response) == null ? void 0 : _error_response.data,
2915
- http: {
2916
- status_code: (_error_response1 = error.response) == null ? void 0 : _error_response1.status
2917
- }
2918
- }, requestBaseUrl && {
2919
- requestBaseUrl
2920
- }, requestUrl && {
2921
- requestUrl
2922
- }, {
2923
- context
3092
+ * Returns a trace context that always has an id, so a retrying operation can hand the same one to each attempt.
3093
+ * The id is 32 lowercase hex characters, a valid W3C trace id the server can continue.
3094
+ */ const withTraceId = (traceContext)=>{
3095
+ var _traceContext_traceId, _traceContext_startTime;
3096
+ return _extends({}, traceContext, {
3097
+ traceId: (_traceContext_traceId = traceContext == null ? void 0 : traceContext.traceId) != null ? _traceContext_traceId : v4().replaceAll('-', ''),
3098
+ startTime: (_traceContext_startTime = traceContext == null ? void 0 : traceContext.startTime) != null ? _traceContext_startTime : Date.now()
2924
3099
  });
2925
- const status = (_error_response2 = error.response) == null ? void 0 : _error_response2.status;
2926
- // A 429 (Too Many Requests) is a transient rate-limit signal, not a code or
2927
- // logic failure — and a request that never got a response is a client-side
2928
- // connectivity problem, not a server fault. Both are logged at `warn` so they
2929
- // don't pollute error dashboards or trigger PagerDuty. The 429 case covers
2930
- // both Cloudflare WAF blocks and application-level rate limiting.
2931
- const inferredLevel = status === 429 || status === undefined ? 'warn' : 'error';
2932
- // A 5xx is a server fault: no call site is entitled to downgrade it, however
2933
- // expected it believes the failure to be. The hint only applies below 500.
2934
- const isServerFault = status !== undefined && status >= 500;
2935
- const logLevel = (options == null ? void 0 : options.level) === 'warn' && !isServerFault ? 'warn' : inferredLevel;
2936
- Logger[logLevel]('[DynamicWaasWalletClient] Axios error: ', logPayload);
2937
- // No response at all: report it as such rather than laundering it into a 500.
2938
- if (status === undefined) {
2939
- throw new WalletApiError(NETWORK_ERROR_STATUS, NETWORK_ERROR_MESSAGE, NETWORK_ERROR_CODE);
2940
- }
2941
- var _resolveStatusMessage;
2942
- // Preserve the real status. Collapsing every unmapped code to 500 made a 404
2943
- // (backup not found) and a 504 (gateway timeout) both surface as
2944
- // "Internal server error", so neither the caller nor a monitor could tell a
2945
- // missing resource from an outage.
2946
- const resolvedMessage = (_resolveStatusMessage = resolveStatusMessage(status, error)) != null ? _resolveStatusMessage : `Request failed with status ${status}`;
2947
- throw new WalletApiError(status, resolvedMessage, extractServerErrorCode(error));
2948
3100
  };
2949
3101
 
2950
- export { AuthMode, BITCOIN_ADDRESS_TYPE_CONFIG, BITCOIN_DERIVATION_PATHS, BROWSER_SDK_ROUTE_PREFIX, BackupLocation, BitcoinAddressType, BitcoinNetwork, BusinessAccountClient, BusinessAccountMemberRole, BusinessAccountSignerType, CreateRoomPartiesOptions, DEFAULT_ED25519_VARIANT, DELEGATED_SHARE_COUNT, DYNAMIC_AUTH_BASE_API_URL_MAP, DYNAMIC_AUTH_DEV_BASE_API_URL, DYNAMIC_AUTH_PREPROD_BASE_API_URL, DYNAMIC_AUTH_PROD_BASE_API_URL, DYNAMIC_CLIENT_RELAY_REDCOAST_API_KEY_MAP, DYNAMIC_CLIENT_RELAY_REDCOAST_APP_ID_MAP, DYNAMIC_FORWARD_MPC_DEV_ENCLAVE_URL, DYNAMIC_FORWARD_MPC_ENCLAVE_ATTESTATION_CONFIG_MAP, DYNAMIC_FORWARD_MPC_ENCLAVE_URL_MAP, DYNAMIC_FORWARD_MPC_PREPROD_ENCLAVE_URL, DYNAMIC_FORWARD_MPC_PROD_ENCLAVE_URL, DYNAMIC_KEYSHARES_RELAY_MAP, DYNAMIC_KEYSHARES_RELAY_PREPROD_BASE_API_URL, DYNAMIC_KEYSHARES_RELAY_PROD_BASE_API_URL, DynamicApiClient, DynamicClientSessionSignature, DynamicElevatedAccessTokenHeader, DynamicForwardMPCHeader, DynamicMfaTokenHeader, DynamicRequestIdHeader, DynamicSessionPublicKeyHeader, DynamicTraceElapsedTimeHeader, DynamicTraceIdHeader, ENCRYPTED_SHARES_STORAGE_SUFFIX, ENVIRONMENT_ENUM, EVM_COMPATIBLE_CHAINS, FEATURE_FLAGS, IFRAME_DOMAIN_MAP, Logger, MIDNIGHT_DERIVATION_PATHS, MPC_CHAIN_CONFIG, MPC_CONFIG, MPC_RELAY_DEV_API_URL, MPC_RELAY_PREPROD_API_URL, MPC_RELAY_PROD_API_URL, MPC_RELAY_URL_MAP, NETWORK_ERROR_CODE, NETWORK_ERROR_STATUS, NoopLogger, PREPROD_RELAY_API_KEY, PREPROD_RELAY_APP_ID, PROD_RELAY_API_KEY, PROD_RELAY_APP_ID, RELAY_API_KEY_HEADER, RELAY_APP_ID_HEADER, SDK_NAMESPACE, SERVER_SDK_ROUTE_PREFIX, SOLANA_RPC_URL, SigningConsentRequiredError, SuccessEventType, ThresholdSignatureScheme, URL_PATTERNS, WalletApiError, WalletOperation, WalletReadyState, assertDynamicLocationsHaveExternalKeyShareId, buildForwardMpcEnclaveUrl, chain, chainEnumToVerifiedCredentialName, createRejectablePromise, createSigningIntent, extractServerErrorCode, formatNamespacedVersion, formatRawMessageForSigning, getBitcoinChainConfig, getClientThreshold, getDynamicServerThreshold, getEnvironmentFromUrl, getMPCChainConfig, getRequiredExternalKeyShareId, getReshareConfig, getServerWalletReshareConfig, getTSSConfig, getVersionNamespace, getVersionWithoutNamespace, handleAxiosError, invokeCeremonyComplete, isBusinessAccountActionRequired, isEvmCompatibleChain, isRecoveredForwardMpcConnectionChurn, normalizeAndValidatePrivateKey, parseNamespacedVersion, prepareRawMessageSigningPayload, sanitizeBusinessAccountSignedIntent, serializeError, serializeMessageForForwardMPC, verifiedCredentialNameToChainEnum };
3102
+ /** The ids a caller can quote to support so one failed attempt can be found in client and server logs. */ /** Sets `traceId` and `requestId` on a thrown error so apps can quote them, keeping any id already set. */ const attachRequestIds = (error, { traceId, requestId })=>{
3103
+ var _target, _target1;
3104
+ if (!error || typeof error !== 'object' || !Object.isExtensible(error)) {
3105
+ return;
3106
+ }
3107
+ const target = error;
3108
+ var _traceId;
3109
+ (_traceId = (_target = target).traceId) != null ? _traceId : _target.traceId = traceId;
3110
+ var _requestId;
3111
+ (_requestId = (_target1 = target).requestId) != null ? _requestId : _target1.requestId = requestId;
3112
+ };
3113
+
3114
+ export { AuthMode, BITCOIN_ADDRESS_TYPE_CONFIG, BITCOIN_DERIVATION_PATHS, BROWSER_SDK_ROUTE_PREFIX, BackupLocation, BitcoinAddressType, BitcoinNetwork, BusinessAccountClient, BusinessAccountMemberRole, BusinessAccountSignerType, CreateRoomPartiesOptions, DEFAULT_ED25519_VARIANT, DELEGATED_SHARE_COUNT, DYNAMIC_AUTH_BASE_API_URL_MAP, DYNAMIC_AUTH_DEV_BASE_API_URL, DYNAMIC_AUTH_PREPROD_BASE_API_URL, DYNAMIC_AUTH_PROD_BASE_API_URL, DYNAMIC_CLIENT_RELAY_REDCOAST_API_KEY_MAP, DYNAMIC_CLIENT_RELAY_REDCOAST_APP_ID_MAP, DYNAMIC_FORWARD_MPC_DEV_ENCLAVE_URL, DYNAMIC_FORWARD_MPC_ENCLAVE_ATTESTATION_CONFIG_MAP, DYNAMIC_FORWARD_MPC_ENCLAVE_URL_MAP, DYNAMIC_FORWARD_MPC_PREPROD_ENCLAVE_URL, DYNAMIC_FORWARD_MPC_PROD_ENCLAVE_URL, DYNAMIC_KEYSHARES_RELAY_MAP, DYNAMIC_KEYSHARES_RELAY_PREPROD_BASE_API_URL, DYNAMIC_KEYSHARES_RELAY_PROD_BASE_API_URL, DynamicApiClient, DynamicClientSessionSignature, DynamicElevatedAccessTokenHeader, DynamicForwardMPCHeader, DynamicMfaTokenHeader, DynamicRequestIdHeader, DynamicSessionPublicKeyHeader, DynamicTraceElapsedTimeHeader, DynamicTraceIdHeader, ENCRYPTED_SHARES_STORAGE_SUFFIX, ENVIRONMENT_ENUM, EVM_COMPATIBLE_CHAINS, FEATURE_FLAGS, IFRAME_DOMAIN_MAP, Logger, MIDNIGHT_DERIVATION_PATHS, MPC_CHAIN_CONFIG, MPC_CONFIG, MPC_RELAY_DEV_API_URL, MPC_RELAY_PREPROD_API_URL, MPC_RELAY_PROD_API_URL, MPC_RELAY_URL_MAP, NETWORK_ERROR_CODE, NETWORK_ERROR_STATUS, NoopLogger, PREPROD_RELAY_API_KEY, PREPROD_RELAY_APP_ID, PROD_RELAY_API_KEY, PROD_RELAY_APP_ID, RELAY_API_KEY_HEADER, RELAY_APP_ID_HEADER, SDK_NAMESPACE, SERVER_SDK_ROUTE_PREFIX, SOLANA_RPC_URL, SignAndBroadcastRequestTooLargeError, SigningConsentRequiredError, SuccessEventType, ThresholdSignatureScheme, URL_PATTERNS, WalletApiError, WalletOperation, WalletReadyState, assertDynamicLocationsHaveExternalKeyShareId, attachRequestIds, buildForwardMpcEnclaveUrl, chain, chainEnumToVerifiedCredentialName, createRejectablePromise, createSigningIntent, extractServerErrorCode, formatNamespacedVersion, formatRawMessageForSigning, getBitcoinChainConfig, getClientThreshold, getDynamicServerThreshold, getEnvironmentFromUrl, getMPCChainConfig, getRequiredExternalKeyShareId, getReshareConfig, getServerWalletReshareConfig, getTSSConfig, getVersionNamespace, getVersionWithoutNamespace, handleAxiosError, invokeCeremonyComplete, isBusinessAccountActionRequired, isEvmCompatibleChain, isRecoveredForwardMpcConnectionChurn, normalizeAndValidatePrivateKey, parseNamespacedVersion, prepareRawMessageSigningPayload, sanitizeBusinessAccountSignedIntent, serializeError, serializeMessageForForwardMPC, toJsonSafeContext, verifiedCredentialNameToChainEnum, withTraceId };
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@dynamic-labs-wallet/core",
3
- "version": "1.1.33",
3
+ "version": "1.1.35",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "dependencies": {
7
7
  "@dynamic-labs/sdk-api-core": "0.50.0",
8
- "@dynamic-labs-wallet/primitives": "1.1.33",
8
+ "@dynamic-labs-wallet/primitives": "1.1.35",
9
9
  "axios": "1.20.0",
10
10
  "uuid": "11.1.0"
11
11
  },
package/src/api/api.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import type { ForwardMPCClientV2 } from '@dynamic-labs-wallet/forward-mpc-client';
2
- import type { BackupKeySharesToLocationsRequest, CreateRoomsRequest, CreateRoomsWithoutWalletIdRequest, CreateWaasAccountRequest, ExportAleoViewKeyRequest, ExportWaasWalletPrivateKeyRequest, ImportWaasPrivateKeyRequest, InlineObject1, InlineObject2, InlineObject4, InlineResponse2003, InlineResponse2005, NoncesResponse, ReshareRequest as ApiReshareRequest, SignMessageWithWaasRequest, SignMessageEvmTransaction, SignMessageEvmUserOperation, SignMessageSvmTransaction } from '#internal/sdk-api-core';
2
+ import type { BackupKeySharesToLocationsRequest, CreateRoomsRequest, CreateRoomsWithoutWalletIdRequest, CreateWaasAccountRequest, ExportAleoViewKeyRequest, ExportWaasWalletPrivateKeyRequest, ImportWaasPrivateKeyRequest, InlineObject1, InlineObject2, InlineObject4, InlineResponse2003, InlineResponse2005, NoncesResponse, ReshareRequest as ApiReshareRequest, SignMessageWithWaasRequest, SignMessageContext, SignMessageEvmTransaction, SignMessageEvmUserOperation, SignMessageSvmTransaction } from '#internal/sdk-api-core';
3
3
  import { type CeremonyCompleteCallback, type SlowDeliveryMeta } from '../eventStream/utils.js';
4
4
  import { ThresholdSignatureScheme } from '../mpc/constants.js';
5
- import { AuthMode, type AddBusinessAccountMemberRequest, type GetBusinessAccountRequest, type RequestWithTraceContext, type ApprovalQuorumRequestContext, type BackupLocationWithExternalKeyShareId, type BitcoinConfig, type EncryptedDelegatedKeyShareEnvelope, type ILogger, type KeygenCompleteResponse, type LinkWalletToBusinessAccountRequest, type OpenRoomResponse, type ReshareResponse, type RevokeDelegationResponse, type ShareSetType, type TraceContext, type WaasWalletResponse } from '../types.js';
5
+ import { AuthMode, type AddBusinessAccountMemberRequest, type GetBusinessAccountRequest, type RequestWithTraceContext, type ApprovalQuorumRequestContext, type BackupLocationWithExternalKeyShareId, type BitcoinConfig, type EncryptedDelegatedKeyShareEnvelope, type ILogger, type KeygenCompleteResponse, type LinkWalletToBusinessAccountRequest, type OpenRoomResponse, type ReshareResponse, type RevokeDelegationResponse, type ShareSetType, type SignAndBroadcastResponse, type SignAndBroadcastOptions, type TraceContext, type WaasWalletResponse } from '../types.js';
6
6
  import { type BusinessAccountActionRequired, type CreateBusinessAccountSdkRequest, type BusinessAccount, type BusinessAccountDetail, type BusinessAccountList, type BusinessAccountMember, type BusinessAccountSigner, type BusinessAccountSignedIntent, type BusinessAccountSignerType, type TargetSignerIdentity } from '../businessAccounts/types.js';
7
7
  import { BaseClient } from './client.js';
8
8
  import type { AleoFeemasterPolicyResponse, AleoScannerPublicKeyResponse, BackupKeySharesResponse, GetAvailableEVMGaslessRelayerResponse, GetEVMSponsoredTransactionStatusResponse, SponsorEVMTransactionRequest, SponsorEVMTransactionResponse, SponsorSVMTransactionRequest, SponsorSVMTransactionResponse, StoreEncryptedBackupResponse, WalletAdditionalAddressRequest } from './types.js';
@@ -116,6 +116,48 @@ export declare class DynamicApiClient extends BaseClient {
116
116
  requestSignMessageJson({ dynamicRequestId, walletId, shareSetId, message, onError, isFormatted, mfaToken, elevatedAccessToken, roomId, context, approvalQuorum, idempotencyKey, forwardMPCClientEnabled, traceContext, bitcoinConfig, }: SignMessageApiRequest & {
117
117
  idempotencyKey: string;
118
118
  }): Promise<OpenRoomResponse>;
119
+ /**
120
+ * Starts a sign-and-broadcast: Dynamic opens one signing room per message, you sign in each, and Dynamic then sends the transaction. The signature never comes back.
121
+ *
122
+ * @param params.walletId - The wallet that signs.
123
+ * @param params.shareSetId - Which of the wallet's key sets to use. Defaults to its active one.
124
+ * @param params.messages - The transaction once per fee attempt, in fee order, as the bytes to sign (RLP hex on EVM, message bytes on Solana). Not hashed.
125
+ * @param params.contexts - What each message does in readable form (chain, recipient, amount), one per message in the same order. Dynamic checks each against the wallet and its policies.
126
+ * @param params.broadcastOptions - Solana send settings: other required signers' signatures, and skipping the pre-send dry run.
127
+ * @param params.isFormatted - `true` if each message is already a hash. `false` for a serialized transaction.
128
+ * @param params.requestedCertainty - How sure to be before calling it confirmed: `fast`, `safe` or `final`, or `chain:` plus a chain-specific value. Defaults to `safe`.
129
+ * @param params.externalReference - Your own label, returned on every event. Not unique.
130
+ * @param params.roomIds - Signing rooms you already opened, one per message. Send all or none; Dynamic opens them when omitted.
131
+ * @param params.approvalQuorum - Approvers' consent for a governed wallet, sent on the retry after `intent_required`.
132
+ * @param params.idempotencyKey - Makes a retry safe: the same key replays the stored answer instead of sending twice.
133
+ * @param params.dynamicRequestId - Id of this one request, to match client and server logs.
134
+ * @param params.traceContext - Ties this call's logs to the server's, across retries. Created when omitted.
135
+ * @param params.mfaToken - Proof of the user's multi-factor check, when the wallet needs one.
136
+ * @param params.elevatedAccessToken - Proof of a recent re-login, when the wallet needs one.
137
+ * @param params.onError - Called with the failure's message, never the raw error.
138
+ * @returns The transaction record: its id, its status, and the signing rooms while signing is pending.
139
+ * @throws SignAndBroadcastRequestTooLargeError when the body is over the server limit.
140
+ * @throws SigningConsentRequiredError when a governed wallet needs approvers' consent first.
141
+ * @throws WalletApiError when the server rejects it, with its reason and code.
142
+ */
143
+ signAndBroadcast({ dynamicRequestId, walletId, shareSetId, contexts, broadcastOptions, messages, isFormatted, requestedCertainty, externalReference, roomIds, approvalQuorum, idempotencyKey, onError, mfaToken, elevatedAccessToken, traceContext, }: {
144
+ dynamicRequestId?: string;
145
+ walletId: string;
146
+ shareSetId?: string;
147
+ contexts: SignMessageContext[];
148
+ broadcastOptions?: SignAndBroadcastOptions;
149
+ messages: string[];
150
+ isFormatted?: boolean;
151
+ requestedCertainty?: string;
152
+ externalReference?: string;
153
+ roomIds?: string[];
154
+ approvalQuorum?: ApprovalQuorumRequestContext;
155
+ idempotencyKey: string;
156
+ onError?: (error: Error) => void;
157
+ mfaToken?: string;
158
+ elevatedAccessToken?: string;
159
+ traceContext?: TraceContext;
160
+ }): Promise<SignAndBroadcastResponse>;
119
161
  refreshWalletAccountShares({ dynamicRequestId, walletId, shareSetId, onError, onCeremonyComplete, mfaToken, elevatedAccessToken, traceContext, }: {
120
162
  dynamicRequestId?: string;
121
163
  walletId: string;
@@ -152,6 +194,24 @@ export declare class DynamicApiClient extends BaseClient {
152
194
  onError?: (error: Error) => void;
153
195
  traceContext?: TraceContext;
154
196
  }): Promise<import("@dynamic-labs/sdk-api-core").OpenRoomResponseWithServerKeygenIds>;
197
+ /**
198
+ * Plain-JSON (non-SSE) variant of `exportKey`. Hits the same
199
+ * `/privateKey/export` endpoint; the `x-idempotency-key` header tells the
200
+ * backend (when `enable-http-waas-transport-export` is on) to respond with
201
+ * the OpenRoomResponse synchronously instead of streaming `room_created`
202
+ * over SSE.
203
+ */
204
+ requestExportKeyJson({ mfaToken, elevatedAccessToken, dynamicRequestId, walletId, shareSetId, exportId, bitcoinConfig, onError, traceContext, idempotencyKey, timeoutMs, }: Omit<ExportWaasWalletPrivateKeyRequest, 'addressType'> & {
205
+ mfaToken?: string;
206
+ elevatedAccessToken?: string;
207
+ dynamicRequestId?: string;
208
+ walletId: string;
209
+ bitcoinConfig?: BitcoinConfig;
210
+ onError?: (error: Error) => void;
211
+ traceContext?: TraceContext;
212
+ idempotencyKey: string;
213
+ timeoutMs?: number;
214
+ }): Promise<OpenRoomResponse>;
155
215
  /**
156
216
  * Kick off the Aleo view-key export MPC ceremony. Returns the MPC room once
157
217
  * redcoast + wallet-service have joined; the client then joins the same