@usherlabs/cex-broker 0.2.20 → 0.2.21

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.
@@ -315278,6 +315278,8 @@ function hashMarketMetadata(payload) {
315278
315278
 
315279
315279
  // src/helpers/broker-execution-archive/types.ts
315280
315280
  var BROKER_WRITE_SOURCE = "broker_write";
315281
+ var ARCHIVE_SCHEMA_VERSION = "1";
315282
+ var FILL_EVENT_KIND = "trade_history_fill";
315281
315283
 
315282
315284
  // src/helpers/broker-execution-archive/rows.ts
315283
315285
  function firstString2(...values2) {
@@ -315291,6 +315293,26 @@ function firstString2(...values2) {
315291
315293
  }
315292
315294
  return;
315293
315295
  }
315296
+ function firstNumber2(...values2) {
315297
+ for (const value of values2) {
315298
+ const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
315299
+ if (Number.isFinite(numeric)) {
315300
+ return numeric;
315301
+ }
315302
+ }
315303
+ return;
315304
+ }
315305
+ function normalizeTimestamp2(value) {
315306
+ if (typeof value === "string" && value.trim()) {
315307
+ return value.trim();
315308
+ }
315309
+ const ms = firstNumber2(value);
315310
+ if (ms === undefined) {
315311
+ return;
315312
+ }
315313
+ const date = new Date(ms);
315314
+ return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
315315
+ }
315294
315316
  function compactUndefined2(record) {
315295
315317
  return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
315296
315318
  }
@@ -315355,6 +315377,99 @@ function buildSubscribeStreamArchiveRow(input) {
315355
315377
  })
315356
315378
  };
315357
315379
  }
315380
+ function quantityString(...values2) {
315381
+ return firstString2(...values2);
315382
+ }
315383
+ function buildTransferEventArchiveRow(input) {
315384
+ const { tags, transfer } = input;
315385
+ return {
315386
+ table: "broker_execution.transfer_events",
315387
+ row: compactUndefined2({
315388
+ ...tags,
315389
+ schema_version: ARCHIVE_SCHEMA_VERSION,
315390
+ event_kind: transfer.eventKind,
315391
+ lifecycle_action: transfer.lifecycleAction,
315392
+ status: transfer.status ?? "",
315393
+ asset_symbol: tags.symbol,
315394
+ amount: transfer.amount,
315395
+ address: transfer.address,
315396
+ network: transfer.network,
315397
+ external_id: transfer.externalId ?? "",
315398
+ txid: transfer.txid,
315399
+ result_index: transfer.resultIndex ?? 0,
315400
+ fee_amount: transfer.feeAmount,
315401
+ fee_currency: transfer.feeCurrency,
315402
+ exchange_timestamp: transfer.exchangeTimestamp,
315403
+ error_summary: transfer.errorSummary,
315404
+ payload_json: JSON.stringify(transfer.payload)
315405
+ })
315406
+ };
315407
+ }
315408
+ function normalizeCcxtTransactionForArchive(transaction) {
315409
+ const record = asRecord(transaction);
315410
+ const info = asRecord(record?.info);
315411
+ const fee = asRecord(record?.fee);
315412
+ return compactUndefined2({
315413
+ externalId: firstString2(record?.id, info?.id, record?.txid, info?.txId),
315414
+ txid: firstString2(record?.txid, info?.txId, info?.txid, info?.tx_hash),
315415
+ address: firstString2(record?.address, record?.addressTo, info?.address),
315416
+ network: firstString2(record?.network, info?.network),
315417
+ amount: quantityString(info?.amount, record?.amount),
315418
+ assetSymbol: firstString2(record?.currency, info?.coin, info?.asset),
315419
+ status: firstString2(record?.status, info?.status)?.toLowerCase(),
315420
+ feeAmount: quantityString(fee?.cost, record?.feeCost),
315421
+ feeCurrency: firstString2(fee?.currency, record?.feeCurrency),
315422
+ exchangeTimestamp: normalizeTimestamp2(firstValueForTransfer(record?.timestamp, record?.datetime, info?.applyTime))
315423
+ });
315424
+ }
315425
+ function firstValueForTransfer(...values2) {
315426
+ return values2.find((value) => value !== undefined && value !== null);
315427
+ }
315428
+ function buildFillEventArchiveRow(input) {
315429
+ const { tags, fill } = input;
315430
+ return {
315431
+ table: "broker_execution.fill_events",
315432
+ row: compactUndefined2({
315433
+ ...tags,
315434
+ schema_version: ARCHIVE_SCHEMA_VERSION,
315435
+ event_kind: FILL_EVENT_KIND,
315436
+ order_id: fill.orderId ?? "",
315437
+ client_order_id: fill.clientOrderId,
315438
+ fill_id: fill.fillId,
315439
+ fill_index: fill.fillIndex ?? 0,
315440
+ side: fill.side,
315441
+ order_type: fill.orderType,
315442
+ price: fill.price,
315443
+ base_quantity: fill.baseQuantity,
315444
+ quote_quantity: fill.quoteQuantity,
315445
+ fee_amount: fill.feeAmount,
315446
+ fee_currency: fill.feeCurrency,
315447
+ fee_rate: fill.feeRate,
315448
+ exchange_timestamp: fill.exchangeTimestamp,
315449
+ payload_json: JSON.stringify(fill.payload)
315450
+ })
315451
+ };
315452
+ }
315453
+ function normalizeCcxtTradeForArchive(trade) {
315454
+ const record = asRecord(trade);
315455
+ const info = asRecord(record?.info);
315456
+ const fee = asRecord(record?.fee);
315457
+ return {
315458
+ orderId: firstString2(record?.order, info?.orderId, info?.orderID),
315459
+ clientOrderId: firstString2(record?.clientOrderId, info?.clientOrderId, info?.origClientOrderId),
315460
+ fillId: firstString2(record?.id, info?.id, info?.tradeId),
315461
+ side: firstString2(record?.side, info?.side)?.toLowerCase(),
315462
+ orderType: firstString2(record?.type, info?.type)?.toLowerCase(),
315463
+ price: quantityString(info?.price, record?.price),
315464
+ baseQuantity: quantityString(info?.qty, record?.amount),
315465
+ quoteQuantity: quantityString(info?.quoteQty, record?.cost),
315466
+ feeAmount: quantityString(fee?.cost, info?.commission),
315467
+ feeCurrency: firstString2(fee?.currency, info?.commissionAsset),
315468
+ feeRate: quantityString(fee?.rate),
315469
+ exchangeTimestamp: normalizeTimestamp2(firstValueForTransfer(record?.timestamp, record?.datetime, info?.time)),
315470
+ payload: trade
315471
+ };
315472
+ }
315358
315473
  function buildMarketMetadataSnapshotRow(input) {
315359
315474
  const redactedSnapshot = redactStreamPayload(input.marketSnapshot);
315360
315475
  const metadataHash = hashMarketMetadata(redactedSnapshot);
@@ -315423,6 +315538,25 @@ function archiveSubscribeStreamInBackground(archiver, input) {
315423
315538
  }
315424
315539
  });
