@usherlabs/cex-broker 0.2.18 → 0.2.20

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