@usherlabs/cex-broker 0.2.17 → 0.2.19

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.
Files changed (28) hide show
  1. package/README.md +22 -1
  2. package/dist/commands/cli.js +2719 -1279
  3. package/dist/handlers/execute-action/context.d.ts +2 -0
  4. package/dist/handlers/execute-action/handler.d.ts +2 -0
  5. package/dist/handlers/subscribe/handler.d.ts +2 -0
  6. package/dist/helpers/broker-execution-archive/capture.d.ts +27 -0
  7. package/dist/helpers/broker-execution-archive/index.d.ts +5 -0
  8. package/dist/helpers/broker-execution-archive/redact.d.ts +7 -0
  9. package/dist/helpers/broker-execution-archive/rows.d.ts +31 -0
  10. package/dist/helpers/broker-execution-archive/types.d.ts +16 -0
  11. package/dist/helpers/broker-execution-archive/writer.d.ts +57 -0
  12. package/dist/helpers/market-data-archive/capture.d.ts +20 -0
  13. package/dist/helpers/market-data-archive/index.d.ts +9 -0
  14. package/dist/helpers/market-data-archive/ohlcv-bar-tracker.d.ts +11 -0
  15. package/dist/helpers/market-data-archive/ohlcv-bootstrap.d.ts +1 -0
  16. package/dist/helpers/market-data-archive/ohlcv-history.d.ts +8 -0
  17. package/dist/helpers/market-data-archive/orderbook-depth.d.ts +5 -0
  18. package/dist/helpers/market-data-archive/orderbook-sampler.d.ts +12 -0
  19. package/dist/helpers/market-data-archive/parse-stream.d.ts +26 -0
  20. package/dist/helpers/market-data-archive/rows.d.ts +18 -0
  21. package/dist/helpers/market-data-archive/types.d.ts +55 -0
  22. package/dist/helpers/order-telemetry.d.ts +1 -1
  23. package/dist/helpers/travel-rule-deposit-reconciler.d.ts +7 -0
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +5689 -4252
  26. package/dist/index.js.map +27 -13
  27. package/dist/server.d.ts +2 -1
  28. package/package.json +9 -1
@@ -313774,9 +313774,6 @@ function createBrokerPool(cfg) {
313774
313774
  }
313775
313775
  return pool;
313776
313776
  }