315425
315540
  }
315541
+ function archiveTransferEventInBackground(archiver, input) {
315542
+ if (!archiver?.isEnabled()) {
315543
+ return;
315544
+ }
315545
+ queueMicrotask(() => {
315546
+ try {
315547
+ const tags = buildCommonArchiveTags({
315548
+ deploymentId: archiver.getDeploymentId(),
315549
+ accountSelector: input.accountSelector,
315550
+ exchange: input.exchange,
315551
+ symbol: input.assetSymbol,
315552
+ brokerObservedTimestamp: input.brokerObservedTimestamp
315553
+ });
315554
+ archiver.enqueue(buildTransferEventArchiveRow({ tags, transfer: input.transfer }));
315555
+ } catch (archiveError) {
315556
+ log.warn("Failed to archive transfer event", { error: archiveError });
315557
+ }
315558
+ });
315559
+ }
315426
315560
  async function captureMarketMetadataSnapshot(archiver, broker, input) {
315427
315561
  if (!archiver?.canPersistMarketMetadataSnapshot()) {
315428
315562
  return;
@@ -315678,11 +315812,27 @@ class BrokerExecutionArchiver {
315678
315812
  }
315679
315813
  }
315680
315814
  this.stats.flushed += batch.length;
315815
+ this.recordFlushHealth(batch);
315681
315816
  return true;
315682
315817
  }
