@dynamic-labs-wallet/core 1.1.34 → 1.1.36
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 +328 -135
- package/index.esm.js +324 -137
- package/package.json +2 -2
- package/src/api/api.d.ts +49 -3
- package/src/api/api.d.ts.map +1 -1
- package/src/api/index.d.ts +1 -0
- package/src/api/index.d.ts.map +1 -1
- package/src/api/signAndBroadcastRequestTooLargeError.d.ts +8 -0
- package/src/api/signAndBroadcastRequestTooLargeError.d.ts.map +1 -0
- package/src/eventStream/utils.d.ts +7 -0
- package/src/eventStream/utils.d.ts.map +1 -1
- package/src/exportIntent.d.ts +13 -0
- package/src/exportIntent.d.ts.map +1 -0
- package/src/index.d.ts +1 -0
- package/src/index.d.ts.map +1 -1
- package/src/types.d.ts +72 -0
- package/src/types.d.ts.map +1 -1
- package/src/utils/attachRequestIds.d.ts +8 -0
- package/src/utils/attachRequestIds.d.ts.map +1 -0
- package/src/utils/index.d.ts +3 -0
- package/src/utils/index.d.ts.map +1 -1
- package/src/utils/toJsonSafeContext.d.ts +4 -0
- package/src/utils/toJsonSafeContext.d.ts.map +1 -0
- package/src/utils/withTraceId.d.ts +7 -0
- package/src/utils/withTraceId.d.ts.map +1 -0
package/index.cjs
CHANGED
|
@@ -1279,6 +1279,155 @@ const getElapsedTime = (startTime)=>{
|
|
|
1279
1279
|
};
|
|
1280
1280
|
};
|
|
1281
1281
|
|
|
1282
|
+
/**
|
|
1283
|
+
* Thrown when sign-and-broadcast answers 413: the request body is over the server's 640kb limit,
|
|
1284
|
+
* which multiple large messages plus calldata can reach.
|
|
1285
|
+
*/ class SignAndBroadcastRequestTooLargeError extends Error {
|
|
1286
|
+
constructor(){
|
|
1287
|
+
super('The sign-and-broadcast request is too large. Send less calldata or fewer fee attempts.');
|
|
1288
|
+
this.name = 'SignAndBroadcastRequestTooLargeError';
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
class WalletApiError extends Error {
|
|
1293
|
+
constructor(status, message, code){
|
|
1294
|
+
super(message);
|
|
1295
|
+
this.name = 'WalletApiError';
|
|
1296
|
+
this.status = status;
|
|
1297
|
+
this.code = code;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
const getResponseRecord = (error)=>{
|
|
1301
|
+
var _error_response;
|
|
1302
|
+
const data = (_error_response = error.response) == null ? void 0 : _error_response.data;
|
|
1303
|
+
return data && typeof data === 'object' ? data : undefined;
|
|
1304
|
+
};
|
|
1305
|
+
// The Key Share Service returns its machine-readable cause under `error_code`; other
|
|
1306
|
+
// backends use `code` or a bare `error`. Exported for callers that must skip handleAxiosError's own logging.
|
|
1307
|
+
const extractServerErrorCode = (error)=>{
|
|
1308
|
+
const record = getResponseRecord(error);
|
|
1309
|
+
const candidates = [
|
|
1310
|
+
record == null ? void 0 : record['code'],
|
|
1311
|
+
record == null ? void 0 : record['error_code'],
|
|
1312
|
+
record == null ? void 0 : record['error']
|
|
1313
|
+
];
|
|
1314
|
+
return candidates.find((value)=>typeof value === 'string');
|
|
1315
|
+
};
|
|
1316
|
+
const INTERNAL_SERVER_ERROR_MESSAGE = 'Internal server error';
|
|
1317
|
+
/**
|
|
1318
|
+
* `status` for a request that never received an HTTP response at all — the
|
|
1319
|
+
* client lost connectivity, the request was aborted, DNS/TLS failed, or CORS
|
|
1320
|
+
* blocked it. 0 matches the XHR/`fetch` convention for "no response".
|
|
1321
|
+
*
|
|
1322
|
+
* This used to be reported as `500 Internal server error`, which made a
|
|
1323
|
+
* client-side network failure indistinguishable from a backend fault: the
|
|
1324
|
+
* "Backend 500s" monitor counted 1,395 events on a day when the API returned
|
|
1325
|
+
* only 33 real 5xx responses.
|
|
1326
|
+
*/ const NETWORK_ERROR_STATUS = 0;
|
|
1327
|
+
const NETWORK_ERROR_CODE = 'NETWORK_ERROR';
|
|
1328
|
+
const NETWORK_ERROR_MESSAGE = 'Network error - no response received';
|
|
1329
|
+
const STATUS_MESSAGES = {
|
|
1330
|
+
400: 'Invalid request',
|
|
1331
|
+
403: 'Forbidden',
|
|
1332
|
+
422: 'Unprocessable content',
|
|
1333
|
+
429: 'Rate limited',
|
|
1334
|
+
500: INTERNAL_SERVER_ERROR_MESSAGE
|
|
1335
|
+
};
|
|
1336
|
+
const MISSING_AUTH_MESSAGE = 'Authorization header or cookie missing';
|
|
1337
|
+
// A 401 can mean the request carried no credentials, or carried credentials the
|
|
1338
|
+
// backend rejected — the two need different messages. The Authorization header
|
|
1339
|
+
// is readable so we can confirm its presence; the auth cookie is httpOnly, so we
|
|
1340
|
+
// can only report whether cookie mode was enabled (`withCredentials`), not that a
|
|
1341
|
+
// cookie was actually attached.
|
|
1342
|
+
const getRequestCredentials = (error)=>{
|
|
1343
|
+
var _error_config, _error_config1;
|
|
1344
|
+
const requestHeaders = (_error_config = error.config) == null ? void 0 : _error_config.headers;
|
|
1345
|
+
var _requestHeaders_Authorization;
|
|
1346
|
+
const hasAuthorizationHeader = Boolean((_requestHeaders_Authorization = requestHeaders == null ? void 0 : requestHeaders['Authorization']) != null ? _requestHeaders_Authorization : requestHeaders == null ? void 0 : requestHeaders['authorization']);
|
|
1347
|
+
const usesCookieAuth = Boolean((_error_config1 = error.config) == null ? void 0 : _error_config1.withCredentials);
|
|
1348
|
+
return {
|
|
1349
|
+
hasAuthorizationHeader,
|
|
1350
|
+
usesCookieAuth
|
|
1351
|
+
};
|
|
1352
|
+
};
|
|
1353
|
+
const resolveUnauthorizedMessage = (error)=>{
|
|
1354
|
+
const { hasAuthorizationHeader, usesCookieAuth } = getRequestCredentials(error);
|
|
1355
|
+
if (!hasAuthorizationHeader && !usesCookieAuth) {
|
|
1356
|
+
return MISSING_AUTH_MESSAGE;
|
|
1357
|
+
}
|
|
1358
|
+
const credentialDescriptions = [];
|
|
1359
|
+
if (hasAuthorizationHeader) {
|
|
1360
|
+
credentialDescriptions.push('Authorization header present');
|
|
1361
|
+
}
|
|
1362
|
+
if (usesCookieAuth) {
|
|
1363
|
+
credentialDescriptions.push('withCredentials enabled (cookie auth)');
|
|
1364
|
+
}
|
|
1365
|
+
return `Unauthorized (${credentialDescriptions.join(', ')})`;
|
|
1366
|
+
};
|
|
1367
|
+
const resolveStatusMessage = (status, error)=>{
|
|
1368
|
+
if (status === 401) {
|
|
1369
|
+
return resolveUnauthorizedMessage(error);
|
|
1370
|
+
}
|
|
1371
|
+
if (status && STATUS_MESSAGES[status]) {
|
|
1372
|
+
return STATUS_MESSAGES[status];
|
|
1373
|
+
}
|
|
1374
|
+
return undefined;
|
|
1375
|
+
};
|
|
1376
|
+
const handleAxiosError = (error, message, context, /**
|
|
1377
|
+
* Optional severity hint from the caller, for failures the caller knows are
|
|
1378
|
+
* expected (e.g. a business-rule rejection). It can only *lower* severity to
|
|
1379
|
+
* `warn` — a caller must not be able to escalate, and a genuine 5xx has to
|
|
1380
|
+
* stay at `error` regardless of what the call site believes.
|
|
1381
|
+
*
|
|
1382
|
+
* Without this the hint that `logError` accepts was silently discarded for
|
|
1383
|
+
* every AxiosError, which is the most common error type in the SDK.
|
|
1384
|
+
*/ options)=>{
|
|
1385
|
+
var _error_config, _error_config1, _error_response, _error_response1, _error_response2;
|
|
1386
|
+
const requestUrl = (_error_config = error.config) == null ? void 0 : _error_config.url;
|
|
1387
|
+
const requestBaseUrl = (_error_config1 = error.config) == null ? void 0 : _error_config1.baseURL;
|
|
1388
|
+
// The HTTP status MUST NOT be logged as a top-level `status` field: Datadog
|
|
1389
|
+
// reserves `status` for the log level and its remapper turns a numeric value
|
|
1390
|
+
// into `info`, silently burying every 4xx/5xx we emit here. `http.status_code`
|
|
1391
|
+
// is Datadog's standard attribute, so the level survives and these logs join
|
|
1392
|
+
// against edge/CDN logs on the same field.
|
|
1393
|
+
const logPayload = _extends({
|
|
1394
|
+
message,
|
|
1395
|
+
error: (_error_response = error.response) == null ? void 0 : _error_response.data,
|
|
1396
|
+
http: {
|
|
1397
|
+
status_code: (_error_response1 = error.response) == null ? void 0 : _error_response1.status
|
|
1398
|
+
}
|
|
1399
|
+
}, requestBaseUrl && {
|
|
1400
|
+
requestBaseUrl
|
|
1401
|
+
}, requestUrl && {
|
|
1402
|
+
requestUrl
|
|
1403
|
+
}, {
|
|
1404
|
+
context
|
|
1405
|
+
});
|
|
1406
|
+
const status = (_error_response2 = error.response) == null ? void 0 : _error_response2.status;
|
|
1407
|
+
// A 429 (Too Many Requests) is a transient rate-limit signal, not a code or
|
|
1408
|
+
// logic failure — and a request that never got a response is a client-side
|
|
1409
|
+
// connectivity problem, not a server fault. Both are logged at `warn` so they
|
|
1410
|
+
// don't pollute error dashboards or trigger PagerDuty. The 429 case covers
|
|
1411
|
+
// both Cloudflare WAF blocks and application-level rate limiting.
|
|
1412
|
+
const inferredLevel = status === 429 || status === undefined ? 'warn' : 'error';
|
|
1413
|
+
// A 5xx is a server fault: no call site is entitled to downgrade it, however
|
|
1414
|
+
// expected it believes the failure to be. The hint only applies below 500.
|
|
1415
|
+
const isServerFault = status !== undefined && status >= 500;
|
|
1416
|
+
const logLevel = (options == null ? void 0 : options.level) === 'warn' && !isServerFault ? 'warn' : inferredLevel;
|
|
1417
|
+
Logger[logLevel]('[DynamicWaasWalletClient] Axios error: ', logPayload);
|
|
1418
|
+
// No response at all: report it as such rather than laundering it into a 500.
|
|
1419
|
+
if (status === undefined) {
|
|
1420
|
+
throw new WalletApiError(NETWORK_ERROR_STATUS, NETWORK_ERROR_MESSAGE, NETWORK_ERROR_CODE);
|
|
1421
|
+
}
|
|
1422
|
+
var _resolveStatusMessage;
|
|
1423
|
+
// Preserve the real status. Collapsing every unmapped code to 500 made a 404
|
|
1424
|
+
// (backup not found) and a 504 (gateway timeout) both surface as
|
|
1425
|
+
// "Internal server error", so neither the caller nor a monitor could tell a
|
|
1426
|
+
// missing resource from an outage.
|
|
1427
|
+
const resolvedMessage = (_resolveStatusMessage = resolveStatusMessage(status, error)) != null ? _resolveStatusMessage : `Request failed with status ${status}`;
|
|
1428
|
+
throw new WalletApiError(status, resolvedMessage, extractServerErrorCode(error));
|
|
1429
|
+
};
|
|
1430
|
+
|
|
1282
1431
|
const getRequiredExternalKeyShareId = (ks)=>{
|
|
1283
1432
|
if (!ks.externalKeyShareId) {
|
|
1284
1433
|
throw new Error(`Missing externalKeyShareId for key share (id: ${ks.id}, keygenId: ${ks.keygenId})`);
|
|
@@ -1293,7 +1442,7 @@ const getRequiredExternalKeyShareId = (ks)=>{
|
|
|
1293
1442
|
* before it is posted and surfaces in Datadog exactly as the recovery path does.
|
|
1294
1443
|
*/ const assertDynamicLocationsHaveExternalKeyShareId = (locations)=>locations.filter((location)=>location.location === BackupLocation.DYNAMIC).forEach((location)=>getRequiredExternalKeyShareId(location));
|
|
1295
1444
|
|
|
1296
|
-
var version = "1.1.
|
|
1445
|
+
var version = "1.1.36";
|
|
1297
1446
|
|
|
1298
1447
|
/**
|
|
1299
1448
|
* Reads the Authorization header off an axios request config, which stores
|
|
@@ -1458,6 +1607,29 @@ const createWalletAccountRequestBody = ({ chainName, clientKeygenIds, thresholdS
|
|
|
1458
1607
|
businessAccountId
|
|
1459
1608
|
} : {});
|
|
1460
1609
|
};
|
|
1610
|
+
const MAX_SERVER_MESSAGE_LENGTH = 200;
|
|
1611
|
+
/**
|
|
1612
|
+
* 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.
|
|
1613
|
+
*
|
|
1614
|
+
* @param error - What the request threw.
|
|
1615
|
+
*/ const toSignAndBroadcastApiError = (error)=>{
|
|
1616
|
+
if (!axios.isAxiosError(error) || !error.response) {
|
|
1617
|
+
return error;
|
|
1618
|
+
}
|
|
1619
|
+
const { status, data } = error.response;
|
|
1620
|
+
const code = extractServerErrorCode(error);
|
|
1621
|
+
const record = data && typeof data === 'object' ? data : undefined;
|
|
1622
|
+
const serverMessage = [
|
|
1623
|
+
record == null ? void 0 : record['error'],
|
|
1624
|
+
record == null ? void 0 : record['message']
|
|
1625
|
+
].find((candidate)=>typeof candidate === 'string' && candidate.length > 0 && candidate !== code);
|
|
1626
|
+
const detail = [
|
|
1627
|
+
serverMessage == null ? void 0 : serverMessage.slice(0, MAX_SERVER_MESSAGE_LENGTH),
|
|
1628
|
+
code && `code: ${code}`
|
|
1629
|
+
].filter(Boolean).join(', ');
|
|
1630
|
+
const suffix = detail ? ` (${detail})` : '';
|
|
1631
|
+
return new WalletApiError(status, `Sign and broadcast failed with status ${status}${suffix}`, code);
|
|
1632
|
+
};
|
|
1461
1633
|
class DynamicApiClient extends BaseClient {
|
|
1462
1634
|
async getBackupRelayToken({ traceContext }) {
|
|
1463
1635
|
const { data } = await this.apiClient.get(`${this.waasRoutePrefix}/keyShares/backup/token`, {
|
|
@@ -1586,6 +1758,74 @@ class DynamicApiClient extends BaseClient {
|
|
|
1586
1758
|
traceContext
|
|
1587
1759
|
});
|
|
1588
1760
|
}
|
|
1761
|
+
/**
|
|
1762
|
+
* 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.
|
|
1763
|
+
*
|
|
1764
|
+
* @param params.walletId - The wallet that signs.
|
|
1765
|
+
* @param params.shareSetId - Which of the wallet's key sets to use. Defaults to its active one.
|
|
1766
|
+
* @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.
|
|
1767
|
+
* @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.
|
|
1768
|
+
* @param params.broadcastOptions - Solana send settings: other required signers' signatures, and skipping the pre-send dry run.
|
|
1769
|
+
* @param params.isFormatted - `true` if each message is already a hash. `false` for a serialized transaction.
|
|
1770
|
+
* @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`.
|
|
1771
|
+
* @param params.externalReference - Your own label, returned on every event. Not unique.
|
|
1772
|
+
* @param params.roomIds - Signing rooms you already opened, one per message. Send all or none; Dynamic opens them when omitted.
|
|
1773
|
+
* @param params.approvalQuorum - Approvers' consent for a governed wallet, sent on the retry after `intent_required`.
|
|
1774
|
+
* @param params.idempotencyKey - Makes a retry safe: the same key replays the stored answer instead of sending twice.
|
|
1775
|
+
* @param params.dynamicRequestId - Id of this one request, to match client and server logs.
|
|
1776
|
+
* @param params.traceContext - Ties this call's logs to the server's, across retries. Created when omitted.
|
|
1777
|
+
* @param params.mfaToken - Proof of the user's multi-factor check, when the wallet needs one.
|
|
1778
|
+
* @param params.elevatedAccessToken - Proof of a recent re-login, when the wallet needs one.
|
|
1779
|
+
* @param params.onError - Called with the failure's message, never the raw error.
|
|
1780
|
+
* @returns The transaction record: its id, its status, and the signing rooms while signing is pending.
|
|
1781
|
+
* @throws SignAndBroadcastRequestTooLargeError when the body is over the server limit.
|
|
1782
|
+
* @throws SigningConsentRequiredError when a governed wallet needs approvers' consent first.
|
|
1783
|
+
* @throws WalletApiError when the server rejects it, with its reason and code.
|
|
1784
|
+
*/ async signAndBroadcast({ dynamicRequestId, walletId, shareSetId, contexts, broadcastOptions, messages, isFormatted, requestedCertainty, externalReference, roomIds, approvalQuorum, idempotencyKey, onError, mfaToken, elevatedAccessToken, traceContext }) {
|
|
1785
|
+
try {
|
|
1786
|
+
return await requestJson({
|
|
1787
|
+
apiClient: this.apiClient,
|
|
1788
|
+
dynamicRequestId,
|
|
1789
|
+
idempotencyKey,
|
|
1790
|
+
endpoint: `${this.waasRoutePrefix}/${walletId}/transactions/signAndBroadcast`,
|
|
1791
|
+
body: _extends({
|
|
1792
|
+
messages,
|
|
1793
|
+
contexts
|
|
1794
|
+
}, broadcastOptions ? {
|
|
1795
|
+
broadcastOptions
|
|
1796
|
+
} : {}, {
|
|
1797
|
+
isFormatted
|
|
1798
|
+
}, requestedCertainty ? {
|
|
1799
|
+
requestedCertainty
|
|
1800
|
+
} : {}, externalReference ? {
|
|
1801
|
+
externalReference
|
|
1802
|
+
} : {}, roomIds ? {
|
|
1803
|
+
roomIds
|
|
1804
|
+
} : {}, approvalQuorum ? {
|
|
1805
|
+
approvalQuorum
|
|
1806
|
+
} : {}, shareSetId ? {
|
|
1807
|
+
shareSetId
|
|
1808
|
+
} : {}),
|
|
1809
|
+
timeoutMs: 20000,
|
|
1810
|
+
timeoutMessage: 'Sign and broadcast timed out',
|
|
1811
|
+
onError,
|
|
1812
|
+
mfaToken,
|
|
1813
|
+
elevatedAccessToken,
|
|
1814
|
+
traceContext
|
|
1815
|
+
});
|
|
1816
|
+
} catch (error) {
|
|
1817
|
+
const response = error.response;
|
|
1818
|
+
if ((response == null ? void 0 : response.status) === 413) {
|
|
1819
|
+
throw new SignAndBroadcastRequestTooLargeError();
|
|
1820
|
+
}
|
|
1821
|
+
// A governed wallet with nothing signed yet answers 422 intent_required, as plain signing's stream does.
|
|
1822
|
+
const intentRequired = (response == null ? void 0 : response.status) === 422 ? asIntentRequiredFrom422Body(response.data) : undefined;
|
|
1823
|
+
if (intentRequired) {
|
|
1824
|
+
throw new SigningConsentRequiredError(intentRequired);
|
|
1825
|
+
}
|
|
1826
|
+
throw toSignAndBroadcastApiError(error);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1589
1829
|
async refreshWalletAccountShares({ dynamicRequestId, walletId, shareSetId, onError, onCeremonyComplete, mfaToken, elevatedAccessToken, traceContext }) {
|
|
1590
1830
|
return createEventStreamPromise({
|
|
1591
1831
|
apiClient: this.apiClient,
|
|
@@ -1630,7 +1870,7 @@ class DynamicApiClient extends BaseClient {
|
|
|
1630
1870
|
traceContext
|
|
1631
1871
|
});
|
|
1632
1872
|
}
|
|
1633
|
-
async exportKey({ mfaToken, elevatedAccessToken, dynamicRequestId, walletId, shareSetId, exportId, bitcoinConfig, onError, traceContext }) {
|
|
1873
|
+
async exportKey({ mfaToken, elevatedAccessToken, dynamicRequestId, walletId, shareSetId, exportId, bitcoinConfig, approvalQuorum, proposalId, onError, traceContext }) {
|
|
1634
1874
|
const { addressType } = bitcoinConfig || {};
|
|
1635
1875
|
const requestBody = _extends({
|
|
1636
1876
|
exportId
|
|
@@ -1638,6 +1878,10 @@ class DynamicApiClient extends BaseClient {
|
|
|
1638
1878
|
addressType
|
|
1639
1879
|
} : {}, shareSetId ? {
|
|
1640
1880
|
shareSetId
|
|
1881
|
+
} : {}, approvalQuorum ? {
|
|
1882
|
+
approvalQuorum
|
|
1883
|
+
} : {}, proposalId ? {
|
|
1884
|
+
proposalId
|
|
1641
1885
|
} : {});
|
|
1642
1886
|
return createEventStreamPromise({
|
|
1643
1887
|
apiClient: this.apiClient,
|
|
@@ -2652,6 +2896,55 @@ const createSigningIntent = (request, prepared, options = {})=>{
|
|
|
2652
2896
|
return addRequestMetadata(createEnvelope(request, now, options.intentId), request, prepared);
|
|
2653
2897
|
};
|
|
2654
2898
|
|
|
2899
|
+
/** Must match redcoast's and wallet-service's `WAAS_EXPORT_INTENT_ACTION`. */ const WAAS_EXPORT_INTENT_ACTION = 'waasExportQuorumApprove';
|
|
2900
|
+
/** How long approvers have when the caller doesn't ask for a shorter window. */ const DEFAULT_COMPLETE_WITHIN_MS = 24 * 60 * 60 * 1000;
|
|
2901
|
+
const assertIdentity = (request)=>{
|
|
2902
|
+
if (!request.accountAddress || !request.businessAccountId || !request.initiatorUserId || !request.exportId) {
|
|
2903
|
+
throw new Error('Export intent identity is required');
|
|
2904
|
+
}
|
|
2905
|
+
};
|
|
2906
|
+
const assertValidNow = (now)=>{
|
|
2907
|
+
if (Number.isNaN(now.getTime())) throw new Error('Invalid export intent creation time');
|
|
2908
|
+
};
|
|
2909
|
+
/** The caller's `completeBy`, or the default window, once it is known to be a canonical future ISO timestamp. */ const resolveCompleteBy = (requested, now)=>{
|
|
2910
|
+
const completeBy = requested != null ? requested : new Date(now.getTime() + DEFAULT_COMPLETE_WITHIN_MS).toISOString();
|
|
2911
|
+
const timestamp = Date.parse(completeBy);
|
|
2912
|
+
const isCanonicalFutureTimestamp = Number.isFinite(timestamp) && new Date(timestamp).toISOString() === completeBy && timestamp > now.getTime();
|
|
2913
|
+
if (!isCanonicalFutureTimestamp) {
|
|
2914
|
+
throw new Error('Export intent completion time must be a future ISO timestamp');
|
|
2915
|
+
}
|
|
2916
|
+
return completeBy;
|
|
2917
|
+
};
|
|
2918
|
+
/**
|
|
2919
|
+
* Builds the export Intent the initiator signs. It binds one `exportId`, so a
|
|
2920
|
+
* set of approvals can't be replayed onto a different export. The server
|
|
2921
|
+
* clamps `completeBy` to the policy rule's own window; it can only tighten it.
|
|
2922
|
+
*/ const createExportIntent = (request, options = {})=>{
|
|
2923
|
+
var _globalThis_crypto_randomUUID, _globalThis_crypto;
|
|
2924
|
+
var _options_now;
|
|
2925
|
+
const now = (_options_now = options.now) != null ? _options_now : new Date();
|
|
2926
|
+
assertValidNow(now);
|
|
2927
|
+
assertIdentity(request);
|
|
2928
|
+
const completeBy = resolveCompleteBy(request.completeBy, now);
|
|
2929
|
+
var _options_intentId;
|
|
2930
|
+
const intentId = (_options_intentId = options.intentId) != null ? _options_intentId : (_globalThis_crypto = globalThis.crypto) == null ? void 0 : (_globalThis_crypto_randomUUID = _globalThis_crypto.randomUUID) == null ? void 0 : _globalThis_crypto_randomUUID.call(_globalThis_crypto);
|
|
2931
|
+
if (!intentId) throw new Error('Secure UUID generation is unavailable');
|
|
2932
|
+
return {
|
|
2933
|
+
accountAddress: request.accountAddress,
|
|
2934
|
+
actionType: WAAS_EXPORT_INTENT_ACTION,
|
|
2935
|
+
businessAccountId: request.businessAccountId,
|
|
2936
|
+
completeBy,
|
|
2937
|
+
createdAt: now.toISOString(),
|
|
2938
|
+
initiatorUserId: request.initiatorUserId,
|
|
2939
|
+
intentId,
|
|
2940
|
+
operation: {
|
|
2941
|
+
exportId: request.exportId,
|
|
2942
|
+
kind: 'export',
|
|
2943
|
+
restriction: 'blockExport'
|
|
2944
|
+
}
|
|
2945
|
+
};
|
|
2946
|
+
};
|
|
2947
|
+
|
|
2655
2948
|
const SDK_NAMESPACE = {
|
|
2656
2949
|
REACT: 'WalletKit',
|
|
2657
2950
|
CLIENT: 'ClientSDK'
|
|
@@ -2837,143 +3130,37 @@ const prepareRawMessageSigningPayload = (chainName, message)=>{
|
|
|
2837
3130
|
return typeof formatted === 'string' ? formatted : Buffer.from(formatted).toString('hex');
|
|
2838
3131
|
};
|
|
2839
3132
|
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
3133
|
+
/** Returns the context as plain JSON, with each bigint (which `JSON.stringify` throws on) turned into a string. */ const toJsonSafeContext = (context)=>{
|
|
3134
|
+
try {
|
|
3135
|
+
return JSON.parse(JSON.stringify(context, (_key, value)=>typeof value === 'bigint' ? String(value) : value));
|
|
3136
|
+
} catch (error) {
|
|
3137
|
+
throw new Error(`Could not serialize the signing context: ${error instanceof Error ? error.message : String(error)}`, {
|
|
3138
|
+
cause: error
|
|
3139
|
+
});
|
|
2846
3140
|
}
|
|
2847
|
-
}
|
|
2848
|
-
const getResponseRecord = (error)=>{
|
|
2849
|
-
var _error_response;
|
|
2850
|
-
const data = (_error_response = error.response) == null ? void 0 : _error_response.data;
|
|
2851
|
-
return data && typeof data === 'object' ? data : undefined;
|
|
2852
3141
|
};
|
|
2853
|
-
|
|
2854
|
-
// backends use `code` or a bare `error`. Exported for callers that must skip handleAxiosError's own logging.
|
|
2855
|
-
const extractServerErrorCode = (error)=>{
|
|
2856
|
-
const record = getResponseRecord(error);
|
|
2857
|
-
const candidates = [
|
|
2858
|
-
record == null ? void 0 : record['code'],
|
|
2859
|
-
record == null ? void 0 : record['error_code'],
|
|
2860
|
-
record == null ? void 0 : record['error']
|
|
2861
|
-
];
|
|
2862
|
-
return candidates.find((value)=>typeof value === 'string');
|
|
2863
|
-
};
|
|
2864
|
-
const INTERNAL_SERVER_ERROR_MESSAGE = 'Internal server error';
|
|
3142
|
+
|
|
2865
3143
|
/**
|
|
2866
|
-
*
|
|
2867
|
-
*
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
* only 33 real 5xx responses.
|
|
2874
|
-
*/ const NETWORK_ERROR_STATUS = 0;
|
|
2875
|
-
const NETWORK_ERROR_CODE = 'NETWORK_ERROR';
|
|
2876
|
-
const NETWORK_ERROR_MESSAGE = 'Network error - no response received';
|
|
2877
|
-
const STATUS_MESSAGES = {
|
|
2878
|
-
400: 'Invalid request',
|
|
2879
|
-
403: 'Forbidden',
|
|
2880
|
-
422: 'Unprocessable content',
|
|
2881
|
-
429: 'Rate limited',
|
|
2882
|
-
500: INTERNAL_SERVER_ERROR_MESSAGE
|
|
2883
|
-
};
|
|
2884
|
-
const MISSING_AUTH_MESSAGE = 'Authorization header or cookie missing';
|
|
2885
|
-
// A 401 can mean the request carried no credentials, or carried credentials the
|
|
2886
|
-
// backend rejected — the two need different messages. The Authorization header
|
|
2887
|
-
// is readable so we can confirm its presence; the auth cookie is httpOnly, so we
|
|
2888
|
-
// can only report whether cookie mode was enabled (`withCredentials`), not that a
|
|
2889
|
-
// cookie was actually attached.
|
|
2890
|
-
const getRequestCredentials = (error)=>{
|
|
2891
|
-
var _error_config, _error_config1;
|
|
2892
|
-
const requestHeaders = (_error_config = error.config) == null ? void 0 : _error_config.headers;
|
|
2893
|
-
var _requestHeaders_Authorization;
|
|
2894
|
-
const hasAuthorizationHeader = Boolean((_requestHeaders_Authorization = requestHeaders == null ? void 0 : requestHeaders['Authorization']) != null ? _requestHeaders_Authorization : requestHeaders == null ? void 0 : requestHeaders['authorization']);
|
|
2895
|
-
const usesCookieAuth = Boolean((_error_config1 = error.config) == null ? void 0 : _error_config1.withCredentials);
|
|
2896
|
-
return {
|
|
2897
|
-
hasAuthorizationHeader,
|
|
2898
|
-
usesCookieAuth
|
|
2899
|
-
};
|
|
2900
|
-
};
|
|
2901
|
-
const resolveUnauthorizedMessage = (error)=>{
|
|
2902
|
-
const { hasAuthorizationHeader, usesCookieAuth } = getRequestCredentials(error);
|
|
2903
|
-
if (!hasAuthorizationHeader && !usesCookieAuth) {
|
|
2904
|
-
return MISSING_AUTH_MESSAGE;
|
|
2905
|
-
}
|
|
2906
|
-
const credentialDescriptions = [];
|
|
2907
|
-
if (hasAuthorizationHeader) {
|
|
2908
|
-
credentialDescriptions.push('Authorization header present');
|
|
2909
|
-
}
|
|
2910
|
-
if (usesCookieAuth) {
|
|
2911
|
-
credentialDescriptions.push('withCredentials enabled (cookie auth)');
|
|
2912
|
-
}
|
|
2913
|
-
return `Unauthorized (${credentialDescriptions.join(', ')})`;
|
|
2914
|
-
};
|
|
2915
|
-
const resolveStatusMessage = (status, error)=>{
|
|
2916
|
-
if (status === 401) {
|
|
2917
|
-
return resolveUnauthorizedMessage(error);
|
|
2918
|
-
}
|
|
2919
|
-
if (status && STATUS_MESSAGES[status]) {
|
|
2920
|
-
return STATUS_MESSAGES[status];
|
|
2921
|
-
}
|
|
2922
|
-
return undefined;
|
|
2923
|
-
};
|
|
2924
|
-
const handleAxiosError = (error, message, context, /**
|
|
2925
|
-
* Optional severity hint from the caller, for failures the caller knows are
|
|
2926
|
-
* expected (e.g. a business-rule rejection). It can only *lower* severity to
|
|
2927
|
-
* `warn` — a caller must not be able to escalate, and a genuine 5xx has to
|
|
2928
|
-
* stay at `error` regardless of what the call site believes.
|
|
2929
|
-
*
|
|
2930
|
-
* Without this the hint that `logError` accepts was silently discarded for
|
|
2931
|
-
* every AxiosError, which is the most common error type in the SDK.
|
|
2932
|
-
*/ options)=>{
|
|
2933
|
-
var _error_config, _error_config1, _error_response, _error_response1, _error_response2;
|
|
2934
|
-
const requestUrl = (_error_config = error.config) == null ? void 0 : _error_config.url;
|
|
2935
|
-
const requestBaseUrl = (_error_config1 = error.config) == null ? void 0 : _error_config1.baseURL;
|
|
2936
|
-
// The HTTP status MUST NOT be logged as a top-level `status` field: Datadog
|
|
2937
|
-
// reserves `status` for the log level and its remapper turns a numeric value
|
|
2938
|
-
// into `info`, silently burying every 4xx/5xx we emit here. `http.status_code`
|
|
2939
|
-
// is Datadog's standard attribute, so the level survives and these logs join
|
|
2940
|
-
// against edge/CDN logs on the same field.
|
|
2941
|
-
const logPayload = _extends({
|
|
2942
|
-
message,
|
|
2943
|
-
error: (_error_response = error.response) == null ? void 0 : _error_response.data,
|
|
2944
|
-
http: {
|
|
2945
|
-
status_code: (_error_response1 = error.response) == null ? void 0 : _error_response1.status
|
|
2946
|
-
}
|
|
2947
|
-
}, requestBaseUrl && {
|
|
2948
|
-
requestBaseUrl
|
|
2949
|
-
}, requestUrl && {
|
|
2950
|
-
requestUrl
|
|
2951
|
-
}, {
|
|
2952
|
-
context
|
|
3144
|
+
* Returns a trace context that always has an id, so a retrying operation can hand the same one to each attempt.
|
|
3145
|
+
* The id is 32 lowercase hex characters, a valid W3C trace id the server can continue.
|
|
3146
|
+
*/ const withTraceId = (traceContext)=>{
|
|
3147
|
+
var _traceContext_traceId, _traceContext_startTime;
|
|
3148
|
+
return _extends({}, traceContext, {
|
|
3149
|
+
traceId: (_traceContext_traceId = traceContext == null ? void 0 : traceContext.traceId) != null ? _traceContext_traceId : uuid.v4().replaceAll('-', ''),
|
|
3150
|
+
startTime: (_traceContext_startTime = traceContext == null ? void 0 : traceContext.startTime) != null ? _traceContext_startTime : Date.now()
|
|
2953
3151
|
});
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
// No response at all: report it as such rather than laundering it into a 500.
|
|
2967
|
-
if (status === undefined) {
|
|
2968
|
-
throw new WalletApiError(NETWORK_ERROR_STATUS, NETWORK_ERROR_MESSAGE, NETWORK_ERROR_CODE);
|
|
2969
|
-
}
|
|
2970
|
-
var _resolveStatusMessage;
|
|
2971
|
-
// Preserve the real status. Collapsing every unmapped code to 500 made a 404
|
|
2972
|
-
// (backup not found) and a 504 (gateway timeout) both surface as
|
|
2973
|
-
// "Internal server error", so neither the caller nor a monitor could tell a
|
|
2974
|
-
// missing resource from an outage.
|
|
2975
|
-
const resolvedMessage = (_resolveStatusMessage = resolveStatusMessage(status, error)) != null ? _resolveStatusMessage : `Request failed with status ${status}`;
|
|
2976
|
-
throw new WalletApiError(status, resolvedMessage, extractServerErrorCode(error));
|
|
3152
|
+
};
|
|
3153
|
+
|
|
3154
|
+
/** 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 })=>{
|
|
3155
|
+
var _target, _target1;
|
|
3156
|
+
if (!error || typeof error !== 'object' || !Object.isExtensible(error)) {
|
|
3157
|
+
return;
|
|
3158
|
+
}
|
|
3159
|
+
const target = error;
|
|
3160
|
+
var _traceId;
|
|
3161
|
+
(_traceId = (_target = target).traceId) != null ? _traceId : _target.traceId = traceId;
|
|
3162
|
+
var _requestId;
|
|
3163
|
+
(_requestId = (_target1 = target).requestId) != null ? _requestId : _target1.requestId = requestId;
|
|
2977
3164
|
};
|
|
2978
3165
|
|
|
2979
3166
|
Object.defineProperty(exports, "SigningAlgorithm", {
|
|
@@ -3069,17 +3256,21 @@ exports.RELAY_APP_ID_HEADER = RELAY_APP_ID_HEADER;
|
|
|
3069
3256
|
exports.SDK_NAMESPACE = SDK_NAMESPACE;
|
|
3070
3257
|
exports.SERVER_SDK_ROUTE_PREFIX = SERVER_SDK_ROUTE_PREFIX;
|
|
3071
3258
|
exports.SOLANA_RPC_URL = SOLANA_RPC_URL;
|
|
3259
|
+
exports.SignAndBroadcastRequestTooLargeError = SignAndBroadcastRequestTooLargeError;
|
|
3072
3260
|
exports.SigningConsentRequiredError = SigningConsentRequiredError;
|
|
3073
3261
|
exports.SuccessEventType = SuccessEventType;
|
|
3074
3262
|
exports.ThresholdSignatureScheme = ThresholdSignatureScheme;
|
|
3075
3263
|
exports.URL_PATTERNS = URL_PATTERNS;
|
|
3264
|
+
exports.WAAS_EXPORT_INTENT_ACTION = WAAS_EXPORT_INTENT_ACTION;
|
|
3076
3265
|
exports.WalletApiError = WalletApiError;
|
|
3077
3266
|
exports.WalletOperation = WalletOperation;
|
|
3078
3267
|
exports.WalletReadyState = WalletReadyState;
|
|
3079
3268
|
exports.assertDynamicLocationsHaveExternalKeyShareId = assertDynamicLocationsHaveExternalKeyShareId;
|
|
3269
|
+
exports.attachRequestIds = attachRequestIds;
|
|
3080
3270
|
exports.buildForwardMpcEnclaveUrl = buildForwardMpcEnclaveUrl;
|
|
3081
3271
|
exports.chain = chain;
|
|
3082
3272
|
exports.chainEnumToVerifiedCredentialName = chainEnumToVerifiedCredentialName;
|
|
3273
|
+
exports.createExportIntent = createExportIntent;
|
|
3083
3274
|
exports.createRejectablePromise = createRejectablePromise;
|
|
3084
3275
|
exports.createSigningIntent = createSigningIntent;
|
|
3085
3276
|
exports.extractServerErrorCode = extractServerErrorCode;
|
|
@@ -3107,4 +3298,6 @@ exports.prepareRawMessageSigningPayload = prepareRawMessageSigningPayload;
|
|
|
3107
3298
|
exports.sanitizeBusinessAccountSignedIntent = sanitizeBusinessAccountSignedIntent;
|
|
3108
3299
|
exports.serializeError = serializeError;
|
|
3109
3300
|
exports.serializeMessageForForwardMPC = serializeMessageForForwardMPC;
|
|
3301
|
+
exports.toJsonSafeContext = toJsonSafeContext;
|
|
3110
3302
|
exports.verifiedCredentialNameToChainEnum = verifiedCredentialNameToChainEnum;
|
|
3303
|
+
exports.withTraceId = withTraceId;
|