313777
- function selectBroker(brokers, metadata) {
313778
- return selectBrokerAccount(brokers, metadata)?.exchange ?? null;
313779
- }
313780
313777
  function getCurrentBrokerSelector(metadata) {
313781
313778
  const use_secondary_key = metadata.get("use-secondary-key");
313782
313779
  if (!use_secondary_key || use_secondary_key.length === 0) {
@@ -313971,6 +313968,10 @@ function authenticateRequest(call, whitelistIps) {
313971
313968
  }
313972
313969
  return true;
313973
313970
  }
313971
+ // src/helpers/travel-rule-deposit-reconciler.ts
313972
+ import { request as httpRequest } from "node:http";
313973
+ import { request as httpsRequest } from "node:https";
313974
+
313974
313975
  // src/helpers/deposit.ts
313975
313976
  function depositField(deposit, fields) {
313976
313977
  for (const field of fields) {
@@ -314051,36 +314052,54 @@ function parseLocalEntityDeposit(raw) {
314051
314052
  };
314052
314053
  }
314053
314054
  var EVM_TX_HASH = /^0x[0-9a-fA-F]{64}$/;
314054
- async function resolveOnChainSender(rpcUrl, txHash, timeoutMs = 1e4) {
314055
+ function resolveOnChainSender(rpcUrl, txHash, timeoutMs = 1e4) {
314055
314056
  if (!EVM_TX_HASH.test(txHash))
314056
- return null;
314057
- const controller = new AbortController;
314058
- const timeout2 = setTimeout(() => controller.abort(), timeoutMs);
314059
- let response;
314060
- try {
314061
- response = await fetch(rpcUrl, {
314057
+ return Promise.resolve(null);
314058
+ const body = JSON.stringify({
314059
+ jsonrpc: "2.0",
314060
+ id: 1,
314061
+ method: "eth_getTransactionByHash",
314062
+ params: [txHash]
314063
+ });
314064
+ const url2 = new URL(rpcUrl);
314065
+ const doRequest = url2.protocol === "http:" ? httpRequest : httpsRequest;
314066
+ return new Promise((resolve, reject) => {
314067
+ const req = doRequest(url2, {
314062
314068
  method: "POST",
314063
- headers: { "content-type": "application/json" },
314064
- signal: controller.signal,
314065
- body: JSON.stringify({
314066
- jsonrpc: "2.0",
314067
- id: 1,
314068
- method: "eth_getTransactionByHash",
314069
- params: [txHash]
314070
- })
314069
+ headers: {
314070
+ "content-type": "application/json",
314071
+ "content-length": Buffer.byteLength(body)
314072
+ },
314073
+ timeout: timeoutMs
314074
+ }, (res) => {
314075
+ const chunks = [];
314076
+ res.on("data", (chunk) => chunks.push(chunk));
314077
+ res.on("end", () => {
314078
+ const status = res.statusCode ?? 0;
314079
+ if (status < 200 || status >= 300) {
314080
+ reject(new Error(`travel_rule_rpc_http_${status}`));
314081
+ return;
314082
+ }
314083
+ try {
314084
+ const json3 = JSON.parse(Buffer.concat(chunks).toString("utf8"));
314085
+ if (json3.error) {
314086
+ reject(new Error(`travel_rule_rpc_error: ${JSON.stringify(json3.error)}`));
314087
+ return;
314088
+ }
314089
+ const from = json3.result?.from;
314090
+ resolve(typeof from === "string" && from.length > 0 ? from.toLowerCase() : null);
314091
+ } catch (parseError) {
314092
+ reject(new Error(`travel_rule_rpc_parse_error: ${String(parseError)}`));
314093
+ }
314094
+ });
314071
314095
  });
314072
- } finally {
314073
- clearTimeout(timeout2);
314074
- }
314075
- if (!response.ok) {
314076
- throw new Error(`travel_rule_rpc_http_${response.status}`);
314077
- }
314078
- const json3 = await response.json();
314079
- if (json3.error) {
314080
- throw new Error(`travel_rule_rpc_error: ${JSON.stringify(json3.error)}`);
314081
- }
314082
- const from = json3.result?.from;
314083
- return typeof from === "string" && from.length > 0 ? from.toLowerCase() : null;
314096
+ req.on("error", reject);
314097
+ req.on("timeout", () => {
314098
+ req.destroy(new Error("travel_rule_rpc_timeout"));
314099
+ });
314100
+ req.write(body);
314101
+ req.end();
314102
+ });
314084
314103
  }
314085
314104
  function errorText(error) {
314086
314105
  if (error instanceof Error)
@@ -314182,17 +314201,19 @@ async function reconcileAccountOnce(deps) {
314182
314201
  if (now3 < state.rateLimitedUntil)
314183
314202
  break;
314184
314203
  let sender = null;
314204
+ let resolveError;
314185
314205
  try {
314186
314206
  sender = await deps.resolveSender(deposit.network, deposit.txId);
314187
314207
  } catch (error) {
314188
314208
  sender = null;
314209
+ resolveError = errorText(error);
314189
314210
  if (isRateLimitError(error)) {
314190
314211
  state.rateLimitedUntil = now3 + deps.rateLimitCooldownMs;
314191
314212
  }
314192
314213
  }
314193
314214
  if (!sender) {
314194
314215
  state.backoffUntil.set(deposit.tranId, now3 + deps.failureBackoffMs);
314195
- outcomes.push({ kind: "unproven-origin", deposit });
314216
+ outcomes.push({ kind: "unproven-origin", deposit, error: resolveError });
314196
314217
  continue;
314197
314218
  }
314198
314219
  const questionnaire = deps.resolveQuestionnaire(deps.depositConfig, sender);
@@ -314448,7 +314469,8 @@ class TravelRuleDepositReconciler {
314448
314469
  tranId: outcome.deposit.tranId,
314449
314470
  coin: outcome.deposit.coin,
314450
314471
  network: outcome.deposit.network,
314451
- txId: outcome.deposit.txId
314472
+ txId: outcome.deposit.txId,
314473
+ rpcError: outcome.error ?? null
314452
314474
  });
314453
314475
  metrics?.recordCounter("travel_rule_deposit_anomalies_total", 1, {
314454
314476
  ...base2,
@@ -314971,464 +314993,1228 @@ function validateDeposit(policy, exchange, network, ticker) {
314971
314993
  return { valid: true };
314972
314994
  }
314973
314995
 
314974
- // src/helpers/otel.ts
314975
- var import_api2 = __toESM(require_src5(), 1);
314976
- var import_api_logs2 = __toESM(require_src7(), 1);
314977
- var import_exporter_logs_otlp_http = __toESM(require_src14(), 1);
314978
- var import_exporter_metrics_otlp_http = __toESM(require_src15(), 1);
314979
- var import_resources = __toESM(require_src11(), 1);
314980
- var import_sdk_logs = __toESM(require_src16(), 1);
314981
- var import_sdk_metrics = __toESM(require_src12(), 1);
314982
- var DEFAULT_SERVICE = "cex-broker";
314983
- var DEFAULT_OTLP_PORT = 4318;
314984
- var EXPORT_INTERVAL_MS = 5000;
314996
+ // src/helpers/shared/guards.ts
314997
+ function isRecord(value) {
314998
+ return value !== null && typeof value === "object" && !Array.isArray(value);
314999
+ }
315000
+ function asRecord(value) {
315001
+ return isRecord(value) ? value : undefined;
315002
+ }
314985
315003
 
314986
- class BaseOtelSignal {
314987
- signal;
314988
- provider = null;
314989
- isEnabled = false;
314990
- serviceName;
314991
- constructor(config, signal) {
314992
- this.signal = signal;
314993
- this.serviceName = config?.serviceName ?? DEFAULT_SERVICE;
314994
- const endpointResolution = resolveOtlpEndpoint(this.signal, config);
314995
- if (!endpointResolution) {
314996
- log.info(`OTel ${signal} disabled: no OTLP endpoint or host provided`);
314997
- return;
315004
+ // src/helpers/order-telemetry.ts
315005
+ var NUMERIC_METRICS = [
315006
+ ["requestedQuantity", "cex_market_action_requested_quantity"],
315007
+ ["requestedNotional", "cex_market_action_requested_notional"],
315008
+ ["executedBaseQuantity", "cex_market_action_executed_base_quantity"],
315009
+ ["executedQuoteQuantity", "cex_market_action_executed_quote_quantity"],
315010
+ ["averageExecutionPrice", "cex_market_action_average_execution_price"],
315011
+ ["filledAmount", "cex_market_action_filled_amount"],
315012
+ ["remainingAmount", "cex_market_action_remaining_amount"],
315013
+ ["feeAmount", "cex_market_action_fee_amount"],
315014
+ ["feeRate", "cex_market_action_fee_rate"]
315015
+ ];
315016
+ var REDACTED_ERROR_MESSAGE = "redacted_error";
315017
+ async function emitOrderExecutionTelemetry(otelMetrics, context2, order, error) {
315018
+ try {
315019
+ const telemetry = buildOrderExecutionTelemetry(context2, order, error);
315020
+ log.info("CEX market action execution telemetry", telemetry);
315021
+ const labels = {
315022
+ action: telemetry.action,
315023
+ cex: telemetry.cex,
315024
+ account: telemetry.accountLabel,
315025
+ symbol: telemetry.symbol,
315026
+ side: telemetry.side,
315027
+ order_type: telemetry.orderType,
315028
+ status: telemetry.status
315029
+ };
315030
+ await otelMetrics?.recordCounter("cex_market_action_executions_total", 1, {
315031
+ ...labels,
315032
+ result: error ? "error" : "ok"
315033
+ });
315034
+ for (const [key, metricName] of NUMERIC_METRICS) {
315035
+ const value = telemetry[key];
315036
+ if (typeof value === "number" && Number.isFinite(value)) {
315037
+ await otelMetrics?.recordHistogram(metricName, value, labels);
315038
+ }
314998
315039
  }
315040
+ return telemetry;
315041
+ } catch (telemetryError) {
314999
315042
  try {
315000
- this.provider = this.createProvider(endpointResolution.endpoint, this.serviceName, endpointResolution.appendSignalPath);
315001
- this.onProviderCreated(this.provider);
315002
- this.isEnabled = true;
315003
- log.info(`OTel ${signal} enabled: ${endpointResolution.endpoint}`);
315004
- } catch (error) {
315005
- log.error(`Failed to initialize OTel ${signal}:`, error);
315006
- this.isEnabled = false;
315007
- this.provider = null;
315008
- }
315009
- }
315010
- onProviderCreated(_provider) {}
315011
- onProviderClosed() {}
315012
- getProvider() {
315013
- return this.provider;
315014
- }
315015
- getServiceName() {
315016
- return this.serviceName;
315017
- }
315018
- isOtelEnabled() {
315019
- return this.isEnabled && this.provider !== null;
315043
+ log.error("Failed to emit CEX order telemetry", {
315044
+ error: telemetryError
315045
+ });
315046
+ } catch {}
315047
+ return;
315020
315048
  }
315021
- async close() {
315022
- if (!this.provider) {
315023
- return;
315024
- }
315049
+ }
315050
+ function buildOrderExecutionTelemetry(context2, order, error) {
315051
+ const record = asRecord(order);
315052
+ const info = asRecord(record?.info);
315053
+ const fees = getFees(record, info);
315054
+ const fee = summarizeFees(fees);
315055
+ const executedBaseQuantity = firstNumber(record?.filled, info?.executedQty, info?.cumExecQty) ?? computeFilledFromAmount(record);
315056
+ const executedQuoteQuantity = firstNumber(record?.cost, info?.cummulativeQuoteQty, info?.cumQuote, info?.cumExecValue);
315057
+ const averageExecutionPrice = firstNumber(record?.average, info?.avgPrice) ?? computeAveragePrice(executedBaseQuantity, executedQuoteQuantity);
315058
+ const exchangeTimestamp = normalizeTimestamp(firstValue(record?.timestamp, info?.time, info?.transactTime, record?.datetime));
315059
+ const status = firstString(record?.status, info?.status) ?? (error ? "failed" : "unknown");
315060
+ const errorRecord = error instanceof Error ? error : undefined;
315061
+ return compactUndefined({
315062
+ event: "cex_market_action_execution",
315063
+ action: context2.action,
315064
+ cex: context2.cex.trim().toLowerCase() || "unknown",
315065
+ accountLabel: context2.accountLabel ?? "unknown",
315066
+ symbol: firstString(record?.symbol, info?.symbol, context2.symbol) ?? "unknown",
315067
+ side: firstString(record?.side, info?.side, context2.side) ?? "unknown",
315068
+ orderType: firstString(record?.type, info?.type, context2.orderType) ?? "unknown",
315069
+ orderId: firstString(record?.id, info?.orderId, info?.orderID),
315070
+ clientOrderId: firstString(context2.clientOrderId, record?.clientOrderId, record?.clientOrderID, record?.clientOid, info?.clientOrderId, info?.clientOrderID, info?.clientOid),
315071
+ idempotencyId: context2.idempotencyId,
315072
+ makerActionId: context2.makerActionId,
315073
+ status: status.toLowerCase(),
315074
+ requestedQuantity: context2.requestedQuantity ?? firstNumber(record?.amount),
315075
+ requestedNotional: context2.requestedNotional,
315076
+ executedBaseQuantity,
315077
+ executedQuoteQuantity,
315078
+ averageExecutionPrice,
315079
+ filledAmount: firstNumber(record?.filled, info?.executedQty),
315080
+ remainingAmount: firstNumber(record?.remaining, info?.remainingQty),
315081
+ feeAmount: fee.amount,
315082
+ feeCurrency: fee.currency,
315083
+ feeRate: fee.rate,
315084
+ exchangeTimestamp,
315085
+ brokerObservedTimestamp: context2.brokerObservedTimestamp ?? new Date().toISOString(),
315086
+ errorType: errorRecord?.name,
315087
+ errorMessage: errorRecord ? REDACTED_ERROR_MESSAGE : undefined
315088
+ });
315089
+ }
315090
+ function emitOrderExecutionTelemetryInBackground(otelMetrics, context2, order, error) {
315091
+ emitOrderExecutionTelemetry(otelMetrics, context2, order, error).catch((telemetryError) => {
315025
315092
  try {
315026
- await this.shutdownProvider(this.provider);
315027
- log.info(`OTel ${this.signal} provider shut down`);
315028
- } catch (error) {
315029
- log.error(`Error shutting down OTel ${this.signal} provider:`, error);
315093
+ log.warn("Telemetry emit failed", { error: telemetryError });
315094
+ } catch {
315095
+ console.warn("Telemetry emit failed", telemetryError);
315096
+ }
315097
+ });
315098
+ }
315099
+ function extractOrderTelemetryIds(params) {
315100
+ const record = params ?? {};
315101
+ return {
315102
+ clientOrderId: firstString(record.clientOrderId, record.clientOrderID, record.newClientOrderId, record.clientOid),
315103
+ idempotencyId: firstString(record.idempotencyId, record.idempotencyID, record.idempotencyKey, record.requestId, record.requestID),
315104
+ makerActionId: firstString(record.makerActionId, record.maker_action_id, record.actionId, record.action_id)
315105
+ };
315106
+ }
315107
+ function firstValue(...values2) {
315108
+ return values2.find((value) => value !== undefined && value !== null);
315109
+ }
315110
+ function firstString(...values2) {
315111
+ for (const value of values2) {
315112
+ if (typeof value === "string" && value.trim()) {
315113
+ return value.trim();
315114
+ }
315115
+ if (typeof value === "number" && Number.isFinite(value)) {
315116
+ return String(value);
315030
315117
  }
315031
- this.provider = null;
315032
- this.isEnabled = false;
315033
- this.onProviderClosed();
315034
315118
  }
315119
+ return;
315035
315120
  }
315036
- function toAttributes(labels, service) {
315037
- const attrs = { ...labels, service };
315038
- for (const key of Object.keys(attrs)) {
315039
- const v = attrs[key];
315040
- if (typeof v !== "string" && typeof v !== "number") {
315041
- attrs[key] = String(v);
315121
+ function firstNumber(...values2) {
315122
+ for (const value of values2) {
315123
+ const numberValue = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
315124
+ if (Number.isFinite(numberValue)) {
315125
+ return numberValue;
315042
315126
  }
315043
315127
  }
315044
- return attrs;
315128
+ return;
315045
315129
  }
315046
-
315047
- class OtelMetrics extends BaseOtelSignal {
315048
- counters = new Map;
315049
- histograms = new Map;
315050
- constructor(config) {
315051
- super(config, "metrics");
315130
+ function computeFilledFromAmount(record) {
315131
+ const amount = firstNumber(record?.amount);
315132
+ const remaining = firstNumber(record?.remaining);
315133
+ if (amount === undefined || remaining === undefined) {
315134
+ return;
315052
315135
  }
315053
- createProvider(endpoint, serviceName, appendSignalPath) {
315054
- const exporter = new import_exporter_metrics_otlp_http.OTLPMetricExporter({
315055
- url: appendOtlpPath(endpoint, "metrics", appendSignalPath)
315056
- });
315057
- const reader = new import_sdk_metrics.PeriodicExportingMetricReader({
315058
- exporter,
315059
- exportIntervalMillis: EXPORT_INTERVAL_MS
315060
- });
315061
- const resource = import_resources.resourceFromAttributes({
315062
- "service.name": serviceName
315063
- });
315064
- return new import_sdk_metrics.MeterProvider({
315065
- resource,
315066
- readers: [reader]
315067
- });
315136
+ return amount - remaining;
315137
+ }
315138
+ function computeAveragePrice(executedBaseQuantity, executedQuoteQuantity) {
315139
+ if (executedBaseQuantity === undefined || executedQuoteQuantity === undefined || executedBaseQuantity === 0) {
315140
+ return;
315068
315141
  }
315069
- onProviderCreated(provider) {
315070
- import_api2.metrics.setGlobalMeterProvider(provider);
315142
+ return executedQuoteQuantity / executedBaseQuantity;
315143
+ }
315144
+ function normalizeTimestamp(value) {
315145
+ if (typeof value === "string" && value.trim()) {
315146
+ return value.trim();
315071
315147
  }
315072
- shutdownProvider(provider) {
315073
- return provider.shutdown();
315148
+ const timestamp = firstNumber(value);
315149
+ if (timestamp === undefined) {
315150
+ return;
315074
315151
  }
315075
- async initialize() {
315076
- if (this.isOtelEnabled()) {
315077
- log.info("OTel metrics initialized (storage is handled by the collector)");
315152
+ const date = new Date(timestamp);
315153
+ return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
315154
+ }
315155
+ function getFees(record, info) {
315156
+ const fees = [];
315157
+ if (record?.fee)
315158
+ fees.push(record.fee);
315159
+ if (Array.isArray(record?.fees))
315160
+ fees.push(...record.fees);
315161
+ if (Array.isArray(record?.trades)) {
315162
+ for (const trade of record.trades) {
315163
+ const tradeRecord = asRecord(trade);
315164
+ if (tradeRecord?.fee)
315165
+ fees.push(tradeRecord.fee);
315166
+ if (Array.isArray(tradeRecord?.fees))
315167
+ fees.push(...tradeRecord.fees);
315078
315168
  }
315079
315169
  }
315080
- async insertMetric(metric) {
315081
- if (!this.isOtelEnabled())
315082
- return;
315083
- const labels = metric.labels ? JSON.parse(metric.labels) : {};
315084
- try {
315085
- if (metric.metric_type === "counter") {
315086
- await this.recordCounter(metric.metric_name, metric.value, labels, metric.service);
315087
- } else if (metric.metric_type === "gauge") {
315088
- await this.recordGauge(metric.metric_name, metric.value, labels, metric.service);
315089
- } else {
315090
- await this.recordHistogram(metric.metric_name, metric.value, labels, metric.service);
315170
+ if (Array.isArray(info?.fills)) {
315171
+ for (const fill of info.fills) {
315172
+ const fillRecord = asRecord(fill);
315173
+ const commission = firstNumber(fillRecord?.commission);
315174
+ if (commission !== undefined) {
315175
+ fees.push({
315176
+ cost: commission,
315177
+ currency: firstString(fillRecord?.commissionAsset)
315178
+ });
315091
315179
  }
315092
- } catch {}
315180
+ }
315093
315181
  }
315094
- async insertMetrics(metricsList) {
315095
- if (!this.isOtelEnabled() || metricsList.length === 0)
315096
- return;
315097
- for (const m of metricsList) {
315098
- await this.insertMetric(m);
315099
- }
315100
- }
315101
- async recordCounter(metricName, value, labels, service = this.getServiceName()) {
315102
- const provider = this.getProvider();
315103
- if (!this.isOtelEnabled() || !provider)
315104
- return;
315105
- try {
315106
- let counter = this.counters.get(metricName);
315107
- if (!counter) {
315108
- const meter = provider.getMeter("cex-broker-metrics", "1.0.0");
315109
- counter = meter.createCounter(metricName, { description: metricName });
315110
- this.counters.set(metricName, counter);
315111
- }
315112
- counter.add(value, toAttributes(labels, service));
315113
- } catch (error) {
315114
- log.error("Failed to record counter:", error);
315115
- }
315116
- }
315117
- async recordGauge(metricName, value, labels, service = this.getServiceName()) {
315118
- const provider = this.getProvider();
315119
- if (!this.isOtelEnabled() || !provider)
315120
- return;
315121
- try {
315122
- let hist = this.histograms.get(`gauge_${metricName}`);
315123
- if (!hist) {
315124
- const meter = provider.getMeter("cex-broker-metrics", "1.0.0");
315125
- hist = meter.createHistogram(`${metricName}_gauge`, {
315126
- description: metricName
315127
- });
315128
- this.histograms.set(`gauge_${metricName}`, hist);
315129
- }
315130
- hist.record(value, toAttributes(labels, service));
315131
- } catch (error) {
315132
- log.error("Failed to record gauge:", error);
315133
- }
315134
- }
315135
- async recordHistogram(metricName, value, labels, service = this.getServiceName()) {
315136
- const provider = this.getProvider();
315137
- if (!this.isOtelEnabled() || !provider)
315138
- return;
315139
- try {
315140
- let hist = this.histograms.get(metricName);
315141
- if (!hist) {
315142
- const meter = provider.getMeter("cex-broker-metrics", "1.0.0");
315143
- hist = meter.createHistogram(metricName, { description: metricName });
315144
- this.histograms.set(metricName, hist);
315145
- }
315146
- hist.record(value, toAttributes(labels, service));
315147
- } catch (error) {
315148
- log.error("Failed to record histogram:", error);
315182
+ return fees;
315183
+ }
315184
+ function summarizeFees(fees) {
315185
+ let amount = 0;
315186
+ let amountFound = false;
315187
+ let currency;
315188
+ let rate;
315189
+ for (const rawFee of fees) {
315190
+ const fee = asRecord(rawFee);
315191
+ if (!fee)
315192
+ continue;
315193
+ const cost = firstNumber(fee.cost, fee.amount);
315194
+ if (cost !== undefined) {
315195
+ amount += cost;
315196
+ amountFound = true;
315149
315197
  }
315198
+ currency ??= firstString(fee.currency);
315199
+ rate ??= firstNumber(fee.rate);
315150
315200
  }
315201
+ return {
315202
+ amount: amountFound ? amount : undefined,
315203
+ currency,
315204
+ rate
315205
+ };
315206
+ }
315207
+ function compactUndefined(record) {
315208
+ return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
315151
315209
  }
315152
315210
 
315153
- class OtelLogs extends BaseOtelSignal {
315154
- logger = null;
315155
- constructor(config) {
315156
- super(config, "logs");
315157
- }
315158
- createProvider(endpoint, serviceName, appendSignalPath) {
315159
- const exporter = new import_exporter_logs_otlp_http.OTLPLogExporter({
315160
- url: appendOtlpPath(endpoint, "logs", appendSignalPath)
315161
- });
315162
- const processor = new import_sdk_logs.BatchLogRecordProcessor(exporter);
315163
- const resource = import_resources.resourceFromAttributes({
315164
- "service.name": serviceName
315165
- });
315166
- return new import_sdk_logs.LoggerProvider({
315167
- resource,
315168
- processors: [processor]
315169
- });
315211
+ // src/helpers/broker-execution-archive/redact.ts
315212
+ import { createHash } from "node:crypto";
315213
+ var SECRET_KEY_PATTERN = /\b(api[_-]?key|api[_-]?secret|secret|signature|passphrase|password|token|credential)\b/i;
315214
+ var SECRET_VALUE_PATTERN = /(\b(?:apiKey|api_key|apiSecret|api_secret|secret|signature|passphrase|password|token)\b\s*[=:]\s*)[^\s&,;)}\]]+/gi;
315215
+ var SECRET_JSON_PATTERN = /("(?:apiKey|api_key|apiSecret|api_secret|secret|signature|passphrase|password|token)"\s*:\s*")[^"]*(")/gi;
315216
+ function redactSecretLiterals(value, secretLiterals = []) {
315217
+ let redacted = value;
315218
+ for (const literal of secretLiterals) {
315219
+ if (literal.length > 0) {
315220
+ redacted = redacted.split(literal).join("[redacted]");
315221
+ }
315170
315222
  }
315171
- onProviderCreated(provider) {
315172
- import_api_logs2.logs.setGlobalLoggerProvider(provider);
315173
- this.logger = provider.getLogger("cex-broker-logs", "1.0.0");
315223
+ return redacted.replace(SECRET_VALUE_PATTERN, "$1[redacted]").replace(SECRET_JSON_PATTERN, "$1[redacted]$2");
315224
+ }
315225
+ function redactUnknownValue(value, secretLiterals) {
315226
+ if (value === null || value === undefined) {
315227
+ return value;
315174
315228
  }
315175
- shutdownProvider(provider) {
315176
- return provider.forceFlush().then(() => provider.shutdown());
315229
+ if (typeof value === "string") {
315230
+ return redactSecretLiterals(value, secretLiterals);
315177
315231
  }
315178
- onProviderClosed() {
315179
- this.logger = null;
315232
+ if (typeof value === "number" || typeof value === "boolean") {
315233
+ return value;
315180
315234
  }
315181
- emit(record) {
315182
- if (!this.isOtelEnabled() || !this.logger) {
315183
- return;
315235
+ if (Array.isArray(value)) {
315236
+ return value.map((entry) => redactUnknownValue(entry, secretLiterals));
315237
+ }
315238
+ if (typeof value === "object") {
315239
+ const record = value;
315240
+ const redacted = {};
315241
+ for (const [key, entry] of Object.entries(record)) {
315242
+ if (SECRET_KEY_PATTERN.test(key)) {
315243
+ redacted[key] = "[redacted]";
315244
+ continue;
315245
+ }
315246
+ redacted[key] = redactUnknownValue(entry, secretLiterals);
315184
315247
  }
315185
- this.logger.emit(record);
315248
+ return redacted;
315186
315249
  }
315250
+ return String(value);
315187
315251
  }
315188
- function resolveOtlpEndpoint(signal, config) {
315189
- if (config?.otlpEndpoint) {
315252
+ function redactStreamPayload(payload, secretLiterals = []) {
315253
+ if (Array.isArray(payload)) {
315190
315254
  return {
315191
- endpoint: normalizeOtlpEndpoint(config.otlpEndpoint),
315192
- appendSignalPath: true
315255
+ items: payload.map((entry) => redactUnknownValue(entry, secretLiterals))
315193
315256
  };
315194
315257
  }
315195
- const signalEndpoint = signal === "metrics" ? process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT : process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT;
315196
- if (signalEndpoint) {
315197
- return {
315198
- endpoint: signalEndpoint,
315199
- appendSignalPath: false
315200
- };
315258
+ const record = asRecord(payload);
315259
+ if (!record) {
315260
+ return {};
315201
315261
  }
315202
- if (process.env.OTEL_EXPORTER_OTLP_ENDPOINT) {
315203
- return {
315204
- endpoint: normalizeOtlpEndpoint(process.env.OTEL_EXPORTER_OTLP_ENDPOINT),
315205
- appendSignalPath: true
315206
- };
315262
+ return redactUnknownValue(record, secretLiterals);
315263
+ }
315264
+ function hashMarketMetadata(payload) {
315265
+ if (payload === undefined || payload === null) {
315266
+ return;
315207
315267
  }
315208
- if (config?.host) {
315209
- const protocol = config.protocol || "http";
315210
- const port = config.port ?? DEFAULT_OTLP_PORT;
315211
- return {
315212
- endpoint: `${protocol}://${config.host}:${port}`,
315213
- appendSignalPath: true
315214
- };
315268
+ try {
315269
+ const normalized = JSON.stringify(payload);
315270
+ if (!normalized || normalized === "{}") {
315271
+ return;
315272
+ }
315273
+ return createHash("sha256").update(normalized).digest("hex");
315274
+ } catch {
315275
+ return;
315215
315276
  }
315216
- return null;
315217
315277
  }
315218
- function appendOtlpPath(endpoint, signal, appendSignalPath) {
315219
- if (!appendSignalPath) {
315220
- return endpoint;
315278
+
315279
+ // src/helpers/broker-execution-archive/types.ts
315280
+ var BROKER_WRITE_SOURCE = "broker_write";
315281
+
315282
+ // src/helpers/broker-execution-archive/rows.ts
315283
+ function firstString2(...values2) {
315284
+ for (const value of values2) {
315285
+ if (typeof value === "string" && value.trim()) {
315286
+ return value.trim();
315287
+ }
315288
+ if (typeof value === "number" && Number.isFinite(value)) {
315289
+ return String(value);
315290
+ }
315221
315291
  }
315222
- const baseEndpoint = normalizeOtlpEndpoint(endpoint);
315223
- return `${baseEndpoint}/v1/${signal}`;
315292
+ return;
315224
315293
  }
315225
- function normalizeOtlpEndpoint(endpoint) {
315226
- return endpoint.replace(/\/v1\/(metrics|logs)\/?$/, "").replace(/\/+$/, "");
315294
+ function compactUndefined2(record) {
315295
+ return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
315227
315296
  }
315228
- function getOtelHostFromEnv() {
315229
- return process.env.CEX_BROKER_OTEL_HOST ?? process.env.CEX_BROKER_CLICKHOUSE_HOST;
315297
+ function buildCommonArchiveTags(input) {
315298
+ return {
315299
+ source: BROKER_WRITE_SOURCE,
315300
+ deployment_id: input.deploymentId,
315301
+ account_selector: input.accountSelector ?? "unknown",
315302
+ exchange: input.exchange.trim().toLowerCase() || "unknown",
315303
+ symbol: input.symbol?.trim() || "unknown",
315304
+ broker_observed_timestamp: input.brokerObservedTimestamp ?? new Date().toISOString()
315305
+ };
315230
315306
  }
315231
- function getOtelPortFromEnv() {
315232
- const port = process.env.CEX_BROKER_OTEL_PORT ?? process.env.CEX_BROKER_CLICKHOUSE_PORT;
315233
- return port ? Number.parseInt(port, 10) : undefined;
315307
+ function buildOrderEventArchiveRow(input) {
315308
+ const { tags, telemetry, action } = input;
315309
+ return {
315310
+ table: "broker_execution.order_events",
315311
+ row: compactUndefined2({
315312
+ ...tags,
315313
+ event_kind: input.eventKind ?? "execute_action",
315314
+ action,
315315
+ subscription_type: input.subscriptionType,
315316
+ order_id: telemetry.orderId,
315317
+ client_order_id: telemetry.clientOrderId,
315318
+ idempotency_id: telemetry.idempotencyId,
315319
+ maker_action_id: telemetry.makerActionId,
315320
+ market_metadata_hash: input.marketMetadataHash,
315321
+ status: telemetry.status,
315322
+ side: telemetry.side,
315323
+ order_type: telemetry.orderType,
315324
+ requested_quantity: telemetry.requestedQuantity,
315325
+ requested_notional: telemetry.requestedNotional,
315326
+ executed_base_quantity: telemetry.executedBaseQuantity,
315327
+ executed_quote_quantity: telemetry.executedQuoteQuantity,
315328
+ average_execution_price: telemetry.averageExecutionPrice,
315329
+ filled_amount: telemetry.filledAmount,
315330
+ remaining_amount: telemetry.remainingAmount,
315331
+ fee_amount: telemetry.feeAmount,
315332
+ fee_currency: telemetry.feeCurrency,
315333
+ fee_rate: telemetry.feeRate,
315334
+ exchange_timestamp: telemetry.exchangeTimestamp,
315335
+ error_type: telemetry.errorType,
315336
+ error_message: telemetry.errorMessage,
315337
+ payload_json: JSON.stringify(telemetry)
315338
+ })
315339
+ };
315234
315340
  }
315235
- function getOtelProtocolFromEnv() {
315236
- const protocol = process.env.CEX_BROKER_OTEL_PROTOCOL ?? process.env.CEX_BROKER_CLICKHOUSE_PROTOCOL;
315237
- return protocol || "http";
315341
+ function buildSubscribeStreamArchiveRow(input) {
315342
+ const redactedPayload = redactStreamPayload(input.streamPayload, input.secretLiterals);
315343
+ const record = asRecord(input.streamPayload);
315344
+ const info = asRecord(record?.info);
315345
+ return {
315346
+ table: "broker_execution.order_events",
315347
+ row: compactUndefined2({
315348
+ ...input.tags,
315349
+ event_kind: "subscribe_stream",
315350
+ subscription_type: input.subscriptionType,
315351
+ order_id: firstString2(record?.id, record?.orderId, record?.i, info?.orderId, info?.i),
315352
+ client_order_id: firstString2(record?.clientOrderId, record?.clientOrderID, record?.c, info?.clientOrderId, info?.c),
315353
+ status: firstString2(record?.status, record?.X, info?.status, info?.X)?.toLowerCase(),
315354
+ payload_json: JSON.stringify(redactedPayload)
315355
+ })
315356
+ };
315238
315357
  }
315239
- function createOtelMetricsFromEnv() {
315240
- const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
315241
- const host = getOtelHostFromEnv();
315242
- if (otlpEndpoint) {
315243
- return new OtelMetrics({
315244
- otlpEndpoint: otlpEndpoint.replace(/\/v1\/metrics\/?$/, ""),
315245
- serviceName: process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE
315246
- });
315358
+ function buildMarketMetadataSnapshotRow(input) {
315359
+ const redactedSnapshot = redactStreamPayload(input.marketSnapshot);
315360
+ const metadataHash = hashMarketMetadata(redactedSnapshot);
315361
+ return {
315362
+ table: "broker_execution.market_metadata_snapshots",
315363
+ row: compactUndefined2({
315364
+ ...input.tags,
315365
+ client_order_id: input.clientOrderId,
315366
+ order_id: input.orderId,
315367
+ maker_action_id: input.makerActionId,
315368
+ idempotency_id: input.idempotencyId,
315369
+ market_metadata_hash: metadataHash,
315370
+ snapshot_json: JSON.stringify(redactedSnapshot)
315371
+ })
315372
+ };
315373
+ }
315374
+
315375
+ // src/helpers/broker-execution-archive/capture.ts
315376
+ function archiveOrderExecutionInBackground(archiver, context2, order, error, options) {
315377
+ if (!archiver?.isEnabled()) {
315378
+ return;
315247
315379
  }
315248
- if (!host) {
315249
- return new OtelMetrics;
315380
+ queueMicrotask(() => {
315381
+ try {
315382
+ const telemetry = buildOrderExecutionTelemetry(context2, order, error);
315383
+ const tags = buildCommonArchiveTags({
315384
+ deploymentId: archiver.getDeploymentId(),
315385
+ accountSelector: context2.accountLabel,
315386
+ exchange: context2.cex,
315387
+ symbol: telemetry.symbol,
315388
+ brokerObservedTimestamp: telemetry.brokerObservedTimestamp
315389
+ });
315390
+ archiver.enqueue(buildOrderEventArchiveRow({
315391
+ tags,
315392
+ action: context2.action,
315393
+ telemetry,
315394
+ marketMetadataHash: options?.marketMetadataHash
315395
+ }));
315396
+ } catch (archiveError) {
315397
+ log.warn("Failed to archive order execution", { error: archiveError });
315398
+ }
315399
+ });
315400
+ }
315401
+ function archiveSubscribeStreamInBackground(archiver, input) {
315402
+ if (!archiver?.isEnabled()) {
315403
+ return;
315250
315404
  }
315251
- const port = getOtelPortFromEnv();
315252
- const config = {
315253
- host,
315254
- port: port ?? DEFAULT_OTLP_PORT,
315255
- protocol: getOtelProtocolFromEnv(),
315256
- serviceName: process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE
315257
- };
315258
- return new OtelMetrics(config);
315405
+ queueMicrotask(() => {
315406
+ try {
315407
+ const tags = buildCommonArchiveTags({
315408
+ deploymentId: archiver.getDeploymentId(),
315409
+ accountSelector: input.accountSelector,
315410
+ exchange: input.exchange,
315411
+ symbol: input.symbol
315412
+ });
315413
+ archiver.enqueue(buildSubscribeStreamArchiveRow({
315414
+ tags,
315415
+ subscriptionType: input.subscriptionType,
315416
+ streamPayload: input.streamPayload,
315417
+ secretLiterals: input.secretLiterals
315418
+ }));
315419
+ } catch (archiveError) {
315420
+ log.warn("Failed to archive subscribe stream event", {
315421
+ error: archiveError
315422
+ });
315423
+ }
315424
+ });
315259
315425
  }
315260
- function createOtelLogsFromEnv() {
315261
- const logsEndpoint = process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT;
315262
- const genericEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
315263
- const host = getOtelHostFromEnv();
315264
- if (logsEndpoint) {
315265
- return new OtelLogs({
315266
- serviceName: process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE
315267
- });
315426
+ async function captureMarketMetadataSnapshot(archiver, broker, input) {
315427
+ if (!archiver?.canPersistMarketMetadataSnapshot()) {
315428
+ return;
315268
315429
  }
315269
- if (genericEndpoint) {
315270
- return new OtelLogs({
315271
- otlpEndpoint: genericEndpoint,
315272
- serviceName: process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE
315430
+ try {
315431
+ const fetchOrderBook = broker.fetchOrderBook;
315432
+ if (typeof fetchOrderBook !== "function") {
315433
+ return;
315434
+ }
315435
+ const orderBook = await fetchOrderBook.call(broker, input.symbol, 5);
315436
+ const record = asRecord(orderBook);
315437
+ const snapshot = {
315438
+ action: input.action,
315439
+ symbol: input.symbol,
315440
+ bids: record?.bids,
315441
+ asks: record?.asks,
315442
+ timestamp: record?.timestamp ?? Date.now(),
315443
+ datetime: record?.datetime,
315444
+ nonce: record?.nonce
315445
+ };
315446
+ const tags = buildCommonArchiveTags({
315447
+ deploymentId: archiver.getDeploymentId(),
315448
+ accountSelector: input.accountSelector,
315449
+ exchange: input.exchange,
315450
+ symbol: input.symbol,
315451
+ brokerObservedTimestamp: input.brokerObservedTimestamp
315452
+ });
315453
+ const row = buildMarketMetadataSnapshotRow({
315454
+ tags,
315455
+ clientOrderId: input.clientOrderId,
315456
+ orderId: input.orderId,
315457
+ makerActionId: input.makerActionId,
315458
+ idempotencyId: input.idempotencyId,
315459
+ marketSnapshot: snapshot
315273
315460
  });
315461
+ archiver.enqueue(row);
315462
+ const hash4 = row.row.market_metadata_hash;
315463
+ return typeof hash4 === "string" ? hash4 : undefined;
315464
+ } catch (archiveError) {
315465
+ log.warn("Failed to capture market metadata snapshot", {
315466
+ error: archiveError
315467
+ });
315468
+ return;
315274
315469
  }
315470
+ }
315471
+ // src/helpers/broker-execution-archive/writer.ts
315472
+ var import_api_logs2 = __toESM(require_src7(), 1);
315473
+
315474
+ // src/helpers/market-data-archive/types.ts
315475
+ var MARKET_ARCHIVE_TABLES = new Set([
315476
+ "market_data.orderbook_snapshots",
315477
+ "market_data.candles",
315478
+ "market_data.cex_stream_events",
315479
+ "market_data.cex_ticker_events",
315480
+ "market_data.cex_trades"
315481
+ ]);
315482
+ function isMarketArchiveTable(table) {
315483
+ return MARKET_ARCHIVE_TABLES.has(table);
315484
+ }
315485
+
315486
+ // src/helpers/broker-execution-archive/writer.ts
315487
+ var DEFAULT_MAX_QUEUE_SIZE = 1e4;
315488
+ var DEFAULT_BATCH_SIZE = 10;
315489
+ var DEFAULT_FLUSH_INTERVAL_MS = 1000;
315490
+ var DEFAULT_FORWARDER_TIMEOUT_MS = 3000;
315491
+ var DEFAULT_ARCHIVE_FORWARDER_PATH = "/archive";
315492
+ var DEFAULT_ARCHIVE_FORWARDER_PORT = 8090;
315493
+ function isArchiveOtelLogsEnabled() {
315494
+ return process.env.CEX_BROKER_ARCHIVE_OTEL_LOGS_ENABLED === "true";
315495
+ }
315496
+ function resolveArchiveForwarderUrlFromEnv() {
315497
+ const explicit = process.env.CEX_BROKER_ARCHIVE_FORWARDER_URL?.trim();
315498
+ if (explicit) {
315499
+ return explicit;
315500
+ }
315501
+ const host = process.env.CEX_BROKER_ARCHIVE_FORWARDER_HOST?.trim() || process.env.CEX_BROKER_CLICKHOUSE_HOST?.trim();
315275
315502
  if (!host) {
315276
- return new OtelLogs;
315503
+ return;
315277
315504
  }
315278
- const port = getOtelPortFromEnv();
315279
- const config = {
315280
- host,
315281
- port: port ?? DEFAULT_OTLP_PORT,
315282
- protocol: getOtelProtocolFromEnv(),
315283
- serviceName: process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE
315284
- };
315285
- return new OtelLogs(config);
315505
+ const protocol = process.env.CEX_BROKER_CLICKHOUSE_PROTOCOL || "http";
315506
+ const port = parsePositiveInt(process.env.CEX_BROKER_ARCHIVE_FORWARDER_PORT, DEFAULT_ARCHIVE_FORWARDER_PORT);
315507
+ const path = process.env.CEX_BROKER_ARCHIVE_FORWARDER_PATH?.trim() || DEFAULT_ARCHIVE_FORWARDER_PATH;
315508
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
315509
+ return `${protocol}://${host}:${port}${normalizedPath}`;
315286
315510
  }
315287
315511
 
315288
- // src/server.ts
315289
- var grpc14 = __toESM(require_src3(), 1);
315290
-
315291
- // src/handlers/execute-action/deposit.ts
315292
- var grpc3 = __toESM(require_src3(), 1);
315293
-
315294
- // src/helpers/shared/errors.ts
315295
- function getErrorMessage(error) {
315296
- return error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
315297
- }
315298
- function safeLogError(context2, error) {
315299
- try {
315300
- log.error(context2, { error });
315301
- } catch {
315302
- console.error(context2, error);
315512
+ class BrokerExecutionArchiver {
315513
+ deploymentId;
315514
+ otelLogs;
315515
+ otelMetrics;
315516
+ forwarderUrl;
315517
+ maxQueueSize;
315518
+ batchSize;
315519
+ flushIntervalMs;
315520
+ forwarderTimeoutMs;
315521
+ queue = [];
315522
+ stats = {
315523
+ enqueued: 0,
315524
+ shed: 0,
315525
+ flushed: 0,
315526
+ forwarderFailures: 0
315527
+ };
315528
+ flushTimer = null;
315529
+ flushInFlight = null;
315530
+ closed = false;
315531
+ loggedMissingMarketForwarder = false;
315532
+ enabled;
315533
+ forwarderAuthToken;
315534
+ constructor(options) {
315535
+ this.deploymentId = options.deploymentId?.trim() || process.env.CEX_BROKER_DEPLOYMENT_ID?.trim() || "unknown";
315536
+ this.otelLogs = options.otelLogs;
315537
+ this.otelMetrics = options.otelMetrics;
315538
+ this.forwarderUrl = options.forwarderUrl?.trim();
315539
+ this.maxQueueSize = options.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;
315540
+ this.batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
315541
+ this.flushIntervalMs = options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
315542
+ this.forwarderTimeoutMs = options.forwarderTimeoutMs ?? DEFAULT_FORWARDER_TIMEOUT_MS;
315543
+ this.enabled = options.enabled;
315544
+ this.forwarderAuthToken = process.env.CEX_BROKER_ARCHIVE_FORWARDER_TOKEN?.trim() || undefined;
315545
+ if (this.enabled) {
315546
+ this.flushTimer = setInterval(() => {
315547
+ this.flush().catch((error) => {
315548
+ log.warn("Broker execution archive flush failed", { error });
315549
+ });
315550
+ }, this.flushIntervalMs);
315551
+ this.flushTimer.unref?.();
315552
+ }
315303
315553
  }
315304
- }
315305
-
315306
- // src/helpers/shared/guards.ts
315307
- function isRecord(value) {
315308
- return value !== null && typeof value === "object" && !Array.isArray(value);
315309
- }
315310
- function asRecord(value) {
315311
- return isRecord(value) ? value : undefined;
315312
- }
315313
-
315314
- // src/helpers/treasury-discovery.ts
315315
- function callArgs(args, params) {
315316
- const argsArray = Array.isArray(args) ? [...args] : [];
315317
- if (params && Object.keys(params).length > 0) {
315318
- argsArray.push(params);
315554
+ static disabled() {
315555
+ return new BrokerExecutionArchiver({ enabled: false });
315319
315556
  }
315320
- return argsArray;
315321
- }
315322
- async function handleTreasuryDiscoveryCall(broker, functionName, args, params) {
315323
- const discoveryBroker = broker;
315324
- if (functionName === "fetchMarkets") {
315325
- if (typeof discoveryBroker.fetchMarkets === "function" && discoveryBroker.has?.fetchMarkets !== false) {
315326
- return {
315327
- handled: true,
315328
- result: await discoveryBroker.fetchMarkets(...callArgs(args, params))
315329
- };
315557
+ static create(options) {
315558
+ const hasSink = Boolean(options.otelLogs?.isOtelEnabled()) || Boolean(options.forwarderUrl);
315559
+ const enabled = process.env.CEX_BROKER_ARCHIVE_ENABLED !== "false" && hasSink;
315560
+ return new BrokerExecutionArchiver({ ...options, enabled });
315561
+ }
315562
+ getDeploymentId() {
315563
+ return this.deploymentId;
315564
+ }
315565
+ isEnabled() {
315566
+ return this.enabled && !this.closed && (Boolean(this.otelLogs) || Boolean(this.forwarderUrl));
315567
+ }
315568
+ canPersistMarketMetadataSnapshot() {
315569
+ return this.isEnabled() && (Boolean(this.otelLogs?.isOtelEnabled()) || Boolean(this.forwarderUrl));
315570
+ }
315571
+ enqueue(row) {
315572
+ if (!this.enabled || this.closed || !this.otelLogs && !this.forwarderUrl) {
315573
+ return;
315330
315574
  }
315331
- if (typeof discoveryBroker.loadMarkets === "function") {
315332
- const loaded = await discoveryBroker.loadMarkets(false, params);
315333
- const markets = loaded && typeof loaded === "object" && !Array.isArray(loaded) ? Object.values(loaded) : Object.values(discoveryBroker.markets ?? {});
315334
- return { handled: true, result: markets };
315575
+ if (isMarketArchiveTable(row.table) && !this.forwarderUrl) {
315576
+ if (!this.loggedMissingMarketForwarder) {
315577
+ this.loggedMissingMarketForwarder = true;
315578
+ log.warn("Market data archive row dropped: configure CEX_BROKER_ARCHIVE_FORWARDER_URL or CEX_BROKER_CLICKHOUSE_HOST", { table: row.table });
315579
+ }
315580
+ return;
315581
+ }
315582
+ if (this.queue.length >= this.maxQueueSize) {
315583
+ this.queue.shift();
315584
+ this.stats.shed += 1;
315585
+ this.recordArchiveMetric("cex_archive_rows_shed_total", {
315586
+ table: row.table
315587
+ });
315588
+ }
315589
+ this.queue.push(row);
315590
+ this.stats.enqueued += 1;
315591
+ this.recordArchiveMetric("cex_archive_rows_enqueued_total", {
315592
+ table: row.table
315593
+ });
315594
+ if (this.queue.length >= this.batchSize) {
315595
+ this.flush().catch((error) => {
315596
+ log.warn("Broker execution archive flush failed", { error });
315597
+ });
315335
315598
  }
315336
- throw new Error("venue_discovery_unavailable: fetchMarkets unavailable on broker");
315337
315599
  }
315338
- if (functionName === "fetchCurrencies") {
315339
- if (typeof discoveryBroker.fetchCurrencies === "function" && discoveryBroker.has?.fetchCurrencies !== false) {
315340
- return {
315341
- handled: true,
315342
- result: await discoveryBroker.fetchCurrencies(...callArgs(args, params))
315343
- };
315600
+ enqueueInBackground(row) {
315601
+ queueMicrotask(() => this.enqueue(row));
315602
+ }
315603
+ async flush() {
315604
+ if (!this.enabled || this.closed || this.queue.length === 0) {
315605
+ return;
315344
315606
  }
315345
- if (discoveryBroker.currencies && Object.keys(discoveryBroker.currencies).length > 0) {
315346
- return { handled: true, result: discoveryBroker.currencies };
315607
+ if (this.flushInFlight) {
315608
+ return this.flushInFlight;
315609
+ }
315610
+ const inFlight = this.flushBatch().then(() => {
315611
+ return;
315612
+ }).finally(() => {
315613
+ this.flushInFlight = null;
315614
+ });
315615
+ this.flushInFlight = inFlight;
315616
+ return inFlight;
315617
+ }
315618
+ async close() {
315619
+ if (this.flushTimer) {
315620
+ clearInterval(this.flushTimer);
315621
+ this.flushTimer = null;
315622
+ }
315623
+ while (this.queue.length > 0 || this.flushInFlight) {
315624
+ if (this.flushInFlight) {
315625
+ await this.flushInFlight;
315626
+ continue;
315627
+ }
315628
+ const depthBefore = this.queue.length;
315629
+ const flushed = await this.flushBatch();
315630
+ if (!flushed && this.queue.length >= depthBefore && depthBefore > 0) {
315631
+ const dropped = this.queue.length;
315632
+ this.queue.length = 0;
315633
+ log.warn(`Archive shutdown dropped ${dropped} undelivered row(s) after forwarder failure`);
315634
+ break;
315635
+ }
315636
+ }
315637
+ this.closed = true;
315638
+ }
315639
+ getStats() {
315640
+ return { ...this.stats };
315641
+ }
315642
+ getQueueDepth() {
315643
+ return this.queue.length;
315644
+ }
315645
+ enforceQueueBound() {
315646
+ while (this.queue.length > this.maxQueueSize) {
315647
+ const dropped = this.queue.shift();
315648
+ this.stats.shed += 1;
315649
+ this.recordArchiveMetric("cex_archive_rows_shed_total", {
315650
+ table: dropped?.table ?? "unknown"
315651
+ });
315652
+ }
315653
+ }
315654
+ async flushBatch() {
315655
+ const batch = this.queue.splice(0, this.batchSize);
315656
+ if (batch.length === 0) {
315657
+ return true;
315658
+ }
315659
+ for (const entry of batch) {
315660
+ if (!isMarketArchiveTable(entry.table)) {
315661
+ this.emitOtelLog(entry);
315662
+ }
315663
+ }
315664
+ if (this.forwarderUrl) {
315665
+ try {
315666
+ await this.postToForwarder(batch);
315667
+ } catch (error) {
315668
+ this.stats.forwarderFailures += 1;
315669
+ this.queue.push(...batch);
315670
+ this.enforceQueueBound();
315671
+ this.recordArchiveMetric("cex_archive_forwarder_failures_total", {
315672
+ count: batch.length
315673
+ });
315674
+ log.warn("Broker execution archive forwarder failed", { error });
315675
+ return false;
315676
+ }
315677
+ }
315678
+ this.stats.flushed += batch.length;
315679
+ return true;
315680
+ }
315681
+ async recordArchiveMetric(metricName, labels) {
315682
+ try {
315683
+ await this.otelMetrics?.recordCounter(metricName, 1, labels);
315684
+ } catch {}
315685
+ }
315686
+ emitOtelLog(entry) {
315687
+ if (!this.otelLogs?.isOtelEnabled()) {
315688
+ return;
315689
+ }
315690
+ try {
315691
+ this.otelLogs.emit({
315692
+ body: entry.table,
315693
+ severityNumber: import_api_logs2.SeverityNumber.INFO,
315694
+ severityText: "INFO",
315695
+ attributes: flattenArchiveAttributes(entry)
315696
+ });
315697
+ } catch (error) {
315698
+ log.warn("Broker execution archive OTLP emit failed", { error });
315699
+ }
315700
+ }
315701
+ async postToForwarder(batch) {
315702
+ if (!this.forwarderUrl || batch.length === 0) {
315703
+ return;
315704
+ }
315705
+ const controller = new AbortController;
315706
+ const timeout2 = setTimeout(() => controller.abort(), this.forwarderTimeoutMs);
315707
+ const headers = {
315708
+ "content-type": "application/json"
315709
+ };
315710
+ if (this.forwarderAuthToken) {
315711
+ headers.authorization = `Bearer ${this.forwarderAuthToken}`;
315712
+ }
315713
+ try {
315714
+ const response = await fetch(this.forwarderUrl, {
315715
+ method: "POST",
315716
+ headers,
315717
+ body: JSON.stringify({
315718
+ source: "broker_write",
315719
+ deployment_id: this.deploymentId,
315720
+ rows: batch
315721
+ }),
315722
+ signal: controller.signal
315723
+ });
315724
+ if (!response.ok) {
315725
+ throw new Error(`Archive forwarder returned ${response.status} ${response.statusText}`);
315726
+ }
315727
+ } finally {
315728
+ clearTimeout(timeout2);
315347
315729
  }
315348
- throw new Error("venue_discovery_unavailable: fetchCurrencies unavailable on broker");
315349
315730
  }
315350
- return { handled: false };
315351
315731
  }
315352
- async function fetchCurrencyMetadata(broker, assetCode) {
315353
- const discoveryBroker = broker;
315354
- const normalizedAsset = assetCode.trim().toUpperCase();
315355
- if (typeof discoveryBroker.fetchCurrencies === "function" && discoveryBroker.has?.fetchCurrencies !== false) {
315356
- const currencies = await discoveryBroker.fetchCurrencies();
315357
- return currencies[normalizedAsset];
315732
+ function flattenArchiveAttributes(entry) {
315733
+ const attributes = {
315734
+ ch_table: entry.table
315735
+ };
315736
+ for (const [key, value] of Object.entries(entry.row)) {
315737
+ if (value === undefined || value === null) {
315738
+ continue;
315739
+ }
315740
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
315741
+ attributes[key] = value;
315742
+ } else {
315743
+ attributes[key] = JSON.stringify(value);
315744
+ }
315358
315745
  }
315359
- return discoveryBroker.currencies?.[normalizedAsset];
315746
+ return attributes;
315360
315747
  }
315361
-
315362
- // src/helpers/transfer-network.ts
315363
- function networkAliasSet(brokerNetworkId, networkKey) {
315364
- const aliases = new Set([
315365
- brokerNetworkId,
315366
- networkKey.trim().toUpperCase()
315367
- ]);
315368
- if (brokerNetworkId === "BNB") {
315369
- aliases.add("BNB");
315370
- aliases.add("BSC");
315371
- aliases.add("BEP20");
315748
+ function createBrokerExecutionArchiverFromEnv(otelLogs, otelMetrics) {
315749
+ const forwarderUrl = resolveArchiveForwarderUrlFromEnv();
315750
+ const archiveOtelLogs = isArchiveOtelLogsEnabled() ? otelLogs : undefined;
315751
+ return BrokerExecutionArchiver.create({
315752
+ otelLogs: archiveOtelLogs,
315753
+ otelMetrics,
315754
+ forwarderUrl,
315755
+ deploymentId: process.env.CEX_BROKER_DEPLOYMENT_ID,
315756
+ maxQueueSize: parsePositiveInt(process.env.CEX_BROKER_ARCHIVE_QUEUE_MAX, DEFAULT_MAX_QUEUE_SIZE),
315757
+ batchSize: parsePositiveInt(process.env.CEX_BROKER_ARCHIVE_BATCH_SIZE, DEFAULT_BATCH_SIZE),
315758
+ flushIntervalMs: parsePositiveInt(process.env.CEX_BROKER_ARCHIVE_FLUSH_INTERVAL_MS, DEFAULT_FLUSH_INTERVAL_MS)
315759
+ });
315760
+ }
315761
+ function parsePositiveInt(value, fallback) {
315762
+ if (!value) {
315763
+ return fallback;
315372
315764
  }
315373
- return [...aliases].filter((alias) => alias.length > 0);
315765
+ const parsed = Number.parseInt(value, 10);
315766
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
315374
315767
  }
315375
- function buildTransferNetworkEvidence(currencyInfo) {
315376
- const rawNetworks = isRecord(currencyInfo.networks) ? currencyInfo.networks : {};
315377
- const networks = {};
315378
- const aliases = {};
315379
- for (const [networkKey, networkValue] of Object.entries(rawNetworks)) {
315380
- const networkRecord = isRecord(networkValue) ? networkValue : {};
315381
- const exchangeNetworkId = String(networkRecord.id ?? networkRecord.network ?? networkKey);
315382
- const brokerNetworkId = normalizeBrokerNetworkId(String(networkRecord.network ?? networkKey));
315383
- const evidence = {
315384
- operatorAlias: networkKey,
315385
- brokerNetworkId,
315386
- exchangeNetworkId,
315387
- networkKey
315388
- };
315389
- networks[networkKey] = {
315390
- ...networkRecord,
315391
- operatorAlias: networkKey,
315392
- brokerNetworkId,
315393
- exchangeNetworkId
315394
- };
315395
- for (const alias of networkAliasSet(brokerNetworkId, networkKey)) {
315396
- aliases[alias] = { ...evidence, operatorAlias: alias };
315768
+ // src/helpers/otel.ts
315769
+ var import_api2 = __toESM(require_src5(), 1);
315770
+ var import_api_logs3 = __toESM(require_src7(), 1);
315771
+ var import_exporter_logs_otlp_http = __toESM(require_src14(), 1);
315772
+ var import_exporter_metrics_otlp_http = __toESM(require_src15(), 1);
315773
+ var import_resources = __toESM(require_src11(), 1);
315774
+ var import_sdk_logs = __toESM(require_src16(), 1);
315775
+ var import_sdk_metrics = __toESM(require_src12(), 1);
315776
+ var DEFAULT_SERVICE = "cex-broker";
315777
+ var DEFAULT_OTLP_PORT = 4318;
315778
+ var EXPORT_INTERVAL_MS = 5000;
315779
+
315780
+ class BaseOtelSignal {
315781
+ signal;
315782
+ provider = null;
315783
+ isEnabled = false;
315784
+ serviceName;
315785
+ constructor(config, signal) {
315786
+ this.signal = signal;
315787
+ this.serviceName = config?.serviceName ?? DEFAULT_SERVICE;
315788
+ const endpointResolution = resolveOtlpEndpoint(this.signal, config);
315789
+ if (!endpointResolution) {
315790
+ log.info(`OTel ${signal} disabled: no OTLP endpoint or host provided`);
315791
+ return;
315792
+ }
315793
+ try {
315794
+ this.provider = this.createProvider(endpointResolution.endpoint, this.serviceName, endpointResolution.appendSignalPath);
315795
+ this.onProviderCreated(this.provider);
315796
+ this.isEnabled = true;
315797
+ log.info(`OTel ${signal} enabled: ${endpointResolution.endpoint}`);
315798
+ } catch (error) {
315799
+ log.error(`Failed to initialize OTel ${signal}:`, error);
315800
+ this.isEnabled = false;
315801
+ this.provider = null;
315397
315802
  }
315398
315803
  }
315399
- return { networks, aliases };
315400
- }
315401
- async function resolveTransferNetwork(broker, assetCode, operatorAlias) {
315402
- const requestedAlias = operatorAlias.trim().toUpperCase();
315403
- const brokerNetworkId = normalizeBrokerNetworkId(requestedAlias);
315404
- let currencyInfo = null;
315405
- try {
315406
- currencyInfo = await fetchCurrencyMetadata(broker, assetCode);
315407
- } catch (error) {
315408
- safeLogError(`Network discovery failed for ${assetCode}/${operatorAlias}; using operator alias as exchange network id`, error);
315804
+ onProviderCreated(_provider) {}
315805
+ onProviderClosed() {}
315806
+ getProvider() {
315807
+ return this.provider;
315409
315808
  }
315410
- if (currencyInfo) {
315411
- const evidence = buildTransferNetworkEvidence(currencyInfo);
315412
- const resolved = evidence.aliases[requestedAlias] ?? evidence.aliases[brokerNetworkId];
315413
- if (resolved) {
315414
- return { ...resolved, operatorAlias: requestedAlias, brokerNetworkId };
315809
+ getServiceName() {
315810
+ return this.serviceName;
315811
+ }
315812
+ isOtelEnabled() {
315813
+ return this.isEnabled && this.provider !== null;
315814
+ }
315815
+ async close() {
315816
+ if (!this.provider) {
315817
+ return;
315415
315818
  }
315416
- throw new Error(`network_alias_unresolved: ${assetCode}/${requestedAlias} is not available in discovered transfer networks`);
315819
+ try {
315820
+ await this.shutdownProvider(this.provider);
315821
+ log.info(`OTel ${this.signal} provider shut down`);
315822
+ } catch (error) {
315823
+ log.error(`Error shutting down OTel ${this.signal} provider:`, error);
315824
+ }
315825
+ this.provider = null;
315826
+ this.isEnabled = false;
315827
+ this.onProviderClosed();
315417
315828
  }
315418
- return {
315419
- operatorAlias: requestedAlias,
315420
- brokerNetworkId,
315421
- exchangeNetworkId: requestedAlias,
315422
- networkKey: null
315423
- };
315424
315829
  }
315425
-
315426
- // node_modules/zod/v4/classic/external.js
315427
- var exports_external = {};
315428
- __export(exports_external, {
315429
- xor: () => xor,
315430
- xid: () => xid2,
315431
- void: () => _void2,
315830
+ function toAttributes(labels, service) {
315831
+ const attrs = { ...labels, service };
315832
+ for (const key of Object.keys(attrs)) {
315833
+ const v = attrs[key];
315834
+ if (typeof v !== "string" && typeof v !== "number") {
315835
+ attrs[key] = String(v);
315836
+ }
315837
+ }
315838
+ return attrs;
315839
+ }
315840
+
315841
+ class OtelMetrics extends BaseOtelSignal {
315842
+ counters = new Map;
315843
+ histograms = new Map;
315844
+ constructor(config) {
315845
+ super(config, "metrics");
315846
+ }
315847
+ createProvider(endpoint, serviceName, appendSignalPath) {
315848
+ const exporter = new import_exporter_metrics_otlp_http.OTLPMetricExporter({
315849
+ url: appendOtlpPath(endpoint, "metrics", appendSignalPath)
315850
+ });
315851
+ const reader = new import_sdk_metrics.PeriodicExportingMetricReader({
315852
+ exporter,
315853
+ exportIntervalMillis: EXPORT_INTERVAL_MS
315854
+ });
315855
+ const resource = import_resources.resourceFromAttributes({
315856
+ "service.name": serviceName
315857
+ });
315858
+ return new import_sdk_metrics.MeterProvider({
315859
+ resource,
315860
+ readers: [reader]
315861
+ });
315862
+ }
315863
+ onProviderCreated(provider) {
315864
+ import_api2.metrics.setGlobalMeterProvider(provider);
315865
+ }
315866
+ shutdownProvider(provider) {
315867
+ return provider.shutdown();
315868
+ }
315869
+ async initialize() {
315870
+ if (this.isOtelEnabled()) {
315871
+ log.info("OTel metrics initialized (storage is handled by the collector)");
315872
+ }
315873
+ }
315874
+ async insertMetric(metric) {
315875
+ if (!this.isOtelEnabled())
315876
+ return;
315877
+ const labels = metric.labels ? JSON.parse(metric.labels) : {};
315878
+ try {
315879
+ if (metric.metric_type === "counter") {
315880
+ await this.recordCounter(metric.metric_name, metric.value, labels, metric.service);
315881
+ } else if (metric.metric_type === "gauge") {
315882
+ await this.recordGauge(metric.metric_name, metric.value, labels, metric.service);
315883
+ } else {
315884
+ await this.recordHistogram(metric.metric_name, metric.value, labels, metric.service);
315885
+ }
315886
+ } catch {}
315887
+ }
315888
+ async insertMetrics(metricsList) {
315889
+ if (!this.isOtelEnabled() || metricsList.length === 0)
315890
+ return;
315891
+ for (const m of metricsList) {
315892
+ await this.insertMetric(m);
315893
+ }
315894
+ }
315895
+ async recordCounter(metricName, value, labels, service = this.getServiceName()) {
315896
+ const provider = this.getProvider();
315897
+ if (!this.isOtelEnabled() || !provider)
315898
+ return;
315899
+ try {
315900
+ let counter = this.counters.get(metricName);
315901
+ if (!counter) {
315902
+ const meter = provider.getMeter("cex-broker-metrics", "1.0.0");
315903
+ counter = meter.createCounter(metricName, { description: metricName });
315904
+ this.counters.set(metricName, counter);
315905
+ }
315906
+ counter.add(value, toAttributes(labels, service));
315907
+ } catch (error) {
315908
+ log.error("Failed to record counter:", error);
315909
+ }
315910
+ }
315911
+ async recordGauge(metricName, value, labels, service = this.getServiceName()) {
315912
+ const provider = this.getProvider();
315913
+ if (!this.isOtelEnabled() || !provider)
315914
+ return;
315915
+ try {
315916
+ let hist = this.histograms.get(`gauge_${metricName}`);
315917
+ if (!hist) {
315918
+ const meter = provider.getMeter("cex-broker-metrics", "1.0.0");
315919
+ hist = meter.createHistogram(`${metricName}_gauge`, {
315920
+ description: metricName
315921
+ });
315922
+ this.histograms.set(`gauge_${metricName}`, hist);
315923
+ }
315924
+ hist.record(value, toAttributes(labels, service));
315925
+ } catch (error) {
315926
+ log.error("Failed to record gauge:", error);
315927
+ }
315928
+ }
315929
+ async recordHistogram(metricName, value, labels, service = this.getServiceName()) {
315930
+ const provider = this.getProvider();
315931
+ if (!this.isOtelEnabled() || !provider)
315932
+ return;
315933
+ try {
315934
+ let hist = this.histograms.get(metricName);
315935
+ if (!hist) {
315936
+ const meter = provider.getMeter("cex-broker-metrics", "1.0.0");
315937
+ hist = meter.createHistogram(metricName, { description: metricName });
315938
+ this.histograms.set(metricName, hist);
315939
+ }
315940
+ hist.record(value, toAttributes(labels, service));
315941
+ } catch (error) {
315942
+ log.error("Failed to record histogram:", error);
315943
+ }
315944
+ }
315945
+ }
315946
+
315947
+ class OtelLogs extends BaseOtelSignal {
315948
+ logger = null;
315949
+ constructor(config) {
315950
+ super(config, "logs");
315951
+ }
315952
+ createProvider(endpoint, serviceName, appendSignalPath) {
315953
+ const exporter = new import_exporter_logs_otlp_http.OTLPLogExporter({
315954
+ url: appendOtlpPath(endpoint, "logs", appendSignalPath)
315955
+ });
315956
+ const processor = new import_sdk_logs.BatchLogRecordProcessor(exporter);
315957
+ const resource = import_resources.resourceFromAttributes({
315958
+ "service.name": serviceName
315959
+ });
315960
+ return new import_sdk_logs.LoggerProvider({
315961
+ resource,
315962
+ processors: [processor]
315963
+ });
315964
+ }
315965
+ onProviderCreated(provider) {
315966
+ import_api_logs3.logs.setGlobalLoggerProvider(provider);
315967
+ this.logger = provider.getLogger("cex-broker-logs", "1.0.0");
315968
+ }
315969
+ shutdownProvider(provider) {
315970
+ return provider.forceFlush().then(() => provider.shutdown());
315971
+ }
315972
+ onProviderClosed() {
315973
+ this.logger = null;
315974
+ }
315975
+ emit(record) {
315976
+ if (!this.isOtelEnabled() || !this.logger) {
315977
+ return;
315978
+ }
315979
+ this.logger.emit(record);
315980
+ }
315981
+ }
315982
+ function resolveOtlpEndpoint(signal, config) {
315983
+ if (config?.otlpEndpoint) {
315984
+ return {
315985
+ endpoint: normalizeOtlpEndpoint(config.otlpEndpoint),
315986
+ appendSignalPath: true
315987
+ };
315988
+ }
315989
+ const signalEndpoint = signal === "metrics" ? process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT : process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT;
315990
+ if (signalEndpoint) {
315991
+ return {
315992
+ endpoint: signalEndpoint,
315993
+ appendSignalPath: false
315994
+ };
315995
+ }
315996
+ if (process.env.OTEL_EXPORTER_OTLP_ENDPOINT) {
315997
+ return {
315998
+ endpoint: normalizeOtlpEndpoint(process.env.OTEL_EXPORTER_OTLP_ENDPOINT),
315999
+ appendSignalPath: true
316000
+ };
316001
+ }
316002
+ if (config?.host) {
316003
+ const protocol = config.protocol || "http";
316004
+ const port = config.port ?? DEFAULT_OTLP_PORT;
316005
+ return {
316006
+ endpoint: `${protocol}://${config.host}:${port}`,
316007
+ appendSignalPath: true
316008
+ };
316009
+ }
316010
+ return null;
316011
+ }
316012
+ function appendOtlpPath(endpoint, signal, appendSignalPath) {
316013
+ if (!appendSignalPath) {
316014
+ return endpoint;
316015
+ }
316016
+ const baseEndpoint = normalizeOtlpEndpoint(endpoint);
316017
+ return `${baseEndpoint}/v1/${signal}`;
316018
+ }
316019
+ function normalizeOtlpEndpoint(endpoint) {
316020
+ return endpoint.replace(/\/v1\/(metrics|logs)\/?$/, "").replace(/\/+$/, "");
316021
+ }
316022
+ function getOtelHostFromEnv() {
316023
+ return process.env.CEX_BROKER_OTEL_HOST ?? process.env.CEX_BROKER_CLICKHOUSE_HOST;
316024
+ }
316025
+ function getOtelPortFromEnv() {
316026
+ const port = process.env.CEX_BROKER_OTEL_PORT ?? process.env.CEX_BROKER_CLICKHOUSE_PORT;
316027
+ return port ? Number.parseInt(port, 10) : undefined;
316028
+ }
316029
+ function getOtelProtocolFromEnv() {
316030
+ const protocol = process.env.CEX_BROKER_OTEL_PROTOCOL ?? process.env.CEX_BROKER_CLICKHOUSE_PROTOCOL;
316031
+ return protocol || "http";
316032
+ }
316033
+ function createOtelMetricsFromEnv() {
316034
+ const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
316035
+ const host = getOtelHostFromEnv();
316036
+ if (otlpEndpoint) {
316037
+ return new OtelMetrics({
316038
+ otlpEndpoint: otlpEndpoint.replace(/\/v1\/metrics\/?$/, ""),
316039
+ serviceName: process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE
316040
+ });
316041
+ }
316042
+ if (!host) {
316043
+ return new OtelMetrics;
316044
+ }
316045
+ const port = getOtelPortFromEnv();
316046
+ const config = {
316047
+ host,
316048
+ port: port ?? DEFAULT_OTLP_PORT,
316049
+ protocol: getOtelProtocolFromEnv(),
316050
+ serviceName: process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE
316051
+ };
316052
+ return new OtelMetrics(config);
316053
+ }
316054
+ function createOtelLogsFromEnv() {
316055
+ const logsEndpoint = process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT;
316056
+ const genericEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
316057
+ const host = getOtelHostFromEnv();
316058
+ if (logsEndpoint) {
316059
+ return new OtelLogs({
316060
+ serviceName: process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE
316061
+ });
316062
+ }
316063
+ if (genericEndpoint) {
316064
+ return new OtelLogs({
316065
+ otlpEndpoint: genericEndpoint,
316066
+ serviceName: process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE
316067
+ });
316068
+ }
316069
+ if (!host) {
316070
+ return new OtelLogs;
316071
+ }
316072
+ const port = getOtelPortFromEnv();
316073
+ const config = {
316074
+ host,
316075
+ port: port ?? DEFAULT_OTLP_PORT,
316076
+ protocol: getOtelProtocolFromEnv(),
316077
+ serviceName: process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE
316078
+ };
316079
+ return new OtelLogs(config);
316080
+ }
316081
+
316082
+ // src/server.ts
316083
+ var grpc14 = __toESM(require_src3(), 1);
316084
+
316085
+ // src/handlers/execute-action/deposit.ts
316086
+ var grpc3 = __toESM(require_src3(), 1);
316087
+
316088
+ // src/helpers/shared/errors.ts
316089
+ function getErrorMessage(error) {
316090
+ return error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
316091
+ }
316092
+ function safeLogError(context2, error) {
316093
+ try {
316094
+ log.error(context2, { error });
316095
+ } catch {
316096
+ console.error(context2, error);
316097
+ }
316098
+ }
316099
+
316100
+ // src/helpers/treasury-discovery.ts
316101
+ function callArgs(args, params) {
316102
+ const argsArray = Array.isArray(args) ? [...args] : [];
316103
+ if (params && Object.keys(params).length > 0) {
316104
+ argsArray.push(params);
316105
+ }
316106
+ return argsArray;
316107
+ }
316108
+ async function handleTreasuryDiscoveryCall(broker, functionName, args, params) {
316109
+ const discoveryBroker = broker;
316110
+ if (functionName === "fetchMarkets") {
316111
+ if (typeof discoveryBroker.fetchMarkets === "function" && discoveryBroker.has?.fetchMarkets !== false) {
316112
+ return {
316113
+ handled: true,
316114
+ result: await discoveryBroker.fetchMarkets(...callArgs(args, params))
316115
+ };
316116
+ }
316117
+ if (typeof discoveryBroker.loadMarkets === "function") {
316118
+ const loaded = await discoveryBroker.loadMarkets(false, params);
316119
+ const markets = loaded && typeof loaded === "object" && !Array.isArray(loaded) ? Object.values(loaded) : Object.values(discoveryBroker.markets ?? {});
316120
+ return { handled: true, result: markets };
316121
+ }
316122
+ throw new Error("venue_discovery_unavailable: fetchMarkets unavailable on broker");
316123
+ }
316124
+ if (functionName === "fetchCurrencies") {
316125
+ if (typeof discoveryBroker.fetchCurrencies === "function" && discoveryBroker.has?.fetchCurrencies !== false) {
316126
+ return {
316127
+ handled: true,
316128
+ result: await discoveryBroker.fetchCurrencies(...callArgs(args, params))
316129
+ };
316130
+ }
316131
+ if (discoveryBroker.currencies && Object.keys(discoveryBroker.currencies).length > 0) {
316132
+ return { handled: true, result: discoveryBroker.currencies };
316133
+ }
316134
+ throw new Error("venue_discovery_unavailable: fetchCurrencies unavailable on broker");
316135
+ }
316136
+ return { handled: false };
316137
+ }
316138
+ async function fetchCurrencyMetadata(broker, assetCode) {
316139
+ const discoveryBroker = broker;
316140
+ const normalizedAsset = assetCode.trim().toUpperCase();
316141
+ if (typeof discoveryBroker.fetchCurrencies === "function" && discoveryBroker.has?.fetchCurrencies !== false) {
316142
+ const currencies = await discoveryBroker.fetchCurrencies();
316143
+ return currencies[normalizedAsset];
316144
+ }
316145
+ return discoveryBroker.currencies?.[normalizedAsset];
316146
+ }
316147
+
316148
+ // src/helpers/transfer-network.ts
316149
+ function networkAliasSet(brokerNetworkId, networkKey) {
316150
+ const aliases = new Set([
316151
+ brokerNetworkId,
316152
+ networkKey.trim().toUpperCase()
316153
+ ]);
316154
+ if (brokerNetworkId === "BNB") {
316155
+ aliases.add("BNB");
316156
+ aliases.add("BSC");
316157
+ aliases.add("BEP20");
316158
+ }
316159
+ return [...aliases].filter((alias) => alias.length > 0);
316160
+ }
316161
+ function buildTransferNetworkEvidence(currencyInfo) {
316162
+ const rawNetworks = isRecord(currencyInfo.networks) ? currencyInfo.networks : {};
316163
+ const networks = {};
316164
+ const aliases = {};
316165
+ for (const [networkKey, networkValue] of Object.entries(rawNetworks)) {
316166
+ const networkRecord = isRecord(networkValue) ? networkValue : {};
316167
+ const exchangeNetworkId = String(networkRecord.id ?? networkRecord.network ?? networkKey);
316168
+ const brokerNetworkId = normalizeBrokerNetworkId(String(networkRecord.network ?? networkKey));
316169
+ const evidence = {
316170
+ operatorAlias: networkKey,
316171
+ brokerNetworkId,
316172
+ exchangeNetworkId,
316173
+ networkKey
316174
+ };
316175
+ networks[networkKey] = {
316176
+ ...networkRecord,
316177
+ operatorAlias: networkKey,
316178
+ brokerNetworkId,
316179
+ exchangeNetworkId
316180
+ };
316181
+ for (const alias of networkAliasSet(brokerNetworkId, networkKey)) {
316182
+ aliases[alias] = { ...evidence, operatorAlias: alias };
316183
+ }
316184
+ }
316185
+ return { networks, aliases };
316186
+ }
316187
+ async function resolveTransferNetwork(broker, assetCode, operatorAlias) {
316188
+ const requestedAlias = operatorAlias.trim().toUpperCase();
316189
+ const brokerNetworkId = normalizeBrokerNetworkId(requestedAlias);
316190
+ let currencyInfo = null;
316191
+ try {
316192
+ currencyInfo = await fetchCurrencyMetadata(broker, assetCode);
316193
+ } catch (error) {
316194
+ safeLogError(`Network discovery failed for ${assetCode}/${operatorAlias}; using operator alias as exchange network id`, error);
316195
+ }
316196
+ if (currencyInfo) {
316197
+ const evidence = buildTransferNetworkEvidence(currencyInfo);
316198
+ const resolved = evidence.aliases[requestedAlias] ?? evidence.aliases[brokerNetworkId];
316199
+ if (resolved) {
316200
+ return { ...resolved, operatorAlias: requestedAlias, brokerNetworkId };
316201
+ }
316202
+ throw new Error(`network_alias_unresolved: ${assetCode}/${requestedAlias} is not available in discovered transfer networks`);
316203
+ }
316204
+ return {
316205
+ operatorAlias: requestedAlias,
316206
+ brokerNetworkId,
316207
+ exchangeNetworkId: requestedAlias,
316208
+ networkKey: null
316209
+ };
316210
+ }
316211
+
316212
+ // node_modules/zod/v4/classic/external.js
316213
+ var exports_external = {};
316214
+ __export(exports_external, {
316215
+ xor: () => xor,
316216
+ xid: () => xid2,
316217
+ void: () => _void2,
315432
316218
  uuidv7: () => uuidv7,
315433
316219
  uuidv6: () => uuidv6,
315434
316220
  uuidv4: () => uuidv4,
@@ -329684,240 +330470,31 @@ async function handleInternalTransfer(ctx) {
329684
330470
  result: JSON.stringify(result)
329685
330471
  });
329686
330472
  } catch (error48) {
329687
- safeLogError("InternalTransfer failed", error48);
329688
- if (error48 instanceof BrokerAccountPreconditionError) {
329689
- return ctx.wrappedCallback({
329690
- code: grpc5.status.FAILED_PRECONDITION,
329691
- message: getErrorMessage(error48)
329692
- }, null);
329693
- }
329694
- const msg = getErrorMessage(error48);
329695
- let code;
329696
- if (msg.includes("Unsupported transfer direction")) {
329697
- code = grpc5.status.INVALID_ARGUMENT;
329698
- } else if (msg.includes("unavailable in this CCXT build")) {
329699
- code = grpc5.status.UNIMPLEMENTED;
329700
- } else {
329701
- code = mapCcxtErrorToGrpcStatus(error48) ?? grpc5.status.INTERNAL;
329702
- }
329703
- ctx.wrappedCallback({
329704
- code,
329705
- message: `InternalTransfer failed: ${msg}`
329706
- }, null);
329707
- }
329708
- }
329709
-
329710
- // src/handlers/execute-action/orders.ts
329711
- var grpc6 = __toESM(require_src3(), 1);
329712
-
329713
- // src/helpers/order-telemetry.ts
329714
- var NUMERIC_METRICS = [
329715
- ["requestedQuantity", "cex_market_action_requested_quantity"],
329716
- ["requestedNotional", "cex_market_action_requested_notional"],
329717
- ["executedBaseQuantity", "cex_market_action_executed_base_quantity"],
329718
- ["executedQuoteQuantity", "cex_market_action_executed_quote_quantity"],
329719
- ["averageExecutionPrice", "cex_market_action_average_execution_price"],
329720
- ["filledAmount", "cex_market_action_filled_amount"],
329721
- ["remainingAmount", "cex_market_action_remaining_amount"],
329722
- ["feeAmount", "cex_market_action_fee_amount"],
329723
- ["feeRate", "cex_market_action_fee_rate"]
329724
- ];
329725
- var REDACTED_ERROR_MESSAGE = "redacted_error";
329726
- async function emitOrderExecutionTelemetry(otelMetrics, context2, order, error48) {
329727
- try {
329728
- const telemetry = buildOrderExecutionTelemetry(context2, order, error48);
329729
- log.info("CEX market action execution telemetry", telemetry);
329730
- const labels = {
329731
- action: telemetry.action,
329732
- cex: telemetry.cex,
329733
- account: telemetry.accountLabel,
329734
- symbol: telemetry.symbol,
329735
- side: telemetry.side,
329736
- order_type: telemetry.orderType,
329737
- status: telemetry.status
329738
- };
329739
- await otelMetrics?.recordCounter("cex_market_action_executions_total", 1, {
329740
- ...labels,
329741
- result: error48 ? "error" : "ok"
329742
- });
329743
- for (const [key, metricName] of NUMERIC_METRICS) {
329744
- const value = telemetry[key];
329745
- if (typeof value === "number" && Number.isFinite(value)) {
329746
- await otelMetrics?.recordHistogram(metricName, value, labels);
329747
- }
329748
- }
329749
- return telemetry;
329750
- } catch (telemetryError) {
329751
- try {
329752
- log.error("Failed to emit CEX order telemetry", {
329753
- error: telemetryError
329754
- });
329755
- } catch {}
329756
- return;
329757
- }
329758
- }
329759
- function buildOrderExecutionTelemetry(context2, order, error48) {
329760
- const record2 = asRecord(order);
329761
- const info = asRecord(record2?.info);
329762
- const fees = getFees(record2, info);
329763
- const fee = summarizeFees(fees);
329764
- const executedBaseQuantity = firstNumber(record2?.filled, info?.executedQty, info?.cumExecQty) ?? computeFilledFromAmount(record2);
329765
- const executedQuoteQuantity = firstNumber(record2?.cost, info?.cummulativeQuoteQty, info?.cumQuote, info?.cumExecValue);
329766
- const averageExecutionPrice = firstNumber(record2?.average, info?.avgPrice) ?? computeAveragePrice(executedBaseQuantity, executedQuoteQuantity);
329767
- const exchangeTimestamp = normalizeTimestamp(firstValue(record2?.timestamp, info?.time, info?.transactTime, record2?.datetime));
329768
- const status6 = firstString(record2?.status, info?.status) ?? (error48 ? "failed" : "unknown");
329769
- const errorRecord = error48 instanceof Error ? error48 : undefined;
329770
- return compactUndefined({
329771
- event: "cex_market_action_execution",
329772
- action: context2.action,
329773
- cex: context2.cex.trim().toLowerCase() || "unknown",
329774
- accountLabel: context2.accountLabel ?? "unknown",
329775
- symbol: firstString(record2?.symbol, info?.symbol, context2.symbol) ?? "unknown",
329776
- side: firstString(record2?.side, info?.side, context2.side) ?? "unknown",
329777
- orderType: firstString(record2?.type, info?.type, context2.orderType) ?? "unknown",
329778
- orderId: firstString(record2?.id, info?.orderId, info?.orderID),
329779
- clientOrderId: firstString(context2.clientOrderId, record2?.clientOrderId, record2?.clientOrderID, record2?.clientOid, info?.clientOrderId, info?.clientOrderID, info?.clientOid),
329780
- idempotencyId: context2.idempotencyId,
329781
- makerActionId: context2.makerActionId,
329782
- status: status6.toLowerCase(),
329783
- requestedQuantity: context2.requestedQuantity ?? firstNumber(record2?.amount),
329784
- requestedNotional: context2.requestedNotional,
329785
- executedBaseQuantity,
329786
- executedQuoteQuantity,
329787
- averageExecutionPrice,
329788
- filledAmount: firstNumber(record2?.filled, info?.executedQty),
329789
- remainingAmount: firstNumber(record2?.remaining, info?.remainingQty),
329790
- feeAmount: fee.amount,
329791
- feeCurrency: fee.currency,
329792
- feeRate: fee.rate,
329793
- exchangeTimestamp,
329794
- brokerObservedTimestamp: context2.brokerObservedTimestamp ?? new Date().toISOString(),
329795
- errorType: errorRecord?.name,
329796
- errorMessage: errorRecord ? REDACTED_ERROR_MESSAGE : undefined
329797
- });
329798
- }
329799
- function emitOrderExecutionTelemetryInBackground(otelMetrics, context2, order, error48) {
329800
- emitOrderExecutionTelemetry(otelMetrics, context2, order, error48).catch((telemetryError) => {
329801
- try {
329802
- log.warn("Telemetry emit failed", { error: telemetryError });
329803
- } catch {
329804
- console.warn("Telemetry emit failed", telemetryError);
329805
- }
329806
- });
329807
- }
329808
- function extractOrderTelemetryIds(params) {
329809
- const record2 = params ?? {};
329810
- return {
329811
- clientOrderId: firstString(record2.clientOrderId, record2.clientOrderID, record2.newClientOrderId, record2.clientOid),
329812
- idempotencyId: firstString(record2.idempotencyId, record2.idempotencyID, record2.idempotencyKey, record2.requestId, record2.requestID),
329813
- makerActionId: firstString(record2.makerActionId, record2.maker_action_id, record2.actionId, record2.action_id)
329814
- };
329815
- }
329816
- function firstValue(...values2) {
329817
- return values2.find((value) => value !== undefined && value !== null);
329818
- }
329819
- function firstString(...values2) {
329820
- for (const value of values2) {
329821
- if (typeof value === "string" && value.trim()) {
329822
- return value.trim();
329823
- }
329824
- if (typeof value === "number" && Number.isFinite(value)) {
329825
- return String(value);
329826
- }
329827
- }
329828
- return;
329829
- }
329830
- function firstNumber(...values2) {
329831
- for (const value of values2) {
329832
- const numberValue = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
329833
- if (Number.isFinite(numberValue)) {
329834
- return numberValue;
329835
- }
329836
- }
329837
- return;
329838
- }
329839
- function computeFilledFromAmount(record2) {
329840
- const amount = firstNumber(record2?.amount);
329841
- const remaining = firstNumber(record2?.remaining);
329842
- if (amount === undefined || remaining === undefined) {
329843
- return;
329844
- }
329845
- return amount - remaining;
329846
- }
329847
- function computeAveragePrice(executedBaseQuantity, executedQuoteQuantity) {
329848
- if (executedBaseQuantity === undefined || executedQuoteQuantity === undefined || executedBaseQuantity === 0) {
329849
- return;
329850
- }
329851
- return executedQuoteQuantity / executedBaseQuantity;
329852
- }
329853
- function normalizeTimestamp(value) {
329854
- if (typeof value === "string" && value.trim()) {
329855
- return value.trim();
329856
- }
329857
- const timestamp = firstNumber(value);
329858
- if (timestamp === undefined) {
329859
- return;
329860
- }
329861
- const date5 = new Date(timestamp);
329862
- return Number.isNaN(date5.getTime()) ? undefined : date5.toISOString();
329863
- }
329864
- function getFees(record2, info) {
329865
- const fees = [];
329866
- if (record2?.fee)
329867
- fees.push(record2.fee);
329868
- if (Array.isArray(record2?.fees))
329869
- fees.push(...record2.fees);
329870
- if (Array.isArray(record2?.trades)) {
329871
- for (const trade of record2.trades) {
329872
- const tradeRecord = asRecord(trade);
329873
- if (tradeRecord?.fee)
329874
- fees.push(tradeRecord.fee);
329875
- if (Array.isArray(tradeRecord?.fees))
329876
- fees.push(...tradeRecord.fees);
329877
- }
329878
- }
329879
- if (Array.isArray(info?.fills)) {
329880
- for (const fill of info.fills) {
329881
- const fillRecord = asRecord(fill);
329882
- const commission = firstNumber(fillRecord?.commission);
329883
- if (commission !== undefined) {
329884
- fees.push({
329885
- cost: commission,
329886
- currency: firstString(fillRecord?.commissionAsset)
329887
- });
329888
- }
329889
- }
329890
- }
329891
- return fees;
329892
- }
329893
- function summarizeFees(fees) {
329894
- let amount = 0;
329895
- let amountFound = false;
329896
- let currency;
329897
- let rate;
329898
- for (const rawFee of fees) {
329899
- const fee = asRecord(rawFee);
329900
- if (!fee)
329901
- continue;
329902
- const cost = firstNumber(fee.cost, fee.amount);
329903
- if (cost !== undefined) {
329904
- amount += cost;
329905
- amountFound = true;
330473
+ safeLogError("InternalTransfer failed", error48);
330474
+ if (error48 instanceof BrokerAccountPreconditionError) {
330475
+ return ctx.wrappedCallback({
330476
+ code: grpc5.status.FAILED_PRECONDITION,
330477
+ message: getErrorMessage(error48)
330478
+ }, null);
329906
330479
  }
329907
- currency ??= firstString(fee.currency);
329908
- rate ??= firstNumber(fee.rate);
330480
+ const msg = getErrorMessage(error48);
330481
+ let code;
330482
+ if (msg.includes("Unsupported transfer direction")) {
330483
+ code = grpc5.status.INVALID_ARGUMENT;
330484
+ } else if (msg.includes("unavailable in this CCXT build")) {
330485
+ code = grpc5.status.UNIMPLEMENTED;
330486
+ } else {
330487
+ code = mapCcxtErrorToGrpcStatus(error48) ?? grpc5.status.INTERNAL;
330488
+ }
330489
+ ctx.wrappedCallback({
330490
+ code,
330491
+ message: `InternalTransfer failed: ${msg}`
330492
+ }, null);
329909
330493
  }
329910
- return {
329911
- amount: amountFound ? amount : undefined,
329912
- currency,
329913
- rate
329914
- };
329915
- }
329916
- function compactUndefined(record2) {
329917
- return Object.fromEntries(Object.entries(record2).filter(([, value]) => value !== undefined));
329918
330494
  }
329919
330495
 
329920
330496
  // src/handlers/execute-action/orders.ts
330497
+ var grpc6 = __toESM(require_src3(), 1);
329921
330498
  async function handleCreateOrder(ctx) {
329922
330499
  const {
329923
330500
  call,
@@ -329934,7 +330511,8 @@ async function handleCreateOrder(ctx) {
329934
330511
  applyVerityToBroker,
329935
330512
  useVerity,
329936
330513
  verityProverUrl,
329937
- otelMetrics
330514
+ otelMetrics,
330515
+ brokerArchiver
329938
330516
  } = ctx;
329939
330517
  const verityProof = verity.proof;
329940
330518
  const orderValue = parsePayloadForAction(ctx, CreateOrderPayloadSchema);
@@ -329960,8 +330538,18 @@ async function handleCreateOrder(ctx) {
329960
330538
  side: resolution.side,
329961
330539
  requestedQuantity: resolution.amountBase ?? orderValue.amount
329962
330540
  };
330541
+ const telemetryIds = extractOrderTelemetryIds(orderValue.params);
330542
+ const submissionTimestamp = new Date().toISOString();
330543
+ const marketMetadataHash = await captureMarketMetadataSnapshot(brokerArchiver, broker, {
330544
+ exchange: cex3,
330545
+ accountSelector: selectedBrokerAccount?.label,
330546
+ symbol: resolution.symbol,
330547
+ action: "CreateOrder",
330548
+ brokerObservedTimestamp: submissionTimestamp,
330549
+ ...telemetryIds
330550
+ });
329963
330551
  const order = await broker.createOrder(resolution.symbol, orderValue.orderType, resolution.side, resolution.amountBase ?? orderValue.amount, orderValue.price, orderValue.params ?? {});
329964
- emitOrderExecutionTelemetryInBackground(otelMetrics, {
330552
+ const createOrderContext = {
329965
330553
  action: "CreateOrder",
329966
330554
  cex: cex3,
329967
330555
  accountLabel: selectedBrokerAccount?.label,
@@ -329970,12 +330558,15 @@ async function handleCreateOrder(ctx) {
329970
330558
  orderType: orderValue.orderType,
329971
330559
  requestedQuantity: resolvedOrderTelemetry.requestedQuantity,
329972
330560
  requestedNotional: orderValue.amount * orderValue.price,
329973
- ...extractOrderTelemetryIds(orderValue.params)
329974
- }, order);
330561
+ brokerObservedTimestamp: submissionTimestamp,
330562
+ ...telemetryIds
330563
+ };
330564
+ emitOrderExecutionTelemetryInBackground(otelMetrics, createOrderContext, order);
330565
+ archiveOrderExecutionInBackground(brokerArchiver, createOrderContext, order, undefined, { marketMetadataHash });
329975
330566
  ctx.wrappedCallback(null, { result: JSON.stringify({ ...order }) });
329976
330567
  } catch (error48) {
329977
330568
  safeLogError("Order Creation failed", error48);
329978
- emitOrderExecutionTelemetryInBackground(otelMetrics, {
330569
+ const failedCreateContext = {
329979
330570
  action: "CreateOrder",
329980
330571
  cex: cex3,
329981
330572
  accountLabel: selectedBrokerAccount?.label,
@@ -329985,7 +330576,9 @@ async function handleCreateOrder(ctx) {
329985
330576
  requestedQuantity: resolvedOrderTelemetry.requestedQuantity ?? orderValue.amount,
329986
330577
  requestedNotional: orderValue.amount * orderValue.price,
329987
330578
  ...extractOrderTelemetryIds(orderValue.params)
329988
- }, undefined, error48);
330579
+ };
330580
+ emitOrderExecutionTelemetryInBackground(otelMetrics, failedCreateContext, undefined, error48);
330581
+ archiveOrderExecutionInBackground(brokerArchiver, failedCreateContext, undefined, error48);
329989
330582
  ctx.wrappedCallback({
329990
330583
  code: grpc6.status.INTERNAL,
329991
330584
  message: "Order Creation failed"
@@ -330008,7 +330601,8 @@ async function handleGetOrderDetails(ctx) {
330008
330601
  applyVerityToBroker,
330009
330602
  useVerity,
330010
330603
  verityProverUrl,
330011
- otelMetrics
330604
+ otelMetrics,
330605
+ brokerArchiver
330012
330606
  } = ctx;
330013
330607
  const verityProof = verity.proof;
330014
330608
  const getOrderValue = parsePayloadForAction(ctx, GetOrderDetailsPayloadSchema);
@@ -330022,13 +330616,15 @@ async function handleGetOrderDetails(ctx) {
330022
330616
  }, null);
330023
330617
  }
330024
330618
  const orderDetails = await broker.fetchOrder(getOrderValue.orderId, symbol2, { ...getOrderValue.params });
330025
- emitOrderExecutionTelemetryInBackground(otelMetrics, {
330619
+ const getOrderContext = {
330026
330620
  action: "GetOrderDetails",
330027
330621
  cex: cex3,
330028
330622
  accountLabel: selectedBrokerAccount?.label,
330029
330623
  symbol: symbol2,
330030
330624
  ...extractOrderTelemetryIds(getOrderValue.params)
330031
- }, orderDetails);
330625
+ };
330626
+ emitOrderExecutionTelemetryInBackground(otelMetrics, getOrderContext, orderDetails);
330627
+ archiveOrderExecutionInBackground(brokerArchiver, getOrderContext, orderDetails);
330032
330628
  ctx.wrappedCallback(null, {
330033
330629
  result: JSON.stringify({
330034
330630
  orderId: orderDetails.id,
@@ -330043,6 +330639,15 @@ async function handleGetOrderDetails(ctx) {
330043
330639
  });
330044
330640
  } catch (error48) {
330045
330641
  safeLogError(`Error fetching order details from ${cex3}`, error48);
330642
+ const failedGetOrderContext = {
330643
+ action: "GetOrderDetails",
330644
+ cex: cex3,
330645
+ accountLabel: selectedBrokerAccount?.label,
330646
+ symbol: symbol2,
330647
+ ...extractOrderTelemetryIds(getOrderValue.params)
330648
+ };
330649
+ emitOrderExecutionTelemetryInBackground(otelMetrics, failedGetOrderContext, undefined, error48);
330650
+ archiveOrderExecutionInBackground(brokerArchiver, failedGetOrderContext, undefined, error48);
330046
330651
  ctx.wrappedCallback({
330047
330652
  code: grpc6.status.INTERNAL,
330048
330653
  message: `Failed to fetch order details from ${cex3}`
@@ -330065,19 +330670,44 @@ async function handleCancelOrder(ctx) {
330065
330670
  applyVerityToBroker,
330066
330671
  useVerity,
330067
330672
  verityProverUrl,
330068
- otelMetrics
330673
+ otelMetrics,
330674
+ brokerArchiver
330069
330675
  } = ctx;
330070
330676
  const verityProof = verity.proof;
330071
330677
  const cancelOrderValue = parsePayloadForAction(ctx, CancelOrderPayloadSchema);
330072
330678
  if (cancelOrderValue === null)
330073
330679
  return;
330074
330680
  try {
330681
+ if (!broker) {
330682
+ return ctx.wrappedCallback({
330683
+ code: grpc6.status.INVALID_ARGUMENT,
330684
+ message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
330685
+ }, null);
330686
+ }
330687
+ const cancelOrderContext = {
330688
+ action: "CancelOrder",
330689
+ cex: cex3,
330690
+ accountLabel: selectedBrokerAccount?.label,
330691
+ symbol: symbol2,
330692
+ ...extractOrderTelemetryIds(cancelOrderValue.params)
330693
+ };
330075
330694
  const cancelledOrder = await broker.cancelOrder(cancelOrderValue.orderId, symbol2, cancelOrderValue.params ?? {});
330695
+ emitOrderExecutionTelemetryInBackground(otelMetrics, cancelOrderContext, cancelledOrder);
330696
+ archiveOrderExecutionInBackground(brokerArchiver, cancelOrderContext, cancelledOrder);
330076
330697
  ctx.wrappedCallback(null, {
330077
330698
  result: JSON.stringify({ ...cancelledOrder })
330078
330699
  });
330079
330700
  } catch (error48) {
330080
330701
  safeLogError(`Error cancelling order from ${cex3}`, error48);
330702
+ const failedCancelContext = {
330703
+ action: "CancelOrder",
330704
+ cex: cex3,
330705
+ accountLabel: selectedBrokerAccount?.label,
330706
+ symbol: symbol2,
330707
+ ...extractOrderTelemetryIds(cancelOrderValue.params)
330708
+ };
330709
+ emitOrderExecutionTelemetryInBackground(otelMetrics, failedCancelContext, undefined, error48);
330710
+ archiveOrderExecutionInBackground(brokerArchiver, failedCancelContext, undefined, error48);
330081
330711
  ctx.wrappedCallback({
330082
330712
  code: grpc6.status.INTERNAL,
330083
330713
  message: `Failed to cancel order from ${cex3}`
@@ -330454,31 +331084,229 @@ async function handleFetchBalances(ctx) {
330454
331084
  const partial2 = await broker.fetchTotalBalance(params);
330455
331085
  responseBalances = partial2 ?? {};
330456
331086
  }
330457
- if (symbol2) {
330458
- if (typeof responseBalances[symbol2] === "number") {
330459
- responseBalances = {
330460
- [symbol2]: responseBalances[symbol2] ?? 0
330461
- };
330462
- } else {
330463
- responseBalances = {};
330464
- }
331087
+ if (symbol2) {
331088
+ if (typeof responseBalances[symbol2] === "number") {
331089
+ responseBalances = {
331090
+ [symbol2]: responseBalances[symbol2] ?? 0
331091
+ };
331092
+ } else {
331093
+ responseBalances = {};
331094
+ }
331095
+ }
331096
+ ctx.wrappedCallback(null, {
331097
+ proof: ctx.verity.proof,
331098
+ result: JSON.stringify({
331099
+ balances: responseBalances,
331100
+ balanceType
331101
+ })
331102
+ });
331103
+ } catch (error48) {
331104
+ safeLogError(`Error fetching balance from ${cex3}`, error48);
331105
+ ctx.wrappedCallback({
331106
+ code: grpc7.status.INTERNAL,
331107
+ message: `Failed to fetch balance from ${cex3}`
331108
+ }, null);
331109
+ }
331110
+ }
331111
+ async function handleFetchTicker(ctx) {
331112
+ const {
331113
+ call,
331114
+ wrappedCallback,
331115
+ policy,
331116
+ brokers,
331117
+ metadata,
331118
+ normalizedCex,
331119
+ cex: cex3,
331120
+ symbol: symbol2,
331121
+ selectedBrokerAccount,
331122
+ broker,
331123
+ verity,
331124
+ applyVerityToBroker,
331125
+ useVerity,
331126
+ verityProverUrl,
331127
+ otelMetrics
331128
+ } = ctx;
331129
+ const verityProof = verity.proof;
331130
+ if (!symbol2) {
331131
+ return ctx.wrappedCallback({
331132
+ code: grpc7.status.INVALID_ARGUMENT,
331133
+ message: `ValidationError: Symbol required`
331134
+ }, null);
331135
+ }
331136
+ try {
331137
+ const ticker = await broker.fetchTicker(symbol2);
331138
+ ctx.wrappedCallback(null, {
331139
+ proof: ctx.verity.proof,
331140
+ result: JSON.stringify(ticker)
331141
+ });
331142
+ } catch (error48) {
331143
+ safeLogError(`Error fetching ticker from ${cex3}`, error48);
331144
+ ctx.wrappedCallback({
331145
+ code: grpc7.status.INTERNAL,
331146
+ message: `Failed to fetch ticker from ${cex3}`
331147
+ }, null);
331148
+ }
331149
+ }
331150
+ async function handlePassThrough(ctx) {
331151
+ if (ctx.action === Action.FetchCurrency)
331152
+ return handleFetchCurrency(ctx);
331153
+ if (ctx.action === Action.FetchAccountId)
331154
+ return handleFetchAccountId(ctx);
331155
+ if (ctx.action === Action.FetchFees)
331156
+ return handleFetchFees(ctx);
331157
+ if (ctx.action === Action.FetchDepositAddresses)
331158
+ return handleFetchDepositAddresses(ctx);
331159
+ if (ctx.action === Action.FetchBalances)
331160
+ return handleFetchBalances(ctx);
331161
+ if (ctx.action === Action.FetchTicker)
331162
+ return handleFetchTicker(ctx);
331163
+ }
331164
+
331165
+ // src/handlers/execute-action/perp-config.ts
331166
+ var grpc8 = __toESM(require_src3(), 1);
331167
+ function exchangeSupports(broker, capability) {
331168
+ return broker.has?.[capability] === true;
331169
+ }
331170
+ function extractPerpConfigs(positions) {
331171
+ return positions.map((position) => ({
331172
+ symbol: typeof position.symbol === "string" ? position.symbol : undefined,
331173
+ leverage: typeof position.leverage === "number" ? position.leverage : undefined,
331174
+ marginMode: typeof position.marginMode === "string" ? position.marginMode : undefined
331175
+ }));
331176
+ }
331177
+ async function handleGetPerpConfigState(ctx) {
331178
+ const { wrappedCallback, cex: cex3, normalizedCex, broker } = ctx;
331179
+ const payload = parsePayloadForAction(ctx, GetPerpConfigStatePayloadSchema);
331180
+ if (payload === null) {
331181
+ return;
331182
+ }
331183
+ if (!broker) {
331184
+ return wrappedCallback({
331185
+ code: grpc8.status.INVALID_ARGUMENT,
331186
+ message: `Invalid CEX key: ${cex3}`
331187
+ }, null);
331188
+ }
331189
+ const exchange = broker;
331190
+ if (!exchangeSupports(exchange, "fetchPositions")) {
331191
+ return wrappedCallback({
331192
+ code: grpc8.status.UNIMPLEMENTED,
331193
+ message: `${normalizedCex} does not support fetchPositions`
331194
+ }, null);
331195
+ }
331196
+ try {
331197
+ const symbols = payload.symbol ? [payload.symbol] : undefined;
331198
+ const positions = await exchange.fetchPositions?.(symbols, payload.params);
331199
+ ctx.wrappedCallback(null, {
331200
+ result: JSON.stringify({
331201
+ exchange: normalizedCex,
331202
+ configs: extractPerpConfigs(positions ?? []),
331203
+ positions: positions ?? []
331204
+ })
331205
+ });
331206
+ } catch (error48) {
331207
+ safeLogError(`GetPerpConfigState failed for ${cex3}`, error48);
331208
+ ctx.wrappedCallback({
331209
+ code: grpc8.status.INTERNAL,
331210
+ message: getErrorMessage(error48)
331211
+ }, null);
331212
+ }
331213
+ }
331214
+ async function handleSetPerpConfigState(ctx) {
331215
+ const { wrappedCallback, cex: cex3, normalizedCex, broker } = ctx;
331216
+ const payload = parsePayloadForAction(ctx, SetPerpConfigStatePayloadSchema);
331217
+ if (payload === null) {
331218
+ return;
331219
+ }
331220
+ if (!broker) {
331221
+ return wrappedCallback({
331222
+ code: grpc8.status.INVALID_ARGUMENT,
331223
+ message: `Invalid CEX key: ${cex3}`
331224
+ }, null);
331225
+ }
331226
+ const exchange = broker;
331227
+ if (!exchangeSupports(exchange, "setLeverage")) {
331228
+ return wrappedCallback({
331229
+ code: grpc8.status.UNIMPLEMENTED,
331230
+ message: `${normalizedCex} does not support setLeverage`
331231
+ }, null);
331232
+ }
331233
+ try {
331234
+ const response = await exchange.setLeverage?.(payload.leverage, payload.symbol, {
331235
+ marginMode: payload.marginMode ?? "cross",
331236
+ ...payload.params
331237
+ });
331238
+ ctx.wrappedCallback(null, {
331239
+ result: JSON.stringify({
331240
+ exchange: normalizedCex,
331241
+ symbol: payload.symbol,
331242
+ leverage: payload.leverage,
331243
+ marginMode: payload.marginMode ?? "cross",
331244
+ response
331245
+ })
331246
+ });
331247
+ } catch (error48) {
331248
+ safeLogError(`SetPerpConfigState failed for ${cex3}`, error48);
331249
+ ctx.wrappedCallback({
331250
+ code: grpc8.status.INTERNAL,
331251
+ message: getErrorMessage(error48)
331252
+ }, null);
331253
+ }
331254
+ }
331255
+ async function handlePerpConfig(ctx) {
331256
+ if (ctx.action === Action.GetPerpConfigState) {
331257
+ return handleGetPerpConfigState(ctx);
331258
+ }
331259
+ if (ctx.action === Action.SetPerpConfigState) {
331260
+ return handleSetPerpConfigState(ctx);
331261
+ }
331262
+ }
331263
+
331264
+ // src/handlers/execute-action/treasury-call.ts
331265
+ var grpc9 = __toESM(require_src3(), 1);
331266
+ async function handleTreasuryCall(ctx) {
331267
+ const { broker } = ctx;
331268
+ const callValue = parsePayloadForAction(ctx, CallPayloadSchema);
331269
+ if (callValue === null)
331270
+ return;
331271
+ try {
331272
+ if (callValue.functionName.startsWith("_") || callValue.functionName.includes("constructor") || callValue.functionName.includes("prototype")) {
331273
+ return ctx.wrappedCallback({
331274
+ code: grpc9.status.PERMISSION_DENIED,
331275
+ message: "Access to the requested function is denied"
331276
+ }, null);
331277
+ }
331278
+ const argsArray = callArgs(callValue.args, callValue.params ?? {});
331279
+ const treasuryDiscovery = await handleTreasuryDiscoveryCall(broker, callValue.functionName, callValue.args, callValue.params ?? {});
331280
+ if (treasuryDiscovery.handled) {
331281
+ return ctx.wrappedCallback(null, {
331282
+ proof: ctx.verity.proof,
331283
+ result: JSON.stringify(treasuryDiscovery.result)
331284
+ });
331285
+ }
331286
+ const fn = broker[callValue.functionName];
331287
+ if (typeof fn !== "function" || broker.has?.[callValue.functionName] === false) {
331288
+ return ctx.wrappedCallback({
331289
+ code: grpc9.status.INVALID_ARGUMENT,
331290
+ message: `Function not found on broker: ${callValue.functionName}`
331291
+ }, null);
330465
331292
  }
331293
+ const result = await fn.apply(broker, argsArray);
330466
331294
  ctx.wrappedCallback(null, {
330467
331295
  proof: ctx.verity.proof,
330468
- result: JSON.stringify({
330469
- balances: responseBalances,
330470
- balanceType
330471
- })
331296
+ result: JSON.stringify(result)
330472
331297
  });
330473
331298
  } catch (error48) {
330474
- safeLogError(`Error fetching balance from ${cex3}`, error48);
330475
- ctx.wrappedCallback({
330476
- code: grpc7.status.INTERNAL,
330477
- message: `Failed to fetch balance from ${cex3}`
330478
- }, null);
331299
+ safeLogError("Call failed", error48);
331300
+ rejectWithGrpcError(ctx, error48, {
331301
+ message: getErrorMessage(error48),
331302
+ preferStableMessageOnly: true
331303
+ });
330479
331304
  }
330480
331305
  }
330481
- async function handleFetchTicker(ctx) {
331306
+
331307
+ // src/handlers/execute-action/withdraw.ts
331308
+ var grpc10 = __toESM(require_src3(), 1);
331309
+ async function handleWithdraw(ctx) {
330482
331310
  const {
330483
331311
  call,
330484
331312
  wrappedCallback,
@@ -330499,671 +331327,1117 @@ async function handleFetchTicker(ctx) {
330499
331327
  const verityProof = verity.proof;
330500
331328
  if (!symbol2) {
330501
331329
  return ctx.wrappedCallback({
330502
- code: grpc7.status.INVALID_ARGUMENT,
331330
+ code: grpc10.status.INVALID_ARGUMENT,
330503
331331
  message: `ValidationError: Symbol required`
330504
331332
  }, null);
330505
331333
  }
331334
+ const transferValue = parsePayloadForAction(ctx, WithdrawPayloadSchema);
331335
+ if (transferValue === null)
331336
+ return;
331337
+ let withdrawNetwork;
330506
331338
  try {
330507
- const ticker = await broker.fetchTicker(symbol2);
331339
+ withdrawNetwork = await resolveTransferNetwork(broker, symbol2, transferValue.chain);
331340
+ } catch (error48) {
331341
+ const message = getErrorMessage(error48);
331342
+ return ctx.wrappedCallback({
331343
+ code: stableGrpcErrorCode(message) ?? grpc10.status.INVALID_ARGUMENT,
331344
+ message
331345
+ }, null);
331346
+ }
331347
+ const transferValidation = validateWithdraw(policy, cex3, withdrawNetwork.brokerNetworkId, transferValue.recipientAddress, transferValue.amount, symbol2);
331348
+ if (!transferValidation.valid) {
331349
+ return ctx.wrappedCallback({
331350
+ code: grpc10.status.PERMISSION_DENIED,
331351
+ message: `policy_withdrawal_denied: ${transferValidation.error}`
331352
+ }, null);
331353
+ }
331354
+ const travelRule = resolveTravelRuleDecision(policy, cex3, transferValue.recipientAddress);
331355
+ if (travelRule.mode === "denied") {
331356
+ return ctx.wrappedCallback({
331357
+ code: grpc10.status.FAILED_PRECONDITION,
331358
+ message: `travel_rule_denied: ${travelRule.error}`
331359
+ }, null);
331360
+ }
331361
+ try {
331362
+ const transaction = travelRule.mode === "localentity" ? await withdrawViaLocalEntity(broker, {
331363
+ code: symbol2,
331364
+ amount: transferValue.amount,
331365
+ address: transferValue.recipientAddress,
331366
+ network: withdrawNetwork.exchangeNetworkId,
331367
+ questionnaire: travelRule.questionnaire,
331368
+ params: transferValue.params
331369
+ }) : await broker.withdraw(symbol2, transferValue.amount, transferValue.recipientAddress, undefined, {
331370
+ ...transferValue.params ?? {},
331371
+ network: withdrawNetwork.exchangeNetworkId
331372
+ });
331373
+ log.info(`Withdraw Result: ${JSON.stringify(transaction)}`);
330508
331374
  ctx.wrappedCallback(null, {
330509
331375
  proof: ctx.verity.proof,
330510
- result: JSON.stringify(ticker)
331376
+ result: JSON.stringify({
331377
+ ...transaction,
331378
+ operatorAlias: withdrawNetwork.operatorAlias,
331379
+ brokerNetworkId: withdrawNetwork.brokerNetworkId,
331380
+ exchangeNetworkId: withdrawNetwork.exchangeNetworkId
331381
+ })
330511
331382
  });
330512
331383
  } catch (error48) {
330513
- safeLogError(`Error fetching ticker from ${cex3}`, error48);
331384
+ safeLogError("Withdraw failed", error48);
331385
+ const code = mapCcxtErrorToGrpcStatus(error48) ?? grpc10.status.INTERNAL;
330514
331386
  ctx.wrappedCallback({
330515
- code: grpc7.status.INTERNAL,
330516
- message: `Failed to fetch ticker from ${cex3}`
331387
+ code,
331388
+ message: `Withdraw failed: ${getErrorMessage(error48)}`
331389
+ }, null);
331390
+ }
331391
+ }
331392
+
331393
+ // src/handlers/execute-action/registry.ts
331394
+ var ACTION_HANDLERS = {
331395
+ [Action.Deposit]: handleDeposit,
331396
+ [Action.Withdraw]: handleWithdraw,
331397
+ [Action.Call]: handleTreasuryCall,
331398
+ [Action.InternalTransfer]: handleInternalTransfer,
331399
+ [Action.CreateOrder]: handleOrders,
331400
+ [Action.GetOrderDetails]: handleOrders,
331401
+ [Action.CancelOrder]: handleOrders,
331402
+ [Action.FetchCurrency]: handlePassThrough,
331403
+ [Action.FetchAccountId]: handlePassThrough,
331404
+ [Action.FetchFees]: handlePassThrough,
331405
+ [Action.FetchDepositAddresses]: handlePassThrough,
331406
+ [Action.FetchBalances]: handlePassThrough,
331407
+ [Action.FetchTicker]: handlePassThrough,
331408
+ [Action.GetPerpConfigState]: handlePerpConfig,
331409
+ [Action.SetPerpConfigState]: handlePerpConfig
331410
+ };
331411
+ async function dispatchExecuteAction(ctx) {
331412
+ const handler = ACTION_HANDLERS[ctx.action];
331413
+ if (!handler) {
331414
+ ctx.wrappedCallback({
331415
+ code: grpc11.status.INVALID_ARGUMENT,
331416
+ message: "Invalid Action"
330517
331417
  }, null);
331418
+ return;
331419
+ }
331420
+ await handler(ctx);
331421
+ }
331422
+
331423
+ // src/handlers/execute-action/handler.ts
331424
+ function createExecuteActionHandler(deps) {
331425
+ const {
331426
+ policy,
331427
+ brokers,
331428
+ whitelistIps,
331429
+ useVerity,
331430
+ verityProverUrl,
331431
+ otelMetrics,
331432
+ brokerArchiver
331433
+ } = deps;
331434
+ return async (call, callback) => {
331435
+ const startTime = Date.now();
331436
+ const { action: rawAction, cex: cex3, symbol: symbol2 } = call.request;
331437
+ const action = resolveAction(rawAction);
331438
+ let actionCompleted = false;
331439
+ const wrappedCallback = (error48, value) => {
331440
+ if (!actionCompleted) {
331441
+ actionCompleted = true;
331442
+ const latency = Date.now() - startTime;
331443
+ const actionName = getActionName(action);
331444
+ otelMetrics?.recordHistogram("execute_action_duration_ms", latency, {
331445
+ action: actionName,
331446
+ cex: cex3 || "unknown"
331447
+ });
331448
+ if (error48) {
331449
+ otelMetrics?.recordCounter("execute_action_errors_total", 1, {
331450
+ action: actionName,
331451
+ cex: cex3 || "unknown",
331452
+ error_type: error48.code ? grpc12.status[error48.code] || "unknown" : "unknown"
331453
+ });
331454
+ } else {
331455
+ otelMetrics?.recordCounter("execute_action_success_total", 1, {
331456
+ action: actionName,
331457
+ cex: cex3 || "unknown"
331458
+ });
331459
+ }
331460
+ }
331461
+ callback(error48, value);
331462
+ };
331463
+ try {
331464
+ log.info(`Request - ExecuteAction:`, { action, cex: cex3, symbol: symbol2 });
331465
+ otelMetrics?.recordCounter("execute_action_requests_total", 1, {
331466
+ action: getActionName(action),
331467
+ cex: cex3 || "unknown"
331468
+ });
331469
+ if (!authenticateRequest(call, whitelistIps)) {
331470
+ return wrappedCallback({
331471
+ code: grpc12.status.PERMISSION_DENIED,
331472
+ message: "Access denied: Unauthorized IP"
331473
+ }, null);
331474
+ }
331475
+ if (!action || !cex3) {
331476
+ return wrappedCallback({
331477
+ code: grpc12.status.INVALID_ARGUMENT,
331478
+ message: "`action` AND `cex` fields are required"
331479
+ }, null);
331480
+ }
331481
+ const normalizedCex = cex3.trim().toLowerCase();
331482
+ const metadata = call.metadata;
331483
+ const selectedBrokerAccount = selectBrokerAccountForCex(normalizedCex, brokers, metadata);
331484
+ const verity = { proof: "" };
331485
+ const applyVerityToBroker = (targetBroker) => {
331486
+ if (!useVerity)
331487
+ return;
331488
+ const override = buildHttpClientOverrideFromMetadata(metadata, verityProverUrl, (proof, notaryPubKey) => {
331489
+ verity.proof = proof;
331490
+ log.debug(`Verity proof:`, { proof, notaryPubKey });
331491
+ });
331492
+ targetBroker.setHttpClientOverride(override, verityHttpClientOverridePredicate);
331493
+ };
331494
+ const preludeCtx = {
331495
+ call,
331496
+ wrappedCallback,
331497
+ action,
331498
+ policy,
331499
+ brokers,
331500
+ metadata,
331501
+ normalizedCex,
331502
+ cex: cex3,
331503
+ symbol: symbol2,
331504
+ selectedBrokerAccount,
331505
+ broker: selectedBrokerAccount?.exchange ?? createBroker(normalizedCex, metadata),
331506
+ verity,
331507
+ applyVerityToBroker,
331508
+ useVerity,
331509
+ verityProverUrl,
331510
+ otelMetrics,
331511
+ brokerArchiver
331512
+ };
331513
+ if (action === Action.Call) {
331514
+ const handled = await handleOrderBookCall(preludeCtx);
331515
+ if (handled)
331516
+ return;
331517
+ }
331518
+ const broker = selectedBrokerAccount?.exchange ?? createBroker(normalizedCex, metadata);
331519
+ if (!broker) {
331520
+ return wrappedCallback({
331521
+ code: grpc12.status.UNAUTHENTICATED,
331522
+ message: `This Exchange is not registered and No API metadata was found`
331523
+ }, null);
331524
+ }
331525
+ applyVerityToBroker(broker);
331526
+ const ctx = { ...preludeCtx, broker };
331527
+ await dispatchExecuteAction(ctx);
331528
+ } catch (error48) {
331529
+ safeLogError("ExecuteAction unhandled error", error48);
331530
+ return wrappedCallback({
331531
+ code: grpc12.status.INTERNAL,
331532
+ message: "ExecuteAction failed unexpectedly"
331533
+ }, null);
331534
+ }
331535
+ };
331536
+ }
331537
+ // src/handlers/subscribe/handler.ts
331538
+ var grpc13 = __toESM(require_src3(), 1);
331539
+
331540
+ // src/helpers/binance-user-data-stream.ts
331541
+ import { Buffer as Buffer2 } from "node:buffer";
331542
+ import { createHmac } from "node:crypto";
331543
+ var BINANCE_SPOT_WS_API_URL = "wss://ws-api.binance.com:443/ws-api/v3";
331544
+ var DEFAULT_BINANCE_USER_DATA_MAX_BUFFERED_EVENTS = 16;
331545
+ var createWebSocket = (url3) => new wrapper_default(url3);
331546
+ function getExchangeString(exchange, key) {
331547
+ const value = exchange[key];
331548
+ if (typeof value !== "string" || value.length === 0) {
331549
+ throw new Error(`Binance user-data stream requires exchange.${key}`);
331550
+ }
331551
+ return value;
331552
+ }
331553
+ function sortedQuery(params) {
331554
+ return Object.entries(params).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`).join("&");
331555
+ }
331556
+ function signUserDataStreamParams(exchange, params) {
331557
+ const signParams = exchange.signParams;
331558
+ if (typeof signParams === "function") {
331559
+ return signParams.call(exchange, params);
330518
331560
  }
331561
+ const secret = getExchangeString(exchange, "secret");
331562
+ return {
331563
+ ...params,
331564
+ signature: createHmac("sha256", secret).update(sortedQuery(params)).digest("hex")
331565
+ };
330519
331566
  }
330520
- async function handlePassThrough(ctx) {
330521
- if (ctx.action === Action.FetchCurrency)
330522
- return handleFetchCurrency(ctx);
330523
- if (ctx.action === Action.FetchAccountId)
330524
- return handleFetchAccountId(ctx);
330525
- if (ctx.action === Action.FetchFees)
330526
- return handleFetchFees(ctx);
330527
- if (ctx.action === Action.FetchDepositAddresses)
330528
- return handleFetchDepositAddresses(ctx);
330529
- if (ctx.action === Action.FetchBalances)
330530
- return handleFetchBalances(ctx);
330531
- if (ctx.action === Action.FetchTicker)
330532
- return handleFetchTicker(ctx);
330533
- }
330534
-
330535
- // src/handlers/execute-action/perp-config.ts
330536
- var grpc8 = __toESM(require_src3(), 1);
330537
- function exchangeSupports(broker, capability) {
330538
- return broker.has?.[capability] === true;
331567
+ function getBinanceSpotWsApiUrl(exchange) {
331568
+ const urls = exchange.urls;
331569
+ return urls?.api?.ws?.["ws-api"]?.spot ?? BINANCE_SPOT_WS_API_URL;
330539
331570
  }
330540
- function extractPerpConfigs(positions) {
330541
- return positions.map((position) => ({
330542
- symbol: typeof position.symbol === "string" ? position.symbol : undefined,
330543
- leverage: typeof position.leverage === "number" ? position.leverage : undefined,
330544
- marginMode: typeof position.marginMode === "string" ? position.marginMode : undefined
330545
- }));
331571
+ function getRecord(value) {
331572
+ return typeof value === "object" && value !== null ? value : null;
330546
331573
  }
330547
- async function handleGetPerpConfigState(ctx) {
330548
- const { wrappedCallback, cex: cex3, normalizedCex, broker } = ctx;
330549
- const payload = parsePayloadForAction(ctx, GetPerpConfigStatePayloadSchema);
330550
- if (payload === null) {
330551
- return;
330552
- }
330553
- if (!broker) {
330554
- return wrappedCallback({
330555
- code: grpc8.status.INVALID_ARGUMENT,
330556
- message: `Invalid CEX key: ${cex3}`
330557
- }, null);
330558
- }
330559
- const exchange = broker;
330560
- if (!exchangeSupports(exchange, "fetchPositions")) {
330561
- return wrappedCallback({
330562
- code: grpc8.status.UNIMPLEMENTED,
330563
- message: `${normalizedCex} does not support fetchPositions`
330564
- }, null);
331574
+ function getMessage(value) {
331575
+ if (value instanceof Error) {
331576
+ return value.message;
330565
331577
  }
330566
- try {
330567
- const symbols = payload.symbol ? [payload.symbol] : undefined;
330568
- const positions = await exchange.fetchPositions?.(symbols, payload.params);
330569
- ctx.wrappedCallback(null, {
330570
- result: JSON.stringify({
330571
- exchange: normalizedCex,
330572
- configs: extractPerpConfigs(positions ?? []),
330573
- positions: positions ?? []
330574
- })
330575
- });
330576
- } catch (error48) {
330577
- safeLogError(`GetPerpConfigState failed for ${cex3}`, error48);
330578
- ctx.wrappedCallback({
330579
- code: grpc8.status.INTERNAL,
330580
- message: getErrorMessage(error48)
330581
- }, null);
331578
+ if (typeof value === "string" && value.length > 0) {
331579
+ return value;
330582
331580
  }
331581
+ const record2 = getRecord(value);
331582
+ const message = record2?.message;
331583
+ return typeof message === "string" && message.length > 0 ? message : null;
330583
331584
  }
330584
- async function handleSetPerpConfigState(ctx) {
330585
- const { wrappedCallback, cex: cex3, normalizedCex, broker } = ctx;
330586
- const payload = parsePayloadForAction(ctx, SetPerpConfigStatePayloadSchema);
330587
- if (payload === null) {
330588
- return;
330589
- }
330590
- if (!broker) {
330591
- return wrappedCallback({
330592
- code: grpc8.status.INVALID_ARGUMENT,
330593
- message: `Invalid CEX key: ${cex3}`
330594
- }, null);
330595
- }
330596
- const exchange = broker;
330597
- if (!exchangeSupports(exchange, "setLeverage")) {
330598
- return wrappedCallback({
330599
- code: grpc8.status.UNIMPLEMENTED,
330600
- message: `${normalizedCex} does not support setLeverage`
330601
- }, null);
330602
- }
330603
- try {
330604
- const response = await exchange.setLeverage?.(payload.leverage, payload.symbol, {
330605
- marginMode: payload.marginMode ?? "cross",
330606
- ...payload.params
330607
- });
330608
- ctx.wrappedCallback(null, {
330609
- result: JSON.stringify({
330610
- exchange: normalizedCex,
330611
- symbol: payload.symbol,
330612
- leverage: payload.leverage,
330613
- marginMode: payload.marginMode ?? "cross",
330614
- response
330615
- })
330616
- });
330617
- } catch (error48) {
330618
- safeLogError(`SetPerpConfigState failed for ${cex3}`, error48);
330619
- ctx.wrappedCallback({
330620
- code: grpc8.status.INTERNAL,
330621
- message: getErrorMessage(error48)
330622
- }, null);
331585
+ function getOptionalExchangeString(exchange, key) {
331586
+ const value = exchange[key];
331587
+ return typeof value === "string" && value.length > 0 ? value : null;
331588
+ }
331589
+ function redactDiagnosticMessage(message, secretValues) {
331590
+ let redacted = message;
331591
+ for (const value of secretValues) {
331592
+ if (value.length > 0) {
331593
+ redacted = redacted.split(value).join("[redacted]");
331594
+ }
330623
331595
  }
331596
+ return redacted.replace(/(\b(?:apiKey|secret|signature)\b\s*=\s*)[^\s&,;)]+/gi, "$1[redacted]").replace(/("(?:apiKey|secret|signature)"\s*:\s*")[^"]*(")/gi, "$1[redacted]$2");
330624
331597
  }
330625
- async function handlePerpConfig(ctx) {
330626
- if (ctx.action === Action.GetPerpConfigState) {
330627
- return handleGetPerpConfigState(ctx);
331598
+ function formatBinanceUserDataWebSocketError(event, secretValues) {
331599
+ const record2 = getRecord(event);
331600
+ const message = getMessage(record2?.error) ?? getMessage(record2?.message) ?? getMessage(event);
331601
+ const safeMessage = message === null ? null : redactDiagnosticMessage(message, secretValues);
331602
+ return new Error(safeMessage ? `Binance user-data WebSocket error: ${safeMessage}` : "Binance user-data WebSocket error");
331603
+ }
331604
+ function getCloseReason(value) {
331605
+ if (typeof value === "string") {
331606
+ return value.length > 0 ? value : null;
330628
331607
  }
330629
- if (ctx.action === Action.SetPerpConfigState) {
330630
- return handleSetPerpConfigState(ctx);
331608
+ if (Buffer2.isBuffer(value)) {
331609
+ const reason = value.toString("utf8");
331610
+ return reason.length > 0 ? reason : null;
330631
331611
  }
330632
- }
330633
-
330634
- // src/handlers/execute-action/treasury-call.ts
330635
- var grpc9 = __toESM(require_src3(), 1);
330636
- async function handleTreasuryCall(ctx) {
330637
- const { broker } = ctx;
330638
- const callValue = parsePayloadForAction(ctx, CallPayloadSchema);
330639
- if (callValue === null)
330640
- return;
330641
- try {
330642
- if (callValue.functionName.startsWith("_") || callValue.functionName.includes("constructor") || callValue.functionName.includes("prototype")) {
330643
- return ctx.wrappedCallback({
330644
- code: grpc9.status.PERMISSION_DENIED,
330645
- message: "Access to the requested function is denied"
330646
- }, null);
330647
- }
330648
- const argsArray = callArgs(callValue.args, callValue.params ?? {});
330649
- const treasuryDiscovery = await handleTreasuryDiscoveryCall(broker, callValue.functionName, callValue.args, callValue.params ?? {});
330650
- if (treasuryDiscovery.handled) {
330651
- return ctx.wrappedCallback(null, {
330652
- proof: ctx.verity.proof,
330653
- result: JSON.stringify(treasuryDiscovery.result)
330654
- });
330655
- }
330656
- const fn = broker[callValue.functionName];
330657
- if (typeof fn !== "function" || broker.has?.[callValue.functionName] === false) {
330658
- return ctx.wrappedCallback({
330659
- code: grpc9.status.INVALID_ARGUMENT,
330660
- message: `Function not found on broker: ${callValue.functionName}`
330661
- }, null);
330662
- }
330663
- const result = await fn.apply(broker, argsArray);
330664
- ctx.wrappedCallback(null, {
330665
- proof: ctx.verity.proof,
330666
- result: JSON.stringify(result)
330667
- });
330668
- } catch (error48) {
330669
- safeLogError("Call failed", error48);
330670
- rejectWithGrpcError(ctx, error48, {
330671
- message: getErrorMessage(error48),
330672
- preferStableMessageOnly: true
330673
- });
331612
+ if (value instanceof Uint8Array) {
331613
+ const reason = Buffer2.from(value).toString("utf8");
331614
+ return reason.length > 0 ? reason : null;
330674
331615
  }
331616
+ return null;
330675
331617
  }
330676
-
330677
- // src/handlers/execute-action/withdraw.ts
330678
- var grpc10 = __toESM(require_src3(), 1);
330679
- async function handleWithdraw(ctx) {
330680
- const {
330681
- call,
330682
- wrappedCallback,
330683
- policy,
330684
- brokers,
330685
- metadata,
330686
- normalizedCex,
330687
- cex: cex3,
330688
- symbol: symbol2,
330689
- selectedBrokerAccount,
330690
- broker,
330691
- verity,
330692
- applyVerityToBroker,
330693
- useVerity,
330694
- verityProverUrl,
330695
- otelMetrics
330696
- } = ctx;
330697
- const verityProof = verity.proof;
330698
- if (!symbol2) {
330699
- return ctx.wrappedCallback({
330700
- code: grpc10.status.INVALID_ARGUMENT,
330701
- message: `ValidationError: Symbol required`
330702
- }, null);
331618
+ function formatBinanceUserDataWebSocketClose(codeOrEvent, reasonOrUndefined, secretValues) {
331619
+ const record2 = getRecord(codeOrEvent);
331620
+ const code = record2 ? record2.code : codeOrEvent;
331621
+ const reason = getCloseReason(record2 ? record2.reason : reasonOrUndefined);
331622
+ const safeReason = reason === null ? null : redactDiagnosticMessage(reason, secretValues);
331623
+ const details = [
331624
+ typeof code === "number" || typeof code === "string" ? `code=${code}` : null,
331625
+ safeReason ? `reason=${safeReason}` : null
331626
+ ].filter((detail) => detail !== null);
331627
+ return new Error(details.length > 0 ? `Binance user-data WebSocket closed unexpectedly (${details.join(", ")})` : "Binance user-data WebSocket closed unexpectedly");
331628
+ }
331629
+ function decodeMessageData(data) {
331630
+ if (typeof data === "string") {
331631
+ return data;
330703
331632
  }
330704
- const transferValue = parsePayloadForAction(ctx, WithdrawPayloadSchema);
330705
- if (transferValue === null)
330706
- return;
330707
- let withdrawNetwork;
330708
- try {
330709
- withdrawNetwork = await resolveTransferNetwork(broker, symbol2, transferValue.chain);
330710
- } catch (error48) {
330711
- const message = getErrorMessage(error48);
330712
- return ctx.wrappedCallback({
330713
- code: stableGrpcErrorCode(message) ?? grpc10.status.INVALID_ARGUMENT,
330714
- message
330715
- }, null);
331633
+ if (Buffer2.isBuffer(data)) {
331634
+ return data.toString("utf8");
330716
331635
  }
330717
- const transferValidation = validateWithdraw(policy, cex3, withdrawNetwork.brokerNetworkId, transferValue.recipientAddress, transferValue.amount, symbol2);
330718
- if (!transferValidation.valid) {
330719
- return ctx.wrappedCallback({
330720
- code: grpc10.status.PERMISSION_DENIED,
330721
- message: `policy_withdrawal_denied: ${transferValidation.error}`
330722
- }, null);
331636
+ if (data instanceof ArrayBuffer) {
331637
+ return Buffer2.from(data).toString("utf8");
330723
331638
  }
330724
- const travelRule = resolveTravelRuleDecision(policy, cex3, transferValue.recipientAddress);
330725
- if (travelRule.mode === "denied") {
330726
- return ctx.wrappedCallback({
330727
- code: grpc10.status.FAILED_PRECONDITION,
330728
- message: `travel_rule_denied: ${travelRule.error}`
330729
- }, null);
331639
+ if (ArrayBuffer.isView(data)) {
331640
+ return Buffer2.from(data.buffer, data.byteOffset, data.byteLength).toString("utf8");
330730
331641
  }
330731
- try {
330732
- const transaction = travelRule.mode === "localentity" ? await withdrawViaLocalEntity(broker, {
330733
- code: symbol2,
330734
- amount: transferValue.amount,
330735
- address: transferValue.recipientAddress,
330736
- network: withdrawNetwork.exchangeNetworkId,
330737
- questionnaire: travelRule.questionnaire,
330738
- params: transferValue.params
330739
- }) : await broker.withdraw(symbol2, transferValue.amount, transferValue.recipientAddress, undefined, {
330740
- ...transferValue.params ?? {},
330741
- network: withdrawNetwork.exchangeNetworkId
330742
- });
330743
- log.info(`Withdraw Result: ${JSON.stringify(transaction)}`);
330744
- ctx.wrappedCallback(null, {
330745
- proof: ctx.verity.proof,
330746
- result: JSON.stringify({
330747
- ...transaction,
330748
- operatorAlias: withdrawNetwork.operatorAlias,
330749
- brokerNetworkId: withdrawNetwork.brokerNetworkId,
330750
- exchangeNetworkId: withdrawNetwork.exchangeNetworkId
330751
- })
330752
- });
330753
- } catch (error48) {
330754
- safeLogError("Withdraw failed", error48);
330755
- const code = mapCcxtErrorToGrpcStatus(error48) ?? grpc10.status.INTERNAL;
330756
- ctx.wrappedCallback({
330757
- code,
330758
- message: `Withdraw failed: ${getErrorMessage(error48)}`
330759
- }, null);
331642
+ if (Array.isArray(data) && data.every((item) => Buffer2.isBuffer(item))) {
331643
+ return Buffer2.concat(data).toString("utf8");
330760
331644
  }
331645
+ return data;
330761
331646
  }
330762
331647
 
330763
- // src/handlers/execute-action/registry.ts
330764
- var ACTION_HANDLERS = {
330765
- [Action.Deposit]: handleDeposit,
330766
- [Action.Withdraw]: handleWithdraw,
330767
- [Action.Call]: handleTreasuryCall,
330768
- [Action.InternalTransfer]: handleInternalTransfer,
330769
- [Action.CreateOrder]: handleOrders,
330770
- [Action.GetOrderDetails]: handleOrders,
330771
- [Action.CancelOrder]: handleOrders,
330772
- [Action.FetchCurrency]: handlePassThrough,
330773
- [Action.FetchAccountId]: handlePassThrough,
330774
- [Action.FetchFees]: handlePassThrough,
330775
- [Action.FetchDepositAddresses]: handlePassThrough,
330776
- [Action.FetchBalances]: handlePassThrough,
330777
- [Action.FetchTicker]: handlePassThrough,
330778
- [Action.GetPerpConfigState]: handlePerpConfig,
330779
- [Action.SetPerpConfigState]: handlePerpConfig
330780
- };
330781
- async function dispatchExecuteAction(ctx) {
330782
- const handler = ACTION_HANDLERS[ctx.action];
330783
- if (!handler) {
330784
- ctx.wrappedCallback({
330785
- code: grpc11.status.INVALID_ARGUMENT,
330786
- message: "Invalid Action"
330787
- }, null);
330788
- return;
331648
+ class BinanceSpotUserDataStream {
331649
+ exchange;
331650
+ ws;
331651
+ secretValues;
331652
+ requestId = `user-data-${Date.now()}-${Math.random()}`;
331653
+ maxBufferedEvents;
331654
+ queue = [];
331655
+ waiters = [];
331656
+ closed = false;
331657
+ closeError = null;
331658
+ subscriptionId = null;
331659
+ constructor(exchange, options = {}) {
331660
+ this.exchange = exchange;
331661
+ this.maxBufferedEvents = options.maxBufferedEvents ?? DEFAULT_BINANCE_USER_DATA_MAX_BUFFERED_EVENTS;
331662
+ this.secretValues = [
331663
+ getOptionalExchangeString(exchange, "apiKey"),
331664
+ getOptionalExchangeString(exchange, "secret")
331665
+ ].filter((value) => value !== null);
331666
+ this.ws = createWebSocket(getBinanceSpotWsApiUrl(exchange));
331667
+ this.ws.on("open", () => this.subscribe());
331668
+ this.ws.on("message", (data) => this.handleMessage(data));
331669
+ this.ws.on("error", (error48) => this.fail(formatBinanceUserDataWebSocketError(error48, this.secretValues)));
331670
+ this.ws.on("close", (code, reason) => this.handleClose(code, reason));
330789
331671
  }
330790
- await handler(ctx);
330791
- }
330792
-
330793
- // src/handlers/execute-action/handler.ts
330794
- function createExecuteActionHandler(deps) {
330795
- const {
330796
- policy,
330797
- brokers,
330798
- whitelistIps,
330799
- useVerity,
330800
- verityProverUrl,
330801
- otelMetrics
330802
- } = deps;
330803
- return async (call, callback) => {
330804
- const startTime = Date.now();
330805
- const { action: rawAction, cex: cex3, symbol: symbol2 } = call.request;
330806
- const action = resolveAction(rawAction);
330807
- let actionCompleted = false;
330808
- const wrappedCallback = (error48, value) => {
330809
- if (!actionCompleted) {
330810
- actionCompleted = true;
330811
- const latency = Date.now() - startTime;
330812
- const actionName = getActionName(action);
330813
- otelMetrics?.recordHistogram("execute_action_duration_ms", latency, {
330814
- action: actionName,
330815
- cex: cex3 || "unknown"
330816
- });
330817
- if (error48) {
330818
- otelMetrics?.recordCounter("execute_action_errors_total", 1, {
330819
- action: actionName,
330820
- cex: cex3 || "unknown",
330821
- error_type: error48.code ? grpc12.status[error48.code] || "unknown" : "unknown"
330822
- });
330823
- } else {
330824
- otelMetrics?.recordCounter("execute_action_success_total", 1, {
330825
- action: actionName,
330826
- cex: cex3 || "unknown"
330827
- });
330828
- }
331672
+ async* [Symbol.asyncIterator]() {
331673
+ while (true) {
331674
+ const event = await this.nextEvent();
331675
+ if (!event) {
331676
+ break;
330829
331677
  }
330830
- callback(error48, value);
330831
- };
331678
+ yield event;
331679
+ }
331680
+ }
331681
+ close() {
331682
+ if (this.closed) {
331683
+ return;
331684
+ }
331685
+ this.closed = true;
331686
+ this.queue.length = 0;
330832
331687
  try {
330833
- log.info(`Request - ExecuteAction:`, { action, cex: cex3, symbol: symbol2 });
330834
- otelMetrics?.recordCounter("execute_action_requests_total", 1, {
330835
- action: getActionName(action),
330836
- cex: cex3 || "unknown"
330837
- });
330838
- if (!authenticateRequest(call, whitelistIps)) {
330839
- return wrappedCallback({
330840
- code: grpc12.status.PERMISSION_DENIED,
330841
- message: "Access denied: Unauthorized IP"
330842
- }, null);
331688
+ this.ws.close();
331689
+ } catch {}
331690
+ this.flushWaiters();
331691
+ }
331692
+ handleClose(code, reason) {
331693
+ if (this.closed) {
331694
+ return;
331695
+ }
331696
+ this.fail(formatBinanceUserDataWebSocketClose(code, reason, this.secretValues));
331697
+ }
331698
+ subscribe() {
331699
+ const apiKey = getExchangeString(this.exchange, "apiKey");
331700
+ const signedParams = signUserDataStreamParams(this.exchange, {
331701
+ apiKey,
331702
+ timestamp: Date.now()
331703
+ });
331704
+ this.ws.send(JSON.stringify({
331705
+ id: this.requestId,
331706
+ method: "userDataStream.subscribe.signature",
331707
+ params: signedParams
331708
+ }));
331709
+ }
331710
+ handleMessage(data) {
331711
+ if (this.closed) {
331712
+ return;
331713
+ }
331714
+ let message;
331715
+ try {
331716
+ const decodedData = decodeMessageData(data);
331717
+ message = typeof decodedData === "string" ? JSON.parse(decodedData) : decodedData;
331718
+ } catch (error48) {
331719
+ this.fail(error48 instanceof Error ? error48 : new Error("Invalid Binance user-data message"));
331720
+ return;
331721
+ }
331722
+ if ("id" in message && message.id === this.requestId) {
331723
+ if (message.status !== 200) {
331724
+ this.fail(new Error(message.error?.msg ?? message.error?.message ?? `Binance user-data subscription failed with status ${message.status}`));
331725
+ return;
330843
331726
  }
330844
- if (!action || !cex3) {
330845
- return wrappedCallback({
330846
- code: grpc12.status.INVALID_ARGUMENT,
330847
- message: "`action` AND `cex` fields are required"
330848
- }, null);
331727
+ this.subscriptionId = message.result?.subscriptionId ?? null;
331728
+ return;
331729
+ }
331730
+ if (!("event" in message) || !message.event) {
331731
+ return;
331732
+ }
331733
+ const subscriptionId = message.subscriptionId ?? this.subscriptionId;
331734
+ if (subscriptionId === null || subscriptionId === undefined) {
331735
+ return;
331736
+ }
331737
+ this.push({ subscriptionId, event: message.event });
331738
+ }
331739
+ push(event) {
331740
+ if (this.closed) {
331741
+ return;
331742
+ }
331743
+ const waiter = this.waiters.shift();
331744
+ if (waiter) {
331745
+ waiter.resolve(event);
331746
+ return;
331747
+ }
331748
+ if (this.queue.length >= this.maxBufferedEvents) {
331749
+ this.fail(new Error(`Binance user-data stream buffered event limit exceeded (${this.maxBufferedEvents}); downstream consumer is not keeping up`));
331750
+ return;
331751
+ }
331752
+ this.queue.push(event);
331753
+ }
331754
+ nextEvent() {
331755
+ const event = this.queue.shift();
331756
+ if (event) {
331757
+ return Promise.resolve(event);
331758
+ }
331759
+ if (this.closeError) {
331760
+ return Promise.reject(this.closeError);
331761
+ }
331762
+ if (this.closed) {
331763
+ return Promise.resolve(null);
331764
+ }
331765
+ return new Promise((resolve, reject) => {
331766
+ this.waiters.push({ resolve, reject });
331767
+ });
331768
+ }
331769
+ fail(error48) {
331770
+ if (this.closeError) {
331771
+ return;
331772
+ }
331773
+ this.closeError = error48;
331774
+ this.closed = true;
331775
+ this.queue.length = 0;
331776
+ this.flushWaiters();
331777
+ try {
331778
+ this.ws.close();
331779
+ } catch {}
331780
+ }
331781
+ flushWaiters() {
331782
+ const error48 = this.closeError;
331783
+ for (const waiter of this.waiters.splice(0)) {
331784
+ if (error48) {
331785
+ waiter.reject(error48);
331786
+ } else {
331787
+ waiter.resolve(null);
330849
331788
  }
330850
- const normalizedCex = cex3.trim().toLowerCase();
330851
- const metadata = call.metadata;
330852
- const selectedBrokerAccount = selectBrokerAccountForCex(normalizedCex, brokers, metadata);
330853
- const verity = { proof: "" };
330854
- const applyVerityToBroker = (targetBroker) => {
330855
- if (!useVerity)
330856
- return;
330857
- const override = buildHttpClientOverrideFromMetadata(metadata, verityProverUrl, (proof, notaryPubKey) => {
330858
- verity.proof = proof;
330859
- log.debug(`Verity proof:`, { proof, notaryPubKey });
331789
+ }
331790
+ }
331791
+ }
331792
+ function isBinanceBalanceUserDataEvent(event) {
331793
+ return event.e === "outboundAccountPosition" || event.e === "balanceUpdate" || event.e === "externalLockUpdate";
331794
+ }
331795
+ function isBinanceOrderUserDataEvent(event) {
331796
+ return event.e === "executionReport" || event.e === "listStatus";
331797
+ }
331798
+
331799
+ // src/helpers/market-data-archive/ohlcv-bar-tracker.ts
331800
+ function isFiniteNumber(value) {
331801
+ return typeof value === "number" && Number.isFinite(value);
331802
+ }
331803
+ function parseOhlcvBar(value) {
331804
+ if (!Array.isArray(value) || value.length < 6) {
331805
+ return null;
331806
+ }
331807
+ const [openTimeMs, open, high, low, close, volume, quoteVolume] = value;
331808
+ if (!isFiniteNumber(openTimeMs) || !isFiniteNumber(open) || !isFiniteNumber(high) || !isFiniteNumber(low) || !isFiniteNumber(close) || !isFiniteNumber(volume)) {
331809
+ return null;
331810
+ }
331811
+ const bar = {
331812
+ openTimeMs,
331813
+ open,
331814
+ high,
331815
+ low,
331816
+ close,
331817
+ volume
331818
+ };
331819
+ if (isFiniteNumber(quoteVolume)) {
331820
+ bar.quoteVolume = quoteVolume;
331821
+ }
331822
+ return bar;
331823
+ }
331824
+ function extractOhlcvBars(payload) {
331825
+ if (!Array.isArray(payload) || payload.length === 0) {
331826
+ return [];
331827
+ }
331828
+ const rawBars = Array.isArray(payload[0]) ? payload : [payload];
331829
+ const byOpenTime = new Map;
331830
+ for (const entry of rawBars) {
331831
+ const bar = parseOhlcvBar(entry);
331832
+ if (bar) {
331833
+ byOpenTime.set(bar.openTimeMs, bar);
331834
+ }
331835
+ }
331836
+ return [...byOpenTime.values()].sort((a, b2) => a.openTimeMs - b2.openTimeMs);
331837
+ }
331838
+ class OhlcvBarTracker {
331839
+ lastOpenTimeMs = null;
331840
+ lastBar = null;
331841
+ process(payload, brokerVersion) {
331842
+ const bars = extractOhlcvBars(payload);
331843
+ if (bars.length === 0) {
331844
+ return [];
331845
+ }
331846
+ if (bars.length === 1) {
331847
+ const [bar] = bars;
331848
+ return bar ? this.processSingleBar(bar, brokerVersion) : [];
331849
+ }
331850
+ return this.processBatch(bars, brokerVersion);
331851
+ }
331852
+ processSingleBar(currentBar, brokerVersion) {
331853
+ if (this.lastOpenTimeMs !== null && currentBar.openTimeMs < this.lastOpenTimeMs) {
331854
+ return [];
331855
+ }
331856
+ const candidates = [];
331857
+ if (this.lastOpenTimeMs !== null && this.lastBar !== null && currentBar.openTimeMs !== this.lastOpenTimeMs) {
331858
+ candidates.push({
331859
+ bar: this.lastBar,
331860
+ isClosed: true,
331861
+ brokerVersion
331862
+ });
331863
+ }
331864
+ candidates.push({
331865
+ bar: currentBar,
331866
+ isClosed: false,
331867
+ brokerVersion
331868
+ });
331869
+ this.lastOpenTimeMs = currentBar.openTimeMs;
331870
+ this.lastBar = currentBar;
331871
+ return candidates;
331872
+ }
331873
+ processBatch(bars, brokerVersion) {
331874
+ const firstBar = bars[0];
331875
+ const lastBar = bars[bars.length - 1];
331876
+ if (!firstBar || !lastBar) {
331877
+ return [];
331878
+ }
331879
+ if (this.lastOpenTimeMs !== null && lastBar.openTimeMs < this.lastOpenTimeMs) {
331880
+ return [];
331881
+ }
331882
+ const candidates = [];
331883
+ if (this.lastBar !== null && this.lastOpenTimeMs !== null && !bars.some((bar) => bar.openTimeMs === this.lastOpenTimeMs) && this.lastOpenTimeMs < firstBar.openTimeMs) {
331884
+ candidates.push({
331885
+ bar: this.lastBar,
331886
+ isClosed: true,
331887
+ brokerVersion
331888
+ });
331889
+ }
331890
+ for (let index2 = 0;index2 < bars.length - 1; index2 += 1) {
331891
+ const bar = bars[index2];
331892
+ if (bar) {
331893
+ candidates.push({
331894
+ bar,
331895
+ isClosed: true,
331896
+ brokerVersion
330860
331897
  });
330861
- targetBroker.setHttpClientOverride(override, verityHttpClientOverridePredicate);
330862
- };
330863
- const preludeCtx = {
330864
- call,
330865
- wrappedCallback,
330866
- action,
330867
- policy,
330868
- brokers,
330869
- metadata,
330870
- normalizedCex,
330871
- cex: cex3,
330872
- symbol: symbol2,
330873
- selectedBrokerAccount,
330874
- broker: selectedBrokerAccount?.exchange ?? createBroker(normalizedCex, metadata),
330875
- verity,
330876
- applyVerityToBroker,
330877
- useVerity,
330878
- verityProverUrl,
330879
- otelMetrics
330880
- };
330881
- if (action === Action.Call) {
330882
- const handled = await handleOrderBookCall(preludeCtx);
330883
- if (handled)
330884
- return;
330885
- }
330886
- const broker = selectedBrokerAccount?.exchange ?? createBroker(normalizedCex, metadata);
330887
- if (!broker) {
330888
- return wrappedCallback({
330889
- code: grpc12.status.UNAUTHENTICATED,
330890
- message: `This Exchange is not registered and No API metadata was found`
330891
- }, null);
330892
331898
  }
330893
- applyVerityToBroker(broker);
330894
- const ctx = { ...preludeCtx, broker };
330895
- await dispatchExecuteAction(ctx);
330896
- } catch (error48) {
330897
- safeLogError("ExecuteAction unhandled error", error48);
330898
- return wrappedCallback({
330899
- code: grpc12.status.INTERNAL,
330900
- message: "ExecuteAction failed unexpectedly"
330901
- }, null);
330902
331899
  }
330903
- };
331900
+ candidates.push({
331901
+ bar: lastBar,
331902
+ isClosed: false,
331903
+ brokerVersion
331904
+ });
331905
+ this.lastOpenTimeMs = lastBar.openTimeMs;
331906
+ this.lastBar = lastBar;
331907
+ return candidates;
331908
+ }
330904
331909
  }
330905
- // src/handlers/subscribe/handler.ts
330906
- var grpc13 = __toESM(require_src3(), 1);
330907
331910
 
330908
- // src/helpers/binance-user-data-stream.ts
330909
- import { Buffer as Buffer2 } from "node:buffer";
330910
- import { createHmac } from "node:crypto";
330911
- var BINANCE_SPOT_WS_API_URL = "wss://ws-api.binance.com:443/ws-api/v3";
330912
- var DEFAULT_BINANCE_USER_DATA_MAX_BUFFERED_EVENTS = 16;
330913
- var createWebSocket = (url3) => new wrapper_default(url3);
330914
- function getExchangeString(exchange, key) {
330915
- const value = exchange[key];
330916
- if (typeof value !== "string" || value.length === 0) {
330917
- throw new Error(`Binance user-data stream requires exchange.${key}`);
331911
+ // src/helpers/market-data-archive/orderbook-sampler.ts
331912
+ var DEFAULT_ORDERBOOK_INTERVAL_MS = 1000;
331913
+ function getOrderbookIntervalMs() {
331914
+ const raw = process.env.CEX_BROKER_ORDERBOOK_INTERVAL_MS ?? process.env.CEX_BROKER_ORDERBOOK_TOB_INTERVAL_MS;
331915
+ if (!raw) {
331916
+ return DEFAULT_ORDERBOOK_INTERVAL_MS;
330918
331917
  }
330919
- return value;
331918
+ const parsed = Number.parseInt(raw, 10);
331919
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_ORDERBOOK_INTERVAL_MS;
330920
331920
  }
330921
- function sortedQuery(params) {
330922
- return Object.entries(params).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`).join("&");
331921
+ function isMarketArchiveEnabled() {
331922
+ return process.env.CEX_BROKER_MARKET_ARCHIVE_ENABLED !== "false";
330923
331923
  }
330924
- function signUserDataStreamParams(exchange, params) {
330925
- const signParams = exchange.signParams;
330926
- if (typeof signParams === "function") {
330927
- return signParams.call(exchange, params);
331924
+
331925
+ class OrderbookSampler {
331926
+ intervalMs;
331927
+ lastEmitMs = null;
331928
+ constructor(intervalMs = getOrderbookIntervalMs()) {
331929
+ this.intervalMs = intervalMs;
331930
+ }
331931
+ shouldEmit(nowMs = Date.now()) {
331932
+ if (this.lastEmitMs !== null && nowMs < this.lastEmitMs) {
331933
+ this.lastEmitMs = nowMs;
331934
+ return true;
331935
+ }
331936
+ if (this.lastEmitMs !== null && nowMs - this.lastEmitMs < this.intervalMs) {
331937
+ return false;
331938
+ }
331939
+ this.lastEmitMs = nowMs;
331940
+ return true;
330928
331941
  }
330929
- const secret = getExchangeString(exchange, "secret");
330930
- return {
330931
- ...params,
330932
- signature: createHmac("sha256", secret).update(sortedQuery(params)).digest("hex")
330933
- };
330934
331942
  }
330935
- function getBinanceSpotWsApiUrl(exchange) {
330936
- const urls = exchange.urls;
330937
- return urls?.api?.ws?.["ws-api"]?.spot ?? BINANCE_SPOT_WS_API_URL;
331943
+
331944
+ // src/helpers/market-data-archive/parse-stream.ts
331945
+ function isFiniteNumber2(value) {
331946
+ return typeof value === "number" && Number.isFinite(value);
330938
331947
  }
330939
- function getRecord(value) {
330940
- return typeof value === "object" && value !== null ? value : null;
331948
+ function toNumber2(value) {
331949
+ if (isFiniteNumber2(value)) {
331950
+ return value;
331951
+ }
331952
+ if (typeof value === "string") {
331953
+ const parsed = Number.parseFloat(value);
331954
+ if (Number.isFinite(parsed)) {
331955
+ return parsed;
331956
+ }
331957
+ }
331958
+ return;
330941
331959
  }
330942
- function getMessage(value) {
330943
- if (value instanceof Error) {
330944
- return value.message;
331960
+ function toStringId(value) {
331961
+ if (typeof value === "string" && value.trim()) {
331962
+ return value.trim();
330945
331963
  }
330946
- if (typeof value === "string" && value.length > 0) {
330947
- return value;
331964
+ if (typeof value === "number" && Number.isFinite(value)) {
331965
+ return String(value);
330948
331966
  }
330949
- const record2 = getRecord(value);
330950
- const message = record2?.message;
330951
- return typeof message === "string" && message.length > 0 ? message : null;
331967
+ return;
330952
331968
  }
330953
- function getOptionalExchangeString(exchange, key) {
330954
- const value = exchange[key];
330955
- return typeof value === "string" && value.length > 0 ? value : null;
331969
+ function scalarTimestampMs(value, fallbackMs) {
331970
+ const numeric = toNumber2(value);
331971
+ if (numeric !== undefined) {
331972
+ return numeric < 1000000000000 ? numeric * 1000 : numeric;
331973
+ }
331974
+ if (typeof value === "string") {
331975
+ const parsed = Date.parse(value);
331976
+ if (Number.isFinite(parsed)) {
331977
+ return parsed;
331978
+ }
331979
+ }
331980
+ return fallbackMs;
330956
331981
  }
330957
- function redactDiagnosticMessage(message, secretValues) {
330958
- let redacted = message;
330959
- for (const value of secretValues) {
330960
- if (value.length > 0) {
330961
- redacted = redacted.split(value).join("[redacted]");
331982
+ function parseTrade(value, fallbackMs = Date.now()) {
331983
+ const record2 = asRecord(value);
331984
+ if (!record2) {
331985
+ return null;
331986
+ }
331987
+ const tradeId = toStringId(record2.id);
331988
+ const price = toNumber2(record2.price);
331989
+ const amount = toNumber2(record2.amount);
331990
+ const side = typeof record2.side === "string" ? record2.side.toLowerCase() : undefined;
331991
+ if (!tradeId || price === undefined || amount === undefined || !side) {
331992
+ return null;
331993
+ }
331994
+ const parsed = {
331995
+ tradeId,
331996
+ eventTimeMs: scalarTimestampMs(record2.timestamp, fallbackMs),
331997
+ side,
331998
+ price,
331999
+ amount
332000
+ };
332001
+ const cost = toNumber2(record2.cost);
332002
+ if (cost !== undefined) {
332003
+ parsed.cost = cost;
332004
+ }
332005
+ if (typeof record2.takerOrMaker === "string") {
332006
+ parsed.takerOrMaker = record2.takerOrMaker;
332007
+ }
332008
+ return parsed;
332009
+ }
332010
+ function extractTrades(payload, fallbackMs = Date.now()) {
332011
+ if (Array.isArray(payload)) {
332012
+ return payload.map((entry) => parseTrade(entry, fallbackMs)).filter((entry) => entry !== null);
332013
+ }
332014
+ const single = parseTrade(payload, fallbackMs);
332015
+ return single ? [single] : [];
332016
+ }
332017
+ function parseTicker(value, fallbackMs) {
332018
+ const record2 = asRecord(value);
332019
+ if (!record2) {
332020
+ return null;
332021
+ }
332022
+ const parsed = {
332023
+ eventTimeMs: scalarTimestampMs(record2.timestamp, fallbackMs)
332024
+ };
332025
+ const fields = [
332026
+ ["last", record2.last],
332027
+ ["bid", record2.bid],
332028
+ ["ask", record2.ask],
332029
+ ["high", record2.high],
332030
+ ["low", record2.low],
332031
+ ["open", record2.open],
332032
+ ["close", record2.close],
332033
+ ["baseVolume", record2.baseVolume],
332034
+ ["quoteVolume", record2.quoteVolume],
332035
+ ["change", record2.change],
332036
+ ["percentage", record2.percentage]
332037
+ ];
332038
+ for (const [key, rawValue] of fields) {
332039
+ const numeric = toNumber2(rawValue);
332040
+ if (numeric !== undefined) {
332041
+ parsed[key] = numeric;
330962
332042
  }
330963
332043
  }
330964
- return redacted.replace(/(\b(?:apiKey|secret|signature)\b\s*=\s*)[^\s&,;)]+/gi, "$1[redacted]").replace(/("(?:apiKey|secret|signature)"\s*:\s*")[^"]*(")/gi, "$1[redacted]$2");
332044
+ return parsed;
330965
332045
  }
330966
- function formatBinanceUserDataWebSocketError(event, secretValues) {
330967
- const record2 = getRecord(event);
330968
- const message = getMessage(record2?.error) ?? getMessage(record2?.message) ?? getMessage(event);
330969
- const safeMessage = message === null ? null : redactDiagnosticMessage(message, secretValues);
330970
- return new Error(safeMessage ? `Binance user-data WebSocket error: ${safeMessage}` : "Binance user-data WebSocket error");
332046
+
332047
+ // src/helpers/market-data-archive/orderbook-depth.ts
332048
+ var DEFAULT_ORDERBOOK_ARCHIVE_DEPTH_LIMIT = 25;
332049
+ var MAX_ORDERBOOK_ARCHIVE_DEPTH_LIMIT = 500;
332050
+ function getOrderbookArchiveDepthLimit() {
332051
+ const raw = process.env.CEX_BROKER_ORDERBOOK_ARCHIVE_DEPTH_LIMIT;
332052
+ if (!raw) {
332053
+ return DEFAULT_ORDERBOOK_ARCHIVE_DEPTH_LIMIT;
332054
+ }
332055
+ const parsed = Number.parseInt(raw, 10);
332056
+ if (!Number.isFinite(parsed) || parsed <= 0) {
332057
+ return DEFAULT_ORDERBOOK_ARCHIVE_DEPTH_LIMIT;
332058
+ }
332059
+ return Math.min(parsed, MAX_ORDERBOOK_ARCHIVE_DEPTH_LIMIT);
330971
332060
  }
330972
- function getCloseReason(value) {
330973
- if (typeof value === "string") {
330974
- return value.length > 0 ? value : null;
332061
+ function splitOrderBookSide(levels, limit) {
332062
+ const prices = [];
332063
+ const sizes = [];
332064
+ for (const level of levels.slice(0, limit)) {
332065
+ if (!Array.isArray(level) || level.length < 2) {
332066
+ continue;
332067
+ }
332068
+ const price = level[0];
332069
+ const size = level[1];
332070
+ if (price === undefined || size === undefined || !Number.isFinite(price) || !Number.isFinite(size)) {
332071
+ continue;
332072
+ }
332073
+ prices.push(price);
332074
+ sizes.push(size);
330975
332075
  }
330976
- if (Buffer2.isBuffer(value)) {
330977
- const reason = value.toString("utf8");
330978
- return reason.length > 0 ? reason : null;
332076
+ return { prices, sizes };
332077
+ }
332078
+
332079
+ // src/helpers/market-data-archive/rows.ts
332080
+ function compactUndefined3(record2) {
332081
+ return Object.fromEntries(Object.entries(record2).filter(([, value]) => value !== undefined));
332082
+ }
332083
+ function scalarTimestampMs2(value) {
332084
+ if (typeof value === "number" && Number.isFinite(value)) {
332085
+ return value;
330979
332086
  }
330980
- if (value instanceof Uint8Array) {
330981
- const reason = Buffer2.from(value).toString("utf8");
330982
- return reason.length > 0 ? reason : null;
332087
+ if (typeof value === "string") {
332088
+ if (/^\d+$/.test(value)) {
332089
+ const numeric = Number.parseInt(value, 10);
332090
+ if (Number.isFinite(numeric)) {
332091
+ return numeric;
332092
+ }
332093
+ }
332094
+ const parsed = Date.parse(value);
332095
+ if (Number.isFinite(parsed)) {
332096
+ return parsed;
332097
+ }
330983
332098
  }
330984
- return null;
332099
+ return Date.now();
330985
332100
  }
330986
- function formatBinanceUserDataWebSocketClose(codeOrEvent, reasonOrUndefined, secretValues) {
330987
- const record2 = getRecord(codeOrEvent);
330988
- const code = record2 ? record2.code : codeOrEvent;
330989
- const reason = getCloseReason(record2 ? record2.reason : reasonOrUndefined);
330990
- const safeReason = reason === null ? null : redactDiagnosticMessage(reason, secretValues);
330991
- const details = [
330992
- typeof code === "number" || typeof code === "string" ? `code=${code}` : null,
330993
- safeReason ? `reason=${safeReason}` : null
330994
- ].filter((detail) => detail !== null);
330995
- return new Error(details.length > 0 ? `Binance user-data WebSocket closed unexpectedly (${details.join(", ")})` : "Binance user-data WebSocket closed unexpectedly");
332101
+ function parseSequence(value) {
332102
+ if (typeof value === "number" && Number.isFinite(value)) {
332103
+ return value;
332104
+ }
332105
+ if (typeof value === "string" && /^\d+$/.test(value)) {
332106
+ return Number.parseInt(value, 10);
332107
+ }
332108
+ return;
330996
332109
  }
330997
- function decodeMessageData(data) {
330998
- if (typeof data === "string") {
330999
- return data;
332110
+ function topOfBookLevel(levels) {
332111
+ const level = levels[0];
332112
+ if (!level || level.length < 2) {
332113
+ return null;
331000
332114
  }
331001
- if (Buffer2.isBuffer(data)) {
331002
- return data.toString("utf8");
332115
+ const price = level[0];
332116
+ const size = level[1];
332117
+ if (price === undefined || size === undefined || !Number.isFinite(price) || !Number.isFinite(size)) {
332118
+ return null;
331003
332119
  }
331004
- if (data instanceof ArrayBuffer) {
331005
- return Buffer2.from(data).toString("utf8");
332120
+ return { price, size };
332121
+ }
332122
+ function computeSpreadBps(bestBid, bestAsk) {
332123
+ if (bestBid <= 0 || bestAsk <= 0 || bestAsk < bestBid) {
332124
+ return 0;
331006
332125
  }
331007
- if (ArrayBuffer.isView(data)) {
331008
- return Buffer2.from(data.buffer, data.byteOffset, data.byteLength).toString("utf8");
332126
+ const mid = (bestBid + bestAsk) / 2;
332127
+ if (mid <= 0) {
332128
+ return 0;
331009
332129
  }
331010
- if (Array.isArray(data) && data.every((item) => Buffer2.isBuffer(item))) {
331011
- return Buffer2.concat(data).toString("utf8");
332130
+ return (bestAsk - bestBid) / mid * 1e4;
332131
+ }
332132
+ function buildOrderbookArchiveTags(input, receivedTimeMs) {
332133
+ return buildCommonArchiveTags({
332134
+ deploymentId: input.deploymentId,
332135
+ accountSelector: input.accountSelector,
332136
+ exchange: input.exchange,
332137
+ symbol: input.symbol,
332138
+ brokerObservedTimestamp: new Date(receivedTimeMs).toISOString()
332139
+ });
332140
+ }
332141
+ function buildOrderbookSnapshotRow(input) {
332142
+ const bid = topOfBookLevel(input.snapshot.bids);
332143
+ const ask = topOfBookLevel(input.snapshot.asks);
332144
+ if (!bid || !ask) {
332145
+ return null;
331012
332146
  }
331013
- return data;
332147
+ const archiveDepthLimit = getOrderbookArchiveDepthLimit();
332148
+ const bids = splitOrderBookSide(input.snapshot.bids, archiveDepthLimit);
332149
+ const asks = splitOrderBookSide(input.snapshot.asks, archiveDepthLimit);
332150
+ if (bids.prices.length === 0 || asks.prices.length === 0) {
332151
+ return null;
332152
+ }
332153
+ const eventTimeMs = scalarTimestampMs2(input.snapshot.timestamp);
332154
+ const receivedTimeMs = input.snapshot.receivedTimestamp;
332155
+ const mid = (bid.price + ask.price) / 2;
332156
+ const sequence = parseSequence(input.snapshot.sequence);
332157
+ return {
332158
+ table: "market_data.orderbook_snapshots",
332159
+ row: compactUndefined3({
332160
+ ...buildOrderbookArchiveTags(input, receivedTimeMs),
332161
+ asset_type: input.assetType,
332162
+ event_time_ms: eventTimeMs,
332163
+ received_time_ms: receivedTimeMs,
332164
+ best_bid: bid.price,
332165
+ best_ask: ask.price,
332166
+ bid_size: bid.size,
332167
+ ask_size: ask.size,
332168
+ mid,
332169
+ spread_bps: computeSpreadBps(bid.price, ask.price),
332170
+ depth_limit: archiveDepthLimit,
332171
+ bid_levels: bids.prices.length,
332172
+ ask_levels: asks.prices.length,
332173
+ bids_price: bids.prices,
332174
+ bids_size: bids.sizes,
332175
+ asks_price: asks.prices,
332176
+ asks_size: asks.sizes,
332177
+ sequence
332178
+ })
332179
+ };
332180
+ }
332181
+ function buildCandleRow(input) {
332182
+ const { context: context2, bar, isClosed, brokerVersion, receivedTimestamp } = input;
332183
+ const tags = buildCommonArchiveTags({
332184
+ deploymentId: context2.deploymentId,
332185
+ accountSelector: context2.accountSelector,
332186
+ exchange: context2.exchange,
332187
+ symbol: context2.symbol,
332188
+ brokerObservedTimestamp: new Date(receivedTimestamp).toISOString()
332189
+ });
332190
+ return {
332191
+ table: "market_data.candles",
332192
+ row: compactUndefined3({
332193
+ ...tags,
332194
+ asset_type: context2.assetType,
332195
+ timeframe: context2.timeframe ?? "1m",
332196
+ open_time_ms: bar.openTimeMs,
332197
+ open: bar.open,
332198
+ high: bar.high,
332199
+ low: bar.low,
332200
+ close: bar.close,
332201
+ volume: bar.volume,
332202
+ quote_volume: bar.quoteVolume,
332203
+ is_closed: isClosed ? 1 : 0,
332204
+ broker_version: brokerVersion
332205
+ })
332206
+ };
332207
+ }
332208
+ function buildCexStreamEventRow(input) {
332209
+ const receivedTimeMs = input.receivedTimestamp;
332210
+ const redactedPayload = redactStreamPayload(input.payload);
332211
+ const tags = buildCommonArchiveTags({
332212
+ deploymentId: input.deploymentId,
332213
+ accountSelector: input.accountSelector,
332214
+ exchange: input.exchange,
332215
+ symbol: input.symbol,
332216
+ brokerObservedTimestamp: new Date(receivedTimeMs).toISOString()
332217
+ });
332218
+ return {
332219
+ table: "market_data.cex_stream_events",
332220
+ row: compactUndefined3({
332221
+ ...tags,
332222
+ asset_type: input.assetType,
332223
+ stream_type: input.streamType,
332224
+ event_time_ms: input.eventTimeMs ?? receivedTimeMs,
332225
+ received_time_ms: receivedTimeMs,
332226
+ payload_json: JSON.stringify(redactedPayload)
332227
+ })
332228
+ };
332229
+ }
332230
+ function buildCexTickerEventRow(input, ticker) {
332231
+ const receivedTimeMs = input.receivedTimestamp;
332232
+ const tags = buildCommonArchiveTags({
332233
+ deploymentId: input.deploymentId,
332234
+ accountSelector: input.accountSelector,
332235
+ exchange: input.exchange,
332236
+ symbol: input.symbol,
332237
+ brokerObservedTimestamp: new Date(receivedTimeMs).toISOString()
332238
+ });
332239
+ return {
332240
+ table: "market_data.cex_ticker_events",
332241
+ row: compactUndefined3({
332242
+ ...tags,
332243
+ asset_type: input.assetType,
332244
+ event_time_ms: ticker.eventTimeMs,
332245
+ received_time_ms: receivedTimeMs,
332246
+ last: ticker.last,
332247
+ bid: ticker.bid,
332248
+ ask: ticker.ask,
332249
+ high: ticker.high,
332250
+ low: ticker.low,
332251
+ open: ticker.open,
332252
+ close: ticker.close,
332253
+ base_volume: ticker.baseVolume,
332254
+ quote_volume: ticker.quoteVolume,
332255
+ change: ticker.change,
332256
+ percentage: ticker.percentage,
332257
+ payload_json: JSON.stringify(redactStreamPayload(input.payload))
332258
+ })
332259
+ };
332260
+ }
332261
+ function buildCexTradeRow(input, trade) {
332262
+ const receivedTimeMs = input.receivedTimestamp;
332263
+ const tags = buildCommonArchiveTags({
332264
+ deploymentId: input.deploymentId,
332265
+ accountSelector: input.accountSelector,
332266
+ exchange: input.exchange,
332267
+ symbol: input.symbol,
332268
+ brokerObservedTimestamp: new Date(receivedTimeMs).toISOString()
332269
+ });
332270
+ return {
332271
+ table: "market_data.cex_trades",
332272
+ row: compactUndefined3({
332273
+ ...tags,
332274
+ asset_type: input.assetType,
332275
+ trade_id: trade.tradeId,
332276
+ event_time_ms: trade.eventTimeMs,
332277
+ received_time_ms: receivedTimeMs,
332278
+ side: trade.side,
332279
+ price: trade.price,
332280
+ amount: trade.amount,
332281
+ cost: trade.cost,
332282
+ taker_or_maker: trade.takerOrMaker
332283
+ })
332284
+ };
331014
332285
  }
331015
332286
 
331016
- class BinanceSpotUserDataStream {
331017
- exchange;
331018
- ws;
331019
- secretValues;
331020
- requestId = `user-data-${Date.now()}-${Math.random()}`;
331021
- maxBufferedEvents;
331022
- queue = [];
331023
- waiters = [];
331024
- closed = false;
331025
- closeError = null;
331026
- subscriptionId = null;
331027
- constructor(exchange, options = {}) {
331028
- this.exchange = exchange;
331029
- this.maxBufferedEvents = options.maxBufferedEvents ?? DEFAULT_BINANCE_USER_DATA_MAX_BUFFERED_EVENTS;
331030
- this.secretValues = [
331031
- getOptionalExchangeString(exchange, "apiKey"),
331032
- getOptionalExchangeString(exchange, "secret")
331033
- ].filter((value) => value !== null);
331034
- this.ws = createWebSocket(getBinanceSpotWsApiUrl(exchange));
331035
- this.ws.on("open", () => this.subscribe());
331036
- this.ws.on("message", (data) => this.handleMessage(data));
331037
- this.ws.on("error", (error48) => this.fail(formatBinanceUserDataWebSocketError(error48, this.secretValues)));
331038
- this.ws.on("close", (code, reason) => this.handleClose(code, reason));
332287
+ // src/helpers/market-data-archive/capture.ts
332288
+ async function recordWatchMetric(otelMetrics, metricName, labels) {
332289
+ try {
332290
+ await otelMetrics?.recordCounter(metricName, 1, labels);
332291
+ } catch {}
332292
+ }
332293
+ function watchLabels(stream4, input) {
332294
+ return {
332295
+ stream: stream4,
332296
+ exchange: input.exchange,
332297
+ symbol: input.symbol
332298
+ };
332299
+ }
332300
+ function archiveOrderbookInBackground(archiver, otelMetrics, input, options) {
332301
+ const labels = watchLabels("orderbook", input);
332302
+ recordWatchMetric(otelMetrics, "cex_watch_frames_received_total", labels);
332303
+ if (!isMarketArchiveEnabled() || !archiver?.isEnabled()) {
332304
+ return;
331039
332305
  }
331040
- async* [Symbol.asyncIterator]() {
331041
- while (true) {
331042
- const event = await this.nextEvent();
331043
- if (!event) {
331044
- break;
331045
- }
331046
- yield event;
331047
- }
332306
+ if (options?.sampledOut) {
332307
+ recordWatchMetric(otelMetrics, "cex_watch_frames_sampled_out_total", labels);
332308
+ return;
331048
332309
  }
331049
- close() {
331050
- if (this.closed) {
331051
- return;
331052
- }
331053
- this.closed = true;
331054
- this.queue.length = 0;
332310
+ queueMicrotask(() => {
331055
332311
  try {
331056
- this.ws.close();
331057
- } catch {}
331058
- this.flushWaiters();
331059
- }
331060
- handleClose(code, reason) {
331061
- if (this.closed) {
331062
- return;
332312
+ const row = buildOrderbookSnapshotRow(input);
332313
+ if (row) {
332314
+ archiver.enqueue(row);
332315
+ recordWatchMetric(otelMetrics, "cex_watch_frames_archived_total", labels);
332316
+ }
332317
+ } catch (error48) {
332318
+ log.warn("Failed to archive orderbook snapshot", { error: error48 });
331063
332319
  }
331064
- this.fail(formatBinanceUserDataWebSocketClose(code, reason, this.secretValues));
331065
- }
331066
- subscribe() {
331067
- const apiKey = getExchangeString(this.exchange, "apiKey");
331068
- const signedParams = signUserDataStreamParams(this.exchange, {
331069
- apiKey,
331070
- timestamp: Date.now()
331071
- });
331072
- this.ws.send(JSON.stringify({
331073
- id: this.requestId,
331074
- method: "userDataStream.subscribe.signature",
331075
- params: signedParams
331076
- }));
332320
+ });
332321
+ }
332322
+ function archiveOhlcvInBackground(archiver, otelMetrics, tracker, input) {
332323
+ const labels = watchLabels("ohlcv", input);
332324
+ recordWatchMetric(otelMetrics, "cex_watch_frames_received_total", labels);
332325
+ if (!isMarketArchiveEnabled() || !archiver?.isEnabled()) {
332326
+ return;
331077
332327
  }
331078
- handleMessage(data) {
331079
- if (this.closed) {
331080
- return;
331081
- }
331082
- let message;
332328
+ queueMicrotask(() => {
331083
332329
  try {
331084
- const decodedData = decodeMessageData(data);
331085
- message = typeof decodedData === "string" ? JSON.parse(decodedData) : decodedData;
332330
+ const candidates = tracker.process(input.payload, input.receivedTimestamp);
332331
+ for (const candidate of candidates) {
332332
+ const row = buildCandleRow({
332333
+ context: input,
332334
+ bar: candidate.bar,
332335
+ isClosed: candidate.isClosed,
332336
+ brokerVersion: candidate.brokerVersion,
332337
+ receivedTimestamp: input.receivedTimestamp
332338
+ });
332339
+ archiver.enqueue(row);
332340
+ }
332341
+ if (candidates.length > 0) {
332342
+ recordWatchMetric(otelMetrics, "cex_watch_frames_archived_total", labels);
332343
+ }
331086
332344
  } catch (error48) {
331087
- this.fail(error48 instanceof Error ? error48 : new Error("Invalid Binance user-data message"));
331088
- return;
332345
+ log.warn("Failed to archive OHLCV candle", { error: error48 });
331089
332346
  }
331090
- if ("id" in message && message.id === this.requestId) {
331091
- if (message.status !== 200) {
331092
- this.fail(new Error(message.error?.msg ?? message.error?.message ?? `Binance user-data subscription failed with status ${message.status}`));
331093
- return;
332347
+ });
332348
+ }
332349
+ function createOrderbookSampler() {
332350
+ return new OrderbookSampler;
332351
+ }
332352
+ function createOhlcvBarTracker() {
332353
+ return new OhlcvBarTracker;
332354
+ }
332355
+ function archiveMarketRowsInBackground(archiver, otelMetrics, stream4, input, enqueueRows) {
332356
+ const labels = watchLabels(stream4, input);
332357
+ recordWatchMetric(otelMetrics, "cex_watch_frames_received_total", labels);
332358
+ if (!isMarketArchiveEnabled() || !archiver?.isEnabled()) {
332359
+ return;
332360
+ }
332361
+ queueMicrotask(() => {
332362
+ try {
332363
+ const rows = enqueueRows();
332364
+ for (const row of rows) {
332365
+ archiver.enqueue(row);
331094
332366
  }
331095
- this.subscriptionId = message.result?.subscriptionId ?? null;
331096
- return;
331097
- }
331098
- if (!("event" in message) || !message.event) {
331099
- return;
331100
- }
331101
- const subscriptionId = message.subscriptionId ?? this.subscriptionId;
331102
- if (subscriptionId === null || subscriptionId === undefined) {
331103
- return;
332367
+ if (rows.length > 0) {
332368
+ recordWatchMetric(otelMetrics, "cex_watch_frames_archived_total", labels);
332369
+ }
332370
+ } catch (error48) {
332371
+ log.warn(`Failed to archive ${stream4} market data`, { error: error48 });
331104
332372
  }
331105
- this.push({ subscriptionId, event: message.event });
332373
+ });
332374
+ }
332375
+ function archiveTradesInBackground(archiver, otelMetrics, input) {
332376
+ archiveMarketRowsInBackground(archiver, otelMetrics, "trades", input, () => extractTrades(input.payload, input.receivedTimestamp).map((trade) => buildCexTradeRow(input, trade)));
332377
+ }
332378
+ function archiveTickerInBackground(archiver, otelMetrics, input) {
332379
+ archiveMarketRowsInBackground(archiver, otelMetrics, "ticker", input, () => {
332380
+ const ticker = parseTicker(input.payload, input.receivedTimestamp);
332381
+ return ticker ? [buildCexTickerEventRow(input, ticker)] : [];
332382
+ });
332383
+ }
332384
+ function archiveCexStreamEventInBackground(archiver, otelMetrics, input) {
332385
+ archiveMarketRowsInBackground(archiver, otelMetrics, "stream", input, () => [
332386
+ buildCexStreamEventRow(input)
332387
+ ]);
332388
+ }
332389
+ // src/helpers/market-data-archive/ohlcv-bootstrap.ts
332390
+ var DEFAULT_OHLCV_BOOTSTRAP_LIMIT = 100;
332391
+ var MAX_OHLCV_BOOTSTRAP_LIMIT = 1000;
332392
+ function resolveOhlcvBootstrapLimit(optionValue) {
332393
+ const raw = optionValue?.trim() || process.env.CEX_BROKER_OHLCV_ARCHIVE_BOOTSTRAP_LIMIT?.trim();
332394
+ if (raw === "0" || raw?.toLowerCase() === "false") {
332395
+ return 0;
331106
332396
  }
331107
- push(event) {
331108
- if (this.closed) {
331109
- return;
331110
- }
331111
- const waiter = this.waiters.shift();
331112
- if (waiter) {
331113
- waiter.resolve(event);
331114
- return;
331115
- }
331116
- if (this.queue.length >= this.maxBufferedEvents) {
331117
- this.fail(new Error(`Binance user-data stream buffered event limit exceeded (${this.maxBufferedEvents}); downstream consumer is not keeping up`));
331118
- return;
331119
- }
331120
- this.queue.push(event);
332397
+ if (!raw) {
332398
+ return DEFAULT_OHLCV_BOOTSTRAP_LIMIT;
331121
332399
  }
331122
- nextEvent() {
331123
- const event = this.queue.shift();
331124
- if (event) {
331125
- return Promise.resolve(event);
331126
- }
331127
- if (this.closeError) {
331128
- return Promise.reject(this.closeError);
331129
- }
331130
- if (this.closed) {
331131
- return Promise.resolve(null);
331132
- }
331133
- return new Promise((resolve, reject) => {
331134
- this.waiters.push({ resolve, reject });
331135
- });
332400
+ const parsed = Number.parseInt(raw, 10);
332401
+ if (!Number.isFinite(parsed) || parsed <= 0) {
332402
+ return DEFAULT_OHLCV_BOOTSTRAP_LIMIT;
331136
332403
  }
331137
- fail(error48) {
331138
- if (this.closeError) {
331139
- return;
331140
- }
331141
- this.closeError = error48;
331142
- this.closed = true;
331143
- this.queue.length = 0;
331144
- this.flushWaiters();
331145
- try {
331146
- this.ws.close();
331147
- } catch {}
332404
+ return Math.min(parsed, MAX_OHLCV_BOOTSTRAP_LIMIT);
332405
+ }
332406
+
332407
+ // src/helpers/market-data-archive/ohlcv-history.ts
332408
+ function supportsFetchOhlcv(broker) {
332409
+ const fetchOHLCV = broker.fetchOHLCV;
332410
+ const hasValue = broker.has?.fetchOHLCV;
332411
+ return typeof fetchOHLCV === "function" && hasValue !== false;
332412
+ }
332413
+ async function bootstrapOhlcvHistory(broker, archiver, otelMetrics, tracker, input, options) {
332414
+ const limit = resolveOhlcvBootstrapLimit(options?.bootstrapLimit);
332415
+ if (limit <= 0 || !supportsFetchOhlcv(broker)) {
332416
+ return null;
331148
332417
  }
331149
- flushWaiters() {
331150
- const error48 = this.closeError;
331151
- for (const waiter of this.waiters.splice(0)) {
331152
- if (error48) {
331153
- waiter.reject(error48);
331154
- } else {
331155
- waiter.resolve(null);
331156
- }
332418
+ try {
332419
+ const fetchOHLCV = broker.fetchOHLCV.bind(broker);
332420
+ const payload = await fetchOHLCV(input.symbol, input.timeframe ?? "1m", undefined, limit);
332421
+ if (!Array.isArray(payload) || payload.length === 0) {
332422
+ return null;
331157
332423
  }
332424
+ const receivedTimestamp = Date.now();
332425
+ archiveOhlcvInBackground(archiver, otelMetrics, tracker, {
332426
+ ...input,
332427
+ payload,
332428
+ receivedTimestamp
332429
+ });
332430
+ return payload;
332431
+ } catch (error48) {
332432
+ log.warn("OHLCV bootstrap fetch failed", {
332433
+ error: error48,
332434
+ symbol: input.symbol,
332435
+ exchange: input.exchange,
332436
+ timeframe: input.timeframe
332437
+ });
332438
+ return null;
331158
332439
  }
331159
332440
  }
331160
- function isBinanceBalanceUserDataEvent(event) {
331161
- return event.e === "outboundAccountPosition" || event.e === "balanceUpdate" || event.e === "externalLockUpdate";
331162
- }
331163
- function isBinanceOrderUserDataEvent(event) {
331164
- return event.e === "executionReport" || event.e === "listStatus";
331165
- }
331166
-
331167
332441
  // src/handlers/subscribe/handler.ts
331168
332442
  function isBinanceSpotAccountSubscription(cex3, subscriptionType, marketTypeInput) {
331169
332443
  return cex3 === "binance" && parseMarketType(marketTypeInput) === "spot" && (subscriptionType === SubscriptionType.BALANCE || subscriptionType === SubscriptionType.ORDERS);
@@ -331232,7 +332506,7 @@ async function getBinanceMarketId(broker, symbol2) {
331232
332506
  }
331233
332507
  return symbol2.replace("/", "").toUpperCase();
331234
332508
  }
331235
- async function streamBinanceUserData(call, broker, symbol2, subscriptionType, isClosed) {
332509
+ async function streamBinanceUserData(call, broker, symbol2, subscriptionType, isClosed, archiveContext) {
331236
332510
  const userDataStream = new BinanceSpotUserDataStream(broker);
331237
332511
  call.once("close", () => userDataStream.close());
331238
332512
  call.once("cancelled", () => userDataStream.close());
@@ -331257,37 +332531,78 @@ async function streamBinanceUserData(call, broker, symbol2, subscriptionType, is
331257
332531
  continue;
331258
332532
  }
331259
332533
  }
332534
+ const receivedTimestamp = Date.now();
331260
332535
  if (!await writeSubscribeFrame(call, isClosed, {
331261
332536
  data: JSON.stringify({
331262
332537
  subscriptionId: message.subscriptionId,
331263
332538
  event
331264
332539
  }),
331265
- timestamp: Date.now(),
332540
+ timestamp: receivedTimestamp,
331266
332541
  symbol: symbol2,
331267
332542
  type: subscriptionType
331268
332543
  })) {
331269
332544
  break;
331270
332545
  }
332546
+ const archiveSubscriptionType = subscriptionType === SubscriptionType.BALANCE ? "BALANCE" : "ORDERS";
332547
+ archiveSubscribeStreamInBackground(archiveContext?.archiver, {
332548
+ exchange: archiveContext?.exchange ?? "binance",
332549
+ accountSelector: archiveContext?.accountSelector,
332550
+ symbol: symbol2,
332551
+ subscriptionType: archiveSubscriptionType,
332552
+ streamPayload: event
332553
+ });
332554
+ if (archiveContext) {
332555
+ archiveCexStreamEventInBackground(archiveContext.archiver, archiveContext.otelMetrics, {
332556
+ deploymentId: archiveContext.deploymentId,
332557
+ exchange: archiveContext.exchange,
332558
+ symbol: symbol2,
332559
+ assetType: archiveContext.assetType,
332560
+ accountSelector: archiveContext.accountSelector,
332561
+ streamType: archiveSubscriptionType,
332562
+ payload: event,
332563
+ receivedTimestamp
332564
+ });
332565
+ }
331271
332566
  }
331272
332567
  } finally {
331273
332568
  userDataStream.close();
331274
332569
  }
331275
332570
  }
331276
- async function runCcxtSubscribeLoop(call, isClosed, symbol2, subscriptionType, watch) {
332571
+ async function runCcxtSubscribeLoop(call, isClosed, symbol2, subscriptionType, watch, archiveContext) {
331277
332572
  while (!isClosed()) {
331278
332573
  const data = await watch();
332574
+ const receivedTimestamp = Date.now();
331279
332575
  if (!await writeSubscribeFrame(call, isClosed, {
331280
332576
  data: JSON.stringify(data),
331281
- timestamp: Date.now(),
332577
+ timestamp: receivedTimestamp,
331282
332578
  symbol: symbol2,
331283
332579
  type: subscriptionType
331284
332580
  })) {
331285
332581
  break;
331286
332582
  }
332583
+ if (archiveContext?.archiveSubscriptionType) {
332584
+ archiveSubscribeStreamInBackground(archiveContext.archiver, {
332585
+ exchange: archiveContext.exchange,
332586
+ accountSelector: archiveContext.accountSelector,
332587
+ symbol: symbol2,
332588
+ subscriptionType: archiveContext.archiveSubscriptionType,
332589
+ streamPayload: data
332590
+ });
332591
+ archiveCexStreamEventInBackground(archiveContext.archiver, archiveContext.otelMetrics, {
332592
+ deploymentId: archiveContext.deploymentId,
332593
+ exchange: archiveContext.exchange,
332594
+ symbol: symbol2,
332595
+ assetType: archiveContext.assetType,
332596
+ accountSelector: archiveContext.accountSelector,
332597
+ streamType: archiveContext.archiveSubscriptionType,
332598
+ payload: data,
332599
+ receivedTimestamp
332600
+ });
332601
+ }
331287
332602
  }
331288
332603
  }
331289
332604
  function createSubscribeHandler(deps) {
331290
- const { brokers, whitelistIps, otelMetrics } = deps;
332605
+ const { brokers, whitelistIps, otelMetrics, brokerArchiver } = deps;
331291
332606
  return async (call) => {
331292
332607
  const subscribeStartTime = Date.now();
331293
332608
  let streamClosed = false;
@@ -331353,7 +332668,9 @@ function createSubscribeHandler(deps) {
331353
332668
  return;
331354
332669
  }
331355
332670
  const normalizedCex = cex3.trim().toLowerCase();
331356
- const selectedBroker = selectBroker(brokers[normalizedCex], metadata) ?? createBroker(normalizedCex, metadata);
332671
+ const brokerPool = brokers[normalizedCex];
332672
+ const selectedBrokerAccount = selectBrokerAccount(brokerPool, metadata);
332673
+ const selectedBroker = selectedBrokerAccount?.exchange ?? createBroker(normalizedCex, metadata);
331357
332674
  const broker = selectedBroker ?? createPublicBroker(normalizedCex);
331358
332675
  if (!broker) {
331359
332676
  await writeSubscribeError(call, isStreamClosed, {
@@ -331367,8 +332684,19 @@ function createSubscribeHandler(deps) {
331367
332684
  return;
331368
332685
  }
331369
332686
  const resolvedSymbol = await resolveSubscriptionSymbol(broker, symbol2, options?.marketType);
332687
+ const assetType = parseMarketType(options?.marketType);
332688
+ const deploymentId = brokerArchiver?.getDeploymentId() ?? "unknown";
332689
+ const streamArchiveContext = {
332690
+ archiver: brokerArchiver,
332691
+ otelMetrics,
332692
+ exchange: normalizedCex,
332693
+ accountSelector: selectedBrokerAccount?.label,
332694
+ deploymentId,
332695
+ assetType
332696
+ };
331370
332697
  if (isBinanceSpotAccountSubscription(normalizedCex, subscriptionType, options?.marketType)) {
331371
- if (!selectedBroker) {
332698
+ const accountBroker = selectedBrokerAccount?.exchange ?? selectedBroker;
332699
+ if (!accountBroker) {
331372
332700
  await writeSubscribeError(call, isStreamClosed, {
331373
332701
  data: JSON.stringify({
331374
332702
  error: "Binance account subscriptions require API credentials"
@@ -331379,29 +332707,42 @@ function createSubscribeHandler(deps) {
331379
332707
  });
331380
332708
  return;
331381
332709
  }
331382
- await streamBinanceUserData(call, selectedBroker, resolvedSymbol, subscriptionType, isStreamClosed);
332710
+ await streamBinanceUserData(call, accountBroker, resolvedSymbol, subscriptionType, isStreamClosed, streamArchiveContext);
331383
332711
  return;
331384
332712
  }
331385
332713
  switch (subscriptionType) {
331386
332714
  case SubscriptionType.ORDERBOOK:
331387
332715
  try {
332716
+ const orderbookSampler = createOrderbookSampler();
331388
332717
  while (!isStreamClosed()) {
331389
332718
  const depthLimit = parseOptionalDepthLimit(options?.depthLimit ?? options?.limit);
331390
332719
  const orderbook = depthLimit === undefined ? await broker.watchOrderBook(resolvedSymbol) : await broker.watchOrderBook(resolvedSymbol, depthLimit);
331391
332720
  const receivedTimestamp = Date.now();
332721
+ const normalizedSnapshot = normalizeOrderBookSnapshot(orderbook, {
332722
+ exchange: normalizedCex,
332723
+ symbol: resolvedSymbol,
332724
+ depthLimit: depthLimit ?? Math.max(Array.isArray(orderbook?.bids) ? orderbook.bids.length : 0, Array.isArray(orderbook?.asks) ? orderbook.asks.length : 0),
332725
+ receivedTimestamp
332726
+ });
331392
332727
  if (!await writeSubscribeFrame(call, isStreamClosed, {
331393
- data: JSON.stringify(normalizeOrderBookSnapshot(orderbook, {
331394
- exchange: normalizedCex,
331395
- symbol: resolvedSymbol,
331396
- depthLimit: depthLimit ?? Math.max(Array.isArray(orderbook?.bids) ? orderbook.bids.length : 0, Array.isArray(orderbook?.asks) ? orderbook.asks.length : 0),
331397
- receivedTimestamp
331398
- })),
332728
+ data: JSON.stringify(normalizedSnapshot),
331399
332729
  timestamp: receivedTimestamp,
331400
332730
  symbol: resolvedSymbol,
331401
332731
  type: subscriptionType
331402
332732
  })) {
331403
332733
  break;
331404
332734
  }
332735
+ const shouldArchive = orderbookSampler.shouldEmit(receivedTimestamp);
332736
+ archiveOrderbookInBackground(brokerArchiver, otelMetrics, {
332737
+ deploymentId,
332738
+ exchange: normalizedCex,
332739
+ symbol: resolvedSymbol,
332740
+ assetType,
332741
+ accountSelector: selectedBrokerAccount?.label,
332742
+ snapshot: normalizedSnapshot
332743
+ }, {
332744
+ sampledOut: !shouldArchive
332745
+ });
331405
332746
  }
331406
332747
  } catch (error48) {
331407
332748
  log.error(`Error fetching orderbook for ${resolvedSymbol} on ${cex3}:`, error48);
@@ -331418,7 +332759,27 @@ function createSubscribeHandler(deps) {
331418
332759
  break;
331419
332760
  case SubscriptionType.TRADES:
331420
332761
  try {
331421
- await runCcxtSubscribeLoop(call, isStreamClosed, resolvedSymbol, subscriptionType, () => broker.watchTrades(resolvedSymbol));
332762
+ while (!isStreamClosed()) {
332763
+ const data = await broker.watchTrades(resolvedSymbol);
332764
+ const receivedTimestamp = Date.now();
332765
+ if (!await writeSubscribeFrame(call, isStreamClosed, {
332766
+ data: JSON.stringify(data),
332767
+ timestamp: receivedTimestamp,
332768
+ symbol: resolvedSymbol,
332769
+ type: subscriptionType
332770
+ })) {
332771
+ break;
332772
+ }
332773
+ archiveTradesInBackground(brokerArchiver, otelMetrics, {
332774
+ deploymentId,
332775
+ exchange: normalizedCex,
332776
+ symbol: resolvedSymbol,
332777
+ assetType,
332778
+ accountSelector: selectedBrokerAccount?.label,
332779
+ payload: data,
332780
+ receivedTimestamp
332781
+ });
332782
+ }
331422
332783
  } catch (error48) {
331423
332784
  const message = getErrorMessage(error48);
331424
332785
  log.error(`Error fetching trades for ${resolvedSymbol} on ${cex3}:`, error48);
@@ -331434,7 +332795,27 @@ function createSubscribeHandler(deps) {
331434
332795
  break;
331435
332796
  case SubscriptionType.TICKER:
331436
332797
  try {
331437
- await runCcxtSubscribeLoop(call, isStreamClosed, resolvedSymbol, subscriptionType, () => broker.watchTicker(resolvedSymbol));
332798
+ while (!isStreamClosed()) {
332799
+ const data = await broker.watchTicker(resolvedSymbol);
332800
+ const receivedTimestamp = Date.now();
332801
+ if (!await writeSubscribeFrame(call, isStreamClosed, {
332802
+ data: JSON.stringify(data),
332803
+ timestamp: receivedTimestamp,
332804
+ symbol: resolvedSymbol,
332805
+ type: subscriptionType
332806
+ })) {
332807
+ break;
332808
+ }
332809
+ archiveTickerInBackground(brokerArchiver, otelMetrics, {
332810
+ deploymentId,
332811
+ exchange: normalizedCex,
332812
+ symbol: resolvedSymbol,
332813
+ assetType,
332814
+ accountSelector: selectedBrokerAccount?.label,
332815
+ payload: data,
332816
+ receivedTimestamp
332817
+ });
332818
+ }
331438
332819
  } catch (error48) {
331439
332820
  const message = getErrorMessage(error48);
331440
332821
  log.error(`Error fetching ticker for ${resolvedSymbol} on ${cex3}:`, error48);
@@ -331451,7 +332832,53 @@ function createSubscribeHandler(deps) {
331451
332832
  case SubscriptionType.OHLCV:
331452
332833
  try {
331453
332834
  const timeframe = options?.timeframe || "1m";
331454
- await runCcxtSubscribeLoop(call, isStreamClosed, resolvedSymbol, subscriptionType, () => broker.fetchOHLCVWs(resolvedSymbol, timeframe));
332835
+ const ohlcvBarTracker = createOhlcvBarTracker();
332836
+ const ohlcvArchiveInput = {
332837
+ deploymentId,
332838
+ exchange: normalizedCex,
332839
+ symbol: resolvedSymbol,
332840
+ assetType,
332841
+ timeframe,
332842
+ accountSelector: selectedBrokerAccount?.label,
332843
+ receivedTimestamp: Date.now(),
332844
+ payload: []
332845
+ };
332846
+ const bootstrapPayload = await bootstrapOhlcvHistory(broker, brokerArchiver, otelMetrics, ohlcvBarTracker, ohlcvArchiveInput, { bootstrapLimit: options?.bootstrapLimit });
332847
+ let ohlcvStreamActive = true;
332848
+ if (bootstrapPayload) {
332849
+ const bootstrapTimestamp = Date.now();
332850
+ const bootstrapSent = await writeSubscribeFrame(call, isStreamClosed, {
332851
+ data: JSON.stringify(bootstrapPayload),
332852
+ timestamp: bootstrapTimestamp,
332853
+ symbol: resolvedSymbol,
332854
+ type: subscriptionType
332855
+ });
332856
+ if (!bootstrapSent) {
332857
+ ohlcvStreamActive = false;
332858
+ }
332859
+ }
332860
+ while (ohlcvStreamActive && !isStreamClosed()) {
332861
+ const data = await broker.fetchOHLCVWs(resolvedSymbol, timeframe);
332862
+ const receivedTimestamp = Date.now();
332863
+ if (!await writeSubscribeFrame(call, isStreamClosed, {
332864
+ data: JSON.stringify(data),
332865
+ timestamp: receivedTimestamp,
332866
+ symbol: resolvedSymbol,
332867
+ type: subscriptionType
332868
+ })) {
332869
+ break;
332870
+ }
332871
+ archiveOhlcvInBackground(brokerArchiver, otelMetrics, ohlcvBarTracker, {
332872
+ deploymentId,
332873
+ exchange: normalizedCex,
332874
+ symbol: resolvedSymbol,
332875
+ assetType,
332876
+ timeframe,
332877
+ accountSelector: selectedBrokerAccount?.label,
332878
+ payload: data,
332879
+ receivedTimestamp
332880
+ });
332881
+ }
331455
332882
  } catch (error48) {
331456
332883
  log.error(`Error fetching OHLCV for ${resolvedSymbol} on ${cex3}:`, error48);
331457
332884
  const message = getErrorMessage(error48);
@@ -331467,7 +332894,10 @@ function createSubscribeHandler(deps) {
331467
332894
  break;
331468
332895
  case SubscriptionType.BALANCE:
331469
332896
  try {
331470
- await runCcxtSubscribeLoop(call, isStreamClosed, symbol2, subscriptionType, () => broker.watchBalance());
332897
+ await runCcxtSubscribeLoop(call, isStreamClosed, symbol2, subscriptionType, () => broker.watchBalance(), {
332898
+ ...streamArchiveContext,
332899
+ archiveSubscriptionType: "BALANCE"
332900
+ });
331471
332901
  } catch (error48) {
331472
332902
  const message = getErrorMessage(error48);
331473
332903
  log.error(`Error fetching balance for ${cex3}:`, error48);
@@ -331483,7 +332913,10 @@ function createSubscribeHandler(deps) {
331483
332913
  break;
331484
332914
  case SubscriptionType.ORDERS:
331485
332915
  try {
331486
- await runCcxtSubscribeLoop(call, isStreamClosed, resolvedSymbol, subscriptionType, () => broker.watchOrders(resolvedSymbol));
332916
+ await runCcxtSubscribeLoop(call, isStreamClosed, resolvedSymbol, subscriptionType, () => broker.watchOrders(resolvedSymbol), {
332917
+ ...streamArchiveContext,
332918
+ archiveSubscriptionType: "ORDERS"
332919
+ });
331487
332920
  } catch (error48) {
331488
332921
  log.error(`Error fetching orders for ${resolvedSymbol} on ${cex3}:`, error48);
331489
332922
  const message = getErrorMessage(error48);
@@ -331664,7 +333097,7 @@ var CEX_BROKER_PACKAGE_DEFINITION = protoLoader.fromJSON(node_descriptor_default
331664
333097
  // src/server.ts
331665
333098
  var grpcObj = grpc14.loadPackageDefinition(CEX_BROKER_PACKAGE_DEFINITION);
331666
333099
  var cexNode = grpcObj.cex_broker;
331667
- function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, otelMetrics) {
333100
+ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, otelMetrics, brokerArchiver) {
331668
333101
  const server = new grpc14.Server;
331669
333102
  server.addService(cexNode.cex_service.service, {
331670
333103
  ExecuteAction: createExecuteActionHandler({
@@ -331673,12 +333106,14 @@ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, ot
331673
333106
  whitelistIps,
331674
333107
  useVerity,
331675
333108
  verityProverUrl,
331676
- otelMetrics
333109
+ otelMetrics,
333110
+ brokerArchiver
331677
333111
  }),
331678
333112
  Subscribe: createSubscribeHandler({
331679
333113
  brokers,
331680
333114
  whitelistIps,
331681
- otelMetrics
333115
+ otelMetrics,
333116
+ brokerArchiver
331682
333117
  })
331683
333118
  });
331684
333119
  return server;
@@ -331815,6 +333250,7 @@ class CEXBroker {
331815
333250
  useVerity = false;
331816
333251
  otelMetrics;
331817
333252
  otelLogs;
333253
+ brokerArchiver;
331818
333254
  depositReconciler;
331819
333255
  loadEnvConfig() {
331820
333256
  log.info("\uD83D\uDD27 Loading CEX_BROKER_ environment variables:");
@@ -331926,6 +333362,7 @@ class CEXBroker {
331926
333362
  this.otelMetrics = createOtelMetricsFromEnv();
331927
333363
  this.otelLogs = createOtelLogsFromEnv();
331928
333364
  }
333365
+ this.brokerArchiver = createBrokerExecutionArchiverFromEnv(this.otelLogs, this.otelMetrics);
331929
333366
  this.loadExchangeCredentials(apiCredentials);
331930
333367
  this.whitelistIps = [
331931
333368
  ...(config2 ?? { whitelistIps: [] }).whitelistIps ?? [],
@@ -331958,6 +333395,9 @@ class CEXBroker {
331958
333395
  if (this.server) {
331959
333396
  await this.server.forceShutdown();
331960
333397
  }
333398
+ if (this.brokerArchiver) {
333399
+ await this.brokerArchiver.close();
333400
+ }
331961
333401
  if (this.otelMetrics) {
331962
333402
  await this.otelMetrics.close();
331963
333403
  }
@@ -331977,7 +333417,7 @@ class CEXBroker {
331977
333417
  if (this.otelMetrics?.isOtelEnabled()) {
331978
333418
  await this.otelMetrics.initialize();
331979
333419
  }
331980
- this.server = getServer(this.policy, this.brokers, this.whitelistIps, this.useVerity, this.#verityProverUrl, this.otelMetrics);
333420
+ this.server = getServer(this.policy, this.brokers, this.whitelistIps, this.useVerity, this.#verityProverUrl, this.otelMetrics, this.brokerArchiver);
331981
333421
  this.server.bindAsync(`0.0.0.0:${this.port}`, grpc15.ServerCredentials.createInsecure(), (err2, port) => {
331982
333422
  if (err2) {
331983
333423
  log.error(err2);