315683
- async recordArchiveMetric(metricName, labels) {
315818
+ recordFlushHealth(batch) {
315819
+ const countByTable = new Map;
315820
+ for (const entry of batch) {
315821
+ countByTable.set(entry.table, (countByTable.get(entry.table) ?? 0) + 1);
315822
+ }
315823
+ for (const [table, count2] of countByTable) {
315824
+ this.recordArchiveMetric("cex_archive_rows_flushed_total", { table }, count2);
315825
+ }
315826
+ this.recordArchiveGauge("cex_archive_last_flush_success", Math.floor(Date.now() / 1000));
315827
+ }
315828
+ async recordArchiveMetric(metricName, labels, value = 1) {
315829
+ try {
315830
+ await this.otelMetrics?.recordCounter(metricName, value, labels);
315831
+ } catch {}
315832
+ }
315833
+ async recordArchiveGauge(metricName, value) {
315684
315834
  try {
315685
- await this.otelMetrics?.recordCounter(metricName, 1, labels);
315835
+ await this.otelMetrics?.recordGauge(metricName, value, {});
315686
315836
  } catch {}
315687
315837
  }
315688
315838
  emitOtelLog(entry) {
@@ -315779,6 +315929,159 @@ function parsePositiveInt(value, fallback) {
315779
315929
  const parsed = Number.parseInt(value, 10);
315780
315930
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
315781
315931
  }
315932
+ // src/helpers/fill-archive-poller.ts
315933
+ var DEFAULT_CONFIG = {
315934
+ pollIntervalMs: 60000,
315935
+ lookbackMs: 24 * 60 * 60 * 1000,
315936
+ tradesLimit: 500
315937
+ };
315938
+ function nextFillCursor(trades, currentSince) {
315939
+ let next = currentSince;
315940
+ for (const trade of trades) {
315941
+ const ts = asRecord(trade)?.timestamp;
315942
+ if (typeof ts === "number" && Number.isFinite(ts) && ts + 1 > next) {
315943
+ next = ts + 1;
315944
+ }
315945
+ }
315946
+ return next;
315947
+ }
315948
+
315949
+ class FillArchivePoller {
315950
+ params;
315951
+ #timer = null;
315952
+ #stopped = false;
315953
+ #running = false;
315954
+ #cursors = new Map;
315955
+ #config;
315956
+ constructor(params) {
315957
+ this.params = params;
315958
+ this.#config = { ...DEFAULT_CONFIG, ...params.config };
315959
+ }
315960
+ start() {
315961
+ if (this.#timer || this.#stopped) {
315962
+ return;
315963
+ }
315964
+ if (!this.params.archiver.isEnabled()) {
315965
+ return;
315966
+ }
315967
+ log.info("\uD83E\uDDFE Fill archive poller started");
315968
+ this.#timer = setTimeout(() => void this.#tick(), 0);
315969
+ this.#timer.unref?.();
315970
+ }
315971
+ stop() {
315972
+ this.#stopped = true;
315973
+ if (this.#timer) {
315974
+ clearTimeout(this.#timer);
315975
+ this.#timer = null;
315976
+ }
315977
+ }
315978
+ async#tick() {
315979
+ if (this.#stopped || this.#running) {
315980
+ return;
315981
+ }
315982
+ this.#running = true;
315983
+ try {
315984
+ await this.pollTrackedOnce();
315985
+ } catch (error) {
315986
+ log.error("Fill archive poller tick failed", error);
315987
+ } finally {
315988
+ this.#running = false;
315989
+ if (!this.#stopped) {
315990
+ this.#timer = setTimeout(() => void this.#tick(), this.#config.pollIntervalMs);
315991
+ this.#timer.unref?.();
315992
+ }
315993
+ }
315994
+ }
315995
+ async pollTrackedOnce() {
315996
+ for (const entry of this.params.tracker.list()) {
315997
+ if (this.#stopped) {
315998
+ break;
315999
+ }
316000
+ await this.#pollOne(entry.exchangeId, entry.accountLabel, entry.symbol);
316001
+ }
316002
+ }
316003
+ async#pollOne(exchangeId, accountLabel, symbol) {
316004
+ const account = resolveBrokerAccount(this.params.brokers[exchangeId], accountLabel);
316005
+ if (!account) {
316006
+ return;
316007
+ }
316008
+ const exchange = account.exchange;
316009
+ if (typeof exchange.fetchMyTrades !== "function" || exchange.has?.fetchMyTrades === false) {
316010
+ return;
316011
+ }
316012
+ const key = `${exchangeId}|${accountLabel}|${symbol}`;
316013
+ const since = this.#cursors.get(key) ?? Date.now() - this.#config.lookbackMs;
316014
+ let trades;
316015
+ try {
316016
+ trades = await exchange.fetchMyTrades(symbol, since, this.#config.tradesLimit);
316017
+ } catch (error) {
316018
+ this.params.metrics?.recordCounter("cex_fill_poller_errors_total", 1, {
316019
+ exchange: exchangeId
316020
+ });
316021
+ log.warn("Fill archive poll failed", {
316022
+ exchange: exchangeId,
316023
+ account: accountLabel,
316024
+ symbol,
316025
+ error
316026
+ });
316027
+ return;
316028
+ }
316029
+ if (!Array.isArray(trades) || trades.length === 0) {
316030
+ return;
316031
+ }
316032
+ trades.forEach((trade, fillIndex) => {
316033
+ const tags = buildCommonArchiveTags({
316034
+ deploymentId: this.params.archiver.getDeploymentId(),
316035
+ accountSelector: accountLabel,
316036
+ exchange: exchangeId,
316037
+ symbol
316038
+ });
316039
+ this.params.archiver.enqueue(buildFillEventArchiveRow({
316040
+ tags,
316041
+ fill: { ...normalizeCcxtTradeForArchive(trade), fillIndex }
316042
+ }));
316043
+ });
316044
+ this.params.metrics?.recordCounter("cex_fill_poller_trades_archived_total", trades.length, { exchange: exchangeId });
316045
+ this.#cursors.set(key, nextFillCursor(trades, since));
316046
+ }
316047
+ }
316048
+
316049
+ // src/helpers/order-activity-tracker.ts
316050
+ var DEFAULT_MAX_AGE_MS = 6 * 60 * 60 * 1000;
316051
+
316052
+ class OrderActivityTracker {
316053
+ #entries = new Map;
316054
+ #maxAgeMs;
316055
+ constructor(options) {
316056
+ this.#maxAgeMs = options?.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
316057
+ }
316058
+ record(exchangeId, accountLabel, symbol, now3 = Date.now()) {
316059
+ const exchange = exchangeId.trim().toLowerCase();
316060
+ const trimmedSymbol = symbol.trim();
316061
+ if (!exchange || !accountLabel.trim() || !trimmedSymbol) {
316062
+ return;
316063
+ }
316064
+ const key = `${exchange}|${accountLabel}|${trimmedSymbol}`;
316065
+ this.#entries.set(key, {
316066
+ exchangeId: exchange,
316067
+ accountLabel,
316068
+ symbol: trimmedSymbol,
316069
+ lastActivityAt: now3
316070
+ });
316071
+ }
316072
+ list(now3 = Date.now()) {
316073
+ const active = [];
316074
+ for (const [key, entry] of this.#entries) {
316075
+ if (now3 - entry.lastActivityAt > this.#maxAgeMs) {
316076
+ this.#entries.delete(key);
316077
+ continue;
316078
+ }
316079
+ active.push(entry);
316080
+ }
316081
+ return active;
316082
+ }
316083
+ }
316084
+
315782
316085
  // src/helpers/otel.ts
