@usherlabs/cex-broker 0.2.34 → 0.2.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -292048,8 +292048,256 @@ class AccountBalanceArchivePoller {
292048
292048
  }
292049
292049
  }
292050
292050
 
292051
- // src/helpers/fill-archive-poller.ts
292051
+ // src/helpers/deposit-archive-poller.ts
292052
292052
  var DEFAULT_CONFIG2 = {
292053
+ pollIntervalMs: 60000,
292054
+ lookbackMs: 24 * 60 * 60 * 1000,
292055
+ depositsLimit: 50
292056
+ };
292057
+ var ALL_CURRENCIES_CODE = "*";
292058
+ function depositTimestamp(record) {
292059
+ const observedAt = depositField(record, [
292060
+ "timestamp",
292061
+ "creditedAt",
292062
+ "credited_at",
292063
+ "updated",
292064
+ "updatedAt",
292065
+ "datetime"
292066
+ ]);
292067
+ const timestamp = typeof observedAt === "number" ? observedAt : typeof observedAt === "string" ? Date.parse(observedAt) : Number.NaN;
292068
+ return Number.isFinite(timestamp) ? timestamp : undefined;
292069
+ }
292070
+ function archivedDepositStatus(exchangeId, record) {
292071
+ const rawStatus = asRecord(record.info)?.status;
292072
+ if (exchangeId?.toLowerCase() === "binance" && String(rawStatus ?? "").trim() === "6") {
292073
+ return "credited_not_withdrawable";
292074
+ }
292075
+ return normalizeCcxtTransactionForArchive(record).status;
292076
+ }
292077
+ function nextDepositCursor(deposits, currentSince, depositsLimit, exchangeId) {
292078
+ if (deposits.length >= depositsLimit) {
292079
+ return currentSince;
292080
+ }
292081
+ let next = currentSince;
292082
+ let oldestPending = Number.POSITIVE_INFINITY;
292083
+ for (const deposit of deposits) {
292084
+ const record = asRecord(deposit);
292085
+ if (!record) {
292086
+ return currentSince;
292087
+ }
292088
+ const timestamp = depositTimestamp(record);
292089
+ const archiveStatus = archivedDepositStatus(exchangeId, record);
292090
+ const status = normalizeDepositStatus(depositField(record, ["status", "state"]));
292091
+ const isPending = archiveStatus === "credited_not_withdrawable" || status === "pending";
292092
+ if (timestamp === undefined) {
292093
+ if (isPending) {
292094
+ return currentSince;
292095
+ }
292096
+ continue;
292097
+ }
292098
+ if (isPending) {
292099
+ oldestPending = Math.min(oldestPending, timestamp);
292100
+ } else {
292101
+ next = Math.max(next, timestamp + 1);
292102
+ }
292103
+ }
292104
+ return Number.isFinite(oldestPending) ? Math.max(currentSince, oldestPending) : next;
292105
+ }
292106
+
292107
+ class DepositArchivePoller {
292108
+ params;
292109
+ #timer = null;
292110
+ #stopped = false;
292111
+ #running = null;
292112
+ #cursors = new Map;
292113
+ #lastArchivedByTarget = new Map;
292114
+ #unsupportedLogged = new Set;
292115
+ #config;
292116
+ constructor(params) {
292117
+ this.params = params;
292118
+ this.#config = { ...DEFAULT_CONFIG2, ...params.config };
292119
+ }
292120
+ start() {
292121
+ if (this.#timer || this.#stopped || !this.params.archiver.isEnabled()) {
292122
+ return;
292123
+ }
292124
+ log.info("\uD83D\uDCE5 Deposit archive poller started");
292125
+ this.#schedule(0);
292126
+ }
292127
+ async stop() {
292128
+ this.#stopped = true;
292129
+ if (this.#timer) {
292130
+ clearTimeout(this.#timer);
292131
+ this.#timer = null;
292132
+ }
292133
+ await this.#running;
292134
+ }
292135
+ async pollAllOnce() {
292136
+ if (this.#stopped || this.#running || !this.params.archiver.isEnabled()) {
292137
+ return false;
292138
+ }
292139
+ this.#running = this.#pollAllSequentially();
292140
+ try {
292141
+ return await this.#running;
292142
+ } finally {
292143
+ this.#running = null;
292144
+ }
292145
+ }
292146
+ #targets() {
292147
+ const targets = [];
292148
+ for (const [exchangeId, pool] of Object.entries(this.params.brokers)) {
292149
+ for (const account of [pool.primary, ...pool.secondaryBrokers]) {
292150
+ targets.push({
292151
+ exchangeId,
292152
+ account,
292153
+ code: ALL_CURRENCIES_CODE
292154
+ });
292155
+ }
292156
+ }
292157
+ return targets;
292158
+ }
292159
+ async#pollAllSequentially() {
292160
+ for (const target of this.#targets()) {
292161
+ if (this.#stopped) {
292162
+ break;
292163
+ }
292164
+ await this.#pollOne(target);
292165
+ }
292166
+ return true;
292167
+ }
292168
+ async#pollOne(target) {
292169
+ const exchange = target.account.exchange;
292170
+ const key = this.#targetKey(target);
292171
+ if (typeof exchange.fetchDeposits !== "function" || exchange.has?.fetchDeposits === false) {
292172
+ if (!this.#unsupportedLogged.has(key)) {
292173
+ this.#unsupportedLogged.add(key);
292174
+ log.info("Deposit archive poll skipped: fetchDeposits unsupported", {
292175
+ exchange: target.exchangeId,
292176
+ account: target.account.label
292177
+ });
292178
+ }
292179
+ return;
292180
+ }
292181
+ const since = this.#cursors.get(key) ?? Date.now() - this.#config.lookbackMs;
292182
+ let deposits;
292183
+ try {
292184
+ deposits = await exchange.fetchDeposits(undefined, since, this.#config.depositsLimit);
292185
+ } catch (error) {
292186
+ this.params.metrics?.recordCounter("cex_deposit_poller_errors_total", 1, { exchange: target.exchangeId });
292187
+ log.warn("Deposit archive poll failed", {
292188
+ exchange: target.exchangeId,
292189
+ account: target.account.label,
292190
+ error
292191
+ });
292192
+ return;
292193
+ }
292194
+ if (!Array.isArray(deposits) || deposits.length === 0) {
292195
+ return;
292196
+ }
292197
+ let archived = 0;
292198
+ for (const deposit of deposits) {
292199
+ const record = asRecord(deposit);
292200
+ if (!record) {
292201
+ continue;
292202
+ }
292203
+ const assetSymbol = depositField(record, ["currency", "code", "asset"]);
292204
+ const amount = depositField(record, ["amount"]);
292205
+ const address = depositField(record, [
292206
+ "address",
292207
+ "recipientAddress",
292208
+ "to",
292209
+ "destination"
292210
+ ]);
292211
+ const txid = depositField(record, ["txid", "txId", "tx_hash", "txHash"]);
292212
+ const network = depositField(record, ["network", "chain"]);
292213
+ const archiveStatus = archivedDepositStatus(target.exchangeId, record);
292214
+ const creditedAt = depositField(record, [
292215
+ "creditedAt",
292216
+ "credited_at",
292217
+ "updated",
292218
+ "updatedAt",
292219
+ "timestamp",
292220
+ "datetime"
292221
+ ]);
292222
+ const depositTxid = txid === undefined ? undefined : String(txid);
292223
+ const lastArchived = depositTxid === undefined ? undefined : this.#lastArchivedByTarget.get(key)?.get(depositTxid);
292224
+ if (lastArchived && lastArchived.status === archiveStatus) {
292225
+ continue;
292226
+ }
292227
+ this.params.archiver.enqueue(buildTransferEventArchiveRow({
292228
+ tags: buildCommonArchiveTags({
292229
+ deploymentId: this.params.archiver.getDeploymentId(),
292230
+ accountSelector: target.account.label,
292231
+ exchange: target.exchangeId,
292232
+ symbol: assetSymbol === undefined ? undefined : String(assetSymbol)
292233
+ }),
292234
+ transfer: {
292235
+ eventKind: "deposit",
292236
+ lifecycleAction: "observe_deposit",
292237
+ status: archiveStatus,
292238
+ amount: amount === undefined ? undefined : String(amount),
292239
+ address: address === undefined ? undefined : String(address),
292240
+ network: network === undefined ? undefined : String(network),
292241
+ externalId: depositTxid,
292242
+ txid: depositTxid,
292243
+ exchangeTimestamp: typeof creditedAt === "string" ? creditedAt : undefined,
292244
+ payload: record
292245
+ }
292246
+ }));
292247
+ if (depositTxid !== undefined) {
292248
+ let targetDeposits2 = this.#lastArchivedByTarget.get(key);
292249
+ if (!targetDeposits2) {
292250
+ targetDeposits2 = new Map;
292251
+ this.#lastArchivedByTarget.set(key, targetDeposits2);
292252
+ }
292253
+ targetDeposits2.set(depositTxid, {
292254
+ status: archiveStatus,
292255
+ timestamp: depositTimestamp(record)
292256
+ });
292257
+ }
292258
+ archived += 1;
292259
+ }
292260
+ if (archived > 0) {
292261
+ this.params.metrics?.recordCounter("cex_deposit_poller_deposits_archived_total", archived, { exchange: target.exchangeId });
292262
+ }
292263
+ const nextCursor = nextDepositCursor(deposits, since, this.#config.depositsLimit, target.exchangeId);
292264
+ this.#cursors.set(key, nextCursor);
292265
+ const targetDeposits = this.#lastArchivedByTarget.get(key);
292266
+ if (targetDeposits) {
292267
+ for (const [externalId, lastArchived] of targetDeposits) {
292268
+ if (lastArchived.timestamp !== undefined && lastArchived.timestamp < nextCursor) {
292269
+ targetDeposits.delete(externalId);
292270
+ }
292271
+ }
292272
+ if (targetDeposits.size === 0) {
292273
+ this.#lastArchivedByTarget.delete(key);
292274
+ }
292275
+ }
292276
+ }
292277
+ #targetKey(target) {
292278
+ return `${target.exchangeId}|${target.account.label}|${target.code}`;
292279
+ }
292280
+ #schedule(delayMs) {
292281
+ this.#timer = setTimeout(() => void this.#tick(), delayMs);
292282
+ this.#timer.unref?.();
292283
+ }
292284
+ async#tick() {
292285
+ this.#timer = null;
292286
+ try {
292287
+ await this.pollAllOnce();
292288
+ } catch (error) {
292289
+ rethrowArchiveDurabilityError(error);
292290
+ log.error("Deposit archive poller tick failed", error);
292291
+ } finally {
292292
+ if (!this.#stopped) {
292293
+ this.#schedule(this.#config.pollIntervalMs);
292294
+ }
292295
+ }
292296
+ }
292297
+ }
292298
+
292299
+ // src/helpers/fill-archive-poller.ts
292300
+ var DEFAULT_CONFIG3 = {
292053
292301
  pollIntervalMs: 60000,
292054
292302
  lookbackMs: 24 * 60 * 60 * 1000,
292055
292303
  tradesLimit: 500
@@ -292074,7 +292322,7 @@ class FillArchivePoller {
292074
292322
  #config;
292075
292323
  constructor(params) {
292076
292324
  this.params = params;
292077
- this.#config = { ...DEFAULT_CONFIG2, ...params.config };
292325
+ this.#config = { ...DEFAULT_CONFIG3, ...params.config };
292078
292326
  }
292079
292327
  start() {
292080
292328
  if (this.#timer || this.#stopped) {
@@ -292278,6 +292526,7 @@ function toAttributes(labels, service) {
292278
292526
  class OtelMetrics extends BaseOtelSignal {
292279
292527
  counters = new Map;
292280
292528
  histograms = new Map;
292529
+ observableGauges = new Map;
292281
292530
  constructor(config) {
292282
292531
  super(config, "metrics");
292283
292532
  }
@@ -292363,6 +292612,32 @@ class OtelMetrics extends BaseOtelSignal {
292363
292612
  log.error("Failed to record gauge:", error);
292364
292613
  }
292365
292614
  }
292615
+ async setObservableGauge(metricName, value, labels, service = this.getServiceName()) {
292616
+ const provider = this.getProvider();
292617
+ if (!this.isOtelEnabled() || !provider)
292618
+ return;
292619
+ try {
292620
+ let state = this.observableGauges.get(metricName);
292621
+ if (!state) {
292622
+ const observations = new Map;
292623
+ const instrument = provider.getMeter("cex-broker-metrics", "1.0.0").createObservableGauge(metricName, { description: metricName });
292624
+ instrument.addCallback((result) => {
292625
+ for (const observation of observations.values()) {
292626
+ result.observe(observation.value, observation.attributes);
292627
+ }
292628
+ });
292629
+ state = { instrument, observations };
292630
+ this.observableGauges.set(metricName, state);
292631
+ }
292632
+ const attributes = toAttributes(labels, service);
292633
+ state.observations.set(stableAttributeKey(attributes), {
292634
+ value,
292635
+ attributes
292636
+ });
292637
+ } catch (error) {
292638
+ log.error("Failed to set observable gauge:", error);
292639
+ }
292640
+ }
292366
292641
  async recordHistogram(metricName, value, labels, service = this.getServiceName()) {
292367
292642
  const provider = this.getProvider();
292368
292643
  if (!this.isOtelEnabled() || !provider)
@@ -292467,27 +292742,33 @@ function getOtelProtocolFromEnv() {
292467
292742
  const protocol = process.env.CEX_BROKER_OTEL_PROTOCOL ?? process.env.CEX_BROKER_CLICKHOUSE_PROTOCOL;
292468
292743
  return protocol || "http";
292469
292744
  }
292470
- function createOtelMetricsFromEnv() {
292745
+ function createOtelMetricsFromEnv(options = {}) {
292471
292746
  const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
292472
- const host = getOtelHostFromEnv();
292747
+ const serviceName = process.env.OTEL_SERVICE_NAME || options.defaultServiceName || DEFAULT_SERVICE;
292473
292748
  if (otlpEndpoint) {
292474
292749
  return new OtelMetrics({
292475
- otlpEndpoint: otlpEndpoint.replace(/\/v1\/metrics\/?$/, ""),
292476
- serviceName: process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE
292750
+ otlpEndpoint,
292751
+ serviceName
292477
292752
  });
292478
292753
  }
292479
- if (!host) {
292480
- return new OtelMetrics;
292754
+ if (options.allowLegacyBrokerConfig === false) {
292755
+ return new OtelMetrics({ serviceName });
292481
292756
  }
292757
+ const host = getOtelHostFromEnv();
292758
+ if (!host)
292759
+ return new OtelMetrics({ serviceName });
292482
292760
  const port = getOtelPortFromEnv();
292483
292761
  const config = {
292484
292762
  host,
292485
292763
  port: port ?? DEFAULT_OTLP_PORT,
292486
292764
  protocol: getOtelProtocolFromEnv(),
292487
- serviceName: process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE
292765
+ serviceName
292488
292766
  };
292489
292767
  return new OtelMetrics(config);
292490
292768
  }
292769
+ function stableAttributeKey(attributes) {
292770
+ return JSON.stringify(Object.entries(attributes).sort(([left], [right]) => left.localeCompare(right)));
292771
+ }
292491
292772
  function createOtelLogsFromEnv() {
292492
292773
  const logsEndpoint = process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT;
292493
292774
  const genericEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
@@ -306300,6 +306581,12 @@ function parseActionPayload(schema, rawPayload, callback) {
306300
306581
  // src/helpers/grpc/status.ts
306301
306582
  import * as grpc2 from "@grpc/grpc-js";
306302
306583
  function stableGrpcErrorCode(message) {
306584
+ if (message.startsWith("AuthenticationError:")) {
306585
+ return grpc2.status.UNAUTHENTICATED;
306586
+ }
306587
+ if (message.startsWith("InsufficientFunds:")) {
306588
+ return grpc2.status.FAILED_PRECONDITION;
306589
+ }
306303
306590
  if (message.startsWith("venue_discovery_unavailable:")) {
306304
306591
  return grpc2.status.UNIMPLEMENTED;
306305
306592
  }
@@ -306450,7 +306737,8 @@ async function handleDeposit(ctx) {
306450
306737
  message: `deposit_address_mismatch: expected address ${value.recipientAddress}, observed ${String(observedAddress)}`
306451
306738
  }, null);
306452
306739
  }
306453
- const status4 = normalizeDepositStatus(depositField(deposit, ["status", "state"]));
306740
+ const responseStatus = normalizeDepositStatus(depositField(deposit, ["status", "state"]));
306741
+ const archiveStatus = normalizeCcxtTransactionForArchive(deposit).status;
306454
306742
  const depositTxid = String(depositField(deposit, ["txid", "txId", "tx_hash", "txHash"]) ?? value.transactionHash);
306455
306743
  const creditedAt = depositField(deposit, [
306456
306744
  "creditedAt",
@@ -306467,7 +306755,7 @@ async function handleDeposit(ctx) {
306467
306755
  transfer: {
306468
306756
  eventKind: "deposit",
306469
306757
  lifecycleAction: "observe_deposit",
306470
- status: status4,
306758
+ status: archiveStatus,
306471
306759
  amount: observedAmount !== undefined ? String(observedAmount) : undefined,
306472
306760
  address: String(observedAddress ?? value.recipientAddress),
306473
306761
  network: depositNetwork?.exchangeNetworkId,
@@ -306481,7 +306769,7 @@ async function handleDeposit(ctx) {
306481
306769
  return ctx.wrappedCallback(null, {
306482
306770
  proof: ctx.verity.proof,
306483
306771
  result: JSON.stringify({
306484
- status: status4,
306772
+ status: responseStatus,
306485
306773
  exchange: normalizedCex,
306486
306774
  accountSelector: selectedBrokerAccount?.label,
306487
306775
  asset: symbol2,
@@ -307000,6 +307288,12 @@ function identifiesUnsupported(message) {
307000
307288
  return /post[\s-]?only\b.*\b(?:not supported|unsupported|does not support)\b/.test(normalized) || /\b(?:not supported|unsupported|does not support)\b.*\bpost[\s-]?only\b/.test(normalized);
307001
307289
  }
307002
307290
  function classifyPassiveOrderError(error48) {
307291
+ if (error48 instanceof ccxt_default.InsufficientFunds) {
307292
+ return "InsufficientFunds";
307293
+ }
307294
+ if (error48 instanceof ccxt_default.AuthenticationError) {
307295
+ return "AuthenticationError";
307296
+ }
307003
307297
  const message = getErrorMessage(error48);
307004
307298
  if (error48 instanceof ccxt_default.OrderImmediatelyFillable || identifiesWouldCross(message)) {
307005
307299
  return PASSIVE_ORDER_ERROR_CODES.wouldCross;
@@ -307128,9 +307422,9 @@ async function handleCreateOrder(ctx) {
307128
307422
  emitOrderExecutionTelemetryInBackground(otelMetrics, failedCreateContext, undefined, error48);
307129
307423
  archiveOrderExecutionInBackground(brokerArchiver, failedCreateContext, undefined, error48, { marketMetadataHash });
307130
307424
  if (isPassiveOrder && submission === "in_flight") {
307131
- const passiveErrorCode = classifyPassiveOrderError(error48);
307425
+ const stableErrorCode = classifyPassiveOrderError(error48);
307132
307426
  return rejectWithGrpcError(ctx, error48, {
307133
- message: `${passiveErrorCode}: ${sanitizeErrorDetail(error48)}`,
307427
+ message: `${stableErrorCode}: ${sanitizeErrorDetail(error48)}`,
307134
307428
  preferStableMessageOnly: true
307135
307429
  });
307136
307430
  }
@@ -310030,6 +310324,7 @@ class CEXBroker {
310030
310324
  orderActivityTracker = new OrderActivityTracker;
310031
310325
  withdrawalObservationTracker = new WithdrawalObservationTracker;
310032
310326
  fillArchivePoller;
310327
+ depositArchivePoller;
310033
310328
  accountBalanceArchivePoller;
310034
310329
  loadEnvConfig() {
310035
310330
  log.info("\uD83D\uDD27 Loading CEX_BROKER_ environment variables:");
@@ -310175,6 +310470,10 @@ class CEXBroker {
310175
310470
  this.fillArchivePoller.stop();
310176
310471
  this.fillArchivePoller = undefined;
310177
310472
  }
310473
+ if (this.depositArchivePoller) {
310474
+ await this.depositArchivePoller.stop();
310475
+ this.depositArchivePoller = undefined;
310476
+ }
310178
310477
  if (this.accountBalanceArchivePoller) {
310179
310478
  await this.accountBalanceArchivePoller.stop();
310180
310479
  this.accountBalanceArchivePoller = undefined;
@@ -310204,6 +310503,10 @@ class CEXBroker {
310204
310503
  this.fillArchivePoller.stop();
310205
310504
  this.fillArchivePoller = undefined;
310206
310505
  }
310506
+ if (this.depositArchivePoller) {
310507
+ await this.depositArchivePoller.stop();
310508
+ this.depositArchivePoller = undefined;
310509
+ }
310207
310510
  if (this.accountBalanceArchivePoller) {
310208
310511
  await this.accountBalanceArchivePoller.stop();
310209
310512
  this.accountBalanceArchivePoller = undefined;
@@ -310235,6 +310538,12 @@ class CEXBroker {
310235
310538
  metrics: this.otelMetrics
310236
310539
  });
310237
310540
  this.fillArchivePoller.start();
310541
+ this.depositArchivePoller = new DepositArchivePoller({
310542
+ brokers: this.brokers,
310543
+ archiver: this.brokerArchiver,
310544
+ metrics: this.otelMetrics
310545
+ });
310546
+ this.depositArchivePoller.start();
310238
310547
  }
310239
310548
  if (this.brokerArchiver?.canPersistAccountBalanceSnapshots()) {
310240
310549
  this.accountBalanceArchivePoller = new AccountBalanceArchivePoller({
@@ -310251,4 +310560,4 @@ export {
310251
310560
  CEXBroker as default
310252
310561
  };
310253
310562
 
310254
- //# debugId=DF7A1B19BD16DBE764756E2164756E21
310563
+ //# debugId=10F44147004B63EC64756E2164756E21