@usherlabs/cex-broker 0.2.27 → 0.2.28

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/dist/index.js CHANGED
@@ -274246,6 +274246,65 @@ function validateDeposit(policy, exchange, network, ticker) {
274246
274246
  return { valid: true };
274247
274247
  }
274248
274248
 
274249
+ // src/helpers/shared/errors.ts
274250
+ var MAX_ERROR_DETAIL_LENGTH = 512;
274251
+ var REDACTED_ERROR_MESSAGE = "redacted_error";
274252
+ function getErrorMessage(error) {
274253
+ return error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
274254
+ }
274255
+ function errorClassName(error) {
274256
+ if (!(error instanceof Error)) {
274257
+ return;
274258
+ }
274259
+ const name = error.constructor?.name || error.name;
274260
+ return name && name !== "Error" ? name : undefined;
274261
+ }
274262
+ function errorCode(error) {
274263
+ if (error === null || typeof error !== "object") {
274264
+ return;
274265
+ }
274266
+ const code = error.code;
274267
+ if (typeof code === "string") {
274268
+ return code.trim() || undefined;
274269
+ }
274270
+ if (typeof code === "number" && Number.isFinite(code)) {
274271
+ return String(code);
274272
+ }
274273
+ if (typeof code === "bigint") {
274274
+ return String(code);
274275
+ }
274276
+ return;
274277
+ }
274278
+ function sanitizeErrorDetail(error, options = {}) {
274279
+ const className = errorClassName(error);
274280
+ const message = getErrorMessage(error);
274281
+ const code = options.includeCode ? errorCode(error) : undefined;
274282
+ const prefix = [className, code ? `[code=${code}]` : undefined].filter((part) => Boolean(part)).join(" ");
274283
+ const detail = prefix ? `${prefix}: ${message}` : message;
274284
+ return detail.replace(/\s+/g, " ").trim().slice(0, MAX_ERROR_DETAIL_LENGTH);
274285
+ }
274286
+ function safeLogError(context2, error) {
274287
+ try {
274288
+ log.error(context2, { error });
274289
+ } catch {
274290
+ console.error(context2, error);
274291
+ }
274292
+ }
274293
+ function safeLogRedactedError(context2, error) {
274294
+ const errorType = errorClassName(error) ?? (error instanceof Error ? error.name : typeof error);
274295
+ try {
274296
+ log.error(context2, {
274297
+ error_type: errorType,
274298
+ error_message: REDACTED_ERROR_MESSAGE
274299
+ });
274300
+ } catch {
274301
+ console.error(context2, {
274302
+ error_type: errorType,
274303
+ error_message: REDACTED_ERROR_MESSAGE
274304
+ });
274305
+ }
274306
+ }
274307
+
274249
274308
  // src/helpers/shared/guards.ts
274250
274309
  function isRecord(value) {
274251
274310
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -274266,7 +274325,6 @@ var NUMERIC_METRICS = [
274266
274325
  ["feeAmount", "cex_market_action_fee_amount"],
274267
274326
  ["feeRate", "cex_market_action_fee_rate"]
274268
274327
  ];
274269
- var REDACTED_ERROR_MESSAGE = "redacted_error";
274270
274328
  async function emitOrderExecutionTelemetry(otelMetrics, context2, order, error) {
274271
274329
  try {
274272
274330
  const telemetry = buildOrderExecutionTelemetry(context2, order, error);
@@ -274758,7 +274816,7 @@ function buildOrderEventArchiveRow(input) {
274758
274816
  fee_rate: telemetry.feeRate,
274759
274817
  exchange_timestamp: telemetry.exchangeTimestamp,
274760
274818
  error_type: telemetry.errorType,
274761
- error_message: telemetry.errorMessage,
274819
+ error_message: input.errorDetail ?? telemetry.errorMessage,
274762
274820
  payload_json: JSON.stringify(telemetry)
274763
274821
  })
274764
274822
  };
@@ -274890,386 +274948,126 @@ function buildMarketMetadataSnapshotRow(input) {
274890
274948
  };
274891
274949
  }
274892
274950
 
