@usherlabs/cex-broker 0.2.52 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/README.md +116 -8
  2. package/dist/commands/cli.js +1515 -467
  3. package/dist/index.js +1516 -468
  4. package/dist/index.js.map +31 -26
  5. package/dist/proto/node.descriptor.ts +3 -1
  6. package/dist/proto/node.proto +2 -0
  7. package/package.json +5 -28
  8. package/dist/commands/cex-canonical-orderbook-export.d.ts +0 -23
  9. package/dist/commands/cex-canonical-orderbook-export.js +0 -34275
  10. package/dist/commands/cex-canonical-orderbook-export.js.map +0 -266
  11. package/dist/commands/market-data-vendor-backfill.d.ts +0 -24
  12. package/dist/commands/market-data-vendor-backfill.js +0 -34930
  13. package/dist/commands/market-data-vendor-backfill.js.map +0 -268
  14. package/dist/handlers/execute-action/context.d.ts +0 -50
  15. package/dist/handlers/execute-action/deposit.d.ts +0 -2
  16. package/dist/handlers/execute-action/handler.d.ts +0 -19
  17. package/dist/handlers/execute-action/index.d.ts +0 -10
  18. package/dist/handlers/execute-action/internal-transfer.d.ts +0 -2
  19. package/dist/handlers/execute-action/order-book-call.d.ts +0 -3
  20. package/dist/handlers/execute-action/orders.d.ts +0 -2
  21. package/dist/handlers/execute-action/pass-through.d.ts +0 -2
  22. package/dist/handlers/execute-action/perp-config.d.ts +0 -2
  23. package/dist/handlers/execute-action/registry.d.ts +0 -5
  24. package/dist/handlers/execute-action/treasury-call.d.ts +0 -2
  25. package/dist/handlers/execute-action/withdraw.d.ts +0 -2
  26. package/dist/handlers/subscribe/broker-lifecycle.d.ts +0 -13
  27. package/dist/handlers/subscribe/handler.d.ts +0 -20
  28. package/dist/handlers/subscribe/index.d.ts +0 -2
  29. package/dist/handlers/types.d.ts +0 -23
  30. package/dist/helpers/account-balance-archive-poller.d.ts +0 -19
  31. package/dist/helpers/auth.d.ts +0 -5
  32. package/dist/helpers/balance-update-archive-consumer.d.ts +0 -16
  33. package/dist/helpers/binance-user-data-normalization.d.ts +0 -3
  34. package/dist/helpers/binance-user-data-stream.d.ts +0 -58
  35. package/dist/helpers/broker-execution-archive/capture.d.ts +0 -41
  36. package/dist/helpers/broker-execution-archive/index.d.ts +0 -7
  37. package/dist/helpers/broker-execution-archive/journal-export.d.ts +0 -20
  38. package/dist/helpers/broker-execution-archive/loss-journal.d.ts +0 -36
  39. package/dist/helpers/broker-execution-archive/redact.d.ts +0 -7
  40. package/dist/helpers/broker-execution-archive/rows.d.ts +0 -134
  41. package/dist/helpers/broker-execution-archive/types.d.ts +0 -26
  42. package/dist/helpers/broker-execution-archive/withdrawal-observation-tracker.d.ts +0 -23
  43. package/dist/helpers/broker-execution-archive/writer.d.ts +0 -105
  44. package/dist/helpers/broker.d.ts +0 -38
  45. package/dist/helpers/canonical-orderbook-export/exact-selection.d.ts +0 -21
  46. package/dist/helpers/canonical-orderbook-export/exporter.d.ts +0 -26
  47. package/dist/helpers/canonical-orderbook-export/parquet-projection.d.ts +0 -19
  48. package/dist/helpers/constants.d.ts +0 -36
  49. package/dist/helpers/deposit-archive-poller.d.ts +0 -66
  50. package/dist/helpers/deposit.d.ts +0 -5
  51. package/dist/helpers/exchange-credentials.d.ts +0 -10
  52. package/dist/helpers/fill-archive-poller.d.ts +0 -42
  53. package/dist/helpers/grpc/broker.d.ts +0 -5
  54. package/dist/helpers/grpc/callbacks.d.ts +0 -9
  55. package/dist/helpers/grpc/payload.d.ts +0 -9
  56. package/dist/helpers/grpc/status.d.ts +0 -8
  57. package/dist/helpers/index.d.ts +0 -44
  58. package/dist/helpers/logger.d.ts +0 -13
  59. package/dist/helpers/market-data-archive/canonical-orderbook.d.ts +0 -20
  60. package/dist/helpers/market-data-archive/capture-context.d.ts +0 -53
  61. package/dist/helpers/market-data-archive/capture-contract.d.ts +0 -30
  62. package/dist/helpers/market-data-archive/capture.d.ts +0 -22
  63. package/dist/helpers/market-data-archive/index.d.ts +0 -13
  64. package/dist/helpers/market-data-archive/legacy-migration.d.ts +0 -37
  65. package/dist/helpers/market-data-archive/ohlcv-bar-tracker.d.ts +0 -11
  66. package/dist/helpers/market-data-archive/ohlcv-bootstrap.d.ts +0 -1
  67. package/dist/helpers/market-data-archive/ohlcv-history.d.ts +0 -8
  68. package/dist/helpers/market-data-archive/orderbook-depth.d.ts +0 -5
  69. package/dist/helpers/market-data-archive/orderbook-sampler.d.ts +0 -12
  70. package/dist/helpers/market-data-archive/parse-stream.d.ts +0 -26
  71. package/dist/helpers/market-data-archive/rows.d.ts +0 -14
  72. package/dist/helpers/market-data-archive/types.d.ts +0 -83
  73. package/dist/helpers/market-data-preparation/conformance-fixtures.d.ts +0 -128
  74. package/dist/helpers/market-data-preparation/contracts.d.ts +0 -2970
  75. package/dist/helpers/market-data-preparation/file-job.d.ts +0 -38
  76. package/dist/helpers/market-data-preparation/required-clock-qualification.d.ts +0 -43
  77. package/dist/helpers/market-data-preparation/source-tape-dependencies.d.ts +0 -19
  78. package/dist/helpers/market-data-preparation/source-tape-operation.d.ts +0 -109
  79. package/dist/helpers/market-data-source-forensics.d.ts +0 -400
  80. package/dist/helpers/market-data-vendor-backfill/archive-reader.d.ts +0 -26
  81. package/dist/helpers/market-data-vendor-backfill/batching.d.ts +0 -10
  82. package/dist/helpers/market-data-vendor-backfill/conformance-fixtures.d.ts +0 -78
  83. package/dist/helpers/market-data-vendor-backfill/contracts.d.ts +0 -358
  84. package/dist/helpers/market-data-vendor-backfill/core.d.ts +0 -101
  85. package/dist/helpers/market-data-vendor-backfill/cryptohftdata.d.ts +0 -118
  86. package/dist/helpers/market-data-vendor-backfill/forwarder-client.d.ts +0 -25
  87. package/dist/helpers/market-data-vendor-backfill/identity.d.ts +0 -10
  88. package/dist/helpers/market-data-vendor-backfill/manifests.d.ts +0 -61
  89. package/dist/helpers/market-data-vendor-backfill/promotion.d.ts +0 -10
  90. package/dist/helpers/market-data-vendor-backfill/qualification.d.ts +0 -20
  91. package/dist/helpers/market-data-vendor-backfill/selection.d.ts +0 -30
  92. package/dist/helpers/market-data-vendor-backfill/semantic-verification.d.ts +0 -25
  93. package/dist/helpers/market-type.d.ts +0 -16
  94. package/dist/helpers/order-activity-tracker.d.ts +0 -22
  95. package/dist/helpers/order-book.d.ts +0 -79
  96. package/dist/helpers/order-telemetry.d.ts +0 -50
  97. package/dist/helpers/otel.d.ts +0 -83
  98. package/dist/helpers/passive-order.d.ts +0 -8
  99. package/dist/helpers/public-market-data-feed/identity.d.ts +0 -12
  100. package/dist/helpers/public-market-data-feed/index.d.ts +0 -5
  101. package/dist/helpers/public-market-data-feed/orderbook-coalescing-evidence.d.ts +0 -79
  102. package/dist/helpers/public-market-data-feed/orderbook-profile.d.ts +0 -54
  103. package/dist/helpers/public-market-data-feed/subscriber-buffer.d.ts +0 -31
  104. package/dist/helpers/public-market-data-feed/supervisor.d.ts +0 -82
  105. package/dist/helpers/shared/errors.d.ts +0 -14
  106. package/dist/helpers/shared/guards.d.ts +0 -2
  107. package/dist/helpers/source-tape-sandbox.d.ts +0 -82
  108. package/dist/helpers/source-tape.d.ts +0 -165
  109. package/dist/helpers/stream-health-publisher.d.ts +0 -41
  110. package/dist/helpers/trace-context.d.ts +0 -8
  111. package/dist/helpers/transfer-network.d.ts +0 -12
  112. package/dist/helpers/travel-rule-deposit-reconciler.d.ts +0 -159
  113. package/dist/helpers/travel-rule.d.ts +0 -75
  114. package/dist/helpers/treasury-discovery.d.ts +0 -17
  115. package/dist/helpers/user-asset-archive-poller.d.ts +0 -19
  116. package/dist/helpers/user-data-stream-supervisor.d.ts +0 -27
  117. package/dist/helpers/verity.d.ts +0 -5
  118. package/dist/index.d.ts +0 -56
  119. package/dist/market-data-preparation/fixtures/conformance-v3.json +0 -621
  120. package/dist/market-data-preparation/policies/capability-policy.json +0 -39
  121. package/dist/market-data-preparation/policies/resource-policy.json +0 -16
  122. package/dist/market-data-preparation/policies/source-tape-capability-v1.json +0 -35
  123. package/dist/market-data-preparation/schema-manifest.json +0 -66
  124. package/dist/market-data-preparation/schemas/archive-selection-v1.schema.json +0 -224
  125. package/dist/market-data-preparation/schemas/backfill-request-v1.schema.json +0 -166
  126. package/dist/market-data-preparation/schemas/backfill-result-v2.schema.json +0 -212
  127. package/dist/market-data-preparation/schemas/canonical-orderbook-export-request-v1.schema.json +0 -61
  128. package/dist/market-data-preparation/schemas/canonical-orderbook-export-result-v2.schema.json +0 -252
  129. package/dist/market-data-preparation/schemas/order-book-depth-summary-parquet-projection-v1.schema.json +0 -256
  130. package/dist/market-data-preparation/schemas/order-book-levels-parquet-projection-v1.schema.json +0 -220
  131. package/dist/market-data-preparation/schemas/preparation-product-pin-v2.schema.json +0 -176
  132. package/dist/market-data-preparation/schemas/promotion-receipt-v1.schema.json +0 -175
  133. package/dist/market-data-preparation/schemas/required-clock-v1.schema.json +0 -47
  134. package/dist/market-data-preparation/schemas/source-forensics-ledger-v1.schema.json +0 -463
  135. package/dist/market-data-preparation/schemas/source-qualification-record-v1.schema.json +0 -394
  136. package/dist/market-data-preparation.d.ts +0 -11
  137. package/dist/market-data-preparation.js +0 -37488
  138. package/dist/market-data-preparation.js.map +0 -277
  139. package/dist/market-data-vendor-backfill.d.ts +0 -21
  140. package/dist/market-data-vendor-backfill.js +0 -22463
  141. package/dist/market-data-vendor-backfill.js.map +0 -199
  142. package/dist/proto/node.descriptor.d.ts +0 -127
  143. package/dist/proto-loader-options.d.ts +0 -7
  144. package/dist/proto-package-definition.d.ts +0 -2
  145. package/dist/schemas/action-payloads.d.ts +0 -88
  146. package/dist/server.d.ts +0 -10
  147. package/dist/types.d.ts +0 -100
package/dist/index.js CHANGED
@@ -50010,7 +50010,7 @@ var require_src13 = __commonJS((exports) => {
50010
50010
  });
50011
50011
 
50012
50012
  // src/index.ts
50013
- import * as grpc15 from "@grpc/grpc-js";
50013
+ import * as grpc17 from "@grpc/grpc-js";
50014
50014
 
50015
50015
  // node_modules/@usherlabs/ccxt/js/src/base/functions.js
50016
50016
  var exports_functions = {};
@@ -290094,7 +290094,9 @@ var Action = {
290094
290094
  FetchFees: 12,
290095
290095
  InternalTransfer: 13,
290096
290096
  GetPerpConfigState: 14,
290097
- SetPerpConfigState: 15
290097
+ SetPerpConfigState: 15,
290098
+ FetchMarketRules: 16,
290099
+ Batch: 17
290098
290100
  };
290099
290101
  var SubscriptionType = {
290100
290102
  NO_ACTION: 0,
@@ -290642,6 +290644,30 @@ function redactUnknownValue(value, secretLiterals) {
290642
290644
  }
290643
290645
  return String(value);
290644
290646
  }
290647
+ function removeSecretMaterial(value, secretLiterals = []) {
290648
+ if (value === null || value === undefined) {
290649
+ return value;
290650
+ }
290651
+ if (typeof value === "string") {
290652
+ return redactSecretLiterals(value, secretLiterals);
290653
+ }
290654
+ if (typeof value === "number" || typeof value === "boolean") {
290655
+ return value;
290656
+ }
290657
+ if (Array.isArray(value)) {
290658
+ return value.map((entry) => removeSecretMaterial(entry, secretLiterals));
290659
+ }
290660
+ if (typeof value === "object") {
290661
+ const clean = {};
290662
+ for (const [key, entry] of Object.entries(value)) {
290663
+ if (!SECRET_KEY_PATTERN.test(key)) {
290664
+ clean[key] = removeSecretMaterial(entry, secretLiterals);
290665
+ }
290666
+ }
290667
+ return clean;
290668
+ }
290669
+ return String(value);
290670
+ }
290645
290671
  function redactStreamPayload(payload, secretLiterals = []) {
290646
290672
  if (Array.isArray(payload)) {
290647
290673
  return {
@@ -291352,6 +291378,22 @@ import {
291352
291378
  import { basename, dirname } from "node:path";
291353
291379
  var EXPORT_CHUNK_BYTES = 8 * 1024 * 1024;
291354
291380
  var SHA256_RECEIPT_PATTERN = /^([0-9a-f]{64}) {2}(.+)\n$/;
291381
+ function sha256File(path) {
291382
+ const hash4 = createHash3("sha256");
291383
+ const fd2 = openSync(path, "r");
291384
+ try {
291385
+ const chunk = Buffer.alloc(EXPORT_CHUNK_BYTES);
291386
+ for (;; ) {
291387
+ const read = readSync(fd2, chunk, 0, chunk.length, null);
291388
+ if (read === 0)
291389
+ break;
291390
+ hash4.update(chunk.subarray(0, read));
291391
+ }
291392
+ } finally {
291393
+ closeSync(fd2);
291394
+ }
291395
+ return hash4.digest("hex");
291396
+ }
291355
291397
 
291356
291398
  class DeadLetterJournalExportError extends Error {
291357
291399
  constructor(message, options) {
@@ -291385,6 +291427,9 @@ function exportDeadLetterJournal(journalPath, exportPath) {
291385
291427
  if (match?.[1] === undefined || match[2] !== basename(exportPath)) {
291386
291428
  throw new Error("export receipt is malformed");
291387
291429
  }
291430
+ if (sha256File(exportPath) !== match[1]) {
291431
+ throw new Error("export digest does not match receipt");
291432
+ }
291388
291433
  } catch (error) {
291389
291434
  throw new DeadLetterJournalExportError("Dead-letter journal export target is incomplete or unverifiable", { cause: error });
291390
291435
  }
@@ -292725,6 +292770,17 @@ function withTimeout(promise, timeoutMs, label) {
292725
292770
  return Promise.race([promise, expiry]).finally(() => clearTimeout(timer));
292726
292771
  }
292727
292772
  var ALL_CURRENCIES_CODE = "*";
292773
+ var MAX_FAILURE_REASON_CHARS = 256;
292774
+ var DEFAULT_FAILURE_REASON = "fetchDeposits failed";
292775
+ function decimal(value) {
292776
+ return value === null || value === undefined ? null : String(value);
292777
+ }
292778
+ function failureReason(error, exchange) {
292779
+ const text = error instanceof Error ? error.message : typeof error === "string" ? error : "";
292780
+ const secrets = [exchange.apiKey, exchange.secret].filter((value) => typeof value === "string" && value.length > 0);
292781
+ const trimmed = redactSecretLiterals(text, secrets).replace(/\s+/g, " ").trim().slice(0, MAX_FAILURE_REASON_CHARS);
292782
+ return trimmed.length > 0 ? trimmed : DEFAULT_FAILURE_REASON;
292783
+ }
292728
292784
  var BINANCE_UNLOCK_PROGRESS_SOURCE = {
292729
292785
  venue: "binance",
292730
292786
  endpoint: "GET /sapi/v1/capital/deposit/hisrec",
@@ -292735,6 +292791,20 @@ var BINANCE_UNLOCK_PROGRESS_SOURCE = {
292735
292791
  completeTime: "info.completeTime"
292736
292792
  }
292737
292793
  };
292794
+ function observedProgress(progress) {
292795
+ if (progress === undefined)
292796
+ return null;
292797
+ return {
292798
+ state: progress.state,
292799
+ progress_state: progress.progress_state,
292800
+ reason: progress.reason,
292801
+ native_status: decimal(progress.native_status),
292802
+ current: decimal(progress.current),
292803
+ credit_required: decimal(progress.credit_required),
292804
+ unlock_required: decimal(progress.unlock_required),
292805
+ complete_time: decimal(progress.complete_time)
292806
+ };
292807
+ }
292738
292808
  function depositTimestamp(record) {
292739
292809
  const observedAt = depositField(record, [
292740
292810
  "timestamp",
@@ -292964,6 +293034,7 @@ class DepositArchivePoller {
292964
293034
  #cursors = new Map;
292965
293035
  #lastArchivedByTarget = new Map;
292966
293036
  #unsupportedLogged = new Set;
293037
+ #coverage = new Map;
292967
293038
  #config;
292968
293039
  constructor(params) {
292969
293040
  this.params = params;
@@ -292974,6 +293045,7 @@ class DepositArchivePoller {
292974
293045
  return;
292975
293046
  }
292976
293047
  log.info("\uD83D\uDCE5 Deposit archive poller started");
293048
+ this.params.coveragePublisher?.start();
292977
293049
  this.#schedule(0);
292978
293050
  }
292979
293051
  async stop() {
@@ -292983,6 +293055,7 @@ class DepositArchivePoller {
292983
293055
  this.#timer = null;
292984
293056
  }
292985
293057
  await this.#running;
293058
+ await this.params.coveragePublisher?.close(this.#coverageSnapshots());
292986
293059
  }
292987
293060
  async pollAllOnce() {
292988
293061
  if (this.#stopped || this.#running || !this.params.archiver.isEnabled()) {
@@ -293018,14 +293091,90 @@ class DepositArchivePoller {
293018
293091
  return true;
293019
293092
  }
293020
293093
  async#pollOne(target) {
293094
+ const attemptedAt = new Date().toISOString();
293021
293095
  let outcome = "error";
293096
+ let observation;
293022
293097
  try {
293023
- outcome = await this.#pollTarget(target);
293098
+ const result = await this.#pollTarget(target, attemptedAt);
293099
+ outcome = result.outcome;
293100
+ observation = result.observation;
293101
+ } catch (error) {
293102
+ observation = this.#observation("error", attemptedAt, {
293103
+ errorReason: failureReason(error, target.account.exchange)
293104
+ });
293105
+ throw error;
293024
293106
  } finally {
293025
293107
  this.params.metrics?.recordCounter("cex_deposit_poller_polls_total", 1, { exchange: target.exchangeId, outcome });
293108
+ this.#recordCoverage(target, outcome, observation ?? this.#observation("error", attemptedAt, {
293109
+ errorReason: "poll aborted before completion"
293110
+ }));
293026
293111
  }
293027
293112
  }
293028
- async#pollTarget(target) {
293113
+ #observation(disposition, attemptedAt, detail = {}) {
293114
+ if (disposition === "success" && detail.completedAt !== undefined && detail.completedAt < attemptedAt) {
293115
+ return this.#observation("error", attemptedAt, {
293116
+ requestSinceMs: detail.requestSinceMs,
293117
+ errorReason: `source clock invalid: completed ${detail.completedAt} before attempted ${attemptedAt}`
293118
+ });
293119
+ }
293120
+ return {
293121
+ version: "1",
293122
+ disposition,
293123
+ attempted_at: attemptedAt,
293124
+ completed_at: disposition === "success" ? detail.completedAt ?? null : null,
293125
+ poll_interval_ms: String(this.#config.pollIntervalMs),
293126
+ fetch_timeout_ms: String(this.#config.fetchTimeoutMs),
293127
+ deposits_limit: String(this.#config.depositsLimit),
293128
+ request_since_ms: decimal(detail.requestSinceMs),
293129
+ next_cursor_ms: disposition === "success" ? decimal(detail.nextCursorMs) : null,
293130
+ response_truncated: disposition === "success" ? detail.responseTruncated ?? null : null,
293131
+ malformed_count: String(detail.malformedCount ?? 0),
293132
+ error_reason: disposition === "error" ? detail.errorReason?.trim() || DEFAULT_FAILURE_REASON : "",
293133
+ observed_deposits: disposition === "success" ? detail.observedDeposits ?? [] : []
293134
+ };
293135
+ }
293136
+ #recordCoverage(target, outcome, observation) {
293137
+ const publisher = this.params.coveragePublisher;
293138
+ if (!publisher)
293139
+ return;
293140
+ const key = this.#targetKey(target);
293141
+ const previous = this.#coverage.get(key);
293142
+ const now3 = observation.completed_at ?? new Date().toISOString();
293143
+ const state = outcome === "ok" ? "connected" : "error";
293144
+ const base2 = previous?.snapshot;
293145
+ const stateChangedAt = base2 && base2.state === state ? base2.stateChangedAt : now3;
293146
+ const attempts = BigInt(base2?.connectAttemptCount ?? "0") + 1n;
293147
+ const errors = BigInt(base2?.errorCount ?? "0") + (outcome === "error" ? 1n : 0n);
293148
+ const reconnects = BigInt(base2?.reconnectCount ?? "0") + (outcome === "ok" && previous?.lastOutcome === "error" ? 1n : 0n);
293149
+ const snapshot = {
293150
+ exchange: target.exchangeId,
293151
+ accountSelector: target.account.label,
293152
+ accountRole: target.account.role,
293153
+ streamKind: "deposit_poller",
293154
+ accountScope: "spot",
293155
+ registryStatus: "active",
293156
+ retiredAt: null,
293157
+ state,
293158
+ stateChangedAt,
293159
+ lastConnectedAt: outcome === "ok" ? observation.completed_at : base2?.lastConnectedAt ?? null,
293160
+ lastAuthenticatedAt: null,
293161
+ lastReceivedAt: outcome === "ok" ? observation.completed_at : base2?.lastReceivedAt ?? null,
293162
+ connectAttemptCount: attempts.toString(),
293163
+ reconnectCount: reconnects.toString(),
293164
+ errorCount: errors.toString(),
293165
+ lastFailureKind: outcome === "ok" ? "none" : outcome === "unsupported" ? "unsupported_connector" : "transport_error",
293166
+ lastFailureReason: outcome === "ok" ? "" : outcome === "unsupported" ? "fetchDeposits unsupported" : observation.error_reason,
293167
+ trafficMode: "continuous",
293168
+ sourceWatermark: null,
293169
+ pollObservation: observation
293170
+ };
293171
+ this.#coverage.set(key, { snapshot, lastOutcome: outcome });
293172
+ publisher.publish(this.#coverageSnapshots());
293173
+ }
293174
+ #coverageSnapshots() {
293175
+ return [...this.#coverage.values()].map((entry) => entry.snapshot);
293176
+ }
293177
+ async#pollTarget(target, attemptedAt) {
293029
293178
  const exchange = target.account.exchange;
293030
293179
  const key = this.#targetKey(target);
293031
293180
  if (typeof exchange.fetchDeposits !== "function" || exchange.has?.fetchDeposits === false) {
@@ -293036,7 +293185,10 @@ class DepositArchivePoller {
293036
293185
  account: target.account.label
293037
293186
  });
293038
293187
  }
293039
- return "unsupported";
293188
+ return {
293189
+ outcome: "unsupported",
293190
+ observation: this.#observation("unsupported", attemptedAt)
293191
+ };
293040
293192
  }
293041
293193
  const since = this.#cursors.get(key) ?? Date.now() - this.#config.lookbackMs;
293042
293194
  let deposits;
@@ -293049,15 +293201,48 @@ class DepositArchivePoller {
293049
293201
  account: target.account.label,
293050
293202
  error
293051
293203
  });
293052
- return "error";
293204
+ return {
293205
+ outcome: "error",
293206
+ observation: this.#observation("error", attemptedAt, {
293207
+ requestSinceMs: since,
293208
+ errorReason: failureReason(error, exchange)
293209
+ })
293210
+ };
293053
293211
  }
293054
- if (!Array.isArray(deposits) || deposits.length === 0) {
293055
- return "ok";
293212
+ if (!Array.isArray(deposits)) {
293213
+ return {
293214
+ outcome: "error",
293215
+ observation: this.#observation("error", attemptedAt, {
293216
+ requestSinceMs: since,
293217
+ errorReason: "fetchDeposits returned a non-array response"
293218
+ })
293219
+ };
293220
+ }
293221
+ if (deposits.length > this.#config.depositsLimit) {
293222
+ return {
293223
+ outcome: "error",
293224
+ observation: this.#observation("error", attemptedAt, {
293225
+ requestSinceMs: since,
293226
+ errorReason: `fetchDeposits returned ${deposits.length} rows for a limit of ${this.#config.depositsLimit}`
293227
+ })
293228
+ };
293229
+ }
293230
+ if (deposits.length === 0) {
293231
+ return this.#completed(this.#observation("success", attemptedAt, {
293232
+ completedAt: new Date().toISOString(),
293233
+ requestSinceMs: since,
293234
+ nextCursorMs: this.#cursors.get(key),
293235
+ responseTruncated: false,
293236
+ observedDeposits: []
293237
+ }));
293056
293238
  }