315783
316086
  var import_api2 = __toESM(require_src5(), 1);
315784
316087
  var import_api_logs3 = __toESM(require_src7(), 1);
@@ -329955,7 +330258,13 @@ function rejectWithGrpcError(ctx, error48, options) {
329955
330258
 
329956
330259
  // src/handlers/execute-action/deposit.ts
329957
330260
  async function handleDeposit(ctx) {
329958
- const { normalizedCex, symbol: symbol2, selectedBrokerAccount, broker } = ctx;
330261
+ const {
330262
+ normalizedCex,
330263
+ symbol: symbol2,
330264
+ selectedBrokerAccount,
330265
+ broker,
330266
+ brokerArchiver
330267
+ } = ctx;
329959
330268
  if (!symbol2) {
329960
330269
  return ctx.wrappedCallback({
329961
330270
  code: grpc3.status.INVALID_ARGUMENT,
@@ -330018,6 +330327,32 @@ async function handleDeposit(ctx) {
330018
330327
  }, null);
330019
330328
  }
330020
330329
  const status4 = normalizeDepositStatus(depositField(deposit, ["status", "state"]));
330330
+ const depositTxid = String(depositField(deposit, ["txid", "txId", "tx_hash", "txHash"]) ?? value.transactionHash);
330331
+ const creditedAt = depositField(deposit, [
330332
+ "creditedAt",
330333
+ "credited_at",
330334
+ "updated",
330335
+ "updatedAt",
330336
+ "timestamp",
330337
+ "datetime"
330338
+ ]);
330339
+ archiveTransferEventInBackground(brokerArchiver, {
330340
+ exchange: normalizedCex,
330341
+ accountSelector: selectedBrokerAccount?.label,
330342
+ assetSymbol: symbol2,
330343
+ transfer: {
330344
+ eventKind: "deposit",
330345
+ lifecycleAction: "observe_deposit",
330346
+ status: status4,
330347
+ amount: observedAmount !== undefined ? String(observedAmount) : undefined,
330348
+ address: String(observedAddress ?? value.recipientAddress),
330349
+ network: depositNetwork?.exchangeNetworkId,
330350
+ externalId: depositTxid,
330351
+ txid: depositTxid,
330352
+ exchangeTimestamp: typeof creditedAt === "string" ? creditedAt : undefined,
330353
+ payload: deposit
330354
+ }
330355
+ });
330021
330356
  log.info(`Amount ${value.amount} at ${value.transactionHash} . Paid to ${value.recipientAddress}`);
330022
330357
  return ctx.wrappedCallback(null, {
330023
330358
  proof: ctx.verity.proof,
@@ -330431,7 +330766,8 @@ async function handleInternalTransfer(ctx) {
330431
330766
  symbol: symbol2,
330432
330767
  verity,
330433
330768
  useVerity,
330434
- verityProverUrl
330769
+ verityProverUrl,
330770
+ brokerArchiver
330435
330771
  } = ctx;
330436
330772
  if (!symbol2) {
330437
330773
  return ctx.wrappedCallback({
@@ -330479,6 +330815,21 @@ async function handleInternalTransfer(ctx) {
330479
330815
  }), verityHttpClientOverridePredicate);
330480
330816
  }
330481
330817
  const result = await transferBinanceInternal(sourceAccount, destAccount, symbol2, transferPayload.amount);
330818
+ const normalized = normalizeCcxtTransactionForArchive(result);
330819
+ archiveTransferEventInBackground(brokerArchiver, {
330820
+ exchange: normalizedCex,
330821
+ accountSelector: fromSelector,
330822
+ assetSymbol: symbol2,
330823
+ transfer: {
330824
+ eventKind: "internal_transfer",
330825
+ lifecycleAction: "submit_internal_transfer",
330826
+ status: normalized.status ?? "ok",
330827
+ amount: normalized.amount ?? String(transferPayload.amount),
330828
+ network: "internal",
330829
+ externalId: normalized.externalId,
330830
+ payload: { from: fromSelector, to: toSelector, result }
330831
+ }
330832
+ });
330482
330833
  ctx.wrappedCallback(null, {
330483
330834
  proof: verity.proof,
330484
330835
  result: JSON.stringify(result)
@@ -330526,7 +330877,8 @@ async function handleCreateOrder(ctx) {
330526
330877
  useVerity,
330527
330878
  verityProverUrl,
330528
330879
  otelMetrics,
330529
- brokerArchiver
330880
+ brokerArchiver,
330881
+ orderActivityTracker
330530
330882
  } = ctx;
330531
330883
  const verityProof = verity.proof;
330532
330884
  const orderValue = parsePayloadForAction(ctx, CreateOrderPayloadSchema);
@@ -330552,6 +330904,9 @@ async function handleCreateOrder(ctx) {
330552
330904
  side: resolution.side,
330553
330905
  requestedQuantity: resolution.amountBase ?? orderValue.amount
330554
330906
  };
330907
+ if (selectedBrokerAccount?.label) {
330908
+ orderActivityTracker?.record(cex3, selectedBrokerAccount.label, resolution.symbol);
330909
+ }
330555
330910
  const telemetryIds = extractOrderTelemetryIds(orderValue.params);
330556
330911
  const submissionTimestamp = new Date().toISOString();
330557
330912
  const marketMetadataHash = await captureMarketMetadataSnapshot(brokerArchiver, broker, {
@@ -330616,7 +330971,8 @@ async function handleGetOrderDetails(ctx) {
330616
330971
  useVerity,
330617
330972
  verityProverUrl,
330618
330973
  otelMetrics,
330619
- brokerArchiver
330974
+ brokerArchiver,
330975
+ orderActivityTracker
330620
330976
  } = ctx;
330621
330977
  const verityProof = verity.proof;
330622
330978
  const getOrderValue = parsePayloadForAction(ctx, GetOrderDetailsPayloadSchema);
@@ -330630,6 +330986,9 @@ async function handleGetOrderDetails(ctx) {
330630
330986
  }, null);
330631
330987
  }
330632
330988
  const orderDetails = await broker.fetchOrder(getOrderValue.orderId, symbol2, { ...getOrderValue.params });
330989
+ if (selectedBrokerAccount?.label && symbol2) {
330990
+ orderActivityTracker?.record(cex3, selectedBrokerAccount.label, symbol2);
330991
+ }
330633
330992
  const getOrderContext = {
330634
330993
  action: "GetOrderDetails",
330635
330994
  cex: cex3,
@@ -330685,7 +331044,8 @@ async function handleCancelOrder(ctx) {
330685
331044
  useVerity,
330686
331045
  verityProverUrl,
330687
331046
  otelMetrics,
330688
- brokerArchiver
331047
+ brokerArchiver,
331048
+ orderActivityTracker
330689
331049
  } = ctx;
330690
331050
  const verityProof = verity.proof;
330691
331051
  const cancelOrderValue = parsePayloadForAction(ctx, CancelOrderPayloadSchema);
@@ -330706,6 +331066,9 @@ async function handleCancelOrder(ctx) {
330706
331066
  ...extractOrderTelemetryIds(cancelOrderValue.params)
330707
331067
  };
330708
331068
  const cancelledOrder = await broker.cancelOrder(cancelOrderValue.orderId, symbol2, cancelOrderValue.params ?? {});
331069
+ if (selectedBrokerAccount?.label && symbol2) {
331070
+ orderActivityTracker?.record(cex3, selectedBrokerAccount.label, symbol2);
331071
+ }
330709
331072
  emitOrderExecutionTelemetryInBackground(otelMetrics, cancelOrderContext, cancelledOrder);
330710
331073
  archiveOrderExecutionInBackground(brokerArchiver, cancelOrderContext, cancelledOrder);
330711
331074
  ctx.wrappedCallback(null, {
@@ -331336,7 +331699,8 @@ async function handleWithdraw(ctx) {
331336
331699
  applyVerityToBroker,
331337
331700
  useVerity,
331338
331701
  verityProverUrl,
331339
- otelMetrics
331702
+ otelMetrics,
331703
+ brokerArchiver
331340
331704
  } = ctx;
331341
331705
  const verityProof = verity.proof;
331342
331706
  if (!symbol2) {
@@ -331385,6 +331749,26 @@ async function handleWithdraw(ctx) {
331385
331749
  network: withdrawNetwork.exchangeNetworkId
331386
331750
  });
331387
331751
  log.info(`Withdraw Result: ${JSON.stringify(transaction)}`);
331752
+ const normalized = normalizeCcxtTransactionForArchive(transaction);
331753
+ archiveTransferEventInBackground(brokerArchiver, {
331754
+ exchange: cex3,
331755
+ accountSelector: selectedBrokerAccount?.label,
331756
+ assetSymbol: normalized.assetSymbol ?? symbol2,
331757
+ transfer: {
331758
+ eventKind: "withdrawal",
331759
+ lifecycleAction: "submit_withdrawal",
331760
+ status: normalized.status,
331761
+ amount: normalized.amount ?? String(transferValue.amount),
331762
+ address: normalized.address ?? transferValue.recipientAddress,
331763
+ network: normalized.network ?? withdrawNetwork.exchangeNetworkId,
331764
+ externalId: normalized.externalId,
331765
+ txid: normalized.txid,
331766
+ feeAmount: normalized.feeAmount,
331767
+ feeCurrency: normalized.feeCurrency,
331768
+ exchangeTimestamp: normalized.exchangeTimestamp,
331769
+ payload: transaction
331770
+ }
331771
+ });
331388
331772
  ctx.wrappedCallback(null, {
331389
331773
  proof: ctx.verity.proof,
331390
331774
  result: JSON.stringify({
@@ -331396,6 +331780,21 @@ async function handleWithdraw(ctx) {
331396
331780
  });
331397
331781
  } catch (error48) {
331398
331782
  safeLogError("Withdraw failed", error48);
331783
+ archiveTransferEventInBackground(brokerArchiver, {
331784
+ exchange: cex3,
331785
+ accountSelector: selectedBrokerAccount?.label,
331786
+ assetSymbol: symbol2,
331787
+ transfer: {
331788
+ eventKind: "withdrawal",
331789
+ lifecycleAction: "submit_withdrawal",
331790
+ status: "failed",
331791
+ amount: String(transferValue.amount),
331792
+ address: transferValue.recipientAddress,
331793
+ network: withdrawNetwork.exchangeNetworkId,
331794
+ errorSummary: getErrorMessage(error48),
331795
+ payload: { recipientAddress: transferValue.recipientAddress }
331796
+ }
331797
+ });
331399
331798
  const code = mapCcxtErrorToGrpcStatus(error48) ?? grpc10.status.INTERNAL;
331400
331799
  ctx.wrappedCallback({
331401
331800
  code,
@@ -331443,7 +331842,8 @@ function createExecuteActionHandler(deps) {
331443
331842
  useVerity,
331444
331843
  verityProverUrl,
331445
331844
  otelMetrics,
331446
- brokerArchiver
331845
+ brokerArchiver,
331846
+ orderActivityTracker
331447
331847
  } = deps;
331448
331848
  return async (call, callback) => {
331449
331849
  const startTime = Date.now();
@@ -331522,7 +331922,8 @@ function createExecuteActionHandler(deps) {
331522
331922
  useVerity,
331523
331923
  verityProverUrl,
331524
331924
  otelMetrics,
331525
- brokerArchiver
331925
+ brokerArchiver,
331926
+ orderActivityTracker
331526
331927
  };
331527
331928
  if (action === Action.Call) {
331528
331929
  const handled = await handleOrderBookCall(preludeCtx);
@@ -333111,7 +333512,7 @@ var CEX_BROKER_PACKAGE_DEFINITION = protoLoader.fromJSON(node_descriptor_default
333111
333512
  // src/server.ts
333112
333513
  var grpcObj = grpc14.loadPackageDefinition(CEX_BROKER_PACKAGE_DEFINITION);
333113
333514
  var cexNode = grpcObj.cex_broker;
333114
- function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, otelMetrics, brokerArchiver) {
333515
+ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, otelMetrics, brokerArchiver, orderActivityTracker) {
333115
333516
  const server = new grpc14.Server;
333116
333517
  server.addService(cexNode.cex_service.service, {
333117
333518
  ExecuteAction: createExecuteActionHandler({
@@ -333121,7 +333522,8 @@ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, ot
333121
333522
  useVerity,
333122
333523
  verityProverUrl,
333123
333524
  otelMetrics,
333124
- brokerArchiver
333525
+ brokerArchiver,
333526
+ orderActivityTracker
333125
333527
  }),
333126
333528
  Subscribe: createSubscribeHandler({
333127
333529
  brokers,
@@ -333266,6 +333668,8 @@ class CEXBroker {
333266
333668
  otelLogs;
333267
333669
  brokerArchiver;
333268
333670
  depositReconciler;
333671
+ orderActivityTracker = new OrderActivityTracker;
333672
+ fillArchivePoller;
333269
333673
  loadEnvConfig() {
333270
333674
  log.info("\uD83D\uDD27 Loading CEX_BROKER_ environment variables:");
333271
333675
  const configMap = {};
@@ -333406,6 +333810,10 @@ class CEXBroker {
333406
333810
  this.depositReconciler.stop();
333407
333811
  this.depositReconciler = undefined;
333408
333812
  }
333813
+ if (this.fillArchivePoller) {
333814
+ this.fillArchivePoller.stop();
333815
+ this.fillArchivePoller = undefined;
333816
+ }
333409
333817
  if (this.server) {
333410
333818
  await this.server.forceShutdown();
333411
333819
  }
@@ -333427,11 +333835,15 @@ class CEXBroker {
333427
333835
  this.depositReconciler.stop();
333428
333836
  this.depositReconciler = undefined;
333429
333837
  }
333838
+ if (this.fillArchivePoller) {
333839
+ this.fillArchivePoller.stop();
333840
+ this.fillArchivePoller = undefined;
333841
+ }
333430
333842
  log.info(`Running CEXBroker at ${new Date().toISOString()}`);
333431
333843
  if (this.otelMetrics?.isOtelEnabled()) {
333432
333844
  await this.otelMetrics.initialize();
333433
333845
  }
333434
- this.server = getServer(this.policy, this.brokers, this.whitelistIps, this.useVerity, this.#verityProverUrl, this.otelMetrics, this.brokerArchiver);
333846
+ this.server = getServer(this.policy, this.brokers, this.whitelistIps, this.useVerity, this.#verityProverUrl, this.otelMetrics, this.brokerArchiver, this.orderActivityTracker);
333435
333847
  this.server.bindAsync(`0.0.0.0:${this.port}`, grpc15.ServerCredentials.createInsecure(), (err2, port) => {
333436
333848
  if (err2) {
333437
333849
  log.error(err2);
@@ -333446,6 +333858,15 @@ class CEXBroker {
333446
333858
  metrics: this.otelMetrics
333447
333859
  });
333448
333860
  this.depositReconciler.start();
333861
+ if (this.brokerArchiver?.isEnabled()) {
333862
+ this.fillArchivePoller = new FillArchivePoller({
333863
+ brokers: this.brokers,
333864
+ archiver: this.brokerArchiver,
333865
+ tracker: this.orderActivityTracker,
333866
+ metrics: this.otelMetrics
333867
+ });
333868
+ this.fillArchivePoller.start();
333869
+ }
333449
333870
  return this;
333450
333871
  }
333451
333872
  }
@@ -5,6 +5,7 @@ import type { z } from "zod";
5
5
  import type { BrokerAccount, BrokerPoolEntry } from "../../helpers";
6
6
  import type { BrokerExecutionArchiver } from "../../helpers/broker-execution-archive";
7
7
  import type { Action as ActionType } from "../../helpers/constants";
8
+ import type { OrderActivityTracker } from "../../helpers/order-activity-tracker";
8
9
  import type { OtelMetrics } from "../../helpers/otel";
9
10
  import type { PolicyConfig } from "../../types";
10
11
  import type { ActionRequest, ActionResponse } from "../types";
@@ -29,6 +30,7 @@ export type ExecuteActionContext = {
29
30
  verityProverUrl: string;
30
31
  otelMetrics?: OtelMetrics;
31
32
  brokerArchiver?: BrokerExecutionArchiver;
33
+ orderActivityTracker?: OrderActivityTracker;
32
34
  };
33
35
  export declare function requireSymbol(ctx: ExecuteActionContext, message?: string): ctx is ExecuteActionContext & {
34
36
  symbol: string;
@@ -1,6 +1,7 @@
1
1
  import * as grpc from "@grpc/grpc-js";
2
2
  import { type BrokerPoolEntry } from "../../helpers/broker";
3
3
  import type { BrokerExecutionArchiver } from "../../helpers/broker-execution-archive";
4
+ import type { OrderActivityTracker } from "../../helpers/order-activity-tracker";
4
5
  import type { OtelMetrics } from "../../helpers/otel";
5
6
  import type { PolicyConfig } from "../../types";
6
7
  import type { ActionRequest, ActionResponse } from "../types";
@@ -12,5 +13,6 @@ export type ExecuteActionDeps = {
12
13
  verityProverUrl: string;
13
14
  otelMetrics?: OtelMetrics;
14
15
  brokerArchiver?: BrokerExecutionArchiver;
16
+ orderActivityTracker?: OrderActivityTracker;
15
17
  };
16
18
  export declare function createExecuteActionHandler(deps: ExecuteActionDeps): (call: grpc.ServerUnaryCall<ActionRequest, ActionResponse>, callback: grpc.sendUnaryData<ActionResponse>) => Promise<void>;
@@ -1,5 +1,6 @@
1
1
  import type { Exchange } from "@usherlabs/ccxt";
2
2
  import { type OrderTelemetryAction, type OrderTelemetryContext } from "../order-telemetry";
3
+ import { type TransferArchiveFields } from "./rows";
3
4
  import type { SubscribeArchiveType } from "./types";
4
5
  import type { BrokerExecutionArchiver } from "./writer";
5
6
  export declare function archiveOrderExecutionInBackground(archiver: BrokerExecutionArchiver | undefined, context: OrderTelemetryContext, order: unknown, error?: unknown, options?: {
@@ -13,6 +14,13 @@ export declare function archiveSubscribeStreamInBackground(archiver: BrokerExecu
13
14
  streamPayload: unknown;
14
15
  secretLiterals?: readonly string[];
15
16
  }): void;
17
+ export declare function archiveTransferEventInBackground(archiver: BrokerExecutionArchiver | undefined, input: {
18
+ exchange: string;
19
+ accountSelector?: string;
20
+ assetSymbol?: string;
21
+ brokerObservedTimestamp?: string;
22
+ transfer: TransferArchiveFields;
23
+ }): void;
16
24
  export declare function captureMarketMetadataSnapshot(archiver: BrokerExecutionArchiver | undefined, broker: Exchange, input: {
17
25
  exchange: string;
18
26
  accountSelector?: string;