274893
- // src/helpers/broker-execution-archive/capture.ts
274894
- function archiveOrderExecutionInBackground(archiver, context2, order, error, options) {
274895
- if (!archiver?.isEnabled()) {
274896
- return;
274951
+ // src/helpers/broker-execution-archive/writer.ts
274952
+ import { closeSync, fsyncSync, openSync, writeSync } from "fs";
274953
+ import { request as httpRequest2 } from "http";
274954
+ import { request as httpsRequest2 } from "https";
274955
+
274956
+ // node_modules/@opentelemetry/api-logs/build/esm/types/LogRecord.js
274957
+ var SeverityNumber2;
274958
+ (function(SeverityNumber3) {
274959
+ SeverityNumber3[SeverityNumber3["UNSPECIFIED"] = 0] = "UNSPECIFIED";
274960
+ SeverityNumber3[SeverityNumber3["TRACE"] = 1] = "TRACE";
274961
+ SeverityNumber3[SeverityNumber3["TRACE2"] = 2] = "TRACE2";
274962
+ SeverityNumber3[SeverityNumber3["TRACE3"] = 3] = "TRACE3";
274963
+ SeverityNumber3[SeverityNumber3["TRACE4"] = 4] = "TRACE4";
274964
+ SeverityNumber3[SeverityNumber3["DEBUG"] = 5] = "DEBUG";
274965
+ SeverityNumber3[SeverityNumber3["DEBUG2"] = 6] = "DEBUG2";
274966
+ SeverityNumber3[SeverityNumber3["DEBUG3"] = 7] = "DEBUG3";
274967
+ SeverityNumber3[SeverityNumber3["DEBUG4"] = 8] = "DEBUG4";
274968
+ SeverityNumber3[SeverityNumber3["INFO"] = 9] = "INFO";
274969
+ SeverityNumber3[SeverityNumber3["INFO2"] = 10] = "INFO2";
274970
+ SeverityNumber3[SeverityNumber3["INFO3"] = 11] = "INFO3";
274971
+ SeverityNumber3[SeverityNumber3["INFO4"] = 12] = "INFO4";
274972
+ SeverityNumber3[SeverityNumber3["WARN"] = 13] = "WARN";
274973
+ SeverityNumber3[SeverityNumber3["WARN2"] = 14] = "WARN2";
274974
+ SeverityNumber3[SeverityNumber3["WARN3"] = 15] = "WARN3";
274975
+ SeverityNumber3[SeverityNumber3["WARN4"] = 16] = "WARN4";
274976
+ SeverityNumber3[SeverityNumber3["ERROR"] = 17] = "ERROR";
274977
+ SeverityNumber3[SeverityNumber3["ERROR2"] = 18] = "ERROR2";
274978
+ SeverityNumber3[SeverityNumber3["ERROR3"] = 19] = "ERROR3";
274979
+ SeverityNumber3[SeverityNumber3["ERROR4"] = 20] = "ERROR4";
274980
+ SeverityNumber3[SeverityNumber3["FATAL"] = 21] = "FATAL";
274981
+ SeverityNumber3[SeverityNumber3["FATAL2"] = 22] = "FATAL2";
274982
+ SeverityNumber3[SeverityNumber3["FATAL3"] = 23] = "FATAL3";
274983
+ SeverityNumber3[SeverityNumber3["FATAL4"] = 24] = "FATAL4";
274984
+ })(SeverityNumber2 || (SeverityNumber2 = {}));
274985
+ // node_modules/@opentelemetry/api-logs/build/esm/NoopLogger.js
274986
+ class NoopLogger2 {
274987
+ emit(_logRecord) {}
274988
+ }
274989
+ var NOOP_LOGGER2 = new NoopLogger2;
274990
+ // node_modules/@opentelemetry/api-logs/build/esm/internal/global-utils.js
274991
+ var GLOBAL_LOGS_API_KEY2 = Symbol.for("io.opentelemetry.js.api.logs");
274992
+ var _global3 = globalThis;
274993
+ function makeGetter2(requiredVersion, instance, fallback) {
274994
+ return (version3) => version3 === requiredVersion ? instance : fallback;
274995
+ }
274996
+ var API_BACKWARDS_COMPATIBILITY_VERSION2 = 1;
274997
+
274998
+ // node_modules/@opentelemetry/api-logs/build/esm/NoopLoggerProvider.js
274999
+ class NoopLoggerProvider2 {
275000
+ getLogger(_name, _version, _options) {
275001
+ return new NoopLogger2;
274897
275002
  }
274898
- queueMicrotask(() => {
274899
- try {
274900
- const telemetry = buildOrderExecutionTelemetry(context2, order, error);
274901
- const tags = buildCommonArchiveTags({
274902
- deploymentId: archiver.getDeploymentId(),
274903
- accountSelector: context2.accountLabel,
274904
- exchange: context2.cex,
274905
- symbol: telemetry.symbol,
274906
- brokerObservedTimestamp: telemetry.brokerObservedTimestamp
274907
- });
274908
- archiver.enqueue(buildOrderEventArchiveRow({
274909
- tags,
274910
- action: context2.action,
274911
- telemetry,
274912
- marketMetadataHash: options?.marketMetadataHash
274913
- }));
274914
- } catch (archiveError) {
274915
- log.warn("Failed to archive order execution", { error: archiveError });
274916
- }
274917
- });
274918
275003
  }
274919
- function archiveSubscribeStreamInBackground(archiver, input) {
274920
- if (!archiver?.isEnabled()) {
274921
- return;
275004
+ var NOOP_LOGGER_PROVIDER2 = new NoopLoggerProvider2;
275005
+
275006
+ // node_modules/@opentelemetry/api-logs/build/esm/ProxyLogger.js
275007
+ class ProxyLogger2 {
275008
+ constructor(provider, name, version3, options) {
275009
+ this._provider = provider;
275010
+ this.name = name;
275011
+ this.version = version3;
275012
+ this.options = options;
274922
275013
  }
274923
- queueMicrotask(() => {
274924
- try {
274925
- const tags = buildCommonArchiveTags({
274926
- deploymentId: archiver.getDeploymentId(),
274927
- accountSelector: input.accountSelector,
274928
- exchange: input.exchange,
274929
- symbol: input.symbol
274930
- });
274931
- archiver.enqueue(buildSubscribeStreamArchiveRow({
274932
- tags,
274933
- subscriptionType: input.subscriptionType,
274934
- streamPayload: input.streamPayload,
274935
- secretLiterals: input.secretLiterals
274936
- }));
274937
- } catch (archiveError) {
274938
- log.warn("Failed to archive subscribe stream event", {
274939
- error: archiveError
274940
- });
275014
+ emit(logRecord) {
275015
+ this._getLogger().emit(logRecord);
275016
+ }
275017
+ _getLogger() {
275018
+ if (this._delegate) {
275019
+ return this._delegate;
274941
275020
  }
274942
- });
275021
+ const logger = this._provider._getDelegateLogger(this.name, this.version, this.options);
275022
+ if (!logger) {
275023
+ return NOOP_LOGGER2;
275024
+ }
275025
+ this._delegate = logger;
275026
+ return this._delegate;
275027
+ }
274943
275028
  }
274944
- function archiveTransferEventInBackground(archiver, input) {
274945
- if (!archiver?.isEnabled()) {
274946
- return;
275029
+
275030
+ // node_modules/@opentelemetry/api-logs/build/esm/ProxyLoggerProvider.js
275031
+ class ProxyLoggerProvider2 {
275032
+ getLogger(name, version3, options) {
275033
+ var _a3;
275034
+ return (_a3 = this._getDelegateLogger(name, version3, options)) !== null && _a3 !== undefined ? _a3 : new ProxyLogger2(this, name, version3, options);
275035
+ }
275036
+ _getDelegate() {
275037
+ var _a3;
275038
+ return (_a3 = this._delegate) !== null && _a3 !== undefined ? _a3 : NOOP_LOGGER_PROVIDER2;
275039
+ }
275040
+ _setDelegate(delegate) {
275041
+ this._delegate = delegate;
275042
+ }
275043
+ _getDelegateLogger(name, version3, options) {
275044
+ var _a3;
275045
+ return (_a3 = this._delegate) === null || _a3 === undefined ? undefined : _a3.getLogger(name, version3, options);
274947
275046
  }
274948
- queueMicrotask(() => {
274949
- try {
274950
- const tags = buildCommonArchiveTags({
274951
- deploymentId: archiver.getDeploymentId(),
274952
- accountSelector: input.accountSelector,
274953
- exchange: input.exchange,
274954
- symbol: input.assetSymbol,
274955
- brokerObservedTimestamp: input.brokerObservedTimestamp
274956
- });
274957
- archiver.enqueue(buildTransferEventArchiveRow({ tags, transfer: input.transfer }));
274958
- } catch (archiveError) {
274959
- log.warn("Failed to archive transfer event", { error: archiveError });
274960
- }
274961
- });
274962
275047
  }
274963
- function archiveWithdrawalObservationsInBackground(archiver, tracker, input) {
274964
- try {
274965
- if (!archiver?.isEnabled() || !Array.isArray(input.transactions)) {
274966
- return;
275048
+
275049
+ // node_modules/@opentelemetry/api-logs/build/esm/api/logs.js
275050
+ class LogsAPI2 {
275051
+ constructor() {
275052
+ this._proxyLoggerProvider = new ProxyLoggerProvider2;
275053
+ }
275054
+ static getInstance() {
275055
+ if (!this._instance) {
275056
+ this._instance = new LogsAPI2;
274967
275057
  }
274968
- const brokerObservedTimestamp = new Date().toISOString();
274969
- for (const [resultIndex, transaction] of input.transactions.entries()) {
274970
- const normalized = normalizeCcxtTransactionForArchive(transaction);
274971
- const assetSymbol = normalized.assetSymbol;
274972
- if (!tracker.shouldArchive({
274973
- exchange: input.exchange,
274974
- accountSelector: input.accountSelector,
274975
- assetSymbol,
274976
- transaction,
274977
- normalized
274978
- })) {
274979
- continue;
274980
- }
274981
- archiveTransferEventInBackground(archiver, {
274982
- exchange: input.exchange,
274983
- accountSelector: input.accountSelector,
274984
- assetSymbol,
274985
- brokerObservedTimestamp,
274986
- transfer: {
274987
- eventKind: "withdrawal",
274988
- lifecycleAction: "observe_withdrawal",
274989
- status: normalized.status,
274990
- amount: normalized.amount,
274991
- address: normalized.address,
274992
- network: normalized.network,
274993
- externalId: normalized.externalId,
274994
- txid: normalized.txid,
274995
- resultIndex,
274996
- feeAmount: normalized.feeAmount,
274997
- feeCurrency: normalized.feeCurrency,
274998
- exchangeTimestamp: normalized.exchangeTimestamp,
274999
- payload: transaction
275000
- }
275001
- });
275058
+ return this._instance;
275059
+ }
275060
+ setGlobalLoggerProvider(provider) {
275061
+ if (_global3[GLOBAL_LOGS_API_KEY2]) {
275062
+ return this.getLoggerProvider();
275002
275063
  }
275003
- } catch (archiveError) {
275004
- log.warn("Failed to archive withdrawal observations", {
275005
- error: archiveError
275006
- });
275064
+ _global3[GLOBAL_LOGS_API_KEY2] = makeGetter2(API_BACKWARDS_COMPATIBILITY_VERSION2, provider, NOOP_LOGGER_PROVIDER2);
275065
+ this._proxyLoggerProvider._setDelegate(provider);
275066
+ return provider;
275007
275067
  }
275008
- }
275009
- async function captureMarketMetadataSnapshot(archiver, broker, input) {
275010
- if (!archiver?.canPersistMarketMetadataSnapshot()) {
275011
- return;
275012
- }
275013
- try {
275014
- const fetchOrderBook = broker.fetchOrderBook;
275015
- if (typeof fetchOrderBook !== "function") {
275016
- return;
275017
- }
275018
- const orderBook = await fetchOrderBook.call(broker, input.symbol, 5);
275019
- const record = asRecord(orderBook);
275020
- const snapshot = {
275021
- action: input.action,
275022
- symbol: input.symbol,
275023
- bids: record?.bids,
275024
- asks: record?.asks,
275025
- timestamp: record?.timestamp ?? Date.now(),
275026
- datetime: record?.datetime,
275027
- nonce: record?.nonce
275028
- };
275029
- const tags = buildCommonArchiveTags({
275030
- deploymentId: archiver.getDeploymentId(),
275031
- accountSelector: input.accountSelector,
275032
- exchange: input.exchange,
275033
- symbol: input.symbol,
275034
- brokerObservedTimestamp: input.brokerObservedTimestamp
275035
- });
275036
- const row = buildMarketMetadataSnapshotRow({
275037
- tags,
275038
- clientOrderId: input.clientOrderId,
275039
- orderId: input.orderId,
275040
- makerActionId: input.makerActionId,
275041
- idempotencyId: input.idempotencyId,
275042
- marketSnapshot: snapshot
275043
- });
275044
- archiver.enqueue(row);
275045
- const hash4 = row.row.market_metadata_hash;
275046
- return typeof hash4 === "string" ? hash4 : undefined;
275047
- } catch (archiveError) {
275048
- log.warn("Failed to capture market metadata snapshot", {
275049
- error: archiveError
275050
- });
275051
- return;
275052
- }
275053
- }
275054
- // src/helpers/broker-execution-archive/withdrawal-observation-tracker.ts
275055
- var DEFAULT_WITHDRAWAL_OBSERVATION_TRACKER_MAX_ENTRIES = 1e4;
275056
- var VENUE_LIFECYCLE_EVIDENCE_FIELDS = [
275057
- "updated",
275058
- "updatedAt",
275059
- "updated_at",
275060
- "updateTime",
275061
- "update_time",
275062
- "updateTimestamp",
275063
- "lastUpdateTimestamp",
275064
- "completed",
275065
- "completedAt",
275066
- "completed_at",
275067
- "completeTime",
275068
- "complete_time",
275069
- "completionTime",
275070
- "successTime"
275071
- ];
275072
- function fingerprintEvidenceValue(value) {
275073
- if (value === undefined || value === null) {
275074
- return value;
275075
- }
275076
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
275077
- return value;
275078
- }
275079
- if (typeof value === "bigint") {
275080
- return value.toString();
275081
- }
275082
- try {
275083
- return JSON.stringify(value);
275084
- } catch {
275085
- return String(value);
275086
- }
275087
- }
275088
- function venueLifecycleEvidence(transaction) {
275089
- const record = asRecord(transaction);
275090
- const info = asRecord(record?.info);
275091
- const evidence = [];
275092
- for (const [scope, source] of [
275093
- ["transaction", record],
275094
- ["info", info]
275095
- ]) {
275096
- for (const field of VENUE_LIFECYCLE_EVIDENCE_FIELDS) {
275097
- const value = source?.[field];
275098
- if (value !== undefined && value !== null) {
275099
- evidence.push([scope, field, fingerprintEvidenceValue(value)]);
275100
- }
275101
- }
275102
- }
275103
- return evidence;
275104
- }
275105
- function observationFingerprint(observation) {
275106
- const { normalized, transaction } = observation;
275107
- return JSON.stringify({
275108
- status: normalized.status ?? null,
275109
- txid: normalized.txid ?? null,
275110
- amount: normalized.amount ?? null,
275111
- feeAmount: normalized.feeAmount ?? null,
275112
- feeCurrency: normalized.feeCurrency ?? null,
275113
- address: normalized.address ?? null,
275114
- network: normalized.network ?? null,
275115
- exchangeTimestamp: normalized.exchangeTimestamp ?? null,
275116
- venueLifecycleEvidence: venueLifecycleEvidence(transaction)
275117
- });
275118
- }
275119
-
275120
- class WithdrawalObservationTracker {
275121
- #fingerprints = new Map;
275122
- #maxEntries;
275123
- #missingIdSequence = 0n;
275124
- constructor(options) {
275125
- const maxEntries = options?.maxEntries ?? DEFAULT_WITHDRAWAL_OBSERVATION_TRACKER_MAX_ENTRIES;
275126
- this.#maxEntries = Number.isFinite(maxEntries) ? Math.max(1, Math.floor(maxEntries)) : DEFAULT_WITHDRAWAL_OBSERVATION_TRACKER_MAX_ENTRIES;
275127
- }
275128
- shouldArchive(observation) {
275129
- const externalId = observation.normalized.externalId?.trim();
275130
- const identity = JSON.stringify([
275131
- observation.exchange.trim().toLowerCase() || "unknown",
275132
- observation.accountSelector?.trim() || "unknown",
275133
- observation.assetSymbol?.trim().toUpperCase() || "unknown",
275134
- externalId || `missing:${++this.#missingIdSequence}`
275135
- ]);
275136
- const fingerprint = observationFingerprint(observation);
275137
- if (this.#fingerprints.get(identity) === fingerprint) {
275138
- return false;
275139
- }
275140
- this.#fingerprints.delete(identity);
275141
- this.#fingerprints.set(identity, fingerprint);
275142
- while (this.#fingerprints.size > this.#maxEntries) {
275143
- const oldest = this.#fingerprints.keys().next().value;
275144
- if (oldest === undefined)
275145
- break;
275146
- this.#fingerprints.delete(oldest);
275147
- }
275148
- return true;
275149
- }
275150
- getSize() {
275151
- return this.#fingerprints.size;
275152
- }
275153
- }
275154
- // src/helpers/broker-execution-archive/writer.ts
275155
- import { request as httpRequest2 } from "http";
275156
- import { request as httpsRequest2 } from "https";
275157
-
275158
- // node_modules/@opentelemetry/api-logs/build/esm/types/LogRecord.js
275159
- var SeverityNumber2;
275160
- (function(SeverityNumber3) {
275161
- SeverityNumber3[SeverityNumber3["UNSPECIFIED"] = 0] = "UNSPECIFIED";
275162
- SeverityNumber3[SeverityNumber3["TRACE"] = 1] = "TRACE";
275163
- SeverityNumber3[SeverityNumber3["TRACE2"] = 2] = "TRACE2";
275164
- SeverityNumber3[SeverityNumber3["TRACE3"] = 3] = "TRACE3";
275165
- SeverityNumber3[SeverityNumber3["TRACE4"] = 4] = "TRACE4";
275166
- SeverityNumber3[SeverityNumber3["DEBUG"] = 5] = "DEBUG";
275167
- SeverityNumber3[SeverityNumber3["DEBUG2"] = 6] = "DEBUG2";
275168
- SeverityNumber3[SeverityNumber3["DEBUG3"] = 7] = "DEBUG3";
275169
- SeverityNumber3[SeverityNumber3["DEBUG4"] = 8] = "DEBUG4";
275170
- SeverityNumber3[SeverityNumber3["INFO"] = 9] = "INFO";
275171
- SeverityNumber3[SeverityNumber3["INFO2"] = 10] = "INFO2";
275172
- SeverityNumber3[SeverityNumber3["INFO3"] = 11] = "INFO3";
275173
- SeverityNumber3[SeverityNumber3["INFO4"] = 12] = "INFO4";
275174
- SeverityNumber3[SeverityNumber3["WARN"] = 13] = "WARN";
275175
- SeverityNumber3[SeverityNumber3["WARN2"] = 14] = "WARN2";
275176
- SeverityNumber3[SeverityNumber3["WARN3"] = 15] = "WARN3";
275177
- SeverityNumber3[SeverityNumber3["WARN4"] = 16] = "WARN4";
275178
- SeverityNumber3[SeverityNumber3["ERROR"] = 17] = "ERROR";
275179
- SeverityNumber3[SeverityNumber3["ERROR2"] = 18] = "ERROR2";
275180
- SeverityNumber3[SeverityNumber3["ERROR3"] = 19] = "ERROR3";
275181
- SeverityNumber3[SeverityNumber3["ERROR4"] = 20] = "ERROR4";
275182
- SeverityNumber3[SeverityNumber3["FATAL"] = 21] = "FATAL";
275183
- SeverityNumber3[SeverityNumber3["FATAL2"] = 22] = "FATAL2";
275184
- SeverityNumber3[SeverityNumber3["FATAL3"] = 23] = "FATAL3";
275185
- SeverityNumber3[SeverityNumber3["FATAL4"] = 24] = "FATAL4";
275186
- })(SeverityNumber2 || (SeverityNumber2 = {}));
275187
- // node_modules/@opentelemetry/api-logs/build/esm/NoopLogger.js
275188
- class NoopLogger2 {
275189
- emit(_logRecord) {}
275190
- }
275191
- var NOOP_LOGGER2 = new NoopLogger2;
275192
- // node_modules/@opentelemetry/api-logs/build/esm/internal/global-utils.js
275193
- var GLOBAL_LOGS_API_KEY2 = Symbol.for("io.opentelemetry.js.api.logs");
275194
- var _global3 = globalThis;
275195
- function makeGetter2(requiredVersion, instance, fallback) {
275196
- return (version3) => version3 === requiredVersion ? instance : fallback;
275197
- }
275198
- var API_BACKWARDS_COMPATIBILITY_VERSION2 = 1;
275199
-
275200
- // node_modules/@opentelemetry/api-logs/build/esm/NoopLoggerProvider.js
275201
- class NoopLoggerProvider2 {
275202
- getLogger(_name, _version, _options) {
275203
- return new NoopLogger2;
275204
- }
275205
- }
275206
- var NOOP_LOGGER_PROVIDER2 = new NoopLoggerProvider2;
275207
-
275208
- // node_modules/@opentelemetry/api-logs/build/esm/ProxyLogger.js
275209
- class ProxyLogger2 {
275210
- constructor(provider, name, version3, options) {
275211
- this._provider = provider;
275212
- this.name = name;
275213
- this.version = version3;
275214
- this.options = options;
275215
- }
275216
- emit(logRecord) {
275217
- this._getLogger().emit(logRecord);
275218
- }
275219
- _getLogger() {
275220
- if (this._delegate) {
275221
- return this._delegate;
275222
- }
275223
- const logger = this._provider._getDelegateLogger(this.name, this.version, this.options);
275224
- if (!logger) {
275225
- return NOOP_LOGGER2;
275226
- }
275227
- this._delegate = logger;
275228
- return this._delegate;
275229
- }
275230
- }
275231
-
275232
- // node_modules/@opentelemetry/api-logs/build/esm/ProxyLoggerProvider.js
275233
- class ProxyLoggerProvider2 {
275234
- getLogger(name, version3, options) {
275235
- var _a3;
275236
- return (_a3 = this._getDelegateLogger(name, version3, options)) !== null && _a3 !== undefined ? _a3 : new ProxyLogger2(this, name, version3, options);
275237
- }
275238
- _getDelegate() {
275239
- var _a3;
275240
- return (_a3 = this._delegate) !== null && _a3 !== undefined ? _a3 : NOOP_LOGGER_PROVIDER2;
275241
- }
275242
- _setDelegate(delegate) {
275243
- this._delegate = delegate;
275244
- }
275245
- _getDelegateLogger(name, version3, options) {
275246
- var _a3;
275247
- return (_a3 = this._delegate) === null || _a3 === undefined ? undefined : _a3.getLogger(name, version3, options);
275248
- }
275249
- }
275250
-
275251
- // node_modules/@opentelemetry/api-logs/build/esm/api/logs.js
275252
- class LogsAPI2 {
275253
- constructor() {
275254
- this._proxyLoggerProvider = new ProxyLoggerProvider2;
275255
- }
275256
- static getInstance() {
275257
- if (!this._instance) {
275258
- this._instance = new LogsAPI2;
275259
- }
275260
- return this._instance;
275261
- }
275262
- setGlobalLoggerProvider(provider) {
275263
- if (_global3[GLOBAL_LOGS_API_KEY2]) {
275264
- return this.getLoggerProvider();
275265
- }
275266
- _global3[GLOBAL_LOGS_API_KEY2] = makeGetter2(API_BACKWARDS_COMPATIBILITY_VERSION2, provider, NOOP_LOGGER_PROVIDER2);
275267
- this._proxyLoggerProvider._setDelegate(provider);
275268
- return provider;
275269
- }
275270
- getLoggerProvider() {
275271
- var _a3, _b2;
275272
- return (_b2 = (_a3 = _global3[GLOBAL_LOGS_API_KEY2]) === null || _a3 === undefined ? undefined : _a3.call(_global3, API_BACKWARDS_COMPATIBILITY_VERSION2)) !== null && _b2 !== undefined ? _b2 : this._proxyLoggerProvider;
275068
+ getLoggerProvider() {
275069
+ var _a3, _b2;
275070
+ return (_b2 = (_a3 = _global3[GLOBAL_LOGS_API_KEY2]) === null || _a3 === undefined ? undefined : _a3.call(_global3, API_BACKWARDS_COMPATIBILITY_VERSION2)) !== null && _b2 !== undefined ? _b2 : this._proxyLoggerProvider;
275273
275071
  }
275274
275072
  getLogger(name, version3, options) {
275275
275073
  return this.getLoggerProvider().getLogger(name, version3, options);
@@ -275283,18 +275081,6 @@ class LogsAPI2 {
275283
275081
  // node_modules/@opentelemetry/api-logs/build/esm/index.js
275284
275082
  var logs2 = LogsAPI2.getInstance();
275285
275083
 
275286
- // src/helpers/market-data-archive/types.ts
275287
- var MARKET_ARCHIVE_TABLES = new Set([
275288
- "market_data.orderbook_snapshots",
275289
- "market_data.candles",
275290
- "market_data.cex_stream_events",
275291
- "market_data.cex_ticker_events",
275292
- "market_data.cex_trades"
275293
- ]);
275294
- function isMarketArchiveTable(table) {
275295
- return MARKET_ARCHIVE_TABLES.has(table);
275296
- }
275297
-
275298
275084
  // src/helpers/broker-execution-archive/writer.ts
275299
275085
  var BROKER_EXECUTION_ARCHIVE_TABLES = new Set([
275300
275086
  "broker_execution.order_events",
@@ -275305,30 +275091,28 @@ var BROKER_EXECUTION_ARCHIVE_TABLES = new Set([
275305
275091
  function isBrokerExecutionArchiveTable(table) {
275306
275092
  return BROKER_EXECUTION_ARCHIVE_TABLES.has(table);
275307
275093
  }
275094
+
275095
+ class BrokerExecutionArchiveDurabilityError extends Error {
275096
+ constructor(message, options) {
275097
+ super(message, options);
275098
+ this.name = "BrokerExecutionArchiveDurabilityError";
275099
+ }
275100
+ }
275101
+ function rethrowArchiveDurabilityError(error) {
275102
+ if (error instanceof BrokerExecutionArchiveDurabilityError) {
275103
+ throw error;
275104
+ }
275105
+ }
275308
275106
  var DEFAULT_MAX_QUEUE_SIZE = 1e4;
275309
275107
  var DEFAULT_BATCH_SIZE = 10;
275310
275108
  var DEFAULT_FLUSH_INTERVAL_MS = 1000;
275311
275109
  var DEFAULT_FORWARDER_TIMEOUT_MS = 3000;
275312
275110
  var SHED_WARN_INTERVAL_MS = 60000;
275313
- var DEFAULT_ARCHIVE_FORWARDER_PATH = "/archive";
275314
- var DEFAULT_ARCHIVE_FORWARDER_PORT = 8090;
275315
275111
  function isArchiveOtelLogsEnabled() {
275316
275112
  return process.env.CEX_BROKER_ARCHIVE_OTEL_LOGS_ENABLED === "true";
275317
275113
  }
275318
275114
  function resolveArchiveForwarderUrlFromEnv() {
275319
- const explicit = process.env.CEX_BROKER_ARCHIVE_FORWARDER_URL?.trim();
275320
- if (explicit) {
275321
- return explicit;
275322
- }
275323
- const host = process.env.CEX_BROKER_ARCHIVE_FORWARDER_HOST?.trim() || process.env.CEX_BROKER_CLICKHOUSE_HOST?.trim();
275324
- if (!host) {
275325
- return;
275326
- }
275327
- const protocol = process.env.CEX_BROKER_CLICKHOUSE_PROTOCOL || "http";
275328
- const port = parsePositiveInt(process.env.CEX_BROKER_ARCHIVE_FORWARDER_PORT, DEFAULT_ARCHIVE_FORWARDER_PORT);
275329
- const path = process.env.CEX_BROKER_ARCHIVE_FORWARDER_PATH?.trim() || DEFAULT_ARCHIVE_FORWARDER_PATH;
275330
- const normalizedPath = path.startsWith("/") ? path : `/${path}`;
275331
- return `${protocol}://${host}:${port}${normalizedPath}`;
275115
+ return process.env.CEX_BROKER_ARCHIVE_FORWARDER_URL?.trim() || undefined;
275332
275116
  }
275333
275117
 
275334
275118
  class BrokerExecutionArchiver {
@@ -275336,6 +275120,8 @@ class BrokerExecutionArchiver {
275336
275120
  otelLogs;
275337
275121
  otelMetrics;
275338
275122
  forwarderUrl;
275123
+ deadLetterPath;
275124
+ deadLetterFd;
275339
275125
  maxQueueSize;
275340
275126
  batchSize;
275341
275127
  flushIntervalMs;
@@ -275351,7 +275137,6 @@ class BrokerExecutionArchiver {
275351
275137
  flushInFlight = null;
275352
275138
  lastShedWarnAtMs = 0;
275353
275139
  closed = false;
275354
- loggedMissingMarketForwarder = false;
275355
275140
  enabled;
275356
275141
  forwarderAuthToken;
275357
275142
  constructor(options) {
@@ -275359,67 +275144,78 @@ class BrokerExecutionArchiver {
275359
275144
  this.otelLogs = options.otelLogs;
275360
275145
  this.otelMetrics = options.otelMetrics;
275361
275146
  this.forwarderUrl = options.forwarderUrl?.trim();
275147
+ this.deadLetterPath = options.deadLetterPath?.trim();
275362
275148
  this.maxQueueSize = options.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;
275363
275149
  this.batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
275364
275150
  this.flushIntervalMs = options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
275365
275151
  this.forwarderTimeoutMs = options.forwarderTimeoutMs ?? DEFAULT_FORWARDER_TIMEOUT_MS;
275366
275152
  this.enabled = options.enabled;
275367
275153
  this.forwarderAuthToken = process.env.CEX_BROKER_ARCHIVE_FORWARDER_TOKEN?.trim() || undefined;
275368
- if (this.enabled) {
275154
+ if (!this.enabled) {
275155
+ log.info("Broker execution archive disabled", { enabled: false });
275156
+ return;
275157
+ }
275158
+ validateForwarderUrl(this.forwarderUrl);
275159
+ if (!this.deadLetterPath) {
275160
+ throw new Error("Broker execution archive is enabled but CEX_BROKER_ARCHIVE_DEAD_LETTER_PATH is missing");
275161
+ }
275162
+ try {
275163
+ this.deadLetterFd = openSync(this.deadLetterPath, "a", 384);
275164
+ } catch {
275165
+ throw new Error("Broker execution archive cannot open CEX_BROKER_ARCHIVE_DEAD_LETTER_PATH for append");
275166
+ }
275167
+ try {
275369
275168
  this.flushTimer = setInterval(() => {
275370
- this.flush().catch((error) => {
275371
- log.warn("Broker execution archive flush failed", { error });
275372
- });
275169
+ this.flush();
275373
275170
  }, this.flushIntervalMs);
275374
275171
  this.flushTimer.unref?.();
275172
+ log.info("Broker execution archive enabled", {
275173
+ enabled: true,
275174
+ otel_mirror_enabled: Boolean(this.otelLogs?.isOtelEnabled())
275175
+ });
275176
+ } catch (error) {
275177
+ this.closeLossJournal();
275178
+ throw error;
275375
275179
  }
275376
275180
  }
275377
275181
  static disabled() {
275378
275182
  return new BrokerExecutionArchiver({ enabled: false });
275379
275183
  }
275380
275184
  static create(options) {
275381
- const hasSink = Boolean(options.otelLogs?.isOtelEnabled()) || Boolean(options.forwarderUrl);
275382
- const enabled = process.env.CEX_BROKER_ARCHIVE_ENABLED !== "false" && hasSink;
275383
- return new BrokerExecutionArchiver({ ...options, enabled });
275185
+ return new BrokerExecutionArchiver({ ...options, enabled: true });
275384
275186
  }
275385
275187
  getDeploymentId() {
275386
275188
  return this.deploymentId;
275387
275189
  }
275388
275190
  isEnabled() {
275389
- return this.enabled && !this.closed && (Boolean(this.otelLogs) || Boolean(this.forwarderUrl));
275191
+ return this.enabled && !this.closed;
275390
275192
  }
275391
275193
  canPersistMarketMetadataSnapshot() {
275392
- return this.isEnabled() && (Boolean(this.otelLogs?.isOtelEnabled()) || Boolean(this.forwarderUrl));
275194
+ return this.isEnabled();
275393
275195
  }
275394
275196
  canPersistAccountBalanceSnapshots() {
275395
275197
  return this.isEnabled() && Boolean(this.forwarderUrl);
275396
275198
  }
275397
275199
  enqueue(row) {
275398
- if (!this.enabled || this.closed || !this.otelLogs && !this.forwarderUrl) {
275399
- return;
275400
- }
275401
- if (isMarketArchiveTable(row.table) && !this.forwarderUrl) {
275402
- if (!this.loggedMissingMarketForwarder) {
275403
- this.loggedMissingMarketForwarder = true;
275404
- log.warn("Market data archive row dropped: configure CEX_BROKER_ARCHIVE_FORWARDER_URL or CEX_BROKER_CLICKHOUSE_HOST", { table: row.table });
275405
- }
275406
- return;
275407
- }
275408
- if (row.table === "broker_account.balance_snapshots" && !this.forwarderUrl) {
275200
+ if (!this.enabled || this.closed) {
275409
275201
  return;
275410
275202
  }
275411
275203
  if (this.queue.length >= this.maxQueueSize) {
275412
- this.queue.shift();
275204
+ const shedRow = this.queue[0];
275205
+ if (shedRow) {
275206
+ this.appendLossRecords([shedRow], "queue_shed");
275207
+ this.queue.shift();
275208
+ }
275413
275209
  this.stats.shed += 1;
275414
275210
  this.recordArchiveMetric("cex_archive_rows_shed_total", {
275415
- table: row.table
275211
+ table: shedRow?.table ?? "unknown"
275416
275212
  });
275417
275213
  const now3 = Date.now();
275418
275214
  if (now3 - this.lastShedWarnAtMs >= SHED_WARN_INTERVAL_MS) {
275419
275215
  log.warn("Archive queue full: shedding oldest rows", {
275420
275216
  shed_total: this.stats.shed,
275421
275217
  queue_max: this.maxQueueSize,
275422
- table: row.table
275218
+ table: shedRow?.table ?? "unknown"
275423
275219
  });
275424
275220
  this.lastShedWarnAtMs = now3;
275425
275221
  }
@@ -275430,9 +275226,7 @@ class BrokerExecutionArchiver {
275430
275226
  table: row.table
275431
275227
  });
275432
275228
  if (this.queue.length >= this.batchSize) {
275433
- this.flush().catch((error) => {
275434
- log.warn("Broker execution archive flush failed", { error });
275435
- });
275229
+ this.flush();
275436
275230
  }
275437
275231
  }
275438
275232
  enqueueInBackground(row) {
@@ -275458,21 +275252,36 @@ class BrokerExecutionArchiver {
275458
275252
  clearInterval(this.flushTimer);
275459
275253
  this.flushTimer = null;
275460
275254
  }
275461
- while (this.queue.length > 0 || this.flushInFlight) {
275462
- if (this.flushInFlight) {
275463
- await this.flushInFlight;
275464
- continue;
275465
- }
275466
- const depthBefore = this.queue.length;
275467
- const flushed = await this.flushBatch();
275468
- if (!flushed && this.queue.length >= depthBefore && depthBefore > 0) {
275469
- const dropped = this.queue.length;
275470
- this.queue.length = 0;
275471
- log.warn(`Archive shutdown dropped ${dropped} undelivered row(s) after forwarder failure`);
275472
- break;
275255
+ let closeError;
275256
+ try {
275257
+ while (this.queue.length > 0 || this.flushInFlight) {
275258
+ if (this.flushInFlight) {
275259
+ await this.flushInFlight;
275260
+ continue;
275261
+ }
275262
+ const depthBefore = this.queue.length;
275263
+ const flushed = await this.flushBatch();
275264
+ if (!flushed && this.queue.length >= depthBefore && depthBefore > 0) {
275265
+ const undelivered = [...this.queue];
275266
+ this.appendLossRecords(undelivered, "shutdown_forwarder_failure");
275267
+ this.queue.length = 0;
275268
+ break;
275269
+ }
275473
275270
  }
275271
+ } catch (error) {
275272
+ closeError = error;
275474
275273
  }
275475
275274
  this.closed = true;
275275
+ if (this.deadLetterFd !== undefined) {
275276
+ try {
275277
+ this.closeLossJournal();
275278
+ } catch (error) {
275279
+ closeError ??= error;
275280
+ }
275281
+ }
275282
+ if (closeError) {
275283
+ throw closeError;
275284
+ }
275476
275285
  }
275477
275286
  getStats() {
275478
275287
  return { ...this.stats };
@@ -275480,15 +275289,60 @@ class BrokerExecutionArchiver {
275480
275289
  getQueueDepth() {
275481
275290
  return this.queue.length;
275482
275291
  }
275292
+ closeLossJournal() {
275293
+ if (this.deadLetterFd === undefined) {
275294
+ return;
275295
+ }
275296
+ const fd2 = this.deadLetterFd;
275297
+ try {
275298
+ closeSync(fd2);
275299
+ } catch (error) {
275300
+ throw new BrokerExecutionArchiveDurabilityError("Broker execution archive failed to close the configured CEX_BROKER_ARCHIVE_DEAD_LETTER_PATH loss journal", { cause: error });
275301
+ } finally {
275302
+ this.deadLetterFd = undefined;
275303
+ }
275304
+ }
275483
275305
  enforceQueueBound() {
275484
275306
  while (this.queue.length > this.maxQueueSize) {
275485
- const dropped = this.queue.shift();
275307
+ const dropped = this.queue[0];
275308
+ if (!dropped) {
275309
+ return;
275310
+ }
275311
+ this.appendLossRecords([dropped], "queue_shed");
275312
+ this.queue.shift();
275486
275313
  this.stats.shed += 1;
275487
275314
  this.recordArchiveMetric("cex_archive_rows_shed_total", {
275488
275315
  table: dropped?.table ?? "unknown"
275489
275316
  });
275490
275317
  }
275491
275318
  }
275319
+ appendLossRecords(rows, reason) {
275320
+ if (rows.length === 0) {
275321
+ return;
275322
+ }
275323
+ if (this.deadLetterFd === undefined) {
275324
+ throw new BrokerExecutionArchiveDurabilityError(`Broker execution archive cannot record ${reason}: dead-letter file is not open`);
275325
+ }
275326
+ const timestamp = new Date().toISOString();
275327
+ const records = rows.map((payload) => ({
275328
+ timestamp,
275329
+ deployment_id: this.deploymentId,
275330
+ reason,
275331
+ payload
275332
+ }));
275333
+ try {
275334
+ const bytes2 = Buffer.from(records.map((record) => JSON.stringify(record)).join(`
275335
+ `) + `
275336
+ `);
275337
+ const written = writeSync(this.deadLetterFd, bytes2);
275338
+ if (written !== bytes2.length) {
275339
+ throw new Error(`wrote ${written} of ${bytes2.length} bytes`);
275340
+ }
275341
+ fsyncSync(this.deadLetterFd);
275342
+ } catch (error) {
275343
+ throw new BrokerExecutionArchiveDurabilityError(`Broker execution archive failed to durably record ${reason}; affected row(s) were retained`, { cause: error });
275344
+ }
275345
+ }
275492
275346
  async flushBatch() {
275493
275347
  const batch = this.queue.splice(0, this.batchSize);
275494
275348
  if (batch.length === 0) {
@@ -275525,111 +275379,407 @@ class BrokerExecutionArchiver {
275525
275379
  for (const [table, count2] of countByTable) {
275526
275380
  this.recordArchiveMetric("cex_archive_rows_flushed_total", { table }, count2);
275527
275381
  }
275528
- this.recordArchiveGauge("cex_archive_last_flush_success", Math.floor(Date.now() / 1000));
275529
- }
275530
- async recordArchiveMetric(metricName, labels, value = 1) {
275531
- try {
275532
- await this.otelMetrics?.recordCounter(metricName, value, labels);
275533
- } catch {}
275382
+ this.recordArchiveGauge("cex_archive_last_flush_success", Math.floor(Date.now() / 1000));
275383
+ }
275384
+ async recordArchiveMetric(metricName, labels, value = 1) {
275385
+ try {
275386
+ await this.otelMetrics?.recordCounter(metricName, value, labels);
275387
+ } catch {}
275388
+ }
275389
+ async recordArchiveGauge(metricName, value) {
275390
+ try {
275391
+ await this.otelMetrics?.recordGauge(metricName, value, {});
275392
+ } catch {}
275393
+ }
275394
+ emitOtelLog(entry) {
275395
+ if (!this.otelLogs?.isOtelEnabled()) {
275396
+ return;
275397
+ }
275398
+ try {
275399
+ this.otelLogs.emit({
275400
+ body: entry.table,
275401
+ severityNumber: SeverityNumber2.INFO,
275402
+ severityText: "INFO",
275403
+ attributes: flattenArchiveAttributes(redactArchiveErrorForOtel(entry))
275404
+ });
275405
+ } catch (error) {
275406
+ log.warn("Broker execution archive OTLP emit failed", { error });
275407
+ }
275408
+ }
275409
+ postToForwarder(batch) {
275410
+ if (!this.forwarderUrl || batch.length === 0) {
275411
+ return Promise.resolve();
275412
+ }
275413
+ const body = JSON.stringify({
275414
+ source: "broker_write",
275415
+ deployment_id: this.deploymentId,
275416
+ rows: batch
275417
+ });
275418
+ const url2 = new URL(this.forwarderUrl);
275419
+ const doRequest = url2.protocol === "http:" ? httpRequest2 : httpsRequest2;
275420
+ const headers = {
275421
+ "content-type": "application/json",
275422
+ "content-length": Buffer.byteLength(body)
275423
+ };
275424
+ if (this.forwarderAuthToken) {
275425
+ headers.authorization = `Bearer ${this.forwarderAuthToken}`;
275426
+ }
275427
+ return new Promise((resolve, reject) => {
275428
+ const req = doRequest(url2, {
275429
+ method: "POST",
275430
+ headers,
275431
+ timeout: this.forwarderTimeoutMs
275432
+ }, (res) => {
275433
+ res.on("data", () => {});
275434
+ res.on("end", () => {
275435
+ const status = res.statusCode ?? 0;
275436
+ if (status < 200 || status >= 300) {
275437
+ reject(new Error(`Archive forwarder returned ${status} ${res.statusMessage ?? ""}`));
275438
+ return;
275439
+ }
275440
+ resolve();
275441
+ });
275442
+ });
275443
+ req.on("error", reject);
275444
+ req.on("timeout", () => {
275445
+ req.destroy(new Error("Archive forwarder request timed out"));
275446
+ });
275447
+ req.write(body);
275448
+ req.end();
275449
+ });
275450
+ }
275451
+ }
275452
+ function redactArchiveErrorForOtel(entry) {
275453
+ if (entry.table !== "broker_execution.order_events" || typeof entry.row.error_message !== "string" || entry.row.error_message.length === 0) {
275454
+ return entry;
275455
+ }
275456
+ return {
275457
+ ...entry,
275458
+ row: { ...entry.row, error_message: REDACTED_ERROR_MESSAGE }
275459
+ };
275460
+ }
275461
+ function flattenArchiveAttributes(entry) {
275462
+ const attributes = {
275463
+ ch_table: entry.table
275464
+ };
275465
+ for (const [key, value] of Object.entries(entry.row)) {
275466
+ if (value === undefined || value === null) {
275467
+ continue;
275468
+ }
275469
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
275470
+ attributes[key] = value;
275471
+ } else {
275472
+ attributes[key] = JSON.stringify(value);
275473
+ }
275474
+ }
275475
+ return attributes;
275476
+ }
275477
+ function createBrokerExecutionArchiverFromEnv(otelLogs, otelMetrics) {
275478
+ if (process.env.CEX_BROKER_ARCHIVE_ENABLED !== "true") {
275479
+ return BrokerExecutionArchiver.disabled();
275480
+ }
275481
+ const forwarderUrl = resolveArchiveForwarderUrlFromEnv();
275482
+ const archiveOtelLogs = isArchiveOtelLogsEnabled() ? otelLogs : undefined;
275483
+ return BrokerExecutionArchiver.create({
275484
+ otelLogs: archiveOtelLogs,
275485
+ otelMetrics,
275486
+ forwarderUrl: forwarderUrl ?? "",
275487
+ deadLetterPath: process.env.CEX_BROKER_ARCHIVE_DEAD_LETTER_PATH?.trim() ?? "",
275488
+ deploymentId: process.env.CEX_BROKER_DEPLOYMENT_ID,
275489
+ maxQueueSize: parsePositiveInt(process.env.CEX_BROKER_ARCHIVE_QUEUE_MAX, DEFAULT_MAX_QUEUE_SIZE),
275490
+ batchSize: parsePositiveInt(process.env.CEX_BROKER_ARCHIVE_BATCH_SIZE, DEFAULT_BATCH_SIZE),
275491
+ flushIntervalMs: parsePositiveInt(process.env.CEX_BROKER_ARCHIVE_FLUSH_INTERVAL_MS, DEFAULT_FLUSH_INTERVAL_MS)
275492
+ });
275493
+ }
275494
+ function validateForwarderUrl(value) {
275495
+ if (!value) {
275496
+ throw new Error("Broker execution archive is enabled but CEX_BROKER_ARCHIVE_FORWARDER_URL is missing");
275497
+ }
275498
+ let url2;
275499
+ try {
275500
+ url2 = new URL(value);
275501
+ } catch (error) {
275502
+ throw new Error("Broker execution archive requires a valid CEX_BROKER_ARCHIVE_FORWARDER_URL", { cause: error });
275503
+ }
275504
+ if (url2.protocol !== "http:" && url2.protocol !== "https:") {
275505
+ throw new Error("Broker execution archive forwarder URL must use http or https");
275506
+ }
275507
+ return url2;
275508
+ }
275509
+ function parsePositiveInt(value, fallback) {
275510
+ if (!value) {
275511
+ return fallback;
275512
+ }
275513
+ const parsed = Number.parseInt(value, 10);
275514
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
275515
+ }
275516
+
275517
+ // src/helpers/broker-execution-archive/capture.ts
275518
+ function archiveOrderExecutionInBackground(archiver, context2, order, error, options) {
275519
+ if (!archiver?.isEnabled()) {
275520
+ return;
275521
+ }
275522
+ queueMicrotask(() => {
275523
+ try {
275524
+ const telemetry = buildOrderExecutionTelemetry(context2, order, error);
275525
+ const tags = buildCommonArchiveTags({
275526
+ deploymentId: archiver.getDeploymentId(),
275527
+ accountSelector: context2.accountLabel,
275528
+ exchange: context2.cex,
275529
+ symbol: telemetry.symbol,
275530
+ brokerObservedTimestamp: telemetry.brokerObservedTimestamp
275531
+ });
275532
+ archiver.enqueue(buildOrderEventArchiveRow({
275533
+ tags,
275534
+ action: context2.action,
275535
+ telemetry,
275536
+ errorDetail: context2.action === "CreateOrder" && error !== undefined ? sanitizeErrorDetail(error, { includeCode: true }) : undefined,
275537
+ marketMetadataHash: options?.marketMetadataHash
275538
+ }));
275539
+ } catch (archiveError) {
275540
+ rethrowArchiveDurabilityError(archiveError);
275541
+ log.warn("Failed to archive order execution", { error: archiveError });
275542
+ }
275543
+ });
275544
+ }
275545
+ function archiveSubscribeStreamInBackground(archiver, input) {
275546
+ if (!archiver?.isEnabled()) {
275547
+ return;
275548
+ }
275549
+ queueMicrotask(() => {
275550
+ try {
275551
+ const tags = buildCommonArchiveTags({
275552
+ deploymentId: archiver.getDeploymentId(),
275553
+ accountSelector: input.accountSelector,
275554
+ exchange: input.exchange,
275555
+ symbol: input.symbol
275556
+ });
275557
+ archiver.enqueue(buildSubscribeStreamArchiveRow({
275558
+ tags,
275559
+ subscriptionType: input.subscriptionType,
275560
+ streamPayload: input.streamPayload,
275561
+ secretLiterals: input.secretLiterals
275562
+ }));
275563
+ } catch (archiveError) {
275564
+ rethrowArchiveDurabilityError(archiveError);
275565
+ log.warn("Failed to archive subscribe stream event", {
275566
+ error: archiveError
275567
+ });
275568
+ }
275569
+ });
275570
+ }
275571
+ function archiveTransferEventInBackground(archiver, input) {
275572
+ if (!archiver?.isEnabled()) {
275573
+ return;
275574
+ }
275575
+ queueMicrotask(() => {
275576
+ try {
275577
+ const tags = buildCommonArchiveTags({
275578
+ deploymentId: archiver.getDeploymentId(),
275579
+ accountSelector: input.accountSelector,
275580
+ exchange: input.exchange,
275581
+ symbol: input.assetSymbol,
275582
+ brokerObservedTimestamp: input.brokerObservedTimestamp
275583
+ });
275584
+ archiver.enqueue(buildTransferEventArchiveRow({ tags, transfer: input.transfer }));
275585
+ } catch (archiveError) {
275586
+ rethrowArchiveDurabilityError(archiveError);
275587
+ log.warn("Failed to archive transfer event", { error: archiveError });
275588
+ }
275589
+ });
275590
+ }
275591
+ function archiveWithdrawalObservationsInBackground(archiver, tracker, input) {
275592
+ try {
275593
+ if (!archiver?.isEnabled() || !Array.isArray(input.transactions)) {
275594
+ return;
275595
+ }
275596
+ const brokerObservedTimestamp = new Date().toISOString();
275597
+ for (const [resultIndex, transaction] of input.transactions.entries()) {
275598
+ const normalized = normalizeCcxtTransactionForArchive(transaction);
275599
+ const assetSymbol = normalized.assetSymbol;
275600
+ if (!tracker.shouldArchive({
275601
+ exchange: input.exchange,
275602
+ accountSelector: input.accountSelector,
275603
+ assetSymbol,
275604
+ transaction,
275605
+ normalized
275606
+ })) {
275607
+ continue;
275608
+ }
275609
+ archiveTransferEventInBackground(archiver, {
275610
+ exchange: input.exchange,
275611
+ accountSelector: input.accountSelector,
275612
+ assetSymbol,
275613
+ brokerObservedTimestamp,
275614
+ transfer: {
275615
+ eventKind: "withdrawal",
275616
+ lifecycleAction: "observe_withdrawal",
275617
+ status: normalized.status,
275618
+ amount: normalized.amount,
275619
+ address: normalized.address,
275620
+ network: normalized.network,
275621
+ externalId: normalized.externalId,
275622
+ txid: normalized.txid,
275623
+ resultIndex,
275624
+ feeAmount: normalized.feeAmount,
275625
+ feeCurrency: normalized.feeCurrency,
275626
+ exchangeTimestamp: normalized.exchangeTimestamp,
275627
+ payload: transaction
275628
+ }
275629
+ });
275630
+ }
275631
+ } catch (archiveError) {
275632
+ rethrowArchiveDurabilityError(archiveError);
275633
+ log.warn("Failed to archive withdrawal observations", {
275634
+ error: archiveError
275635
+ });
275534
275636
  }
275535
- async recordArchiveGauge(metricName, value) {
275536
- try {
275537
- await this.otelMetrics?.recordGauge(metricName, value, {});
275538
- } catch {}
275637
+ }
275638
+ async function captureMarketMetadataSnapshot(archiver, broker, input) {
275639
+ if (!archiver?.canPersistMarketMetadataSnapshot()) {
275640
+ return;
275539
275641
  }
275540
- emitOtelLog(entry) {
275541
- if (!this.otelLogs?.isOtelEnabled()) {
275642
+ try {
275643
+ const fetchOrderBook = broker.fetchOrderBook;
275644
+ if (typeof fetchOrderBook !== "function") {
275542
275645
  return;
275543
275646
  }
275544
- try {
275545
- this.otelLogs.emit({
275546
- body: entry.table,
275547
- severityNumber: SeverityNumber2.INFO,
275548
- severityText: "INFO",
275549
- attributes: flattenArchiveAttributes(entry)
275550
- });
275551
- } catch (error) {
275552
- log.warn("Broker execution archive OTLP emit failed", { error });
275553
- }
275554
- }
275555
- postToForwarder(batch) {
275556
- if (!this.forwarderUrl || batch.length === 0) {
275557
- return Promise.resolve();
275558
- }
275559
- const body = JSON.stringify({
275560
- source: "broker_write",
275561
- deployment_id: this.deploymentId,
275562
- rows: batch
275563
- });
275564
- const url2 = new URL(this.forwarderUrl);
275565
- const doRequest = url2.protocol === "http:" ? httpRequest2 : httpsRequest2;
275566
- const headers = {
275567
- "content-type": "application/json",
275568
- "content-length": Buffer.byteLength(body)
275647
+ const orderBook = await fetchOrderBook.call(broker, input.symbol, 5);
275648
+ const record = asRecord(orderBook);
275649
+ const snapshot = {
275650
+ action: input.action,
275651
+ symbol: input.symbol,
275652
+ bids: record?.bids,
275653
+ asks: record?.asks,
275654
+ timestamp: record?.timestamp ?? Date.now(),
275655
+ datetime: record?.datetime,
275656
+ nonce: record?.nonce
275569
275657
  };
275570
- if (this.forwarderAuthToken) {
275571
- headers.authorization = `Bearer ${this.forwarderAuthToken}`;
275572
- }
275573
- return new Promise((resolve, reject) => {
275574
- const req = doRequest(url2, {
275575
- method: "POST",
275576
- headers,
275577
- timeout: this.forwarderTimeoutMs
275578
- }, (res) => {
275579
- res.on("data", () => {});
275580
- res.on("end", () => {
275581
- const status = res.statusCode ?? 0;
275582
- if (status < 200 || status >= 300) {
275583
- reject(new Error(`Archive forwarder returned ${status} ${res.statusMessage ?? ""}`));
275584
- return;
275585
- }
275586
- resolve();
275587
- });
275588
- });
275589
- req.on("error", reject);
275590
- req.on("timeout", () => {
275591
- req.destroy(new Error("Archive forwarder request timed out"));
275592
- });
275593
- req.write(body);
275594
- req.end();
275658
+ const tags = buildCommonArchiveTags({
275659
+ deploymentId: archiver.getDeploymentId(),
275660
+ accountSelector: input.accountSelector,
275661
+ exchange: input.exchange,
275662
+ symbol: input.symbol,
275663
+ brokerObservedTimestamp: input.brokerObservedTimestamp
275664
+ });
275665
+ const row = buildMarketMetadataSnapshotRow({
275666
+ tags,
275667
+ clientOrderId: input.clientOrderId,
275668
+ orderId: input.orderId,
275669
+ makerActionId: input.makerActionId,
275670
+ idempotencyId: input.idempotencyId,
275671
+ marketSnapshot: snapshot
275672
+ });
275673
+ archiver.enqueue(row);
275674
+ const hash4 = row.row.market_metadata_hash;
275675
+ return typeof hash4 === "string" ? hash4 : undefined;
275676
+ } catch (archiveError) {
275677
+ rethrowArchiveDurabilityError(archiveError);
275678
+ log.warn("Failed to capture market metadata snapshot", {
275679
+ error: archiveError
275595
275680
  });
275681
+ return;
275596
275682
  }
275597
275683
  }
275598
- function flattenArchiveAttributes(entry) {
275599
- const attributes = {
275600
- ch_table: entry.table
275601
- };
275602
- for (const [key, value] of Object.entries(entry.row)) {
275603
- if (value === undefined || value === null) {
275604
- continue;
275605
- }
275606
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
275607
- attributes[key] = value;
275608
- } else {
275609
- attributes[key] = JSON.stringify(value);
275684
+ // src/helpers/broker-execution-archive/withdrawal-observation-tracker.ts
275685
+ var DEFAULT_WITHDRAWAL_OBSERVATION_TRACKER_MAX_ENTRIES = 1e4;
275686
+ var VENUE_LIFECYCLE_EVIDENCE_FIELDS = [
275687
+ "updated",
275688
+ "updatedAt",
275689
+ "updated_at",
275690
+ "updateTime",
275691
+ "update_time",
275692
+ "updateTimestamp",
275693
+ "lastUpdateTimestamp",
275694
+ "completed",
275695
+ "completedAt",
275696
+ "completed_at",
275697
+ "completeTime",
275698
+ "complete_time",
275699
+ "completionTime",
275700
+ "successTime"
275701
+ ];
275702
+ function fingerprintEvidenceValue(value) {
275703
+ if (value === undefined || value === null) {
275704
+ return value;
275705
+ }
275706
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
275707
+ return value;
275708
+ }
275709
+ if (typeof value === "bigint") {
275710
+ return value.toString();
275711
+ }
275712
+ try {
275713
+ return JSON.stringify(value);
275714
+ } catch {
275715
+ return String(value);
275716
+ }
275717
+ }
275718
+ function venueLifecycleEvidence(transaction) {
275719
+ const record = asRecord(transaction);
275720
+ const info = asRecord(record?.info);
275721
+ const evidence = [];
275722
+ for (const [scope, source] of [
275723
+ ["transaction", record],
275724
+ ["info", info]
275725
+ ]) {
275726
+ for (const field of VENUE_LIFECYCLE_EVIDENCE_FIELDS) {
275727
+ const value = source?.[field];
275728
+ if (value !== undefined && value !== null) {
275729
+ evidence.push([scope, field, fingerprintEvidenceValue(value)]);
275730
+ }
275610
275731
  }
275611
275732
  }
275612
- return attributes;
275733
+ return evidence;
275613
275734
  }
275614
- function createBrokerExecutionArchiverFromEnv(otelLogs, otelMetrics) {
275615
- const forwarderUrl = resolveArchiveForwarderUrlFromEnv();
275616
- const archiveOtelLogs = isArchiveOtelLogsEnabled() ? otelLogs : undefined;
275617
- return BrokerExecutionArchiver.create({
275618
- otelLogs: archiveOtelLogs,
275619
- otelMetrics,
275620
- forwarderUrl,
275621
- deploymentId: process.env.CEX_BROKER_DEPLOYMENT_ID,
275622
- maxQueueSize: parsePositiveInt(process.env.CEX_BROKER_ARCHIVE_QUEUE_MAX, DEFAULT_MAX_QUEUE_SIZE),
275623
- batchSize: parsePositiveInt(process.env.CEX_BROKER_ARCHIVE_BATCH_SIZE, DEFAULT_BATCH_SIZE),
275624
- flushIntervalMs: parsePositiveInt(process.env.CEX_BROKER_ARCHIVE_FLUSH_INTERVAL_MS, DEFAULT_FLUSH_INTERVAL_MS)
275735
+ function observationFingerprint(observation) {
275736
+ const { normalized, transaction } = observation;
275737
+ return JSON.stringify({
275738
+ status: normalized.status ?? null,
275739
+ txid: normalized.txid ?? null,
275740
+ amount: normalized.amount ?? null,
275741
+ feeAmount: normalized.feeAmount ?? null,
275742
+ feeCurrency: normalized.feeCurrency ?? null,
275743
+ address: normalized.address ?? null,
275744
+ network: normalized.network ?? null,
275745
+ exchangeTimestamp: normalized.exchangeTimestamp ?? null,
275746
+ venueLifecycleEvidence: venueLifecycleEvidence(transaction)
275625
275747
  });
275626
275748
  }
275627
- function parsePositiveInt(value, fallback) {
275628
- if (!value) {
275629
- return fallback;
275749
+
275750
+ class WithdrawalObservationTracker {
275751
+ #fingerprints = new Map;
275752
+ #maxEntries;
275753
+ #missingIdSequence = 0n;
275754
+ constructor(options) {
275755
+ const maxEntries = options?.maxEntries ?? DEFAULT_WITHDRAWAL_OBSERVATION_TRACKER_MAX_ENTRIES;
275756
+ this.#maxEntries = Number.isFinite(maxEntries) ? Math.max(1, Math.floor(maxEntries)) : DEFAULT_WITHDRAWAL_OBSERVATION_TRACKER_MAX_ENTRIES;
275757
+ }
275758
+ shouldArchive(observation) {
275759
+ const externalId = observation.normalized.externalId?.trim();
275760
+ const identity = JSON.stringify([
275761
+ observation.exchange.trim().toLowerCase() || "unknown",
275762
+ observation.accountSelector?.trim() || "unknown",
275763
+ observation.assetSymbol?.trim().toUpperCase() || "unknown",
275764
+ externalId || `missing:${++this.#missingIdSequence}`
275765
+ ]);
275766
+ const fingerprint = observationFingerprint(observation);
275767
+ if (this.#fingerprints.get(identity) === fingerprint) {
275768
+ return false;
275769
+ }
275770
+ this.#fingerprints.delete(identity);
275771
+ this.#fingerprints.set(identity, fingerprint);
275772
+ while (this.#fingerprints.size > this.#maxEntries) {
275773
+ const oldest = this.#fingerprints.keys().next().value;
275774
+ if (oldest === undefined)
275775
+ break;
275776
+ this.#fingerprints.delete(oldest);
275777
+ }
275778
+ return true;
275779
+ }
275780
+ getSize() {
275781
+ return this.#fingerprints.size;
275630
275782
  }
275631
- const parsed = Number.parseInt(value, 10);
275632
- return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
275633
275783
  }
275634
275784
  // src/helpers/account-balance-archive-poller.ts
275635
275785
  var DEFAULT_CONFIG = {
@@ -275723,6 +275873,7 @@ class AccountBalanceArchivePoller {
275723
275873
  this.params.metrics?.recordGauge("cex_account_balance_poll_last_success_timestamp_seconds", Math.floor(successMs / 1000), labels);
275724
275874
  this.#recordFreshness(labels, successMs, successMs);
275725
275875
  } catch (error) {
275876
+ rethrowArchiveDurabilityError(error);
275726
275877
  this.params.metrics?.recordCounter("cex_account_balance_poll_failures_total", 1, labels);
275727
275878
  const now3 = Date.now();
275728
275879
  const lastSuccess = this.#lastSuccessMs.get(this.#targetKey(target));
@@ -275752,6 +275903,7 @@ class AccountBalanceArchivePoller {
275752
275903
  try {
275753
275904
  await this.pollAllOnce();
275754
275905
  } catch (error) {
275906
+ rethrowArchiveDurabilityError(error);
275755
275907
  log.error("Account balance archive poller tick failed", error);
275756
275908
  } finally {
275757
275909
  if (!this.#stopped) {
@@ -275815,6 +275967,7 @@ class FillArchivePoller {
275815
275967
  try {
275816
275968
  await this.pollTrackedOnce();
275817
275969
  } catch (error) {
275970
+ rethrowArchiveDurabilityError(error);
275818
275971
  log.error("Fill archive poller tick failed", error);
275819
275972
  } finally {
275820
275973
  this.#running = false;
@@ -279806,32 +279959,6 @@ import * as grpc14 from "@grpc/grpc-js";
279806
279959
  // src/handlers/execute-action/deposit.ts
279807
279960
  import * as grpc3 from "@grpc/grpc-js";
279808
279961
 
279809
- // src/helpers/shared/errors.ts
279810
- var MAX_ERROR_DETAIL_LENGTH = 512;
279811
- function getErrorMessage(error) {
279812
- return error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
279813
- }
279814
- function errorClassName(error) {
279815
- if (!(error instanceof Error)) {
279816
- return;
279817
- }
279818
- const name = error.constructor?.name || error.name;
279819
- return name && name !== "Error" ? name : undefined;
279820
- }
279821
- function sanitizeErrorDetail(error) {
279822
- const className = errorClassName(error);
279823
- const message = getErrorMessage(error);
279824
- const detail = className ? `${className}: ${message}` : message;
279825
- return detail.replace(/\s+/g, " ").trim().slice(0, MAX_ERROR_DETAIL_LENGTH);
279826
- }
279827
- function safeLogError(context3, error) {
279828
- try {
279829
- log.error(context3, { error });
279830
- } catch {
279831
- console.error(context3, error);
279832
- }
279833
- }
279834
-
279835
279962
  // src/helpers/treasury-discovery.ts
279836
279963
  function callArgs(args, params) {
279837
279964
  const argsArray = Array.isArray(args) ? [...args] : [];
@@ -294358,7 +294485,8 @@ async function handleCreateOrder(ctx) {
294358
294485
  archiveOrderExecutionInBackground(brokerArchiver, createOrderContext, order, undefined, { marketMetadataHash });
294359
294486
  ctx.wrappedCallback(null, { result: JSON.stringify({ ...order }) });
294360
294487
  } catch (error48) {
294361
- safeLogError("Order Creation failed", error48);
294488
+ rethrowArchiveDurabilityError(error48);
294489
+ safeLogRedactedError("Order Creation failed", error48);
294362
294490
  const failedCreateContext = {
294363
294491
  action: "CreateOrder",
294364
294492
  cex: cex3,
@@ -295633,8 +295761,8 @@ class BinanceSpotUserDataStream {
295633
295761
  }
295634
295762
  if ("status" in message && typeof message.status === "number" && message.status !== 200) {
295635
295763
  const errorMessage = message.error?.msg ?? message.error?.message ?? `Binance user-data request failed with status ${message.status}`;
295636
- const errorCode = message.error?.code;
295637
- this.fail(new Error(typeof errorCode === "number" ? `${errorMessage} (code ${errorCode})` : errorMessage));
295764
+ const errorCode2 = message.error?.code;
295765
+ this.fail(new Error(typeof errorCode2 === "number" ? `${errorMessage} (code ${errorCode2})` : errorMessage));
295638
295766
  return;
295639
295767
  }
295640
295768
  if (!("event" in message) || !message.event) {
@@ -296232,6 +296360,7 @@ function archiveOrderbookInBackground(archiver, otelMetrics, input, options) {
296232
296360
  recordWatchMetric(otelMetrics, "cex_watch_frames_archived_total", labels);
296233
296361
  }
296234
296362
  } catch (error48) {
296363
+ rethrowArchiveDurabilityError(error48);
296235
296364
  log.warn("Failed to archive orderbook snapshot", { error: error48 });
296236
296365
  }
296237
296366
  });
@@ -296259,6 +296388,7 @@ function archiveOhlcvInBackground(archiver, otelMetrics, tracker, input) {
296259
296388
  recordWatchMetric(otelMetrics, "cex_watch_frames_archived_total", labels);
296260
296389
  }
296261
296390
  } catch (error48) {
296391
+ rethrowArchiveDurabilityError(error48);
296262
296392
  log.warn("Failed to archive OHLCV candle", { error: error48 });
296263
296393
  }
296264
296394
  });
@@ -296285,6 +296415,7 @@ function archiveMarketRowsInBackground(archiver, otelMetrics, stream4, input, en
296285
296415
  recordWatchMetric(otelMetrics, "cex_watch_frames_archived_total", labels);
296286
296416
  }
296287
296417
  } catch (error48) {
296418
+ rethrowArchiveDurabilityError(error48);
296288
296419
  log.warn(`Failed to archive ${stream4} market data`, { error: error48 });
296289
296420
  }
296290
296421
  });
@@ -296320,7 +296451,6 @@ function resolveOhlcvBootstrapLimit(optionValue) {
296320
296451
  }
296321
296452
  return Math.min(parsed, MAX_OHLCV_BOOTSTRAP_LIMIT);
296322
296453
  }
296323
-
296324
296454
  // src/helpers/market-data-archive/ohlcv-history.ts
296325
296455
  function supportsFetchOhlcv(broker) {
296326
296456
  const fetchOHLCV = broker.fetchOHLCV;
@@ -297430,4 +297560,4 @@ export {
297430
297560
  CEXBroker as default
297431
297561
  };
297432
297562
 
297433
- //# debugId=1B2709360791D68C64756E2164756E21
297563
+ //# debugId=9DC3823C033EB0C864756E2164756E21