293057
293239
  let archived = 0;
293240
+ let malformed = 0;
293241
+ const observed = [];
293058
293242
  for (const deposit of deposits) {
293059
293243
  const record = asRecord(deposit);
293060
293244
  if (!record) {
293245
+ malformed += 1;
293061
293246
  continue;
293062
293247
  }
293063
293248
  const info = asRecord(record.info);
@@ -293082,6 +293267,16 @@ class DepositArchivePoller {
293082
293267
  });
293083
293268
  const lastArchived = identity === undefined ? undefined : this.#lastArchivedByTarget.get(key)?.get(identity);
293084
293269
  const classification = classifyDeposit(target.exchangeId, record, lastArchived);
293270
+ const depositTimestampMs = depositTimestamp(record);
293271
+ observed.push({
293272
+ coin: assetSymbol === undefined ? "" : String(assetSymbol),
293273
+ network: network === undefined ? "" : String(network),
293274
+ external_id: depositTxid ?? "",
293275
+ txid: depositTxid ?? "",
293276
+ deposit_timestamp_ms: decimal(depositTimestampMs),
293277
+ status: classification.archiveStatus,
293278
+ progress: observedProgress(classification.unlockProgress)
293279
+ });
293085
293280
  if (lastArchived && lastArchived.status === classification.archiveStatus && lastArchived.progressKey === classification.progressKey) {
293086
293281
  continue;
293087
293282
  }
@@ -293122,7 +293317,7 @@ class DepositArchivePoller {
293122
293317
  }
293123
293318
  targetDeposits2.set(identity, {
293124
293319
  status: classification.archiveStatus,
293125
- timestamp: depositTimestamp(record),
293320
+ timestamp: depositTimestampMs,
293126
293321
  progressKey: classification.progressKey,
293127
293322
  highWatermark: classification.highWatermark
293128
293323
  });
@@ -293145,7 +293340,20 @@ class DepositArchivePoller {
293145
293340
  this.#lastArchivedByTarget.delete(key);
293146
293341
  }
293147
293342
  }
293148
- return "ok";
293343
+ return this.#completed(this.#observation("success", attemptedAt, {
293344
+ completedAt: new Date().toISOString(),
293345
+ requestSinceMs: since,
293346
+ nextCursorMs: nextCursor,
293347
+ responseTruncated: deposits.length >= this.#config.depositsLimit,
293348
+ malformedCount: malformed,
293349
+ observedDeposits: observed
293350
+ }));
293351
+ }
293352
+ #completed(observation) {
293353
+ return {
293354
+ outcome: observation.disposition === "success" ? "ok" : "error",
293355
+ observation
293356
+ };
293149
293357
  }
293150
293358
  #targetKey(target) {
293151
293359
  return `${target.exchangeId}|${target.account.label}|${target.code}`;
@@ -293292,10 +293500,7 @@ import { createHash as createHash4 } from "node:crypto";
293292
293500
  var MARKET_CAPTURE_SCHEMA_VERSION = "1.0.0";
293293
293501
  var CHECKSUM_ALGORITHM = "sha256-canonical-json-v1";
293294
293502
  var ARCHIVE_SOURCES = ["broker_read", "broker_write"];
