@usherlabs/cex-broker 0.2.34 → 0.2.35

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,215 @@ 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 nextDepositCursor(deposits, currentSince, depositsLimit) {
292059
+ if (deposits.length >= depositsLimit) {
292060
+ return currentSince;
292061
+ }
292062
+ let next = currentSince;
292063
+ let oldestPending = Number.POSITIVE_INFINITY;
292064
+ for (const deposit of deposits) {
292065
+ const record = asRecord(deposit);
292066
+ if (!record) {
292067
+ return currentSince;
292068
+ }
292069
+ const observedAt = depositField(record, [
292070
+ "timestamp",
292071
+ "creditedAt",
292072
+ "credited_at",
292073
+ "updated",
292074
+ "updatedAt",
292075
+ "datetime"
292076
+ ]);
292077
+ const timestamp = typeof observedAt === "number" ? observedAt : typeof observedAt === "string" ? Date.parse(observedAt) : Number.NaN;
292078
+ const status = normalizeDepositStatus(depositField(record, ["status", "state"]));
292079
+ if (!Number.isFinite(timestamp)) {
292080
+ if (status === "pending") {
292081
+ return currentSince;
292082
+ }
292083
+ continue;
292084
+ }
292085
+ if (status === "pending") {
292086
+ oldestPending = Math.min(oldestPending, timestamp);
292087
+ } else {
292088
+ next = Math.max(next, timestamp + 1);
292089
+ }
292090
+ }
292091
+ return Number.isFinite(oldestPending) ? Math.max(currentSince, oldestPending) : next;
292092
+ }
292093
+
292094
+ class DepositArchivePoller {
292095
+ params;
292096
+ #timer = null;
292097
+ #stopped = false;
292098
+ #running = null;
292099
+ #cursors = new Map;
292100
+ #unsupportedLogged = new Set;
292101
+ #config;
292102
+ constructor(params) {
292103
+ this.params = params;
292104
+ this.#config = { ...DEFAULT_CONFIG2, ...params.config };
292105
+ }
292106
+ start() {
292107
+ if (this.#timer || this.#stopped || !this.params.archiver.isEnabled()) {
292108
+ return;
292109
+ }
292110
+ log.info("\uD83D\uDCE5 Deposit archive poller started");
292111
+ this.#schedule(0);
292112
+ }
292113
+ async stop() {
292114
+ this.#stopped = true;
292115
+ if (this.#timer) {
292116
+ clearTimeout(this.#timer);
292117
+ this.#timer = null;
292118
+ }
292119
+ await this.#running;
292120
+ }
292121
+ async pollAllOnce() {
292122
+ if (this.#stopped || this.#running || !this.params.archiver.isEnabled()) {
292123
+ return false;
292124
+ }
292125
+ this.#running = this.#pollAllSequentially();
292126
+ try {
292127
+ return await this.#running;
292128
+ } finally {
292129
+ this.#running = null;
292130
+ }
292131
+ }
292132
+ #targets() {
292133
+ const targets = [];
292134
+ for (const [exchangeId, pool] of Object.entries(this.params.brokers)) {
292135
+ for (const account of [pool.primary, ...pool.secondaryBrokers]) {
292136
+ targets.push({
292137
+ exchangeId,
292138
+ account,
292139
+ code: ALL_CURRENCIES_CODE
292140
+ });
292141
+ }
292142
+ }
292143
+ return targets;
292144
+ }
292145
+ async#pollAllSequentially() {
292146
+ for (const target of this.#targets()) {
292147
+ if (this.#stopped) {
292148
+ break;
292149
+ }
292150
+ await this.#pollOne(target);
292151
+ }
292152
+ return true;
292153
+ }
292154
+ async#pollOne(target) {
292155
+ const exchange = target.account.exchange;
292156
+ const key = this.#targetKey(target);
292157
+ if (typeof exchange.fetchDeposits !== "function" || exchange.has?.fetchDeposits === false) {
292158
+ if (!this.#unsupportedLogged.has(key)) {
292159
+ this.#unsupportedLogged.add(key);
292160
+ log.info("Deposit archive poll skipped: fetchDeposits unsupported", {
292161
+ exchange: target.exchangeId,
292162
+ account: target.account.label
292163
+ });
292164
+ }
292165
+ return;
292166
+ }
292167
+ const since = this.#cursors.get(key) ?? Date.now() - this.#config.lookbackMs;
292168
+ let deposits;
292169
+ try {
292170
+ deposits = await exchange.fetchDeposits(undefined, since, this.#config.depositsLimit);
292171
+ } catch (error) {
292172
+ this.params.metrics?.recordCounter("cex_deposit_poller_errors_total", 1, { exchange: target.exchangeId });
292173
+ log.warn("Deposit archive poll failed", {
292174
+ exchange: target.exchangeId,
292175
+ account: target.account.label,
292176
+ error
292177
+ });
292178
+ return;
292179
+ }
292180
+ if (!Array.isArray(deposits) || deposits.length === 0) {
292181
+ return;
292182
+ }
292183
+ let archived = 0;
292184
+ for (const deposit of deposits) {
292185
+ const record = asRecord(deposit);
292186
+ if (!record) {
292187
+ continue;
292188
+ }
292189
+ const assetSymbol = depositField(record, ["currency", "code", "asset"]);
292190
+ const amount = depositField(record, ["amount"]);
292191
+ const address = depositField(record, [
292192
+ "address",
292193
+ "recipientAddress",
292194
+ "to",
292195
+ "destination"
292196
+ ]);
292197
+ const txid = depositField(record, ["txid", "txId", "tx_hash", "txHash"]);
292198
+ const network = depositField(record, ["network", "chain"]);
292199
+ const archiveStatus = normalizeCcxtTransactionForArchive(record).status;
292200
+ const creditedAt = depositField(record, [
292201
+ "creditedAt",
292202
+ "credited_at",
292203
+ "updated",
292204
+ "updatedAt",
292205
+ "timestamp",
292206
+ "datetime"
292207
+ ]);
292208
+ const depositTxid = txid === undefined ? undefined : String(txid);
292209
+ this.params.archiver.enqueue(buildTransferEventArchiveRow({
292210
+ tags: buildCommonArchiveTags({
292211
+ deploymentId: this.params.archiver.getDeploymentId(),
292212
+ accountSelector: target.account.label,
292213
+ exchange: target.exchangeId,
292214
+ symbol: assetSymbol === undefined ? undefined : String(assetSymbol)
292215
+ }),
292216
+ transfer: {
292217
+ eventKind: "deposit",
292218
+ lifecycleAction: "observe_deposit",
292219
+ status: archiveStatus,
292220
+ amount: amount === undefined ? undefined : String(amount),
292221
+ address: address === undefined ? undefined : String(address),
292222
+ network: network === undefined ? undefined : String(network),
292223
+ externalId: depositTxid,
292224
+ txid: depositTxid,
292225
+ exchangeTimestamp: typeof creditedAt === "string" ? creditedAt : undefined,
292226
+ payload: record
292227
+ }
292228
+ }));
292229
+ archived += 1;
292230
+ }
292231
+ if (archived > 0) {
292232
+ this.params.metrics?.recordCounter("cex_deposit_poller_deposits_archived_total", archived, { exchange: target.exchangeId });
292233
+ }
292234
+ this.#cursors.set(key, nextDepositCursor(deposits, since, this.#config.depositsLimit));
292235
+ }
292236
+ #targetKey(target) {
292237
+ return `${target.exchangeId}|${target.account.label}|${target.code}`;
292238
+ }
292239
+ #schedule(delayMs) {
292240
+ this.#timer = setTimeout(() => void this.#tick(), delayMs);
292241
+ this.#timer.unref?.();
292242
+ }
292243
+ async#tick() {
292244
+ this.#timer = null;
292245
+ try {
292246
+ await this.pollAllOnce();
292247
+ } catch (error) {
292248
+ rethrowArchiveDurabilityError(error);
292249
+ log.error("Deposit archive poller tick failed", error);
292250
+ } finally {
292251
+ if (!this.#stopped) {
292252
+ this.#schedule(this.#config.pollIntervalMs);
292253
+ }
292254
+ }
292255
+ }
292256
+ }
292257
+
292258
+ // src/helpers/fill-archive-poller.ts
292259
+ var DEFAULT_CONFIG3 = {
292053
292260
  pollIntervalMs: 60000,
292054
292261
  lookbackMs: 24 * 60 * 60 * 1000,
292055
292262
  tradesLimit: 500
@@ -292074,7 +292281,7 @@ class FillArchivePoller {
292074
292281
  #config;
292075
292282
  constructor(params) {
292076
292283
  this.params = params;
292077
- this.#config = { ...DEFAULT_CONFIG2, ...params.config };
292284
+ this.#config = { ...DEFAULT_CONFIG3, ...params.config };
292078
292285
  }
292079
292286
  start() {
292080
292287
  if (this.#timer || this.#stopped) {
@@ -306300,6 +306507,12 @@ function parseActionPayload(schema, rawPayload, callback) {
306300
306507
  // src/helpers/grpc/status.ts
306301
306508
  import * as grpc2 from "@grpc/grpc-js";
306302
306509
  function stableGrpcErrorCode(message) {
306510
+ if (message.startsWith("AuthenticationError:")) {
306511
+ return grpc2.status.UNAUTHENTICATED;
306512
+ }
306513
+ if (message.startsWith("InsufficientFunds:")) {
306514
+ return grpc2.status.FAILED_PRECONDITION;
306515
+ }
306303
306516
  if (message.startsWith("venue_discovery_unavailable:")) {
306304
306517
  return grpc2.status.UNIMPLEMENTED;
306305
306518
  }
@@ -306450,7 +306663,8 @@ async function handleDeposit(ctx) {
306450
306663
  message: `deposit_address_mismatch: expected address ${value.recipientAddress}, observed ${String(observedAddress)}`
306451
306664
  }, null);
306452
306665
  }
306453
- const status4 = normalizeDepositStatus(depositField(deposit, ["status", "state"]));
306666
+ const responseStatus = normalizeDepositStatus(depositField(deposit, ["status", "state"]));
306667
+ const archiveStatus = normalizeCcxtTransactionForArchive(deposit).status;
306454
306668
  const depositTxid = String(depositField(deposit, ["txid", "txId", "tx_hash", "txHash"]) ?? value.transactionHash);
306455
306669
  const creditedAt = depositField(deposit, [
306456
306670
  "creditedAt",
@@ -306467,7 +306681,7 @@ async function handleDeposit(ctx) {
306467
306681
  transfer: {
306468
306682
  eventKind: "deposit",
306469
306683
  lifecycleAction: "observe_deposit",
306470
- status: status4,
306684
+ status: archiveStatus,
306471
306685
  amount: observedAmount !== undefined ? String(observedAmount) : undefined,
306472
306686
  address: String(observedAddress ?? value.recipientAddress),
306473
306687
  network: depositNetwork?.exchangeNetworkId,
@@ -306481,7 +306695,7 @@ async function handleDeposit(ctx) {
306481
306695
  return ctx.wrappedCallback(null, {
306482
306696
  proof: ctx.verity.proof,
306483
306697
  result: JSON.stringify({
306484
- status: status4,
306698
+ status: responseStatus,
306485
306699
  exchange: normalizedCex,
306486
306700
  accountSelector: selectedBrokerAccount?.label,
306487
306701
  asset: symbol2,
@@ -307000,6 +307214,12 @@ function identifiesUnsupported(message) {
307000
307214
  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
307215
  }
307002
307216
  function classifyPassiveOrderError(error48) {
307217
+ if (error48 instanceof ccxt_default.InsufficientFunds) {
307218
+ return "InsufficientFunds";
307219
+ }
307220
+ if (error48 instanceof ccxt_default.AuthenticationError) {
307221
+ return "AuthenticationError";
307222
+ }
307003
307223
  const message = getErrorMessage(error48);
307004
307224
  if (error48 instanceof ccxt_default.OrderImmediatelyFillable || identifiesWouldCross(message)) {
307005
307225
  return PASSIVE_ORDER_ERROR_CODES.wouldCross;
@@ -307128,9 +307348,9 @@ async function handleCreateOrder(ctx) {
307128
307348
  emitOrderExecutionTelemetryInBackground(otelMetrics, failedCreateContext, undefined, error48);
307129
307349
  archiveOrderExecutionInBackground(brokerArchiver, failedCreateContext, undefined, error48, { marketMetadataHash });
307130
307350
  if (isPassiveOrder && submission === "in_flight") {
307131
- const passiveErrorCode = classifyPassiveOrderError(error48);
307351
+ const stableErrorCode = classifyPassiveOrderError(error48);
307132
307352
  return rejectWithGrpcError(ctx, error48, {
307133
- message: `${passiveErrorCode}: ${sanitizeErrorDetail(error48)}`,
307353
+ message: `${stableErrorCode}: ${sanitizeErrorDetail(error48)}`,
307134
307354
  preferStableMessageOnly: true
307135
307355
  });
307136
307356
  }
@@ -310030,6 +310250,7 @@ class CEXBroker {
310030
310250
  orderActivityTracker = new OrderActivityTracker;
310031
310251
  withdrawalObservationTracker = new WithdrawalObservationTracker;
310032
310252
  fillArchivePoller;
310253
+ depositArchivePoller;
310033
310254
  accountBalanceArchivePoller;
310034
310255
  loadEnvConfig() {
310035
310256
  log.info("\uD83D\uDD27 Loading CEX_BROKER_ environment variables:");
@@ -310175,6 +310396,10 @@ class CEXBroker {
310175
310396
  this.fillArchivePoller.stop();
310176
310397
  this.fillArchivePoller = undefined;
310177
310398
  }
310399
+ if (this.depositArchivePoller) {
310400
+ await this.depositArchivePoller.stop();
310401
+ this.depositArchivePoller = undefined;
310402
+ }
310178
310403
  if (this.accountBalanceArchivePoller) {
310179
310404
  await this.accountBalanceArchivePoller.stop();
310180
310405
  this.accountBalanceArchivePoller = undefined;
@@ -310204,6 +310429,10 @@ class CEXBroker {
310204
310429
  this.fillArchivePoller.stop();
310205
310430
  this.fillArchivePoller = undefined;
310206
310431
  }
310432
+ if (this.depositArchivePoller) {
310433
+ await this.depositArchivePoller.stop();
310434
+ this.depositArchivePoller = undefined;
310435
+ }
310207
310436
  if (this.accountBalanceArchivePoller) {
310208
310437
  await this.accountBalanceArchivePoller.stop();
310209
310438
  this.accountBalanceArchivePoller = undefined;
@@ -310235,6 +310464,12 @@ class CEXBroker {
310235
310464
  metrics: this.otelMetrics
310236
310465
  });
310237
310466
  this.fillArchivePoller.start();
310467
+ this.depositArchivePoller = new DepositArchivePoller({
310468
+ brokers: this.brokers,
310469
+ archiver: this.brokerArchiver,
310470
+ metrics: this.otelMetrics
310471
+ });
310472
+ this.depositArchivePoller.start();
310238
310473
  }
310239
310474
  if (this.brokerArchiver?.canPersistAccountBalanceSnapshots()) {
310240
310475
  this.accountBalanceArchivePoller = new AccountBalanceArchivePoller({
@@ -310251,4 +310486,4 @@ export {
310251
310486
  CEXBroker as default
310252
310487
  };
310253
310488
 
310254
- //# debugId=DF7A1B19BD16DBE764756E2164756E21
310489
+ //# debugId=A02D63306D4E3E3864756E2164756E21