293295
- var MARKET_ARCHIVE_SOURCES = [
293296
- ...ARCHIVE_SOURCES,
293297
- "external_backfill"
293298
- ];
293503
+ var MARKET_ARCHIVE_SOURCES = ARCHIVE_SOURCES;
293299
293504
  var CAPTURE_FEEDS = [
293300
293505
  "ORDERBOOK",
293301
293506
  "TICKER",
@@ -293309,15 +293514,12 @@ var SOURCE_MODES = [
293309
293514
  "broker_bootstrap_fetch_v1",
293310
293515
  "external_ccxt_fallback_v1",
293311
293516
  "external_hummingbot_fallback_v1",
293312
- "legacy_migration_v1",
293313
- "historical_vendor_orderbook_v1",
293314
- "vendor_historical_backfill_v1"
293517
+ "legacy_migration_v1"
293315
293518
  ];
293316
293519
  var RAW_CAPTURE_SCOPES = [
293317
293520
  "ccxt_normalized_object",
293318
293521
  "broker_visible_payload",
293319
- "exchange_wire_frame",
293320
- "vendor_normalized_dataset_file"
293522
+ "exchange_wire_frame"
293321
293523
  ];
293322
293524
  var CHECKSUM_FIELDS = new Set([
293323
293525
  "normalized_row_checksum",
@@ -293506,6 +293708,9 @@ function createRawCapture(context2, input) {
293506
293708
  checksumAlgorithm: context2.checksumAlgorithm
293507
293709
  };
293508
293710
  }
293711
+ function projectRawCapturePayload(rawCapture, redactedPayload) {
293712
+ return { ...rawCapture, redactedPayload };
293713
+ }
293509
293714
  function captureCoreFields(context2, rawCapture) {
293510
293715
  assertCaptureContext(context2);
293511
293716
  return {
@@ -294082,14 +294287,20 @@ function resolveConservativeOrderBookAcquisitionProfile(input) {
294082
294287
  id: `${exchange}:conservative:default`,
294083
294288
  upstreamLimit: undefined,
294084
294289
  guaranteedRetainedDepth: undefined,
294085
- coalescingSupported: false
294290
+ coalescingSupported: false,
294291
+ bidExhaustionEvidence: false,
294292
+ askExhaustionEvidence: false,
294293
+ exhaustionEvidenceValidated: true
294086
294294
  };
294087
294295
  }
294088
294296
  return {
294089
294297
  id: `${exchange}:conservative:limit:${input.requestedDepth}`,
294090
294298
  upstreamLimit: input.requestedDepth,
294091
294299
  guaranteedRetainedDepth: input.requestedDepth,
294092
- coalescingSupported: false
294300
+ coalescingSupported: false,
294301
+ bidExhaustionEvidence: false,
294302
+ askExhaustionEvidence: false,
294303
+ exhaustionEvidenceValidated: true
294093
294304
  };
294094
294305
  }
294095
294306
  function resolveOrderBookAcquisitionProfile(input) {
@@ -294101,7 +294312,10 @@ function resolveOrderBookAcquisitionProfile(input) {
294101
294312
  id: candidateProfileId,
294102
294313
  upstreamLimit: VERIFIED_PROFILE_DEPTH,
294103
294314
  guaranteedRetainedDepth: VERIFIED_PROFILE_DEPTH,
294104
- coalescingSupported: true
294315
+ coalescingSupported: true,
294316
+ bidExhaustionEvidence: false,
294317
+ askExhaustionEvidence: false,
294318
+ exhaustionEvidenceValidated: true
294105
294319
  };
294106
294320
  }
294107
294321
  return resolveConservativeOrderBookAcquisitionProfile(input);
@@ -294251,7 +294465,63 @@ class PublicFeedSubscriberBuffer {
294251
294465
  this.#bytes = 0;
294252
294466
  }
294253
294467
  }
294468
+ // src/helpers/market-data-archive/orderbook-depth.ts
294469
+ var DEFAULT_ORDERBOOK_ARCHIVE_DEPTH_LIMIT = 25;
294470
+ var MAX_ORDERBOOK_ARCHIVE_DEPTH_LIMIT = 500;
294471
+ var MAX_ORDERBOOK_MEASUREMENT_BANDS = 64;
294472
+ var MAX_ORDERBOOK_MEASUREMENT_BAND_BPS = 1e4;
294473
+ var DEFAULT_ORDERBOOK_MEASUREMENT_BANDS_BPS = [10, 25, 50, 100];
294474
+ function parseBoundedDepth(raw, field) {
294475
+ if (!/^[1-9]\d*$/.test(raw)) {
294476
+ throw new Error(`${field} must be an integer between 1 and 500`);
294477
+ }
294478
+ const parsed = Number(raw);
294479
+ if (!Number.isSafeInteger(parsed) || parsed > MAX_ORDERBOOK_ARCHIVE_DEPTH_LIMIT) {
294480
+ throw new Error(`${field} must be an integer between 1 and 500`);
294481
+ }
294482
+ return parsed;
294483
+ }
294484
+ function getOrderbookArchiveDepthLimit() {
294485
+ const raw = process.env.CEX_BROKER_ORDERBOOK_ARCHIVE_DEPTH_LIMIT?.trim();
294486
+ if (!raw) {
294487
+ return DEFAULT_ORDERBOOK_ARCHIVE_DEPTH_LIMIT;
294488
+ }
294489
+ return parseBoundedDepth(raw, "CEX_BROKER_ORDERBOOK_ARCHIVE_DEPTH_LIMIT");
294490
+ }
294491
+ function normalizeOrderbookMeasurementBandsBps(input) {
294492
+ const bands = input ?? DEFAULT_ORDERBOOK_MEASUREMENT_BANDS_BPS;
294493
+ if (bands.length === 0) {
294494
+ throw new Error("ORDERBOOK measurement bands must not be empty");
294495
+ }
294496
+ if (bands.length > MAX_ORDERBOOK_MEASUREMENT_BANDS) {
294497
+ throw new Error(`ORDERBOOK measurement bands must contain at most ${MAX_ORDERBOOK_MEASUREMENT_BANDS} entries`);
294498
+ }
294499
+ for (const band of bands) {
294500
+ if (!Number.isSafeInteger(band) || band <= 0 || band > MAX_ORDERBOOK_MEASUREMENT_BAND_BPS) {
294501
+ throw new Error("ORDERBOOK measurement bands must be integer basis points in 1..10000");
294502
+ }
294503
+ }
294504
+ const normalized = [...new Set(bands)].sort((left, right) => left - right);
294505
+ if (normalized.length > MAX_ORDERBOOK_MEASUREMENT_BANDS) {
294506
+ throw new Error(`ORDERBOOK measurement bands must contain at most ${MAX_ORDERBOOK_MEASUREMENT_BANDS} unique entries`);
294507
+ }
294508
+ return normalized;
294509
+ }
294510
+ function getOrderbookMeasurementBandsBps() {
294511
+ const raw = process.env.CEX_BROKER_ORDERBOOK_MEASUREMENT_BANDS_BPS?.trim();
294512
+ if (!raw) {
294513
+ return [...DEFAULT_ORDERBOOK_MEASUREMENT_BANDS_BPS];
294514
+ }
294515
+ const parts = raw.split(",").map((entry) => entry.trim());
294516
+ if (parts.some((entry) => !/^[1-9]\d*$/.test(entry))) {
294517
+ throw new Error("CEX_BROKER_ORDERBOOK_MEASUREMENT_BANDS_BPS must be a comma-separated list of positive integers");
294518
+ }
294519
+ return normalizeOrderbookMeasurementBandsBps(parts.map(Number));
294520
+ }
294521
+
294254
294522
  // src/helpers/market-data-archive/canonical-orderbook.ts
294523
+ var ORDERBOOK_SUMMARY_SCHEMA_VERSION = "2.0.0";
294524
+
294255
294525
  class OrderBookValidationError extends Error {
294256
294526
  reason;
294257
294527
  constructor(reason) {
@@ -294277,38 +294547,70 @@ function parseSequence(value) {
294277
294547
  }
294278
294548
  return parsed.toString(10);
294279
294549
  }
294280
- function validateSide(side, levels, depthLimit) {
294550
+ function validatePositiveInteger(value, field, maximum = Number.MAX_SAFE_INTEGER) {
294551
+ if (!Number.isSafeInteger(value) || value <= 0 || value > maximum) {
294552
+ throw new OrderBookValidationError(`${field} must be a positive integer${maximum === 500 ? " between 1 and 500" : ""}`);
294553
+ }
294554
+ }
294555
+ function validateSide(side, levels) {
294281
294556
  if (levels.length === 0) {
294282
294557
  throw new OrderBookValidationError(`${side} side is missing`);
294283
294558
  }
294284
- const retained = levels.slice(0, depthLimit);
294285
- for (let index2 = 0;index2 < retained.length; index2 += 1) {
294286
- const entry = retained[index2];
294559
+ const validated = [];
294560
+ for (let index2 = 0;index2 < levels.length; index2 += 1) {
294561
+ const entry = levels[index2];
294287
294562
  const price = entry?.[0];
294288
294563
  const amount = entry?.[1];
294289
294564
  if (!Array.isArray(entry) || entry.length < 2 || price === undefined || amount === undefined || !Number.isFinite(price) || !Number.isFinite(amount) || price <= 0 || amount <= 0) {
294290
294565
  throw new OrderBookValidationError(`${side} level ${index2} has a non-positive or non-finite price/amount`);
294291
294566
  }
294292
294567
  if (index2 > 0) {
294293
- const previous = retained[index2 - 1]?.[0];
294568
+ const previous = validated[index2 - 1]?.price;
294294
294569
  if (previous === undefined || (side === "bid" ? price >= previous : price <= previous)) {
294295
294570
  throw new OrderBookValidationError(`${side} levels are not strictly ${side === "bid" ? "descending" : "ascending"}`);
294296
294571
  }
294297
294572
  }
294573
+ validated.push({ price, amount });
294298
294574
  }
294299
- return retained.map(([price, amount]) => ({
294300
- price,
294301
- amount
294302
- }));
294575
+ return validated;
294303
294576
  }
294304
- function normalizedBands(input) {
294305
- const bands = input ?? [10, 25, 50, 100];
294306
- for (const band of bands) {
294307
- if (!Number.isFinite(band) || band <= 0 || !Number.isInteger(band)) {
294308
- throw new OrderBookValidationError("measurement bands must be positive integer basis points");
294577
+ function validateMetadata(metadata, observedBids, observedAsks) {
294578
+ if (!metadata.captureProfileId.trim()) {
294579
+ throw new OrderBookValidationError("capture_profile_id must not be empty");
294580
+ }
294581
+ validatePositiveInteger(metadata.effectiveCadenceMs, "effective_cadence_ms");
294582
+ if (metadata.requestedUpstreamDepth !== null) {
294583
+ validatePositiveInteger(metadata.requestedUpstreamDepth, "requested_upstream_depth", 500);
294584
+ }
294585
+ validatePositiveInteger(metadata.observedBidCount, "observed_bid_count");
294586
+ validatePositiveInteger(metadata.observedAskCount, "observed_ask_count");
294587
+ if (metadata.observedBidCount !== observedBids.length) {
294588
+ throw new OrderBookValidationError("observed_bid_count does not match the complete observation");
294589
+ }
294590
+ if (metadata.observedAskCount !== observedAsks.length) {
294591
+ throw new OrderBookValidationError("observed_ask_count does not match the complete observation");
294592
+ }
294593
+ const farthestBid = observedBids.at(-1)?.price;
294594
+ const farthestAsk = observedAsks.at(-1)?.price;
294595
+ if (!Number.isFinite(metadata.observedFarthestBid) || metadata.observedFarthestBid <= 0 || metadata.observedFarthestBid !== farthestBid) {
294596
+ throw new OrderBookValidationError("observed_farthest_bid does not match the complete observation");
294597
+ }
294598
+ if (!Number.isFinite(metadata.observedFarthestAsk) || metadata.observedFarthestAsk <= 0 || metadata.observedFarthestAsk !== farthestAsk) {
294599
+ throw new OrderBookValidationError("observed_farthest_ask does not match the complete observation");
294600
+ }
294601
+ for (const [side, evidence] of [
294602
+ ["bid", metadata.exhaustionEvidence.bid],
294603
+ ["ask", metadata.exhaustionEvidence.ask]
294604
+ ]) {
294605
+ if (evidence.validated !== true || typeof evidence.exhausted !== "boolean" || !evidence.source.trim()) {
294606
+ throw new OrderBookValidationError(`${side} exhaustion evidence must be validated and source-bound`);
294309
294607
  }
294310
294608
  }
294311
- return [...new Set(bands)].sort((left, right) => left - right);
294609
+ try {
294610
+ return normalizeOrderbookMeasurementBandsBps(metadata.measurementBandsBps);
294611
+ } catch (error) {
294612
+ throw new OrderBookValidationError(error instanceof Error ? error.message : "invalid measurement bands");
294613
+ }
294312
294614
  }
294313
294615
  function checksumRow(row) {
294314
294616
  return { ...row, normalized_row_checksum: sha256Canonical(row) };
@@ -294319,20 +294621,33 @@ function commonEvidenceFields(context2, rawCapture, input) {
294319
294621
  source_time_ms: input.eventTimeMs,
294320
294622
  received_time_ms: input.receivedTimeMs,
294321
294623
  snapshot_id: input.snapshotId,
294322
- construction_mode: input.constructionMode,
294624
+ construction_mode: "sampled_top_n_snapshot",
294323
294625
  gap_policy: "record_gap",
294324
294626
  depth_limit: input.depthLimit,
294325
294627
  sequence: input.sequence,
294326
294628
  exact_l2_reconstruction_complete: 0
294327
294629
  };
294328
294630
  }
294631
+ function sideBandEvidence(input) {
294632
+ const farthest = input.levels.at(-1)?.price;
294633
+ const depths = input.boundaries.map((boundary) => input.levels.filter(({ price }) => input.side === "bid" ? price >= boundary : price <= boundary).reduce((sum3, { amount }) => sum3 + amount, 0));
294634
+ const statuses = input.boundaries.map((boundary) => {
294635
+ const boundaryReached = input.side === "bid" ? farthest <= boundary : farthest >= boundary;
294636
+ return boundaryReached || input.exhausted ? "exact" : "censored";
294637
+ });
294638
+ return { depths, statuses };
294639
+ }
294329
294640
  function buildCanonicalOrderBookRows(input) {
294330
- if (!Number.isSafeInteger(input.depthLimit) || input.depthLimit <= 0 || input.depthLimit > 500) {
294331
- throw new OrderBookValidationError("depth limit must be an integer between 1 and 500");
294332
- }
294641
+ validatePositiveInteger(input.depthLimit, "depth limit", 500);
294333
294642
  if (input.context.feed !== "ORDERBOOK") {
294334
294643
  throw new OrderBookValidationError("capture context feed is not ORDERBOOK");
294335
294644
  }
294645
+ if (input.context.schemaVersion !== "1.0.0") {
294646
+ throw new OrderBookValidationError("ORDERBOOK raw and level capture schema must be 1.0.0");
294647
+ }
294648
+ if (!input.context.provenanceComplete) {
294649
+ throw new OrderBookValidationError("summary v2 requires complete live provenance");
294650
+ }
294336
294651
  if (input.constructionMode === "exact_l2_reconstruction") {
294337
294652
  throw new OrderBookValidationError("exact L2 requires a complete continuity proof and is unsupported");
294338
294653
  }
@@ -294347,13 +294662,18 @@ function buildCanonicalOrderBookRows(input) {
294347
294662
  if (receivedTimeMs < eventTimeMs) {
294348
294663
  throw new OrderBookValidationError("received timestamp precedes source timestamp");
294349
294664
  }
294350
- const bids = validateSide("bid", input.snapshot.bids, input.depthLimit);
294351
- const asks = validateSide("ask", input.snapshot.asks, input.depthLimit);
294352
- const bestBid = bids[0];
294353
- const bestAsk = asks[0];
294665
+ const observedBids = validateSide("bid", input.snapshot.bids);
294666
+ const observedAsks = validateSide("ask", input.snapshot.asks);
294667
+ const bestBid = observedBids[0];
294668
+ const bestAsk = observedAsks[0];
294354
294669
  if (bestBid.price >= bestAsk.price) {
294355
294670
  throw new OrderBookValidationError("book is crossed or locked");
294356
294671
  }
294672
+ const bands = validateMetadata(input.archiveMetadata, observedBids, observedAsks);
294673
+ const bids = observedBids.slice(0, input.depthLimit);
294674
+ const asks = observedAsks.slice(0, input.depthLimit);
294675
+ const retainedFarthestBid = bids.at(-1);
294676
+ const retainedFarthestAsk = asks.at(-1);
294357
294677
  const sequence = parseSequence(input.snapshot.sequence);
294358
294678
  const midPrice = (bestBid.price + bestAsk.price) / 2;
294359
294679
  const spread2 = bestAsk.price - bestBid.price;
@@ -294373,8 +294693,7 @@ function buildCanonicalOrderBookRows(input) {
294373
294693
  sequence,
294374
294694
  depthLimit: input.depthLimit,
294375
294695
  eventTimeMs,
294376
- receivedTimeMs,
294377
- constructionMode: input.constructionMode ?? "sampled_top_n_snapshot"
294696
+ receivedTimeMs
294378
294697
  });
294379
294698
  const levels = [
294380
294699
  ["bid", bids],
@@ -294392,17 +294711,34 @@ function buildCanonicalOrderBookRows(input) {
294392
294711
  });
294393
294712
  return { table: "market_data.cex_order_book_levels", row };
294394
294713
  }));
294395
- const bands = normalizedBands(input.measurementBandsBps);
294396
- const bidDepth = bands.map((band) => {
294397
- const minimumPrice = bestBid.price * (1 - band / 1e4);
294398
- return bids.filter(({ price }) => price >= minimumPrice).reduce((sum3, { amount }) => sum3 + amount, 0);
294714
+ const bidBoundaries = bands.map((band) => midPrice * (1 - band / 1e4));
294715
+ const askBoundaries = bands.map((band) => midPrice * (1 + band / 1e4));
294716
+ const bidEvidence = sideBandEvidence({
294717
+ levels: observedBids,
294718
+ boundaries: bidBoundaries,
294719
+ side: "bid",
294720
+ exhausted: input.archiveMetadata.exhaustionEvidence.bid.exhausted
294399
294721
  });
294400
- const askDepth = bands.map((band) => {
294401
- const maximumPrice = bestAsk.price * (1 + band / 1e4);
294402
- return asks.filter(({ price }) => price <= maximumPrice).reduce((sum3, { amount }) => sum3 + amount, 0);
294722
+ const askEvidence = sideBandEvidence({
294723
+ levels: observedAsks,
294724
+ boundaries: askBoundaries,
294725
+ side: "ask",
294726
+ exhausted: input.archiveMetadata.exhaustionEvidence.ask.exhausted
294403
294727
  });
294404
294728
  const summaryRow = checksumRow({
294405
294729
  ...common,
294730
+ schema_version: ORDERBOOK_SUMMARY_SCHEMA_VERSION,
294731
+ capture_profile_id: input.archiveMetadata.captureProfileId,
294732
+ effective_cadence_ms: input.archiveMetadata.effectiveCadenceMs,
294733
+ requested_upstream_depth: input.archiveMetadata.requestedUpstreamDepth,
294734
+ observed_bid_count: input.archiveMetadata.observedBidCount,
294735
+ observed_ask_count: input.archiveMetadata.observedAskCount,
294736
+ observed_farthest_bid: input.archiveMetadata.observedFarthestBid,
294737
+ observed_farthest_ask: input.archiveMetadata.observedFarthestAsk,
294738
+ retained_farthest_bid: retainedFarthestBid.price,
294739
+ retained_farthest_ask: retainedFarthestAsk.price,
294740
+ bid_exhausted: input.archiveMetadata.exhaustionEvidence.bid.exhausted ? 1 : 0,
294741
+ ask_exhausted: input.archiveMetadata.exhaustionEvidence.ask.exhausted ? 1 : 0,
294406
294742
  best_bid: bestBid.price,
294407
294743
  best_ask: bestAsk.price,
294408
294744
  best_bid_amount: bestBid.amount,
@@ -294414,8 +294750,12 @@ function buildCanonicalOrderBookRows(input) {
294414
294750
  bid_level_count: bids.length,
294415
294751
  ask_level_count: asks.length,
294416
294752
  measurement_bands_bps: bands,
294417
- bid_depth_by_band: bidDepth,
294418
- ask_depth_by_band: askDepth
294753
+ bid_boundary_price_by_band: bidBoundaries,
294754
+ ask_boundary_price_by_band: askBoundaries,
294755
+ bid_depth_by_band: bidEvidence.depths,
294756
+ ask_depth_by_band: askEvidence.depths,
294757
+ bid_status_by_band: bidEvidence.statuses,
294758
+ ask_status_by_band: askEvidence.statuses
294419
294759
  });
294420
294760
  return {
294421
294761
  snapshotId,
@@ -294545,21 +294885,6 @@ class OhlcvBarTracker {
294545
294885
  }
294546
294886
  }
294547
294887
 
294548
- // src/helpers/market-data-archive/orderbook-depth.ts
294549
- var DEFAULT_ORDERBOOK_ARCHIVE_DEPTH_LIMIT = 25;
294550
- var MAX_ORDERBOOK_ARCHIVE_DEPTH_LIMIT = 500;
294551
- function getOrderbookArchiveDepthLimit() {
294552
- const raw = process.env.CEX_BROKER_ORDERBOOK_ARCHIVE_DEPTH_LIMIT;
294553
- if (!raw) {
294554
- return DEFAULT_ORDERBOOK_ARCHIVE_DEPTH_LIMIT;
294555
- }
294556
- const parsed = Number.parseInt(raw, 10);
294557
- if (!Number.isFinite(parsed) || parsed <= 0) {
294558
- return DEFAULT_ORDERBOOK_ARCHIVE_DEPTH_LIMIT;
294559
- }
294560
- return Math.min(parsed, MAX_ORDERBOOK_ARCHIVE_DEPTH_LIMIT);
294561
- }
294562
-
294563
294888
  // src/helpers/market-data-archive/parse-stream.ts
294564
294889
  function isFiniteNumber2(value) {
294565
294890
  return typeof value === "number" && Number.isFinite(value);
@@ -294683,13 +295008,17 @@ function legacyMarketFields(context2, rawCapture) {
294683
295008
  function legacyDecimal8(value) {
294684
295009
  return value === undefined ? undefined : Number(value.toFixed(8));
294685
295010
  }
294686
- function buildCanonicalCexStreamEventRow(context2, rawCapture) {
295011
+ function buildCanonicalCexStreamEventRow(context2, rawCapture, options) {
295012
+ const payloadEncoding = options?.payloadEncoding ?? "canonical_json_v1";
295013
+ if (payloadEncoding === "orderbook_metadata_only_v1" && context2.feed !== "ORDERBOOK") {
295014
+ throw new Error("Metadata-only raw encoding requires an ORDERBOOK context");
295015
+ }
294687
295016
  const row = withNormalizedChecksum({
294688
295017
  ...captureCoreFields(context2, rawCapture),
294689
295018
  ...legacyMarketFields(context2, rawCapture),
294690
295019
  stream_type: context2.feed,
294691
295020
  event_time_ms: rawCapture.eventTimeMs,
294692
- payload_encoding: "canonical_json_v1",
295021
+ payload_encoding: payloadEncoding,
294693
295022
  payload_json: canonicalSerialize(rawCapture.redactedPayload)
294694
295023
  });
294695
295024
  return { table: "market_data.cex_stream_events", row };
@@ -294833,7 +295162,13 @@ function archiveOrderbookInBackground(archiver, otelMetrics, input, options) {
294833
295162
  queueMicrotask(() => {
294834
295163
  try {
294835
295164
  const context2 = resolveCaptureContext(archiver, input, "ORDERBOOK", options?.sourceMode ?? "broker_live_sampling_v1");
294836
- const rawCapture = createRawCapture(context2, {
295165
+ const depthLimit = options?.depthLimit ?? getOrderbookArchiveDepthLimit();
295166
+ const measurementBandsBps = normalizeOrderbookMeasurementBandsBps(input.archiveMetadata.measurementBandsBps);
295167
+ const archiveMetadata = {
295168
+ ...input.archiveMetadata,
295169
+ measurementBandsBps
295170
+ };
295171
+ const completeRawCapture = createRawCapture(context2, {
294837
295172
  payload: input.snapshot,
294838
295173
  eventTimeMs: input.snapshot.timestamp,
294839
295174
  receivedTimeMs: input.snapshot.receivedTimestamp,
@@ -294842,10 +295177,29 @@ function archiveOrderbookInBackground(archiver, otelMetrics, input, options) {
294842
295177
  const canonical = buildCanonicalOrderBookRows({
294843
295178
  context: context2,
294844
295179
  snapshot: input.snapshot,
294845
- rawCapture,
294846
- depthLimit: options?.depthLimit ?? getOrderbookArchiveDepthLimit()
295180
+ rawCapture: completeRawCapture,
295181
+ depthLimit,
295182
+ archiveMetadata
294847
295183
  });
294848
- archiver.enqueue(buildCanonicalCexStreamEventRow(context2, rawCapture));
295184
+ const rawMetadata = {
295185
+ capture_profile_id: archiveMetadata.captureProfileId,
295186
+ effective_cadence_ms: archiveMetadata.effectiveCadenceMs,
295187
+ requested_upstream_depth: archiveMetadata.requestedUpstreamDepth,
295188
+ archive_depth_limit: depthLimit,
295189
+ observed_bid_count: archiveMetadata.observedBidCount,
295190
+ observed_ask_count: archiveMetadata.observedAskCount,
295191
+ observed_farthest_bid: canonicalDecimal(archiveMetadata.observedFarthestBid),
295192
+ observed_farthest_ask: canonicalDecimal(archiveMetadata.observedFarthestAsk),
295193
+ bid_exhausted: archiveMetadata.exhaustionEvidence.bid.exhausted,
295194
+ ask_exhausted: archiveMetadata.exhaustionEvidence.ask.exhausted,
295195
+ retained_bid_count: canonical.levels.filter(({ row }) => row.side === "bid").length,
295196
+ retained_ask_count: canonical.levels.filter(({ row }) => row.side === "ask").length,
295197
+ measurement_bands_bps: measurementBandsBps
295198
+ };
295199
+ const storedRawCapture = projectRawCapturePayload(completeRawCapture, rawMetadata);
295200
+ archiver.enqueue(buildCanonicalCexStreamEventRow(context2, storedRawCapture, {
295201
+ payloadEncoding: "orderbook_metadata_only_v1"
295202
+ }));
294849
295203
  for (const row of canonical.levels)
294850
295204
  archiver.enqueue(row);
294851
295205
  archiver.enqueue(canonical.summary);
@@ -294967,6 +295321,9 @@ function archiveTickerInBackground(archiver, otelMetrics, input) {
294967
295321
  });
294968
295322
  }
294969
295323
  function archiveCexStreamEventInBackground(archiver, otelMetrics, input) {
295324
+ if (input.streamType === "ORDERBOOK") {
295325
+ throw new Error("Generic ORDERBOOK archive events are unsupported; use archiveOrderbookInBackground");
295326
+ }
294970
295327
  archiveMarketRowsInBackground(archiver, otelMetrics, "stream", input, input.streamType, () => [
294971
295328
  buildCexStreamEventRow({
294972
295329
  ...input,
@@ -294991,6 +295348,82 @@ function resolveOhlcvBootstrapLimit(optionValue) {
294991
295348
  }
294992
295349
  return Math.min(parsed, MAX_OHLCV_BOOTSTRAP_LIMIT);
294993
295350
  }
295351
+ // src/helpers/market-data-archive/summary-v2-conformance.ts
295352
+ var ORDERBOOK_SUMMARY_V2_SUPPORTED_VIEW_FIELDS = [
295353
+ ["source", "String", false],
295354
+ ["deployment_id", "String", false],
295355
+ ["capture_bundle_id", "String", false],
295356
+ ["exchange", "String", false],
295357
+ ["symbol", "String", false],
295358
+ ["trading_pair", "String", false],
295359
+ ["source_symbol", "String", false],
295360
+ ["asset_type", "String", false],
295361
+ ["feed", "String", false],
295362
+ ["provider", "String", false],
295363
+ ["source_mode", "String", false],
295364
+ ["source_time_ms", "UInt64", false],
295365
+ ["received_time_ms", "UInt64", false],
295366
+ ["raw_capture_id", "String", false],
295367
+ ["raw_capture_scope", "String", false],
295368
+ ["schema_version", "String", false],
295369
+ ["checksum_algorithm", "String", false],
295370
+ ["raw_checksum", "String", false],
295371
+ ["provenance_complete", "UInt8", false],
295372
+ ["snapshot_id", "String", false],
295373
+ ["construction_mode", "String", false],
295374
+ ["gap_policy", "String", false],
295375
+ ["depth_limit", "UInt16", false],
295376
+ ["sequence", "UInt64", true],
295377
+ ["exact_l2_reconstruction_complete", "UInt8", false],
295378
+ ["capture_profile_id", "String", false],
295379
+ ["effective_cadence_ms", "UInt32", false],
295380
+ ["requested_upstream_depth", "UInt16", true],
295381
+ ["observed_bid_count", "UInt32", false],
295382
+ ["observed_ask_count", "UInt32", false],
295383
+ ["observed_farthest_bid", "Decimal(38,18)", false],
295384
+ ["observed_farthest_ask", "Decimal(38,18)", false],
295385
+ ["retained_farthest_bid", "Decimal(38,18)", false],
295386
+ ["retained_farthest_ask", "Decimal(38,18)", false],
295387
+ ["bid_exhausted", "UInt8", false],
295388
+ ["ask_exhausted", "UInt8", false],
295389
+ ["best_bid", "Decimal(38,18)", false],
295390
+ ["best_ask", "Decimal(38,18)", false],
295391
+ ["best_bid_amount", "Decimal(38,18)", false],
295392
+ ["best_ask_amount", "Decimal(38,18)", false],
295393
+ ["mid_price", "Decimal(38,18)", false],
295394
+ ["spread", "Decimal(38,18)", false],
295395
+ ["spread_bps", "Float64", false],
295396
+ ["staleness_ms", "UInt64", false],
295397
+ ["bid_level_count", "UInt16", false],
295398
+ ["ask_level_count", "UInt16", false],
295399
+ ["measurement_bands_bps", "Array(UInt32)", false],
295400
+ ["bid_boundary_price_by_band", "Array(Decimal(38,18))", false],
295401
+ ["ask_boundary_price_by_band", "Array(Decimal(38,18))", false],
295402
+ ["bid_depth_by_band", "Array(Decimal(38,18))", false],
295403
+ ["ask_depth_by_band", "Array(Decimal(38,18))", false],
295404
+ ["bid_status_by_band", "Array(Enum8('exact'=1,'censored'=2))", false],
295405
+ ["ask_status_by_band", "Array(Enum8('exact'=1,'censored'=2))", false],
295406
+ ["normalized_row_checksum", "String", false]
295407
+ ];
295408
+ var ORDERBOOK_SUMMARY_V2_SUPPORTED_VIEW_FIELD_NAMES = ORDERBOOK_SUMMARY_V2_SUPPORTED_VIEW_FIELDS.map(([name]) => name);
295409
+ var DECIMAL_38_18_FIELDS = new Set([
295410
+ "observed_farthest_bid",
295411
+ "observed_farthest_ask",
295412
+ "retained_farthest_bid",
295413
+ "retained_farthest_ask",
295414
+ "best_bid",
295415
+ "best_ask",
295416
+ "best_bid_amount",
295417
+ "best_ask_amount",
295418
+ "mid_price",
295419
+ "spread"
295420
+ ]);
295421
+ var DECIMAL_38_18_ARRAY_FIELDS = new Set([
295422
+ "bid_boundary_price_by_band",
295423
+ "ask_boundary_price_by_band",
295424
+ "bid_depth_by_band",
295425
+ "ask_depth_by_band"
295426
+ ]);
294994
295427
  // src/helpers/order-book.ts
294995
295428
  var ORDER_BOOK_CALL_METHODS = {
294996
295429
  FETCH_CAPABILITY: "fetch_order_book_capability",
@@ -295342,7 +295775,7 @@ class PublicFeedWorker {
295342
295775
  if (this.options.profile) {
295343
295776
  this.options.metrics?.recordCounter("public_feed_acquisition_profiles_total", 1, {
295344
295777
  ...this.#metricLabels(),
295345
- profile: this.options.profile.id
295778
+ profile_class: this.options.profile.coalescingSupported ? "coalesced" : "conservative"
295346
295779
  });
295347
295780
  }
295348
295781
  this.#run = this.#watchLoop();
@@ -295410,7 +295843,34 @@ class PublicFeedWorker {
295410
295843
  depthLimit: retainedDepth,
295411
295844
  receivedTimestamp
295412
295845
  });
295413
- this.options.archiveSink.orderbook({ ...context2, snapshot }, { sampledOut: !this.#sampler.shouldEmit(receivedTimestamp) });
295846
+ const observedBidCount = snapshot.bids.length;
295847
+ const observedAskCount = snapshot.asks.length;
295848
+ this.options.archiveSink.orderbook({
295849
+ ...context2,
295850
+ snapshot,
295851
+ archiveMetadata: {
295852
+ captureProfileId: this.options.profile?.id ?? `${this.options.exchangeName}:conservative:default`,
295853
+ effectiveCadenceMs: getOrderbookIntervalMs(),
295854
+ requestedUpstreamDepth: this.options.profile?.upstreamLimit ?? null,
295855
+ observedBidCount,
295856
+ observedAskCount,
295857
+ observedFarthestBid: snapshot.bids.at(-1)?.[0] ?? Number.NaN,
295858
+ observedFarthestAsk: snapshot.asks.at(-1)?.[0] ?? Number.NaN,
295859
+ exhaustionEvidence: {
295860
+ bid: {
295861
+ exhausted: this.options.profile?.bidExhaustionEvidence ?? false,
295862
+ validated: this.options.profile?.exhaustionEvidenceValidated ?? true,
295863
+ source: this.options.profile?.id ?? "broker:conservative"
295864
+ },
295865
+ ask: {
295866
+ exhausted: this.options.profile?.askExhaustionEvidence ?? false,
295867
+ validated: this.options.profile?.exhaustionEvidenceValidated ?? true,
295868
+ source: this.options.profile?.id ?? "broker:conservative"
295869
+ }
295870
+ },
295871
+ measurementBandsBps: getOrderbookMeasurementBandsBps()
295872
+ }
295873
+ }, { sampledOut: !this.#sampler.shouldEmit(receivedTimestamp) });
295414
295874
  this.#recordCounter("public_feed_archive_decisions_total");
295415
295875
  this.options.observer?.archiveDecision?.(this.#observation());
295416
295876
  this.#fanout((subscriber) => this.#frame(projectOrderBookSnapshot(snapshot, subscriber.depthLimit), receivedTimestamp));
@@ -295583,7 +296043,7 @@ class PublicFeedWorker {
295583
296043
  }
295584
296044
  #metricLabels() {
295585
296045
  return {
295586
- exchange: this.options.exchangeName,
296046
+ exchange_bucket: this.options.exchangeOwned ? "dynamic" : "configured",
295587
296047
  feed: this.options.feed,
295588
296048
  market_type: this.options.marketType
295589
296049
  };
@@ -295771,7 +296231,8 @@ import { request as httpsRequest3 } from "node:https";
295771
296231
  import { dirname as dirname2 } from "node:path";
295772
296232
  var SOURCE = "broker_write";
295773
296233
  var TABLE = "broker_stream_health.snapshots";
295774
- var PRODUCER_ID = "cex-broker-user-data";
296234
+ var USER_DATA_STREAM_HEALTH_PRODUCER_ID = "cex-broker-user-data";
296235
+ var DEPOSIT_POLLER_STREAM_HEALTH_PRODUCER_ID = "cex-broker-deposit-poller";
295775
296236
  var STATE_VERSION = 1;
295776
296237
  var HEARTBEAT_MS = 30000;
295777
296238
  var FORWARDER_TIMEOUT_MS = 3000;
@@ -295806,11 +296267,11 @@ function registryRevision(snapshots) {
295806
296267
  })).sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
295807
296268
  return createHash5("sha256").update(JSON.stringify(rows)).digest("hex");
295808
296269
  }
295809
- function validState(value) {
296270
+ function validState(value, producerId) {
295810
296271
  if (!value || typeof value !== "object" || Array.isArray(value))
295811
296272
  return false;
295812
296273
  const state = value;
295813
- return state.version === STATE_VERSION && state.producerId === PRODUCER_ID && typeof state.producerEpoch === "string" && typeof state.runId === "string" && typeof state.nextBatchSequence === "string" && state.nextStreamSequences !== null && typeof state.nextStreamSequences === "object" && (state.pendingBody === undefined || typeof state.pendingBody === "string");
296274
+ return state.version === STATE_VERSION && state.producerId === producerId && typeof state.producerEpoch === "string" && typeof state.runId === "string" && typeof state.nextBatchSequence === "string" && state.nextStreamSequences !== null && typeof state.nextStreamSequences === "object" && (state.pendingBody === undefined || typeof state.pendingBody === "string");
295814
296275
  }
295815
296276
  function forwarderPost(url2, body, authToken, timeoutMs) {
295816
296277
  const request = url2.protocol === "http:" ? httpRequest3 : httpsRequest3;
@@ -295840,6 +296301,7 @@ function forwarderPost(url2, body, authToken, timeoutMs) {
295840
296301
  }
295841
296302
 
295842
296303
  class StreamHealthPublisher {
296304
+ #producerId;
295843
296305
  #deploymentId;
295844
296306
  #statePath;
295845
296307
  #heartbeatMs;
@@ -295854,6 +296316,7 @@ class StreamHealthPublisher {
295854
296316
  #retry = null;
295855
296317
  #retryAttempt = 0;
295856
296318
  constructor(options) {
296319
+ this.#producerId = identifier(options.producerId, "producer_id");
295857
296320
  this.#deploymentId = identifier(options.deploymentId, "deployment_id");
295858
296321
  this.#statePath = options.statePath.trim();
295859
296322
  if (!this.#statePath) {
@@ -295878,7 +296341,7 @@ class StreamHealthPublisher {
295878
296341
  const loaded = this.#read();
295879
296342
  this.#state = loaded ?? {
295880
296343
  version: STATE_VERSION,
295881
- producerId: PRODUCER_ID,
296344
+ producerId: this.#producerId,
295882
296345
  producerEpoch: "1",
295883
296346
  runId: randomUUID3(),
295884
296347
  nextBatchSequence: "1",
@@ -295991,7 +296454,7 @@ class StreamHealthPublisher {
295991
296454
  return {
295992
296455
  table: TABLE,
295993
296456
  row: {
295994
- producer_id: PRODUCER_ID,
296457
+ producer_id: this.#producerId,
295995
296458
  producer_epoch: this.#state.producerEpoch,
295996
296459
  run_id: this.#state.runId,
295997
296460
  batch_sequence: batchSequence,
@@ -296018,7 +296481,8 @@ class StreamHealthPublisher {
296018
296481
  last_failure_kind: snapshot.lastFailureKind,
296019
296482
  last_failure_reason: snapshot.lastFailureReason,
296020
296483
  traffic_mode: snapshot.trafficMode,
296021
- source_watermark: snapshot.sourceWatermark
296484
+ source_watermark: snapshot.sourceWatermark,
296485
+ ...snapshot.streamKind === "deposit_poller" ? { poll_observation: snapshot.pollObservation } : {}
296022
296486
  }
296023
296487
  };
296024
296488
  });
@@ -296046,7 +296510,7 @@ class StreamHealthPublisher {
296046
296510
  #read() {
296047
296511
  try {
296048
296512
  const parsed = JSON.parse(readFileSync2(this.#statePath, "utf8"));
296049
- if (!validState(parsed))
296513
+ if (!validState(parsed, this.#producerId))
296050
296514
  throw new Error("invalid state shape");
296051
296515
  return parsed;
296052
296516
  } catch (error) {
@@ -296109,6 +296573,14 @@ function streamHealthPublisherConfigFromEnv(env = process.env) {
296109
296573
  forwarderAuthToken: env.CEX_BROKER_ARCHIVE_FORWARDER_TOKEN?.trim() || undefined
296110
296574
  };
296111
296575
  }
296576
+ function depositPollerStreamHealthPublisherConfigFromEnv(env = process.env) {
296577
+ const base2 = streamHealthPublisherConfigFromEnv(env);
296578
+ return {
296579
+ ...base2,
296580
+ producerId: DEPOSIT_POLLER_STREAM_HEALTH_PRODUCER_ID,
296581
+ statePath: `${base2.statePath}.deposit-poller`
296582
+ };
296583
+ }
296112
296584
 
296113
296585
  // src/helpers/user-asset-archive-poller.ts
296114
296586
  var DEFAULT_CONFIG4 = {
@@ -296679,7 +297151,7 @@ class AccountWorker {
296679
297151
  for (const subscriber of [...this.#subscribers])
296680
297152
  subscriber.close();
296681
297153
  }
296682
- #transition(state, failureKind, failureReason) {
297154
+ #transition(state, failureKind, failureReason2) {
296683
297155
  const timestamp = now3();
296684
297156
  if (this.#snapshot.state !== state) {
296685
297157
  this.#snapshot.state = state;
@@ -296687,7 +297159,7 @@ class AccountWorker {
296687
297159
  }
296688
297160
  if (failureKind) {
296689
297161
  this.#snapshot.lastFailureKind = failureKind;
296690
- this.#snapshot.lastFailureReason = failureReason ?? "";
297162
+ this.#snapshot.lastFailureReason = failureReason2 ?? "";
296691
297163
  }
296692
297164
  this.onChange();
296693
297165
  }
@@ -296811,7 +297283,7 @@ class UserDataStreamSupervisor {
296811
297283
  }
296812
297284
 
296813
297285
  // src/server.ts
296814
- import * as grpc14 from "@grpc/grpc-js";
297286
+ import * as grpc16 from "@grpc/grpc-js";
296815
297287
 
296816
297288
  // src/handlers/execute-action/deposit.ts
296817
297289
  import * as grpc3 from "@grpc/grpc-js";
@@ -310463,7 +310935,10 @@ config(en_default());
310463
310935
  // src/schemas/action-payloads.ts
310464
310936
  var parseJsonString = (value) => {
310465
310937
  if (typeof value !== "string") {
310466
- return value;
310938
+ if (value === null || value === undefined || typeof value === "number" || typeof value === "boolean" || typeof value === "object") {
310939
+ return value;
310940
+ }
310941
+ return String(value);
310467
310942
  }
310468
310943
  try {
310469
310944
  return JSON.parse(value);
@@ -310472,19 +310947,6 @@ var parseJsonString = (value) => {
310472
310947
  }
310473
310948
  };
310474
310949
  var stringNumberRecordSchema = exports_external.record(exports_external.string(), exports_external.union([exports_external.string(), exports_external.number()]));
310475
- var booleanLikeSchema = exports_external.preprocess((value) => {
310476
- if (typeof value !== "string") {
310477
- return value;
310478
- }
310479
- const normalized = value.trim().toLowerCase();
310480
- if (["true", "1", "yes"].includes(normalized)) {
310481
- return true;
310482
- }
310483
- if (["false", "0", "no"].includes(normalized)) {
310484
- return false;
310485
- }
310486
- return value;
310487
- }, exports_external.boolean());
310488
310950
  var DepositPayloadSchema = exports_external.object({
310489
310951
  recipientAddress: exports_external.string().min(1),
310490
310952
  amount: exports_external.coerce.number().positive(),
@@ -310545,10 +311007,22 @@ var CancelOrderPayloadSchema = exports_external.object({
310545
311007
  orderId: exports_external.string().min(1),
310546
311008
  params: exports_external.preprocess(parseJsonString, stringNumberRecordSchema).default({})
310547
311009
  });
310548
- var FetchFeesPayloadSchema = exports_external.object({
310549
- includeAllFees: booleanLikeSchema.optional().default(false),
310550
- includeFundingFees: booleanLikeSchema.optional()
310551
- });
311010
+ var EmptyActionPayloadSchema = exports_external.object({}).strict();
311011
+ var FetchFeesPayloadSchema = EmptyActionPayloadSchema;
311012
+ var FetchCurrencyPayloadSchema = exports_external.object({
311013
+ network: exports_external.string().trim().min(1)
311014
+ }).strict();
311015
+ var MAX_BATCH_CHILDREN = 32;
311016
+ var MAX_BATCH_REQUEST_BYTES = 256 * 1024;
311017
+ var BatchChildRequestSchema = exports_external.object({
311018
+ id: exports_external.string().trim().min(1),
311019
+ action: exports_external.number().int().nonnegative(),
311020
+ symbol: exports_external.string(),
311021
+ payload: exports_external.record(exports_external.string(), exports_external.string())
311022
+ }).strict();
311023
+ var BatchPayloadSchema = exports_external.object({
311024
+ requests: exports_external.preprocess(parseJsonString, exports_external.array(BatchChildRequestSchema).min(1).max(MAX_BATCH_CHILDREN))
311025
+ }).strict();
310552
311026
 
310553
311027
  // src/helpers/grpc/callbacks.ts
310554
311028
  import * as grpc from "@grpc/grpc-js";
@@ -310597,7 +311071,7 @@ function stableGrpcErrorCode(message) {
310597
311071
  if (message.startsWith("AuthenticationError:")) {
310598
311072
  return grpc2.status.UNAUTHENTICATED;
310599
311073
  }
310600
- if (message.startsWith("InsufficientFunds:")) {
311074
+ if (message.startsWith("InsufficientFunds:") || message.startsWith("fee_unavailable:")) {
310601
311075
  return grpc2.status.FAILED_PRECONDITION;
310602
311076
  }
310603
311077
  if (message.startsWith("venue_discovery_unavailable:")) {
@@ -310657,6 +311131,13 @@ function resolveGrpcError(error48, message) {
310657
311131
  }
310658
311132
 
310659
311133
  // src/handlers/execute-action/context.ts
311134
+ function requireSymbol(ctx, message = "ValidationError: Symbol required") {
311135
+ if (!ctx.symbol) {
311136
+ ctx.wrappedCallback(invalidArgumentError(message), null);
311137
+ return false;
311138
+ }
311139
+ return true;
311140
+ }
310660
311141
  function parsePayloadForAction(ctx, schema) {
310661
311142
  return parseActionPayload(schema, ctx.call.request.payload, ctx.wrappedCallback);
310662
311143
  }
@@ -310679,6 +311160,12 @@ function rejectWithGrpcError(ctx, error48, options) {
310679
311160
  }
310680
311161
  ctx.wrappedCallback({ code, message: finalMessage }, null);
310681
311162
  }
311163
+ function successWithProof(ctx, result) {
311164
+ ctx.wrappedCallback(null, {
311165
+ proof: ctx.verity.proof,
311166
+ result: JSON.stringify(result)
311167
+ });
311168
+ }
310682
311169
 
310683
311170
  // src/handlers/execute-action/deposit.ts
310684
311171
  async function handleDeposit(ctx) {
@@ -310856,7 +311343,7 @@ async function handleDeposit(ctx) {
310856
311343
  }
310857
311344
  }
310858
311345
  // src/handlers/execute-action/handler.ts
310859
- import * as grpc12 from "@grpc/grpc-js";
311346
+ import * as grpc14 from "@grpc/grpc-js";
310860
311347
 
310861
311348
  // src/helpers/grpc/broker.ts
310862
311349
  function selectBrokerAccountForCex(normalizedCex, brokers, metadata) {
@@ -310950,7 +311437,29 @@ async function handleOrderBookCall(ctx) {
310950
311437
  assetType: "spot",
310951
311438
  accountSelector: ctx.selectedBrokerAccount?.label,
310952
311439
  deploymentId: ctx.brokerArchiver?.getDeploymentId() ?? "unarchived",
310953
- snapshot
311440
+ snapshot,
311441
+ archiveMetadata: {
311442
+ captureProfileId: `${orderBookPayload.exchange.trim().toLowerCase()}:current-snapshot:requested:${orderBookPayload.depthLimit}`,
311443
+ effectiveCadenceMs: 1,
311444
+ requestedUpstreamDepth: orderBookPayload.depthLimit,
311445
+ observedBidCount: snapshot.bids.length,
311446
+ observedAskCount: snapshot.asks.length,
311447
+ observedFarthestBid: snapshot.bids.at(-1)?.[0] ?? Number.NaN,
311448
+ observedFarthestAsk: snapshot.asks.at(-1)?.[0] ?? Number.NaN,
311449
+ exhaustionEvidence: {
311450
+ bid: {
311451
+ exhausted: false,
311452
+ validated: true,
311453
+ source: "broker:current-snapshot"
311454
+ },
311455
+ ask: {
311456
+ exhausted: false,
311457
+ validated: true,
311458
+ source: "broker:current-snapshot"
311459
+ }
311460
+ },
311461
+ measurementBandsBps: getOrderbookMeasurementBandsBps()
311462
+ }
310954
311463
  }, {
310955
311464
  sourceMode: "broker_current_snapshot_v1",
310956
311465
  depthLimit: orderBookPayload.depthLimit
@@ -310966,10 +311475,458 @@ async function handleOrderBookCall(ctx) {
310966
311475
  }
310967
311476
 
310968
311477
  // src/handlers/execute-action/registry.ts
310969
- import * as grpc11 from "@grpc/grpc-js";
311478
+ import * as grpc13 from "@grpc/grpc-js";
310970
311479
 
310971
- // src/handlers/execute-action/internal-transfer.ts
311480
+ // src/handlers/execute-action/batch.ts
310972
311481
  import * as grpc5 from "@grpc/grpc-js";
311482
+
311483
+ // src/helpers/venue-evidence.ts
311484
+ var DECIMAL_PATTERN = /^\+?(\d+)(?:\.(\d*))?(?:[eE]([+-]?\d+))?$/;
311485
+ function exchangeSecretLiterals(broker) {
311486
+ const record2 = broker;
311487
+ return [
311488
+ record2.apiKey,
311489
+ record2.secret,
311490
+ record2.password,
311491
+ record2.privateKey
311492
+ ].filter((value) => typeof value === "string" && value.length > 0);
311493
+ }
311494
+ function sanitizeVenueError(error48, broker) {
311495
+ return redactSecretLiterals(sanitizeErrorDetail(error48), exchangeSecretLiterals(broker));
311496
+ }
311497
+ function resolveEvidenceAccountScope(selectedBrokerAccount, metadata) {
311498
+ return {
311499
+ accountSelector: selectedBrokerAccount?.label ?? getCurrentBrokerSelector(metadata),
311500
+ credentialSource: selectedBrokerAccount ? "configured_pool" : "request_metadata"
311501
+ };
311502
+ }
311503
+ function canonicalNonnegativeDecimal(value, field) {
311504
+ const text = typeof value === "number" ? Number.isFinite(value) ? String(value) : "" : typeof value === "string" ? value.trim() : "";
311505
+ const match = text.match(DECIMAL_PATTERN);
311506
+ if (!match) {
311507
+ throw new Error(`venue_discovery_unavailable: ${field} must be decimal`);
311508
+ }
311509
+ const integer2 = match[1] ?? "0";
311510
+ const fraction = match[2] ?? "";
311511
+ const exponent = Number.parseInt(match[3] ?? "0", 10);
311512
+ if (!Number.isSafeInteger(exponent)) {
311513
+ throw new Error(`venue_discovery_unavailable: ${field} exponent is invalid`);
311514
+ }
311515
+ const digits = `${integer2}${fraction}`;
311516
+ const decimalIndex = integer2.length + exponent;
311517
+ let rendered;
311518
+ if (decimalIndex <= 0) {
311519
+ rendered = `0.${"0".repeat(-decimalIndex)}${digits}`;
311520
+ } else if (decimalIndex >= digits.length) {
311521
+ rendered = `${digits}${"0".repeat(decimalIndex - digits.length)}`;
311522
+ } else {
311523
+ rendered = `${digits.slice(0, decimalIndex)}.${digits.slice(decimalIndex)}`;
311524
+ }
311525
+ const [whole = "0", decimals = ""] = rendered.split(".");
311526
+ const normalizedWhole = whole.replace(/^0+(?=\d)/, "") || "0";
311527
+ const normalizedDecimals = decimals.replace(/0+$/, "");
311528
+ return normalizedDecimals ? `${normalizedWhole}.${normalizedDecimals}` : normalizedWhole;
311529
+ }
311530
+ function decimalFractionToBasisPoints(value) {
311531
+ const canonical = canonicalNonnegativeDecimal(value, "fee rate");
311532
+ return canonicalNonnegativeDecimal(`${canonical}e4`, "fee basis points");
311533
+ }
311534
+ function canonicalOptionalDecimal(value, field) {
311535
+ return value === undefined || value === null ? undefined : canonicalNonnegativeDecimal(value, field);
311536
+ }
311537
+ function precisionIncrement(value, precisionMode, field) {
311538
+ if ((precisionMode === 2 || precisionMode === "DECIMAL_PLACES") && (typeof value === "number" || typeof value === "string")) {
311539
+ const decimalPlaces = Number(value);
311540
+ if (Number.isInteger(decimalPlaces) && decimalPlaces >= 0) {
311541
+ return canonicalNonnegativeDecimal(`1e-${decimalPlaces}`, field);
311542
+ }
311543
+ }
311544
+ return canonicalNonnegativeDecimal(value, field);
311545
+ }
311546
+ function evidenceSourceDigest(input) {
311547
+ const source = removeSecretMaterial(input.source, exchangeSecretLiterals(input.broker));
311548
+ return sha256Canonical({
311549
+ action: input.action,
311550
+ exchange: input.exchange,
311551
+ requestedKey: input.requestedKey,
311552
+ accountSelector: input.accountSelector,
311553
+ sourceMethod: input.sourceMethod,
311554
+ source
311555
+ });
311556
+ }
311557
+ async function resolveSpotMarketIdentity(broker, symbol2) {
311558
+ const requestedSymbol = symbol2.trim().toUpperCase();
311559
+ if (!/^[^/\s]+\/[^/\s]+$/.test(requestedSymbol)) {
311560
+ throw new Error("venue_discovery_unavailable: symbol must be a slash-delimited spot pair");
311561
+ }
311562
+ await broker.loadMarkets();
311563
+ const market = broker.market(requestedSymbol);
311564
+ if (!isRecord(market)) {
311565
+ throw new Error(`venue_discovery_unavailable: market not found for ${requestedSymbol}`);
311566
+ }
311567
+ const unifiedSymbol = typeof market.symbol === "string" ? market.symbol.trim().toUpperCase() : requestedSymbol;
311568
+ const baseAsset = typeof market.base === "string" ? market.base.trim().toUpperCase() : "";
311569
+ const quoteAsset = typeof market.quote === "string" ? market.quote.trim().toUpperCase() : "";
311570
+ const sourceSymbol = typeof market.id === "string" ? market.id.trim() : "";
311571
+ const isSpot = market.spot === true || market.type === "spot";
311572
+ if (unifiedSymbol !== requestedSymbol || !baseAsset || !quoteAsset || !sourceSymbol || !isSpot || market.active !== true) {
311573
+ throw new Error(`venue_discovery_unavailable: active spot market identity unavailable for ${requestedSymbol}`);
311574
+ }
311575
+ return {
311576
+ market,
311577
+ canonicalPair: `${baseAsset}-${quoteAsset}`,
311578
+ unifiedSymbol,
311579
+ sourceSymbol,
311580
+ baseAsset,
311581
+ quoteAsset
311582
+ };
311583
+ }
311584
+ function extractTradingFeeRates(response) {
311585
+ if (!isRecord(response)) {
311586
+ throw new Error("fee_unavailable: trading-fee response is not an object");
311587
+ }
311588
+ const info = isRecord(response.info) ? response.info : undefined;
311589
+ const nestedData = isRecord(info?.data) ? info.data : isRecord(response.data) ? response.data : undefined;
311590
+ const maker = [
311591
+ response.maker,
311592
+ response.makerCommission,
311593
+ nestedData?.maker,
311594
+ nestedData?.makerCommission
311595
+ ].find((value) => value !== undefined && value !== null);
311596
+ const taker = [
311597
+ response.taker,
311598
+ response.takerCommission,
311599
+ nestedData?.taker,
311600
+ nestedData?.takerCommission
311601
+ ].find((value) => value !== undefined && value !== null);
311602
+ try {
311603
+ return {
311604
+ makerRate: canonicalNonnegativeDecimal(maker, "maker commission"),
311605
+ takerRate: canonicalNonnegativeDecimal(taker, "taker commission")
311606
+ };
311607
+ } catch (error48) {
311608
+ throw new Error(`fee_unavailable: ${sanitizeErrorDetail(error48)}`, {
311609
+ cause: error48
311610
+ });
311611
+ }
311612
+ }
311613
+ function evidenceExchange(broker) {
311614
+ return broker;
311615
+ }
311616
+
311617
+ // src/schemas/action-evidence.ts
311618
+ var EVIDENCE_DIGEST_ALGORITHM = "sha256-canonical-json-v1";
311619
+ var CanonicalDecimalStringSchema = exports_external.string().regex(/^(?:0|[1-9]\d*)(?:\.\d*[1-9])?$/);
311620
+ var accountScopeShape = {
311621
+ accountSelector: exports_external.string().min(1),
311622
+ credentialSource: exports_external.enum(["configured_pool", "request_metadata"])
311623
+ };
311624
+ var evidenceSourceShape = {
311625
+ observedAt: exports_external.string().datetime({ offset: true }),
311626
+ digestAlgorithm: exports_external.literal(EVIDENCE_DIGEST_ALGORITHM),
311627
+ sourceDigest: exports_external.string().regex(/^[a-f0-9]{64}$/)
311628
+ };
311629
+ var TradingFeeEvidenceSchema = exports_external.object({
311630
+ schemaVersion: exports_external.literal("cex-trading-fee-evidence/v1"),
311631
+ exchange: exports_external.string().min(1),
311632
+ marketType: exports_external.literal("spot"),
311633
+ canonicalPair: exports_external.string().regex(/^[^-\s]+-[^-\s]+$/),
311634
+ unifiedSymbol: exports_external.string().regex(/^[^/\s]+\/[^/\s]+$/),
311635
+ sourceSymbol: exports_external.string().min(1),
311636
+ ...accountScopeShape,
311637
+ ...evidenceSourceShape,
311638
+ sourceMethod: exports_external.literal("ccxt.fetchTradingFee"),
311639
+ makerRate: CanonicalDecimalStringSchema,
311640
+ takerRate: CanonicalDecimalStringSchema,
311641
+ rateUnit: exports_external.literal("decimal_fraction"),
311642
+ makerBasisPoints: CanonicalDecimalStringSchema,
311643
+ takerBasisPoints: CanonicalDecimalStringSchema,
311644
+ basisPointsUnit: exports_external.literal("basis_points")
311645
+ }).strict();
311646
+ var MarketRuleEvidenceSchema = exports_external.object({
311647
+ schemaVersion: exports_external.literal("cex-market-rule-evidence/v1"),
311648
+ exchange: exports_external.string().min(1),
311649
+ marketType: exports_external.literal("spot"),
311650
+ canonicalPair: exports_external.string().regex(/^[^-\s]+-[^-\s]+$/),
311651
+ unifiedSymbol: exports_external.string().regex(/^[^/\s]+\/[^/\s]+$/),
311652
+ sourceSymbol: exports_external.string().min(1),
311653
+ baseAsset: exports_external.string().min(1),
311654
+ quoteAsset: exports_external.string().min(1),
311655
+ active: exports_external.literal(true),
311656
+ precisionMode: exports_external.union([exports_external.string().min(1), exports_external.number().int()]),
311657
+ priceIncrement: CanonicalDecimalStringSchema,
311658
+ amountIncrement: CanonicalDecimalStringSchema,
311659
+ minimumAmount: CanonicalDecimalStringSchema,
311660
+ minimumNotional: CanonicalDecimalStringSchema,
311661
+ maximumAmount: CanonicalDecimalStringSchema.optional(),
311662
+ maximumPrice: CanonicalDecimalStringSchema.optional(),
311663
+ maximumNotional: CanonicalDecimalStringSchema.optional(),
311664
+ ...accountScopeShape,
311665
+ ...evidenceSourceShape,
311666
+ sourceMethod: exports_external.literal("ccxt.loadMarkets")
311667
+ }).strict();
311668
+ var TransferNetworkEvidenceSchema = exports_external.object({
311669
+ schemaVersion: exports_external.literal("cex-transfer-network-evidence/v1"),
311670
+ exchange: exports_external.string().min(1),
311671
+ asset: exports_external.string().min(1),
311672
+ operatorNetworkAlias: exports_external.string().min(1),
311673
+ brokerNetworkId: exports_external.string().min(1),
311674
+ exchangeNetworkId: exports_external.string().min(1),
311675
+ depositAvailable: exports_external.boolean(),
311676
+ withdrawalAvailable: exports_external.boolean(),
311677
+ withdrawalFee: CanonicalDecimalStringSchema.nullable(),
311678
+ withdrawalLimits: exports_external.object({
311679
+ minimum: CanonicalDecimalStringSchema.nullable(),
311680
+ maximum: CanonicalDecimalStringSchema.nullable()
311681
+ }).strict(),
311682
+ ...accountScopeShape,
311683
+ ...evidenceSourceShape,
311684
+ sourceMethod: exports_external.literal("ccxt.fetchCurrencies")
311685
+ }).strict();
311686
+ var BatchSuccessEntrySchema = exports_external.object({
311687
+ id: exports_external.string().min(1),
311688
+ action: exports_external.number().int().nonnegative(),
311689
+ symbol: exports_external.string(),
311690
+ response: exports_external.object({
311691
+ result: exports_external.string(),
311692
+ proof: exports_external.string()
311693
+ }).strict(),
311694
+ error: exports_external.null()
311695
+ }).strict();
311696
+ var BatchErrorEntrySchema = exports_external.object({
311697
+ id: exports_external.string().min(1),
311698
+ action: exports_external.number().int().nonnegative(),
311699
+ symbol: exports_external.string(),
311700
+ response: exports_external.null(),
311701
+ error: exports_external.object({
311702
+ code: exports_external.string().min(1),
311703
+ grpcStatus: exports_external.number().int().nonnegative(),
311704
+ message: exports_external.string()
311705
+ }).strict()
311706
+ }).strict();
311707
+ var BatchResponseEntrySchema = exports_external.union([
311708
+ BatchSuccessEntrySchema,
311709
+ BatchErrorEntrySchema
311710
+ ]);
311711
+ var BatchResponseEnvelopeSchema = exports_external.object({
311712
+ schemaVersion: exports_external.literal("cex-broker-action-batch/v1"),
311713
+ responses: exports_external.array(BatchResponseEntrySchema).max(32)
311714
+ }).strict();
311715
+
311716
+ // src/handlers/execute-action/batch.ts
311717
+ var FORBIDDEN_ROUTING_KEYS = new Set([
311718
+ "account",
311719
+ "accountid",
311720
+ "accountselector",
311721
+ "apikey",
311722
+ "apisecret",
311723
+ "auth",
311724
+ "authorization",
311725
+ "cex",
311726
+ "credential",
311727
+ "credentials",
311728
+ "exchange",
311729
+ "metadata",
311730
+ "password",
311731
+ "secret",
311732
+ "signature",
311733
+ "usesecondarykey"
311734
+ ]);
311735
+ function normalizeRoutingKey(key2) {
311736
+ return key2.replace(/[-_]/g, "").toLowerCase();
311737
+ }
311738
+ function hasForbiddenRoutingKey(value) {
311739
+ if (Array.isArray(value)) {
311740
+ return value.some(hasForbiddenRoutingKey);
311741
+ }
311742
+ if (value === null || typeof value !== "object") {
311743
+ return false;
311744
+ }
311745
+ for (const [key2, entry] of Object.entries(value)) {
311746
+ if (FORBIDDEN_ROUTING_KEYS.has(normalizeRoutingKey(key2))) {
311747
+ return true;
311748
+ }
311749
+ if (hasForbiddenRoutingKey(entry)) {
311750
+ return true;
311751
+ }
311752
+ }
311753
+ return false;
311754
+ }
311755
+ function childContainsRoutingOverride(child) {
311756
+ for (const [key2, value] of Object.entries(child.payload)) {
311757
+ if (FORBIDDEN_ROUTING_KEYS.has(normalizeRoutingKey(key2))) {
311758
+ return true;
311759
+ }
311760
+ try {
311761
+ if (hasForbiddenRoutingKey(JSON.parse(value))) {
311762
+ return true;
311763
+ }
311764
+ } catch {}
311765
+ }
311766
+ return false;
311767
+ }
311768
+ function stableBatchErrorCode(message, grpcStatus) {
311769
+ const prefix = message.match(/^([A-Za-z][A-Za-z0-9_]*):/)?.[1];
311770
+ if (prefix) {
311771
+ return prefix;
311772
+ }
311773
+ return grpc5.status[grpcStatus] ?? "UNKNOWN";
311774
+ }
311775
+ function batchErrorEntry(child, error48, broker) {
311776
+ const resolved = resolveGrpcError(error48);
311777
+ const errorRecord = error48 !== null && typeof error48 === "object" ? error48 : undefined;
311778
+ const grpcStatus = typeof errorRecord?.code === "number" ? errorRecord.code : resolved.code;
311779
+ const rawMessage = typeof errorRecord?.message === "string" ? errorRecord.message : resolved.message;
311780
+ const sanitizedMessage = redactSecretLiterals(sanitizeErrorDetail(rawMessage), exchangeSecretLiterals(broker));
311781
+ return {
311782
+ id: child.id,
311783
+ action: child.action,
311784
+ symbol: child.symbol,
311785
+ response: null,
311786
+ error: {
311787
+ code: stableBatchErrorCode(rawMessage, grpcStatus),
311788
+ grpcStatus,
311789
+ message: sanitizedMessage
311790
+ }
311791
+ };
311792
+ }
311793
+ function childCall(ctx, request) {
311794
+ return {
311795
+ ...ctx.call,
311796
+ request
311797
+ };
311798
+ }
311799
+ async function executeChild(ctx, child, descriptor) {
311800
+ const action = resolveAction(child.action);
311801
+ if (action === undefined) {
311802
+ return batchErrorEntry(child, new Error(`ValidationError: invalid action ${child.action}`), ctx.broker);
311803
+ }
311804
+ const proofState = { proof: "" };
311805
+ let completion;
311806
+ const localCallback = (error48, response) => {
311807
+ if (completion === undefined) {
311808
+ completion = { error: error48, response: response ?? null };
311809
+ }
311810
+ };
311811
+ const request = {
311812
+ action,
311813
+ cex: ctx.cex,
311814
+ symbol: child.symbol,
311815
+ payload: child.payload
311816
+ };
311817
+ const childContext = {
311818
+ ...ctx,
311819
+ call: childCall(ctx, request),
311820
+ wrappedCallback: localCallback,
311821
+ action,
311822
+ symbol: child.symbol,
311823
+ verity: proofState,
311824
+ applyVerityToBroker: (target) => ctx.applyVerityToBroker(target, proofState)
311825
+ };
311826
+ try {
311827
+ childContext.applyVerityToBroker(ctx.broker);
311828
+ await descriptor.handler(childContext);
311829
+ } catch (error48) {
311830
+ completion ??= {
311831
+ error: resolveGrpcError(error48),
311832
+ response: null
311833
+ };
311834
+ }
311835
+ if (completion === undefined) {
311836
+ return batchErrorEntry(child, new Error(`INTERNAL: ${getActionName(action)} completed without a callback`), ctx.broker);
311837
+ }
311838
+ if (completion.error || !completion.response) {
311839
+ return batchErrorEntry(child, completion.error ?? new Error("INTERNAL: child returned no response"), ctx.broker);
311840
+ }
311841
+ return {
311842
+ id: child.id,
311843
+ action: child.action,
311844
+ symbol: child.symbol,
311845
+ response: {
311846
+ result: completion.response.result,
311847
+ proof: completion.response.proof ?? proofState.proof
311848
+ },
311849
+ error: null
311850
+ };
311851
+ }
311852
+ async function handleBatch(ctx, lookupDescriptor) {
311853
+ if (ctx.symbol?.trim()) {
311854
+ return ctx.wrappedCallback({
311855
+ code: grpc5.status.INVALID_ARGUMENT,
311856
+ message: "ValidationError: Batch symbol must be empty"
311857
+ }, null);
311858
+ }
311859
+ const encodedRequests = ctx.call.request.payload?.requests;
311860
+ if (typeof encodedRequests === "string" && Buffer.byteLength(encodedRequests, "utf8") > MAX_BATCH_REQUEST_BYTES) {
311861
+ return ctx.wrappedCallback({
311862
+ code: grpc5.status.INVALID_ARGUMENT,
311863
+ message: `ValidationError: Batch requests exceed ${MAX_BATCH_REQUEST_BYTES} bytes`
311864
+ }, null);
311865
+ }
311866
+ const payload = parsePayloadForAction(ctx, BatchPayloadSchema);
311867
+ if (payload === null) {
311868
+ return;
311869
+ }
311870
+ const seenIds = new Set;
311871
+ const prepared = [];
311872
+ for (const child of payload.requests) {
311873
+ if (seenIds.has(child.id)) {
311874
+ return ctx.wrappedCallback({
311875
+ code: grpc5.status.INVALID_ARGUMENT,
311876
+ message: `ValidationError: duplicate batch child id '${child.id}'`
311877
+ }, null);
311878
+ }
311879
+ seenIds.add(child.id);
311880
+ if (childContainsRoutingOverride(child)) {
311881
+ return ctx.wrappedCallback({
311882
+ code: grpc5.status.INVALID_ARGUMENT,
311883
+ message: `ValidationError: batch child '${child.id}' contains a routing override`
311884
+ }, null);
311885
+ }
311886
+ const action = resolveAction(child.action);
311887
+ const descriptor = action === undefined ? undefined : lookupDescriptor(action);
311888
+ if (!descriptor || descriptor.access !== "read" || !descriptor.batchable) {
311889
+ return ctx.wrappedCallback({
311890
+ code: grpc5.status.INVALID_ARGUMENT,
311891
+ message: `ValidationError: batch child '${child.id}' action ${child.action} is not batchable`
311892
+ }, null);
311893
+ }
311894
+ const validation = descriptor.validateBatchRequest?.({
311895
+ action,
311896
+ cex: ctx.cex,
311897
+ symbol: child.symbol,
311898
+ payload: child.payload
311899
+ });
311900
+ if (validation && !validation.valid) {
311901
+ return ctx.wrappedCallback({
311902
+ code: grpc5.status.INVALID_ARGUMENT,
311903
+ message: `ValidationError: batch child '${child.id}': ${validation.message}`
311904
+ }, null);
311905
+ }
311906
+ prepared.push({ child, descriptor });
311907
+ }
311908
+ const responses = [];
311909
+ for (const { child, descriptor } of prepared) {
311910
+ const response = await executeChild(ctx, child, descriptor);
311911
+ responses.push(response);
311912
+ ctx.otelMetrics?.recordCounter("execute_action_batch_items_total", 1, {
311913
+ action: getActionName(child.action),
311914
+ cex: ctx.normalizedCex,
311915
+ outcome: response.error ? "error" : "success"
311916
+ });
311917
+ }
311918
+ const envelope = BatchResponseEnvelopeSchema.parse({
311919
+ schemaVersion: "cex-broker-action-batch/v1",
311920
+ responses
311921
+ });
311922
+ ctx.wrappedCallback(null, {
311923
+ result: JSON.stringify(envelope),
311924
+ proof: ""
311925
+ });
311926
+ }
311927
+
311928
+ // src/handlers/execute-action/internal-transfer.ts
311929
+ import * as grpc6 from "@grpc/grpc-js";
310973
311930
  async function handleInternalTransfer(ctx) {
310974
311931
  const {
310975
311932
  brokers,
@@ -310983,7 +311940,7 @@ async function handleInternalTransfer(ctx) {
310983
311940
  } = ctx;
310984
311941
  if (!symbol2) {
310985
311942
  return ctx.wrappedCallback({
310986
- code: grpc5.status.INVALID_ARGUMENT,
311943
+ code: grpc6.status.INVALID_ARGUMENT,
310987
311944
  message: `ValidationError: Symbol required`
310988
311945
  }, null);
310989
311946
  }
@@ -310992,14 +311949,14 @@ async function handleInternalTransfer(ctx) {
310992
311949
  return;
310993
311950
  if (normalizedCex !== "binance") {
310994
311951
  return ctx.wrappedCallback({
310995
- code: grpc5.status.UNIMPLEMENTED,
311952
+ code: grpc6.status.UNIMPLEMENTED,
310996
311953
  message: `InternalTransfer is only supported for Binance`
310997
311954
  }, null);
310998
311955
  }
310999
311956
  const pool = brokers[normalizedCex];
311000
311957
  if (!pool) {
311001
311958
  return ctx.wrappedCallback({
311002
- code: grpc5.status.FAILED_PRECONDITION,
311959
+ code: grpc6.status.FAILED_PRECONDITION,
311003
311960
  message: `No broker accounts configured for ${normalizedCex}`
311004
311961
  }, null);
311005
311962
  }
@@ -311008,14 +311965,14 @@ async function handleInternalTransfer(ctx) {
311008
311965
  const sourceAccount = resolveBrokerAccount(pool, fromSelector);
311009
311966
  if (!sourceAccount) {
311010
311967
  return ctx.wrappedCallback({
311011
- code: grpc5.status.INVALID_ARGUMENT,
311968
+ code: grpc6.status.INVALID_ARGUMENT,
311012
311969
  message: `Source account "${fromSelector}" is not configured`
311013
311970
  }, null);
311014
311971
  }
311015
311972
  const destAccount = resolveBrokerAccount(pool, toSelector);
311016
311973
  if (!destAccount) {
311017
311974
  return ctx.wrappedCallback({
311018
- code: grpc5.status.INVALID_ARGUMENT,
311975
+ code: grpc6.status.INVALID_ARGUMENT,
311019
311976
  message: `Destination account "${toSelector}" is not configured`
311020
311977
  }, null);
311021
311978
  }
@@ -311049,18 +312006,18 @@ async function handleInternalTransfer(ctx) {
311049
312006
  safeLogError("InternalTransfer failed", error48);
311050
312007
  if (error48 instanceof BrokerAccountPreconditionError) {
311051
312008
  return ctx.wrappedCallback({
311052
- code: grpc5.status.FAILED_PRECONDITION,
312009
+ code: grpc6.status.FAILED_PRECONDITION,
311053
312010
  message: getErrorMessage(error48)
311054
312011
  }, null);
311055
312012
  }
311056
312013
  const msg = getErrorMessage(error48);
311057
312014
  let code;
311058
312015
  if (msg.includes("Unsupported transfer direction")) {
311059
- code = grpc5.status.INVALID_ARGUMENT;
312016
+ code = grpc6.status.INVALID_ARGUMENT;
311060
312017
  } else if (msg.includes("unavailable in this CCXT build")) {
311061
- code = grpc5.status.UNIMPLEMENTED;
312018
+ code = grpc6.status.UNIMPLEMENTED;
311062
312019
  } else {
311063
- code = mapCcxtErrorToGrpcStatus(error48) ?? grpc5.status.INTERNAL;
312020
+ code = mapCcxtErrorToGrpcStatus(error48) ?? grpc6.status.INTERNAL;
311064
312021
  }
311065
312022
  ctx.wrappedCallback({
311066
312023
  code,
@@ -311070,7 +312027,7 @@ async function handleInternalTransfer(ctx) {
311070
312027
  }
311071
312028
 
311072
312029
  // src/handlers/execute-action/orders.ts
311073
- import * as grpc6 from "@grpc/grpc-js";
312030
+ import * as grpc7 from "@grpc/grpc-js";
311074
312031
 
311075
312032
  // src/helpers/passive-order.ts
311076
312033
  var PASSIVE_ORDER_ERROR_CODES = {
@@ -311131,7 +312088,7 @@ async function handleCreateOrder(ctx) {
311131
312088
  const isPassiveOrder = orderValue.orderIntent === "passive_only";
311132
312089
  if (isPassiveOrder && orderValue.orderType !== "limit") {
311133
312090
  return ctx.wrappedCallback({
311134
- code: grpc6.status.INVALID_ARGUMENT,
312091
+ code: grpc7.status.INVALID_ARGUMENT,
311135
312092
  message: "ValidationError: passive_only order intent requires a limit order"
311136
312093
  }, null);
311137
312094
  }
@@ -311148,14 +312105,14 @@ async function handleCreateOrder(ctx) {
311148
312105
  try {
311149
312106
  if (!broker) {
311150
312107
  return ctx.wrappedCallback({
311151
- code: grpc6.status.INVALID_ARGUMENT,
312108
+ code: grpc7.status.INVALID_ARGUMENT,
311152
312109
  message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
311153
312110
  }, null);
311154
312111
  }
311155
312112
  const resolution = await resolveOrderExecution(policy, broker, cex3, orderValue.fromToken, orderValue.toToken, orderValue.amount, orderValue.price, orderValue.marketType);
311156
312113
  if (!resolution.valid || !resolution.symbol || !resolution.side) {
311157
312114
  return ctx.wrappedCallback({
311158
- code: grpc6.status.INVALID_ARGUMENT,
312115
+ code: grpc7.status.INVALID_ARGUMENT,
311159
312116
  message: resolution.error ?? "Order rejected by policy: market or limits not satisfied"
311160
312117
  }, null);
311161
312118
  }
@@ -311228,7 +312185,7 @@ async function handleCreateOrder(ctx) {
311228
312185
  });
311229
312186
  }
311230
312187
  ctx.wrappedCallback({
311231
- code: grpc6.status.INTERNAL,
312188
+ code: grpc7.status.INTERNAL,
311232
312189
  message: `Order Creation failed: ${sanitizeErrorDetail(error48)}`
311233
312190
  }, null);
311234
312191
  }
@@ -311260,7 +312217,7 @@ async function handleGetOrderDetails(ctx) {
311260
312217
  try {
311261
312218
  if (!broker) {
311262
312219
  return ctx.wrappedCallback({
311263
- code: grpc6.status.INVALID_ARGUMENT,
312220
+ code: grpc7.status.INVALID_ARGUMENT,
311264
312221
  message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
311265
312222
  }, null);
311266
312223
  }
@@ -311301,7 +312258,7 @@ async function handleGetOrderDetails(ctx) {
311301
312258
  emitOrderExecutionTelemetryInBackground(otelMetrics, failedGetOrderContext, undefined, error48);
311302
312259
  archiveOrderExecutionInBackground(brokerArchiver, failedGetOrderContext, undefined, error48);
311303
312260
  ctx.wrappedCallback({
311304
- code: grpc6.status.INTERNAL,
312261
+ code: grpc7.status.INTERNAL,
311305
312262
  message: `Failed to fetch order details from ${cex3}: ${sanitizeErrorDetail(error48)}`
311306
312263
  }, null);
311307
312264
  }
@@ -311333,7 +312290,7 @@ async function handleCancelOrder(ctx) {
311333
312290
  try {
311334
312291
  if (!broker) {
311335
312292
  return ctx.wrappedCallback({
311336
- code: grpc6.status.INVALID_ARGUMENT,
312293
+ code: grpc7.status.INVALID_ARGUMENT,
311337
312294
  message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
311338
312295
  }, null);
311339
312296
  }
@@ -311365,7 +312322,7 @@ async function handleCancelOrder(ctx) {
311365
312322
  emitOrderExecutionTelemetryInBackground(otelMetrics, failedCancelContext, undefined, error48);
311366
312323
  archiveOrderExecutionInBackground(brokerArchiver, failedCancelContext, undefined, error48);
311367
312324
  ctx.wrappedCallback({
311368
- code: grpc6.status.INTERNAL,
312325
+ code: grpc7.status.INTERNAL,
311369
312326
  message: `Failed to cancel order from ${cex3}: ${sanitizeErrorDetail(error48)}`
311370
312327
  }, null);
311371
312328
  }
@@ -311380,261 +312337,254 @@ async function handleOrders(ctx) {
311380
312337
  }
311381
312338
 
311382
312339
  // src/handlers/execute-action/pass-through.ts
311383
- import * as grpc7 from "@grpc/grpc-js";
311384
- async function handleFetchCurrency(ctx) {
311385
- const {
311386
- call,
311387
- wrappedCallback,
311388
- policy,
311389
- brokers,
311390
- metadata,
311391
- normalizedCex,
311392
- cex: cex3,
311393
- symbol: symbol2,
311394
- selectedBrokerAccount,
311395
- broker,
311396
- verity,
311397
- applyVerityToBroker,
311398
- useVerity,
311399
- verityProverUrl,
311400
- otelMetrics
311401
- } = ctx;
311402
- const verityProof = verity.proof;
311403
- if (!symbol2) {
312340
+ import * as grpc9 from "@grpc/grpc-js";
312341
+
312342
+ // src/handlers/execute-action/venue-evidence.ts
312343
+ import * as grpc8 from "@grpc/grpc-js";
312344
+ function failVenueDiscovery(ctx, error48, operation) {
312345
+ safeLogRedactedError(`${operation} failed`, error48);
312346
+ const sanitized = sanitizeVenueError(error48, ctx.broker);
312347
+ const message = sanitized.startsWith("venue_discovery_unavailable:") ? sanitized : `venue_discovery_unavailable: ${sanitized}`;
312348
+ ctx.wrappedCallback({
312349
+ code: stableGrpcErrorCode(message) ?? mapCcxtErrorToGrpcStatus(error48) ?? grpc8.status.UNIMPLEMENTED,
312350
+ message
312351
+ }, null);
312352
+ }
312353
+ function optionalDecimalFields(values2) {
312354
+ const result = {};
312355
+ for (const [key2, value, field] of values2) {
312356
+ const normalized = canonicalOptionalDecimal(value, field);
312357
+ if (normalized !== undefined) {
312358
+ result[key2] = normalized;
312359
+ }
312360
+ }
312361
+ return result;
312362
+ }
312363
+ async function handleFetchFeesEvidence(ctx) {
312364
+ if (!requireSymbol(ctx, "ValidationError: symbol must be a slash-delimited spot pair")) {
312365
+ return;
312366
+ }
312367
+ if (!/^[^/\s]+\/[^/\s]+$/.test(ctx.symbol.trim())) {
311404
312368
  return ctx.wrappedCallback({
311405
- code: grpc7.status.INVALID_ARGUMENT,
311406
- message: `ValidationError: Symbol required`
312369
+ code: grpc8.status.INVALID_ARGUMENT,
312370
+ message: "ValidationError: symbol must be a slash-delimited spot pair"
311407
312371
  }, null);
311408
312372
  }
312373
+ if (parsePayloadForAction(ctx, FetchFeesPayloadSchema) === null) {
312374
+ return;
312375
+ }
311409
312376
  try {
311410
- const assetCode = symbol2.trim().toUpperCase();
311411
- const currencyInfo = await fetchCurrencyMetadata(broker, assetCode);
311412
- if (!currencyInfo) {
311413
- return ctx.wrappedCallback({
311414
- code: grpc7.status.NOT_FOUND,
311415
- message: `venue_discovery_unavailable: currency not found for ${assetCode}`
311416
- }, null);
311417
- }
311418
- const networkEvidence = buildTransferNetworkEvidence(currencyInfo);
311419
- ctx.wrappedCallback(null, {
311420
- proof: ctx.verity.proof,
311421
- result: JSON.stringify({
311422
- ...currencyInfo,
311423
- exchange: normalizedCex,
311424
- asset: assetCode,
311425
- code: currencyInfo.code ?? assetCode,
311426
- id: currencyInfo.id ?? null,
311427
- networks: networkEvidence.networks,
311428
- networkAliases: networkEvidence.aliases,
311429
- raw: currencyInfo
312377
+ const identity2 = await resolveSpotMarketIdentity(ctx.broker, ctx.symbol);
312378
+ const exchange = evidenceExchange(ctx.broker);
312379
+ if (exchange.has?.fetchTradingFee === false || typeof exchange.fetchTradingFee !== "function") {
312380
+ throw new Error(`fee_unavailable: ${ctx.normalizedCex} does not support fetchTradingFee`);
312381
+ }
312382
+ const sourceResponse = await exchange.fetchTradingFee(identity2.unifiedSymbol);
312383
+ if (isRecord(sourceResponse) && typeof sourceResponse.symbol === "string" && sourceResponse.symbol.trim().toUpperCase() !== identity2.unifiedSymbol) {
312384
+ throw new Error(`fee_unavailable: trading-fee response symbol does not match ${identity2.unifiedSymbol}`);
312385
+ }
312386
+ const { makerRate, takerRate } = extractTradingFeeRates(sourceResponse);
312387
+ const accountScope = resolveEvidenceAccountScope(ctx.selectedBrokerAccount, ctx.metadata);
312388
+ const evidence = TradingFeeEvidenceSchema.parse({
312389
+ schemaVersion: "cex-trading-fee-evidence/v1",
312390
+ exchange: ctx.normalizedCex,
312391
+ marketType: "spot",
312392
+ canonicalPair: identity2.canonicalPair,
312393
+ unifiedSymbol: identity2.unifiedSymbol,
312394
+ sourceSymbol: identity2.sourceSymbol,
312395
+ ...accountScope,
312396
+ observedAt: new Date().toISOString(),
312397
+ sourceMethod: "ccxt.fetchTradingFee",
312398
+ makerRate,
312399
+ takerRate,
312400
+ rateUnit: "decimal_fraction",
312401
+ makerBasisPoints: decimalFractionToBasisPoints(makerRate),
312402
+ takerBasisPoints: decimalFractionToBasisPoints(takerRate),
312403
+ basisPointsUnit: "basis_points",
312404
+ digestAlgorithm: "sha256-canonical-json-v1",
312405
+ sourceDigest: evidenceSourceDigest({
312406
+ action: "FetchFees",
312407
+ exchange: ctx.normalizedCex,
312408
+ requestedKey: identity2.canonicalPair,
312409
+ accountSelector: accountScope.accountSelector,
312410
+ sourceMethod: "ccxt.fetchTradingFee",
312411
+ source: sourceResponse,
312412
+ broker: ctx.broker
311430
312413
  })
311431
312414
  });
312415
+ successWithProof(ctx, evidence);
311432
312416
  } catch (error48) {
311433
- safeLogError(`Error fetching currency ${symbol2} from ${cex3}`, error48);
311434
- const message = getErrorMessage(error48);
312417
+ safeLogRedactedError(`FetchFees failed for ${ctx.normalizedCex}/${ctx.symbol}`, error48);
312418
+ const sanitized = sanitizeVenueError(error48, ctx.broker);
312419
+ const message = sanitized.startsWith("fee_unavailable:") ? sanitized : `fee_unavailable: ${sanitized}`;
311435
312420
  ctx.wrappedCallback({
311436
- code: stableGrpcErrorCode(message) ?? mapCcxtErrorToGrpcStatus(error48) ?? grpc7.status.INTERNAL,
311437
- message: message.startsWith("venue_discovery_unavailable:") ? message : `venue_discovery_unavailable: ${message}`
312421
+ code: grpc8.status.FAILED_PRECONDITION,
312422
+ message
311438
312423
  }, null);
311439
312424
  }
311440
312425
  }
311441
- async function handleFetchAccountId(ctx) {
311442
- const {
311443
- call,
311444
- wrappedCallback,
311445
- policy,
311446
- brokers,
311447
- metadata,
311448
- normalizedCex,
311449
- cex: cex3,
311450
- symbol: symbol2,
311451
- selectedBrokerAccount,
311452
- broker,
311453
- verity,
311454
- applyVerityToBroker,
311455
- useVerity,
311456
- verityProverUrl,
311457
- otelMetrics
311458
- } = ctx;
311459
- const verityProof = verity.proof;
312426
+ async function handleFetchMarketRulesEvidence(ctx) {
312427
+ if (!requireSymbol(ctx, "ValidationError: symbol must be a slash-delimited spot pair")) {
312428
+ return;
312429
+ }
312430
+ if (!/^[^/\s]+\/[^/\s]+$/.test(ctx.symbol.trim())) {
312431
+ return ctx.wrappedCallback({
312432
+ code: grpc8.status.INVALID_ARGUMENT,
312433
+ message: "ValidationError: symbol must be a slash-delimited spot pair"
312434
+ }, null);
312435
+ }
312436
+ if (parsePayloadForAction(ctx, EmptyActionPayloadSchema) === null) {
312437
+ return;
312438
+ }
311460
312439
  try {
311461
- const accountId = await broker.fetchAccountId();
311462
- return ctx.wrappedCallback(null, {
311463
- proof: ctx.verity.proof,
311464
- result: JSON.stringify({ accountId })
312440
+ const identity2 = await resolveSpotMarketIdentity(ctx.broker, ctx.symbol);
312441
+ const precision = isRecord(identity2.market.precision) ? identity2.market.precision : {};
312442
+ const limits = isRecord(identity2.market.limits) ? identity2.market.limits : {};
312443
+ const amountLimits = isRecord(limits.amount) ? limits.amount : {};
312444
+ const priceLimits = isRecord(limits.price) ? limits.price : {};
312445
+ const costLimits = isRecord(limits.cost) ? limits.cost : {};
312446
+ const precisionMode = evidenceExchange(ctx.broker).precisionMode;
312447
+ if (precisionMode === undefined || precisionMode === null) {
312448
+ throw new Error("venue_discovery_unavailable: precision mode is unavailable");
312449
+ }
312450
+ const accountScope = resolveEvidenceAccountScope(ctx.selectedBrokerAccount, ctx.metadata);
312451
+ const evidence = MarketRuleEvidenceSchema.parse({
312452
+ schemaVersion: "cex-market-rule-evidence/v1",
312453
+ exchange: ctx.normalizedCex,
312454
+ marketType: "spot",
312455
+ canonicalPair: identity2.canonicalPair,
312456
+ unifiedSymbol: identity2.unifiedSymbol,
312457
+ sourceSymbol: identity2.sourceSymbol,
312458
+ baseAsset: identity2.baseAsset,
312459
+ quoteAsset: identity2.quoteAsset,
312460
+ active: true,
312461
+ precisionMode,
312462
+ priceIncrement: precisionIncrement(precision.price, precisionMode, "price increment"),
312463
+ amountIncrement: precisionIncrement(precision.amount, precisionMode, "amount increment"),
312464
+ minimumAmount: canonicalNonnegativeDecimal(amountLimits.min, "minimum amount"),
312465
+ minimumNotional: canonicalNonnegativeDecimal(costLimits.min, "minimum notional"),
312466
+ ...optionalDecimalFields([
312467
+ ["maximumAmount", amountLimits.max, "maximum amount"],
312468
+ ["maximumPrice", priceLimits.max, "maximum price"],
312469
+ ["maximumNotional", costLimits.max, "maximum notional"]
312470
+ ]),
312471
+ ...accountScope,
312472
+ observedAt: new Date().toISOString(),
312473
+ sourceMethod: "ccxt.loadMarkets",
312474
+ digestAlgorithm: "sha256-canonical-json-v1",
312475
+ sourceDigest: evidenceSourceDigest({
312476
+ action: "FetchMarketRules",
312477
+ exchange: ctx.normalizedCex,
312478
+ requestedKey: identity2.canonicalPair,
312479
+ accountSelector: accountScope.accountSelector,
312480
+ sourceMethod: "ccxt.loadMarkets",
312481
+ source: identity2.market,
312482
+ broker: ctx.broker
312483
+ })
311465
312484
  });
312485
+ successWithProof(ctx, evidence);
311466
312486
  } catch (error48) {
311467
- safeLogError(`Error fetching account ID ${cex3}`, error48);
311468
- ctx.wrappedCallback({
311469
- code: grpc7.status.INTERNAL,
311470
- message: `Error fetching account ID from ${cex3}`
311471
- }, null);
312487
+ failVenueDiscovery(ctx, error48, `FetchMarketRules ${ctx.normalizedCex}/${ctx.symbol}`);
311472
312488
  }
311473
312489
  }
311474
- async function handleFetchFees(ctx) {
311475
- const {
311476
- call,
311477
- wrappedCallback,
311478
- policy,
311479
- brokers,
311480
- metadata,
311481
- normalizedCex,
311482
- cex: cex3,
311483
- symbol: symbol2,
311484
- selectedBrokerAccount,
311485
- broker,
311486
- verity,
311487
- applyVerityToBroker,
311488
- useVerity,
311489
- verityProverUrl,
311490
- otelMetrics
311491
- } = ctx;
311492
- const verityProof = verity.proof;
311493
- if (!symbol2) {
311494
- return ctx.wrappedCallback({
311495
- code: grpc7.status.INVALID_ARGUMENT,
311496
- message: `ValidationError: Symbol required`
311497
- }, null);
312490
+ async function handleFetchCurrencyEvidence(ctx) {
312491
+ if (!requireSymbol(ctx)) {
312492
+ return;
311498
312493
  }
311499
- const feesPayload = parsePayloadForAction(ctx, FetchFeesPayloadSchema);
311500
- if (feesPayload === null)
312494
+ const payload = parsePayloadForAction(ctx, FetchCurrencyPayloadSchema);
312495
+ if (payload === null) {
311501
312496
  return;
311502
- const includeAllFees = feesPayload.includeAllFees || feesPayload.includeFundingFees === true;
312497
+ }
312498
+ const asset = ctx.symbol.trim().toUpperCase();
312499
+ const requestedAlias = payload.network.trim().toUpperCase();
311503
312500
  try {
311504
- await broker.loadMarkets();
311505
- const fetchFundingFees = async (currencyCodes) => {
311506
- let fundingFeeSource2 = "unavailable";
311507
- const fundingFeesByCurrency2 = {};
311508
- if (broker.has.fetchDepositWithdrawFees) {
311509
- try {
311510
- const feeMap = await broker.fetchDepositWithdrawFees(currencyCodes);
311511
- for (const code of currencyCodes) {
311512
- const feeInfo = feeMap[code];
311513
- if (!feeInfo) {
311514
- continue;
311515
- }
311516
- const fallbackFee = feeInfo.fee !== undefined || feeInfo.percentage !== undefined ? {
311517
- fee: feeInfo.fee ?? null,
311518
- percentage: feeInfo.percentage ?? null
311519
- } : null;
311520
- fundingFeesByCurrency2[code] = {
311521
- deposit: feeInfo.deposit ?? fallbackFee,
311522
- withdraw: feeInfo.withdraw ?? fallbackFee,
311523
- networks: feeInfo.networks ?? {}
311524
- };
311525
- }
311526
- if (Object.keys(fundingFeesByCurrency2).length > 0) {
311527
- fundingFeeSource2 = "fetchDepositWithdrawFees";
311528
- }
311529
- } catch (error48) {
311530
- safeLogError(`Error fetching deposit/withdraw fee map for ${symbol2} from ${cex3}`, error48);
311531
- }
311532
- }
311533
- if (fundingFeeSource2 === "unavailable") {
311534
- try {
311535
- const currencies = await broker.fetchCurrencies();
311536
- for (const code of currencyCodes) {
311537
- const currency = currencies[code];
311538
- if (!currency) {
311539
- continue;
311540
- }
311541
- fundingFeesByCurrency2[code] = {
311542
- deposit: {
311543
- enabled: currency.deposit ?? null
311544
- },
311545
- withdraw: {
311546
- enabled: currency.withdraw ?? null,
311547
- fee: currency.fee ?? null,
311548
- limits: currency.limits?.withdraw ?? null
311549
- },
311550
- networks: currency.networks ?? {}
311551
- };
311552
- }
311553
- if (Object.keys(fundingFeesByCurrency2).length > 0) {
311554
- fundingFeeSource2 = "currencies";
311555
- }
311556
- } catch (error48) {
311557
- safeLogError(`Error fetching currency metadata for fees for ${symbol2} from ${cex3}`, error48);
311558
- }
311559
- }
311560
- return { fundingFeeSource: fundingFeeSource2, fundingFeesByCurrency: fundingFeesByCurrency2 };
311561
- };
311562
- const isMarketSymbol = symbol2.includes("/");
311563
- if (isMarketSymbol) {
311564
- const market = await broker.market(symbol2);
311565
- const generalFee = broker.fees ?? null;
311566
- const feeStatus = broker.fees ? "available" : "unknown";
311567
- if (!broker.fees) {
311568
- log.warn(`Fee metadata unavailable for ${cex3}`, { symbol: symbol2 });
311569
- }
311570
- if (!includeAllFees) {
311571
- return ctx.wrappedCallback(null, {
311572
- proof: ctx.verity.proof,
311573
- result: JSON.stringify({
311574
- feeScope: "market",
311575
- generalFee,
311576
- feeStatus,
311577
- market
311578
- })
311579
- });
311580
- }
311581
- const currencyCodes = Array.from(new Set([market.base, market.quote]));
311582
- const { fundingFeeSource: fundingFeeSource2, fundingFeesByCurrency: fundingFeesByCurrency2 } = await fetchFundingFees(currencyCodes);
311583
- return ctx.wrappedCallback(null, {
311584
- proof: ctx.verity.proof,
311585
- result: JSON.stringify({
311586
- feeScope: "market+funding",
311587
- generalFee,
311588
- feeStatus,
311589
- market,
311590
- fundingFeeSource: fundingFeeSource2,
311591
- fundingFeesByCurrency: fundingFeesByCurrency2
311592
- })
311593
- });
311594
- }
311595
- const tokenCode = symbol2.toUpperCase();
311596
- const { fundingFeeSource, fundingFeesByCurrency } = await fetchFundingFees([
311597
- tokenCode
311598
- ]);
312501
+ const exchange = evidenceExchange(ctx.broker);
312502
+ if (exchange.has?.fetchCurrencies === false || typeof exchange.fetchCurrencies !== "function") {
312503
+ throw new Error(`venue_discovery_unavailable: fetchCurrencies unavailable for ${asset}`);
312504
+ }
312505
+ const currencies = await exchange.fetchCurrencies();
312506
+ const currency = currencies[asset];
312507
+ if (!isRecord(currency)) {
312508
+ throw new Error(`venue_discovery_unavailable: currency not found for ${asset}`);
312509
+ }
312510
+ const networkEvidence = buildTransferNetworkEvidence(currency);
312511
+ const brokerNetworkId = normalizeBrokerNetworkId(requestedAlias);
312512
+ const resolution = networkEvidence.aliases[requestedAlias] ?? networkEvidence.aliases[brokerNetworkId];
312513
+ if (!resolution?.networkKey) {
312514
+ throw new Error(`network_alias_unresolved: ${asset}/${requestedAlias} is not available in discovered transfer networks`);
312515
+ }
312516
+ const networks = isRecord(currency.networks) ? currency.networks : {};
312517
+ const networkCandidate = networks[resolution.networkKey];
312518
+ if (!isRecord(networkCandidate) || typeof networkCandidate.deposit !== "boolean" || typeof networkCandidate.withdraw !== "boolean") {
312519
+ throw new Error(`venue_discovery_unavailable: transfer availability is incomplete for ${asset}/${requestedAlias}`);
312520
+ }
312521
+ const network = networkCandidate;
312522
+ const limits = isRecord(network.limits) ? network.limits : {};
312523
+ const withdrawalLimits = isRecord(limits.withdraw) ? limits.withdraw : {};
312524
+ const accountScope = resolveEvidenceAccountScope(ctx.selectedBrokerAccount, ctx.metadata);
312525
+ const evidence = TransferNetworkEvidenceSchema.parse({
312526
+ schemaVersion: "cex-transfer-network-evidence/v1",
312527
+ exchange: ctx.normalizedCex,
312528
+ asset,
312529
+ operatorNetworkAlias: requestedAlias,
312530
+ brokerNetworkId,
312531
+ exchangeNetworkId: resolution.exchangeNetworkId,
312532
+ depositAvailable: network.deposit,
312533
+ withdrawalAvailable: network.withdraw,
312534
+ withdrawalFee: canonicalOptionalDecimal(network.fee, "withdrawal fee") ?? null,
312535
+ withdrawalLimits: {
312536
+ minimum: canonicalOptionalDecimal(withdrawalLimits.min, "minimum withdrawal") ?? null,
312537
+ maximum: canonicalOptionalDecimal(withdrawalLimits.max, "maximum withdrawal") ?? null
312538
+ },
312539
+ ...accountScope,
312540
+ observedAt: new Date().toISOString(),
312541
+ sourceMethod: "ccxt.fetchCurrencies",
312542
+ digestAlgorithm: "sha256-canonical-json-v1",
312543
+ sourceDigest: evidenceSourceDigest({
312544
+ action: "FetchCurrency",
312545
+ exchange: ctx.normalizedCex,
312546
+ requestedKey: `${asset}/${requestedAlias}`,
312547
+ accountSelector: accountScope.accountSelector,
312548
+ sourceMethod: "ccxt.fetchCurrencies",
312549
+ source: network,
312550
+ broker: ctx.broker
312551
+ })
312552
+ });
312553
+ successWithProof(ctx, evidence);
312554
+ } catch (error48) {
312555
+ safeLogRedactedError(`FetchCurrency failed for ${ctx.normalizedCex}/${asset}/${requestedAlias}`, error48);
312556
+ const sanitized = sanitizeVenueError(error48, ctx.broker);
312557
+ const isAliasError = sanitized.startsWith("network_alias_unresolved:");
312558
+ const message = isAliasError || sanitized.startsWith("venue_discovery_unavailable:") ? sanitized : `venue_discovery_unavailable: ${sanitized}`;
312559
+ ctx.wrappedCallback({
312560
+ code: stableGrpcErrorCode(message) ?? mapCcxtErrorToGrpcStatus(error48) ?? grpc8.status.UNIMPLEMENTED,
312561
+ message
312562
+ }, null);
312563
+ }
312564
+ }
312565
+
312566
+ // src/handlers/execute-action/pass-through.ts
312567
+ async function handleFetchAccountId(ctx) {
312568
+ const { cex: cex3, broker } = ctx;
312569
+ try {
312570
+ const accountId = await broker.fetchAccountId();
311599
312571
  return ctx.wrappedCallback(null, {
311600
312572
  proof: ctx.verity.proof,
311601
- result: JSON.stringify({
311602
- feeScope: "token",
311603
- symbol: tokenCode,
311604
- fundingFeeSource,
311605
- fundingFeesByCurrency
311606
- })
312573
+ result: JSON.stringify({ accountId })
311607
312574
  });
311608
312575
  } catch (error48) {
311609
- safeLogError(`Error fetching fees for ${symbol2} from ${cex3}`, error48);
312576
+ safeLogError(`Error fetching account ID ${cex3}`, error48);
311610
312577
  ctx.wrappedCallback({
311611
- code: grpc7.status.INTERNAL,
311612
- message: `Error fetching fees from ${cex3}`
312578
+ code: grpc9.status.INTERNAL,
312579
+ message: `Error fetching account ID from ${cex3}`
311613
312580
  }, null);
311614
312581
  }
311615
312582
  }
311616
312583
  async function handleFetchDepositAddresses(ctx) {
311617
- const {
311618
- call,
311619
- wrappedCallback,
311620
- policy,
311621
- brokers,
311622
- metadata,
311623
- normalizedCex,
311624
- cex: cex3,
311625
- symbol: symbol2,
311626
- selectedBrokerAccount,
311627
- broker,
311628
- verity,
311629
- applyVerityToBroker,
311630
- useVerity,
311631
- verityProverUrl,
311632
- otelMetrics
311633
- } = ctx;
311634
- const verityProof = verity.proof;
312584
+ const { policy, cex: cex3, symbol: symbol2, broker } = ctx;
311635
312585
  if (!symbol2) {
311636
312586
  return ctx.wrappedCallback({
311637
- code: grpc7.status.INVALID_ARGUMENT,
312587
+ code: grpc9.status.INVALID_ARGUMENT,
311638
312588
  message: `ValidationError: Symbol required`
311639
312589
  }, null);
311640
312590
  }
@@ -311647,14 +312597,14 @@ async function handleFetchDepositAddresses(ctx) {
311647
312597
  } catch (error48) {
311648
312598
  const message = getErrorMessage(error48);
311649
312599
  return ctx.wrappedCallback({
311650
- code: stableGrpcErrorCode(message) ?? grpc7.status.INVALID_ARGUMENT,
312600
+ code: stableGrpcErrorCode(message) ?? grpc9.status.INVALID_ARGUMENT,
311651
312601
  message
311652
312602
  }, null);
311653
312603
  }
311654
312604
  const depositValidation = validateDeposit(policy, cex3, depositNetwork.brokerNetworkId, symbol2);
311655
312605
  if (!depositValidation.valid) {
311656
312606
  return ctx.wrappedCallback({
311657
- code: grpc7.status.PERMISSION_DENIED,
312607
+ code: grpc9.status.PERMISSION_DENIED,
311658
312608
  message: `policy_deposit_denied: ${depositValidation.error}`
311659
312609
  }, null);
311660
312610
  }
@@ -311680,45 +312630,30 @@ async function handleFetchDepositAddresses(ctx) {
311680
312630
  });
311681
312631
  }
311682
312632
  ctx.wrappedCallback({
311683
- code: grpc7.status.INTERNAL,
312633
+ code: grpc9.status.INTERNAL,
311684
312634
  message: "Deposit confirmation failed"
311685
312635
  }, null);
311686
312636
  } catch (error48) {
311687
312637
  safeLogError("Fetch Deposit Addresses confirmation failed", error48);
311688
312638
  const message = getErrorMessage(error48);
311689
312639
  ctx.wrappedCallback({
311690
- code: grpc7.status.INTERNAL,
312640
+ code: grpc9.status.INTERNAL,
311691
312641
  message: "Fetch Deposit Addresses confirmation failed: " + message
311692
312642
  }, null);
311693
312643
  }
311694
312644
  }
311695
312645
  async function handleFetchBalances(ctx) {
311696
- const {
311697
- call,
311698
- wrappedCallback,
311699
- policy,
311700
- brokers,
311701
- metadata,
311702
- normalizedCex,
311703
- cex: cex3,
311704
- symbol: symbol2,
311705
- selectedBrokerAccount,
311706
- broker,
311707
- verity,
311708
- applyVerityToBroker,
311709
- useVerity,
311710
- verityProverUrl,
311711
- otelMetrics
311712
- } = ctx;
311713
- const verityProof = verity.proof;
312646
+ const { call, cex: cex3, symbol: symbol2, broker } = ctx;
311714
312647
  try {
311715
- const payload = call.request.payload || {};
311716
- const providedBalanceType = payload.balanceType;
312648
+ const payload = {
312649
+ ...call.request.payload ?? {}
312650
+ };
312651
+ const providedBalanceType = typeof payload.balanceType === "string" ? payload.balanceType : undefined;
311717
312652
  const balanceType = (providedBalanceType ?? "total").toString();
311718
312653
  const validBalanceTypes = new Set(["free", "used", "total"]);
311719
312654
  if (!validBalanceTypes.has(balanceType)) {
311720
312655
  return ctx.wrappedCallback({
311721
- code: grpc7.status.INVALID_ARGUMENT,
312656
+ code: grpc9.status.INVALID_ARGUMENT,
311722
312657
  message: `ValidationError: invalid balanceType '${providedBalanceType}'. Expected one of: free | used | total`
311723
312658
  }, null);
311724
312659
  }
@@ -311759,33 +312694,16 @@ async function handleFetchBalances(ctx) {
311759
312694
  } catch (error48) {
311760
312695
  safeLogError(`Error fetching balance from ${cex3}`, error48);
311761
312696
  ctx.wrappedCallback({
311762
- code: grpc7.status.INTERNAL,
312697
+ code: grpc9.status.INTERNAL,
311763
312698
  message: `Failed to fetch balance from ${cex3}`
311764
312699
  }, null);
311765
312700
  }
311766
312701
  }
311767
312702
  async function handleFetchTicker(ctx) {
311768
- const {
311769
- call,
311770
- wrappedCallback,
311771
- policy,
311772
- brokers,
311773
- metadata,
311774
- normalizedCex,
311775
- cex: cex3,
311776
- symbol: symbol2,
311777
- selectedBrokerAccount,
311778
- broker,
311779
- verity,
311780
- applyVerityToBroker,
311781
- useVerity,
311782
- verityProverUrl,
311783
- otelMetrics
311784
- } = ctx;
311785
- const verityProof = verity.proof;
312703
+ const { cex: cex3, symbol: symbol2, broker } = ctx;
311786
312704
  if (!symbol2) {
311787
312705
  return ctx.wrappedCallback({
311788
- code: grpc7.status.INVALID_ARGUMENT,
312706
+ code: grpc9.status.INVALID_ARGUMENT,
311789
312707
  message: `ValidationError: Symbol required`
311790
312708
  }, null);
311791
312709
  }
@@ -311798,18 +312716,20 @@ async function handleFetchTicker(ctx) {
311798
312716
  } catch (error48) {
311799
312717
  safeLogError(`Error fetching ticker from ${cex3}`, error48);
311800
312718
  ctx.wrappedCallback({
311801
- code: grpc7.status.INTERNAL,
312719
+ code: grpc9.status.INTERNAL,
311802
312720
  message: `Failed to fetch ticker from ${cex3}`
311803
312721
  }, null);
311804
312722
  }
311805
312723
  }
311806
312724
  async function handlePassThrough(ctx) {
311807
312725
  if (ctx.action === Action.FetchCurrency)
311808
- return handleFetchCurrency(ctx);
312726
+ return handleFetchCurrencyEvidence(ctx);
311809
312727
  if (ctx.action === Action.FetchAccountId)
311810
312728
  return handleFetchAccountId(ctx);
311811
312729
  if (ctx.action === Action.FetchFees)
311812
- return handleFetchFees(ctx);
312730
+ return handleFetchFeesEvidence(ctx);
312731
+ if (ctx.action === Action.FetchMarketRules)
312732
+ return handleFetchMarketRulesEvidence(ctx);
311813
312733
  if (ctx.action === Action.FetchDepositAddresses)
311814
312734
  return handleFetchDepositAddresses(ctx);
311815
312735
  if (ctx.action === Action.FetchBalances)
@@ -311819,7 +312739,7 @@ async function handlePassThrough(ctx) {
311819
312739
  }
311820
312740
 
311821
312741
  // src/handlers/execute-action/perp-config.ts
311822
- import * as grpc8 from "@grpc/grpc-js";
312742
+ import * as grpc10 from "@grpc/grpc-js";
311823
312743
  function exchangeSupports(broker, capability) {
311824
312744
  return broker.has?.[capability] === true;
311825
312745
  }
@@ -311838,14 +312758,14 @@ async function handleGetPerpConfigState(ctx) {
311838
312758
  }
311839
312759
  if (!broker) {
311840
312760
  return wrappedCallback({
311841
- code: grpc8.status.INVALID_ARGUMENT,
312761
+ code: grpc10.status.INVALID_ARGUMENT,
311842
312762
  message: `Invalid CEX key: ${cex3}`
311843
312763
  }, null);
311844
312764
  }
311845
312765
  const exchange = broker;
311846
312766
  if (!exchangeSupports(exchange, "fetchPositions")) {
311847
312767
  return wrappedCallback({
311848
- code: grpc8.status.UNIMPLEMENTED,
312768
+ code: grpc10.status.UNIMPLEMENTED,
311849
312769
  message: `${normalizedCex} does not support fetchPositions`
311850
312770
  }, null);
311851
312771
  }
@@ -311862,7 +312782,7 @@ async function handleGetPerpConfigState(ctx) {
311862
312782
  } catch (error48) {
311863
312783
  safeLogError(`GetPerpConfigState failed for ${cex3}`, error48);
311864
312784
  ctx.wrappedCallback({
311865
- code: grpc8.status.INTERNAL,
312785
+ code: grpc10.status.INTERNAL,
311866
312786
  message: `GetPerpConfigState failed: ${sanitizeErrorDetail(error48)}`
311867
312787
  }, null);
311868
312788
  }
@@ -311875,14 +312795,14 @@ async function handleSetPerpConfigState(ctx) {
311875
312795
  }
311876
312796
  if (!broker) {
311877
312797
  return wrappedCallback({
311878
- code: grpc8.status.INVALID_ARGUMENT,
312798
+ code: grpc10.status.INVALID_ARGUMENT,
311879
312799
  message: `Invalid CEX key: ${cex3}`
311880
312800
  }, null);
311881
312801
  }
311882
312802
  const exchange = broker;
311883
312803
  if (!exchangeSupports(exchange, "setLeverage")) {
311884
312804
  return wrappedCallback({
311885
- code: grpc8.status.UNIMPLEMENTED,
312805
+ code: grpc10.status.UNIMPLEMENTED,
311886
312806
  message: `${normalizedCex} does not support setLeverage`
311887
312807
  }, null);
311888
312808
  }
@@ -311903,7 +312823,7 @@ async function handleSetPerpConfigState(ctx) {
311903
312823
  } catch (error48) {
311904
312824
  safeLogError(`SetPerpConfigState failed for ${cex3}`, error48);
311905
312825
  ctx.wrappedCallback({
311906
- code: grpc8.status.INTERNAL,
312826
+ code: grpc10.status.INTERNAL,
311907
312827
  message: `SetPerpConfigState failed: ${sanitizeErrorDetail(error48)}`
311908
312828
  }, null);
311909
312829
  }
@@ -311918,7 +312838,7 @@ async function handlePerpConfig(ctx) {
311918
312838
  }
311919
312839
 
311920
312840
  // src/handlers/execute-action/treasury-call.ts
311921
- import * as grpc9 from "@grpc/grpc-js";
312841
+ import * as grpc11 from "@grpc/grpc-js";
311922
312842
  async function handleTreasuryCall(ctx) {
311923
312843
  const { broker } = ctx;
311924
312844
  const callValue = parsePayloadForAction(ctx, CallPayloadSchema);
@@ -311929,7 +312849,7 @@ async function handleTreasuryCall(ctx) {
311929
312849
  try {
311930
312850
  if (callValue.functionName.startsWith("_") || callValue.functionName.includes("constructor") || callValue.functionName.includes("prototype")) {
311931
312851
  return ctx.wrappedCallback({
311932
- code: grpc9.status.PERMISSION_DENIED,
312852
+ code: grpc11.status.PERMISSION_DENIED,
311933
312853
  message: "Access to the requested function is denied"
311934
312854
  }, null);
311935
312855
  }
@@ -311944,7 +312864,7 @@ async function handleTreasuryCall(ctx) {
311944
312864
  const fn = broker[callValue.functionName];
311945
312865
  if (typeof fn !== "function" || broker.has?.[callValue.functionName] === false) {
311946
312866
  return ctx.wrappedCallback({
311947
- code: grpc9.status.INVALID_ARGUMENT,
312867
+ code: grpc11.status.INVALID_ARGUMENT,
311948
312868
  message: `Function not found on broker: ${callValue.functionName}`
311949
312869
  }, null);
311950
312870
  }
@@ -312023,7 +312943,7 @@ function asFiniteNumber(value) {
312023
312943
  }
312024
312944
 
312025
312945
  // src/handlers/execute-action/withdraw.ts
312026
- import * as grpc10 from "@grpc/grpc-js";
312946
+ import * as grpc12 from "@grpc/grpc-js";
312027
312947
  async function handleWithdraw(ctx) {
312028
312948
  const {
312029
312949
  call,
@@ -312046,7 +312966,7 @@ async function handleWithdraw(ctx) {
312046
312966
  const verityProof = verity.proof;
312047
312967
  if (!symbol2) {
312048
312968
  return ctx.wrappedCallback({
312049
- code: grpc10.status.INVALID_ARGUMENT,
312969
+ code: grpc12.status.INVALID_ARGUMENT,
312050
312970
  message: `ValidationError: Symbol required`
312051
312971
  }, null);
312052
312972
  }
@@ -312059,21 +312979,21 @@ async function handleWithdraw(ctx) {
312059
312979
  } catch (error48) {
312060
312980
  const message = getErrorMessage(error48);
312061
312981
  return ctx.wrappedCallback({
312062
- code: stableGrpcErrorCode(message) ?? grpc10.status.INVALID_ARGUMENT,
312982
+ code: stableGrpcErrorCode(message) ?? grpc12.status.INVALID_ARGUMENT,
312063
312983
  message
312064
312984
  }, null);
312065
312985
  }
312066
312986
  const transferValidation = validateWithdraw(policy, cex3, withdrawNetwork.brokerNetworkId, transferValue.recipientAddress, transferValue.amount, symbol2);
312067
312987
  if (!transferValidation.valid) {
312068
312988
  return ctx.wrappedCallback({
312069
- code: grpc10.status.PERMISSION_DENIED,
312989
+ code: grpc12.status.PERMISSION_DENIED,
312070
312990
  message: `policy_withdrawal_denied: ${transferValidation.error}`
312071
312991
  }, null);
312072
312992
  }
312073
312993
  const travelRule = resolveTravelRuleDecision(policy, cex3, transferValue.recipientAddress);
312074
312994
  if (travelRule.mode === "denied") {
312075
312995
  return ctx.wrappedCallback({
312076
- code: grpc10.status.FAILED_PRECONDITION,
312996
+ code: grpc12.status.FAILED_PRECONDITION,
312077
312997
  message: `travel_rule_denied: ${travelRule.error}`
312078
312998
  }, null);
312079
312999
  }
@@ -312140,7 +313060,7 @@ async function handleWithdraw(ctx) {
312140
313060
  payload: { recipientAddress: transferValue.recipientAddress }
312141
313061
  }
312142
313062
  });
312143
- const code = mapCcxtErrorToGrpcStatus(error48) ?? grpc10.status.INTERNAL;
313063
+ const code = mapCcxtErrorToGrpcStatus(error48) ?? grpc12.status.INTERNAL;
312144
313064
  ctx.wrappedCallback({
312145
313065
  code,
312146
313066
  message: `Withdraw failed: ${sanitizeErrorDetail(error48)}`
@@ -312149,33 +313069,152 @@ async function handleWithdraw(ctx) {
312149
313069
  }
312150
313070
 
312151
313071
  // src/handlers/execute-action/registry.ts
312152
- var ACTION_HANDLERS = {
312153
- [Action.Deposit]: handleDeposit,
312154
- [Action.Withdraw]: handleWithdraw,
312155
- [Action.Call]: handleTreasuryCall,
312156
- [Action.InternalTransfer]: handleInternalTransfer,
312157
- [Action.CreateOrder]: handleOrders,
312158
- [Action.GetOrderDetails]: handleOrders,
312159
- [Action.CancelOrder]: handleOrders,
312160
- [Action.FetchCurrency]: handlePassThrough,
312161
- [Action.FetchAccountId]: handlePassThrough,
312162
- [Action.FetchFees]: handlePassThrough,
312163
- [Action.FetchDepositAddresses]: handlePassThrough,
312164
- [Action.FetchBalances]: handlePassThrough,
312165
- [Action.FetchTicker]: handlePassThrough,
312166
- [Action.GetPerpConfigState]: handlePerpConfig,
312167
- [Action.SetPerpConfigState]: handlePerpConfig
313072
+ function valid() {
313073
+ return { valid: true };
313074
+ }
313075
+ function requireSymbol2(request) {
313076
+ return request.symbol?.trim() ? valid() : { valid: false, message: "symbol is required" };
313077
+ }
313078
+ function requireSpotSymbol(request) {
313079
+ const symbol2 = request.symbol?.trim() ?? "";
313080
+ return /^[^/\s]+\/[^/\s]+$/.test(symbol2) ? valid() : {
313081
+ valid: false,
313082
+ message: "symbol must be a slash-delimited spot pair"
313083
+ };
313084
+ }
313085
+ function validatePayload(schema, request) {
313086
+ const parsed = parsePayload(schema, request.payload);
313087
+ return parsed.success ? valid() : { valid: false, message: parsed.message };
313088
+ }
313089
+ function validateSymbolAndPayload(schema) {
313090
+ return (request) => {
313091
+ const symbolValidation = requireSymbol2(request);
313092
+ return symbolValidation.valid ? validatePayload(schema, request) : symbolValidation;
313093
+ };
313094
+ }
313095
+ function validateSpotSymbolAndPayload(schema) {
313096
+ return (request) => {
313097
+ const symbolValidation = requireSpotSymbol(request);
313098
+ return symbolValidation.valid ? validatePayload(schema, request) : symbolValidation;
313099
+ };
313100
+ }
313101
+ function validateFetchBalances(request) {
313102
+ const balanceType = request.payload?.balanceType;
313103
+ if (balanceType !== undefined && !new Set(["free", "used", "total"]).has(balanceType)) {
313104
+ return {
313105
+ valid: false,
313106
+ message: "balanceType must be free, used, or total"
313107
+ };
313108
+ }
313109
+ return valid();
313110
+ }
313111
+ var ACTION_DESCRIPTORS = {
313112
+ [Action.Deposit]: {
313113
+ handler: handleDeposit,
313114
+ access: "write",
313115
+ batchable: false
313116
+ },
313117
+ [Action.Withdraw]: {
313118
+ handler: handleWithdraw,
313119
+ access: "write",
313120
+ batchable: false
313121
+ },
313122
+ [Action.Call]: {
313123
+ handler: handleTreasuryCall,
313124
+ access: "write",
313125
+ batchable: false
313126
+ },
313127
+ [Action.InternalTransfer]: {
313128
+ handler: handleInternalTransfer,
313129
+ access: "write",
313130
+ batchable: false
313131
+ },
313132
+ [Action.CreateOrder]: {
313133
+ handler: handleOrders,
313134
+ access: "write",
313135
+ batchable: false
313136
+ },
313137
+ [Action.GetOrderDetails]: {
313138
+ handler: handleOrders,
313139
+ access: "read",
313140
+ batchable: false
313141
+ },
313142
+ [Action.CancelOrder]: {
313143
+ handler: handleOrders,
313144
+ access: "write",
313145
+ batchable: false
313146
+ },
313147
+ [Action.FetchCurrency]: {
313148
+ handler: handlePassThrough,
313149
+ access: "read",
313150
+ batchable: true,
313151
+ validateBatchRequest: validateSymbolAndPayload(FetchCurrencyPayloadSchema)
313152
+ },
313153
+ [Action.FetchAccountId]: {
313154
+ handler: handlePassThrough,
313155
+ access: "read",
313156
+ batchable: true,
313157
+ validateBatchRequest: (request) => validatePayload(EmptyActionPayloadSchema, request)
313158
+ },
313159
+ [Action.FetchFees]: {
313160
+ handler: handlePassThrough,
313161
+ access: "read",
313162
+ batchable: true,
313163
+ validateBatchRequest: validateSpotSymbolAndPayload(FetchFeesPayloadSchema)
313164
+ },
313165
+ [Action.FetchDepositAddresses]: {
313166
+ handler: handlePassThrough,
313167
+ access: "read",
313168
+ batchable: false
313169
+ },
313170
+ [Action.FetchBalances]: {
313171
+ handler: handlePassThrough,
313172
+ access: "read",
313173
+ batchable: true,
313174
+ validateBatchRequest: validateFetchBalances
313175
+ },
313176
+ [Action.FetchTicker]: {
313177
+ handler: handlePassThrough,
313178
+ access: "read",
313179
+ batchable: true,
313180
+ validateBatchRequest: validateSymbolAndPayload(EmptyActionPayloadSchema)
313181
+ },
313182
+ [Action.GetPerpConfigState]: {
313183
+ handler: handlePerpConfig,
313184
+ access: "read",
313185
+ batchable: true,
313186
+ validateBatchRequest: (request) => validatePayload(GetPerpConfigStatePayloadSchema, request)
313187
+ },
313188
+ [Action.SetPerpConfigState]: {
313189
+ handler: handlePerpConfig,
313190
+ access: "write",
313191
+ batchable: false
313192
+ },
313193
+ [Action.FetchMarketRules]: {
313194
+ handler: handlePassThrough,
313195
+ access: "read",
313196
+ batchable: true,
313197
+ validateBatchRequest: validateSpotSymbolAndPayload(EmptyActionPayloadSchema)
313198
+ },
313199
+ [Action.Batch]: {
313200
+ handler: (ctx) => handleBatch(ctx, getActionDescriptor),
313201
+ access: "read",
313202
+ batchable: false
313203
+ }
312168
313204
  };
313205
+ function getActionDescriptor(action) {
313206
+ return ACTION_DESCRIPTORS[action];
313207
+ }
312169
313208
  async function dispatchExecuteAction(ctx) {
312170
- const handler = ACTION_HANDLERS[ctx.action];
312171
- if (!handler) {
313209
+ const descriptor = getActionDescriptor(ctx.action);
313210
+ if (!descriptor) {
312172
313211
  ctx.wrappedCallback({
312173
- code: grpc11.status.INVALID_ARGUMENT,
313212
+ code: grpc13.status.INVALID_ARGUMENT,
312174
313213
  message: "Invalid Action"
312175
313214
  }, null);
312176
313215
  return;
312177
313216
  }
312178
- await handler(ctx);
313217
+ await descriptor.handler(ctx);
312179
313218
  }
312180
313219
 
312181
313220
  // src/handlers/execute-action/handler.ts
@@ -312191,7 +313230,7 @@ function grpcStatusName(error48) {
312191
313230
  if (typeof error48.code !== "number") {
312192
313231
  return "UNKNOWN";
312193
313232
  }
312194
- return grpc12.status[error48.code] ?? "UNKNOWN";
313233
+ return grpc14.status[error48.code] ?? "UNKNOWN";
312195
313234
  }
312196
313235
  function createExecuteActionHandler(deps) {
312197
313236
  const {
@@ -312239,7 +313278,7 @@ function createExecuteActionHandler(deps) {
312239
313278
  otelMetrics?.recordCounter("execute_action_errors_total", 1, {
312240
313279
  action: actionName,
312241
313280
  cex: cex3 || "unknown",
312242
- error_type: error48.code ? grpc12.status[error48.code] || "unknown" : "unknown"
313281
+ error_type: error48.code ? grpc14.status[error48.code] || "unknown" : "unknown"
312243
313282
  });
312244
313283
  } else {
312245
313284
  otelMetrics?.recordCounter("execute_action_success_total", 1, {
@@ -312262,13 +313301,13 @@ function createExecuteActionHandler(deps) {
312262
313301
  });
312263
313302
  if (!authenticateRequest(call, whitelistIps)) {
312264
313303
  return wrappedCallback({
312265
- code: grpc12.status.PERMISSION_DENIED,
313304
+ code: grpc14.status.PERMISSION_DENIED,
312266
313305
  message: "Access denied: Unauthorized IP"
312267
313306
  }, null);
312268
313307
  }
312269
313308
  if (!action || !cex3) {
312270
313309
  return wrappedCallback({
312271
- code: grpc12.status.INVALID_ARGUMENT,
313310
+ code: grpc14.status.INVALID_ARGUMENT,
312272
313311
  message: "`action` AND `cex` fields are required"
312273
313312
  }, null);
312274
313313
  }
@@ -312278,17 +313317,20 @@ function createExecuteActionHandler(deps) {
312278
313317
  const broker = selectedBrokerAccount?.exchange ?? createBroker(normalizedCex, call.metadata) ?? (isPublicMarketDataAction(action, call.request.payload) ? createPublicBroker(normalizedCex) : null);
312279
313318
  if (!broker) {
312280
313319
  return wrappedCallback({
312281
- code: grpc12.status.UNAUTHENTICATED,
313320
+ code: grpc14.status.UNAUTHENTICATED,
312282
313321
  message: `This Exchange is not registered and No API metadata was found`
312283
313322
  }, null);
312284
313323
  }
312285
313324
  const verity = { proof: "" };
312286
- const applyVerityToBroker = (targetBroker) => {
313325
+ const applyVerityToBroker = (targetBroker, proofState = verity) => {
312287
313326
  if (!useVerity)
312288
313327
  return;
312289
313328
  const override = buildHttpClientOverrideFromMetadata(metadata, verityProverUrl, (proof, notaryPubKey) => {
312290
- verity.proof = proof;
312291
- log.debug(`Verity proof:`, { proof, notaryPubKey });
313329
+ proofState.proof = proof;
313330
+ log.debug(`Verity proof received`, {
313331
+ has_proof: proof.length > 0,
313332
+ has_notary_public_key: Boolean(notaryPubKey)
313333
+ });
312292
313334
  });
312293
313335
  targetBroker.setHttpClientOverride(override, verityHttpClientOverridePredicate);
312294
313336
  };
@@ -312324,7 +313366,7 @@ function createExecuteActionHandler(deps) {
312324
313366
  } catch (error48) {
312325
313367
  safeLogError("ExecuteAction unhandled error", error48);
312326
313368
  return wrappedCallback({
312327
- code: grpc12.status.INTERNAL,
313369
+ code: grpc14.status.INTERNAL,
312328
313370
  message: "ExecuteAction failed unexpectedly"
312329
313371
  }, null);
312330
313372
  }
@@ -312384,7 +313426,7 @@ class SubscribeBrokerLifecycle {
312384
313426
  }
312385
313427
  }
312386
313428
  // src/handlers/subscribe/handler.ts
312387
- import * as grpc13 from "@grpc/grpc-js";
313429
+ import * as grpc15 from "@grpc/grpc-js";
312388
313430
 
312389
313431
  // src/helpers/binance-user-data-normalization.ts
312390
313432
  function requireQuantity(entry, key2) {
@@ -312495,8 +313537,8 @@ async function writeSubscribeError(call, isClosed, frame) {
312495
313537
  call.end();
312496
313538
  }
312497
313539
  }
312498
- function grpcStatusName2(status14) {
312499
- return typeof status14 === "number" ? grpc13.status[status14] ?? "UNKNOWN" : "UNKNOWN";
313540
+ function grpcStatusName2(status16) {
313541
+ return typeof status16 === "number" ? grpc15.status[status16] ?? "UNKNOWN" : "UNKNOWN";
312500
313542
  }
312501
313543
  function getBinanceEventMarketId(event) {
312502
313544
  const value = event.s;
@@ -312689,9 +313731,9 @@ function createSubscribeHandler(deps) {
312689
313731
  log.withMetadata(fields).info("Subscribe ended");
312690
313732
  }
312691
313733
  };
312692
- const writeTerminalError = async (frame, status14 = grpc13.status.UNKNOWN) => {
313734
+ const writeTerminalError = async (frame, status16 = grpc15.status.UNKNOWN) => {
312693
313735
  terminalOutcome = "error";
312694
- terminalGrpcStatus = grpcStatusName2(status14);
313736
+ terminalGrpcStatus = grpcStatusName2(status16);
312695
313737
  await writeSubscribeError(call, isStreamClosed, frame);
312696
313738
  };
312697
313739
  log.withMetadata(operationalFields).info("Subscribe started");
@@ -312727,7 +313769,7 @@ function createSubscribeHandler(deps) {
312727
313769
  error_type: "permission_denied"
312728
313770
  });
312729
313771
  call.emit("error", {
312730
- code: grpc13.status.PERMISSION_DENIED,
313772
+ code: grpc15.status.PERMISSION_DENIED,
312731
313773
  message: "Access denied: Unauthorized IP"
312732
313774
  }, null);
312733
313775
  call.destroy(new Error("Access denied: Unauthorized IP"));
@@ -312748,7 +313790,7 @@ function createSubscribeHandler(deps) {
312748
313790
  timestamp: Date.now(),
312749
313791
  symbol: symbol2 || "",
312750
313792
  type: subscriptionType2
312751
- }, grpc13.status.INVALID_ARGUMENT);
313793
+ }, grpc15.status.INVALID_ARGUMENT);
312752
313794
  return;
312753
313795
  }
312754
313796
  if (isPublicMarketDataSubscription(subscriptionType2)) {
@@ -312805,7 +313847,7 @@ function createSubscribeHandler(deps) {
312805
313847
  timestamp: Date.now(),
312806
313848
  symbol: symbol2,
312807
313849
  type: subscriptionType2
312808
- }, grpc13.status.NOT_FOUND);
313850
+ }, grpc15.status.NOT_FOUND);
312809
313851
  return;
312810
313852
  }
312811
313853
  if (!selectedBrokerAccount) {
@@ -312840,7 +313882,7 @@ function createSubscribeHandler(deps) {
312840
313882
  timestamp: Date.now(),
312841
313883
  symbol: resolvedSymbol,
312842
313884
  type: subscriptionType2
312843
- }, grpc13.status.FAILED_PRECONDITION);
313885
+ }, grpc15.status.FAILED_PRECONDITION);
312844
313886
  return;
312845
313887
  }
312846
313888
  const marketId = subscriptionType2 === SubscriptionType.ORDERS ? await getBinanceMarketId(accountBroker, resolvedSymbol) : undefined;
@@ -312854,7 +313896,7 @@ function createSubscribeHandler(deps) {
312854
313896
  timestamp: Date.now(),
312855
313897
  symbol: resolvedSymbol,
312856
313898
  type: subscriptionType2
312857
- }, grpc13.status.FAILED_PRECONDITION);
313899
+ }, grpc15.status.FAILED_PRECONDITION);
312858
313900
  return;
312859
313901
  }
312860
313902
  userDataSource = userDataStreamSupervisor.subscribe({
@@ -312912,7 +313954,7 @@ function createSubscribeHandler(deps) {
312912
313954
  timestamp: Date.now(),
312913
313955
  symbol: symbol2,
312914
313956
  type: subscriptionType2
312915
- }, grpc13.status.INVALID_ARGUMENT);
313957
+ }, grpc15.status.INVALID_ARGUMENT);
312916
313958
  }
312917
313959
  } catch (error48) {
312918
313960
  log.error("Error in Subscribe stream:", error48);
@@ -312922,7 +313964,7 @@ function createSubscribeHandler(deps) {
312922
313964
  timestamp: Date.now(),
312923
313965
  symbol: "",
312924
313966
  type: subscriptionType2
312925
- }, grpc13.status.INTERNAL);
313967
+ }, grpc15.status.INTERNAL);
312926
313968
  } finally {
312927
313969
  call.off("cancelled", closeOwnedBrokerOnCallEnd);
312928
313970
  call.off("error", closeOwnedBrokerOnCallEnd);
@@ -313054,7 +314096,9 @@ var descriptor = {
313054
314096
  FetchFees: 12,
313055
314097
  InternalTransfer: 13,
313056
314098
  GetPerpConfigState: 14,
313057
- SetPerpConfigState: 15
314099
+ SetPerpConfigState: 15,
314100
+ FetchMarketRules: 16,
314101
+ Batch: 17
313058
314102
  }
313059
314103
  }
313060
314104
  }
@@ -313076,10 +314120,10 @@ var PROTO_LOADER_OPTIONS = {
313076
314120
  var CEX_BROKER_PACKAGE_DEFINITION = protoLoader.fromJSON(node_descriptor_default, PROTO_LOADER_OPTIONS);
313077
314121
 
313078
314122
  // src/server.ts
313079
- var grpcObj = grpc14.loadPackageDefinition(CEX_BROKER_PACKAGE_DEFINITION);
314123
+ var grpcObj = grpc16.loadPackageDefinition(CEX_BROKER_PACKAGE_DEFINITION);
313080
314124
  var cexNode = grpcObj.cex_broker;
313081
314125
  function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, otelMetrics, brokerArchiver, orderActivityTracker, withdrawalObservationTracker, subscribeBrokerLifecycle, userDataStreamSupervisor, publicMarketDataFeedSupervisor) {
313082
- const server = new grpc14.Server;
314126
+ const server = new grpc16.Server;
313083
314127
  server.addService(cexNode.cex_service.service, {
313084
314128
  ExecuteAction: createExecuteActionHandler({
313085
314129
  policy,
@@ -313468,7 +314512,10 @@ class CEXBroker {
313468
314512
  await this.otelMetrics.initialize();
313469
314513
  }
313470
314514
  if (!this.userDataStreamSupervisor && Object.keys(this.brokers).length > 0) {
313471
- const publisher = new StreamHealthPublisher(streamHealthPublisherConfigFromEnv());
314515
+ const publisher = new StreamHealthPublisher({
314516
+ ...streamHealthPublisherConfigFromEnv(),
314517
+ producerId: USER_DATA_STREAM_HEALTH_PRODUCER_ID
314518
+ });
313472
314519
  this.userDataStreamSupervisor = new UserDataStreamSupervisor({
313473
314520
  brokers: this.brokers,
313474
314521
  publisher
@@ -313481,7 +314528,7 @@ class CEXBroker {
313481
314528
  otelMetrics: this.otelMetrics
313482
314529
  });
313483
314530
  this.server = getServer(this.policy, this.brokers, this.whitelistIps, this.useVerity, this.#verityProverUrl, this.otelMetrics, this.brokerArchiver, this.orderActivityTracker, this.withdrawalObservationTracker, undefined, this.userDataStreamSupervisor, this.publicMarketDataFeedSupervisor);
313484
- this.server.bindAsync(`0.0.0.0:${this.port}`, grpc15.ServerCredentials.createInsecure(), (err2, port) => {
314531
+ this.server.bindAsync(`0.0.0.0:${this.port}`, grpc17.ServerCredentials.createInsecure(), (err2, port) => {
313485
314532
  if (err2) {
313486
314533
  log.error(err2);
313487
314534
  return;
@@ -313506,7 +314553,8 @@ class CEXBroker {
313506
314553
  this.depositArchivePoller = new DepositArchivePoller({
313507
314554
  brokers: this.brokers,
313508
314555
  archiver: this.brokerArchiver,
313509
- metrics: this.otelMetrics
314556
+ metrics: this.otelMetrics,
314557
+ coveragePublisher: Object.keys(this.brokers).length > 0 ? new StreamHealthPublisher(depositPollerStreamHealthPublisherConfigFromEnv()) : undefined
313510
314558
  });
313511
314559
  this.depositArchivePoller.start();
313512
314560
  if (this.userDataStreamSupervisor) {
@@ -313539,4 +314587,4 @@ export {
313539
314587
  CEXBroker as default
313540
314588
  };
313541
314589
 
313542
- //# debugId=72C8C3614857C3F664756E2164756E21
314590
+ //# debugId=9C0805B96506F17064756E2164756E21