@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
@@ -74496,7 +74496,7 @@ var {
74496
74496
  } = import__.default;
74497
74497
 
74498
74498
  // src/index.ts
74499
- var grpc15 = __toESM(require_src3(), 1);
74499
+ var grpc17 = __toESM(require_src3(), 1);
74500
74500
 
74501
74501
  // node_modules/@usherlabs/ccxt/js/src/base/functions.js
74502
74502
  var exports_functions = {};
@@ -314580,7 +314580,9 @@ var Action = {
314580
314580
  FetchFees: 12,
314581
314581
  InternalTransfer: 13,
314582
314582
  GetPerpConfigState: 14,
314583
- SetPerpConfigState: 15
314583
+ SetPerpConfigState: 15,
314584
+ FetchMarketRules: 16,
314585
+ Batch: 17
314584
314586
  };
314585
314587
  var SubscriptionType = {
314586
314588
  NO_ACTION: 0,
@@ -315128,6 +315130,30 @@ function redactUnknownValue(value, secretLiterals) {
315128
315130
  }
315129
315131
  return String(value);
315130
315132
  }
315133
+ function removeSecretMaterial(value, secretLiterals = []) {
315134
+ if (value === null || value === undefined) {
315135
+ return value;
315136
+ }
315137
+ if (typeof value === "string") {
315138
+ return redactSecretLiterals(value, secretLiterals);
315139
+ }
315140
+ if (typeof value === "number" || typeof value === "boolean") {
315141
+ return value;
315142
+ }
315143
+ if (Array.isArray(value)) {
315144
+ return value.map((entry) => removeSecretMaterial(entry, secretLiterals));
315145
+ }
315146
+ if (typeof value === "object") {
315147
+ const clean = {};
315148
+ for (const [key, entry] of Object.entries(value)) {
315149
+ if (!SECRET_KEY_PATTERN.test(key)) {
315150
+ clean[key] = removeSecretMaterial(entry, secretLiterals);
315151
+ }
315152
+ }
315153
+ return clean;
315154
+ }
315155
+ return String(value);
315156
+ }
315131
315157
  function redactStreamPayload(payload, secretLiterals = []) {
315132
315158
  if (Array.isArray(payload)) {
315133
315159
  return {
@@ -315838,6 +315864,22 @@ import {
315838
315864
  import { basename, dirname } from "node:path";
315839
315865
  var EXPORT_CHUNK_BYTES = 8 * 1024 * 1024;
315840
315866
  var SHA256_RECEIPT_PATTERN = /^([0-9a-f]{64}) {2}(.+)\n$/;
315867
+ function sha256File(path) {
315868
+ const hash4 = createHash3("sha256");
315869
+ const fd2 = openSync(path, "r");
315870
+ try {
315871
+ const chunk = Buffer.alloc(EXPORT_CHUNK_BYTES);
315872
+ for (;; ) {
315873
+ const read = readSync(fd2, chunk, 0, chunk.length, null);
315874
+ if (read === 0)
315875
+ break;
315876
+ hash4.update(chunk.subarray(0, read));
315877
+ }
315878
+ } finally {
315879
+ closeSync(fd2);
315880
+ }
315881
+ return hash4.digest("hex");
315882
+ }
315841
315883
 
315842
315884
  class DeadLetterJournalExportError extends Error {
315843
315885
  constructor(message, options) {
@@ -315871,6 +315913,9 @@ function exportDeadLetterJournal(journalPath, exportPath) {
315871
315913
  if (match?.[1] === undefined || match[2] !== basename(exportPath)) {
315872
315914
  throw new Error("export receipt is malformed");
315873
315915
  }
315916
+ if (sha256File(exportPath) !== match[1]) {
315917
+ throw new Error("export digest does not match receipt");
315918
+ }
315874
315919
  } catch (error) {
315875
315920
  throw new DeadLetterJournalExportError("Dead-letter journal export target is incomplete or unverifiable", { cause: error });
315876
315921
  }
@@ -317211,6 +317256,17 @@ function withTimeout(promise, timeoutMs, label) {
317211
317256
  return Promise.race([promise, expiry]).finally(() => clearTimeout(timer));
317212
317257
  }
317213
317258
  var ALL_CURRENCIES_CODE = "*";
317259
+ var MAX_FAILURE_REASON_CHARS = 256;
317260
+ var DEFAULT_FAILURE_REASON = "fetchDeposits failed";
317261
+ function decimal(value) {
317262
+ return value === null || value === undefined ? null : String(value);
317263
+ }
317264
+ function failureReason(error, exchange) {
317265
+ const text = error instanceof Error ? error.message : typeof error === "string" ? error : "";
317266
+ const secrets = [exchange.apiKey, exchange.secret].filter((value) => typeof value === "string" && value.length > 0);
317267
+ const trimmed = redactSecretLiterals(text, secrets).replace(/\s+/g, " ").trim().slice(0, MAX_FAILURE_REASON_CHARS);
317268
+ return trimmed.length > 0 ? trimmed : DEFAULT_FAILURE_REASON;
317269
+ }
317214
317270
  var BINANCE_UNLOCK_PROGRESS_SOURCE = {
317215
317271
  venue: "binance",
317216
317272
  endpoint: "GET /sapi/v1/capital/deposit/hisrec",
@@ -317221,6 +317277,20 @@ var BINANCE_UNLOCK_PROGRESS_SOURCE = {
317221
317277
  completeTime: "info.completeTime"
317222
317278
  }
317223
317279
  };
317280
+ function observedProgress(progress) {
317281
+ if (progress === undefined)
317282
+ return null;
317283
+ return {
317284
+ state: progress.state,
317285
+ progress_state: progress.progress_state,
317286
+ reason: progress.reason,
317287
+ native_status: decimal(progress.native_status),
317288
+ current: decimal(progress.current),
317289
+ credit_required: decimal(progress.credit_required),
317290
+ unlock_required: decimal(progress.unlock_required),
317291
+ complete_time: decimal(progress.complete_time)
317292
+ };
317293
+ }
317224
317294
  function depositTimestamp(record) {
317225
317295
  const observedAt = depositField(record, [
317226
317296
  "timestamp",
@@ -317450,6 +317520,7 @@ class DepositArchivePoller {
317450
317520
  #cursors = new Map;
317451
317521
  #lastArchivedByTarget = new Map;
317452
317522
  #unsupportedLogged = new Set;
317523
+ #coverage = new Map;
317453
317524
  #config;
317454
317525
  constructor(params) {
317455
317526
  this.params = params;
@@ -317460,6 +317531,7 @@ class DepositArchivePoller {
317460
317531
  return;
317461
317532
  }
317462
317533
  log.info("\uD83D\uDCE5 Deposit archive poller started");
317534
+ this.params.coveragePublisher?.start();
317463
317535
  this.#schedule(0);
317464
317536
  }
317465
317537
  async stop() {
@@ -317469,6 +317541,7 @@ class DepositArchivePoller {
317469
317541
  this.#timer = null;
317470
317542
  }
317471
317543
  await this.#running;
317544
+ await this.params.coveragePublisher?.close(this.#coverageSnapshots());
317472
317545
  }
317473
317546
  async pollAllOnce() {
317474
317547
  if (this.#stopped || this.#running || !this.params.archiver.isEnabled()) {
@@ -317504,14 +317577,90 @@ class DepositArchivePoller {
317504
317577
  return true;
317505
317578
  }
317506
317579
  async#pollOne(target) {
317580
+ const attemptedAt = new Date().toISOString();
317507
317581
  let outcome = "error";
317582
+ let observation;
317508
317583
  try {
317509
- outcome = await this.#pollTarget(target);
317584
+ const result = await this.#pollTarget(target, attemptedAt);
317585
+ outcome = result.outcome;
317586
+ observation = result.observation;
317587
+ } catch (error) {
317588
+ observation = this.#observation("error", attemptedAt, {
317589
+ errorReason: failureReason(error, target.account.exchange)
317590
+ });
317591
+ throw error;
317510
317592
  } finally {
317511
317593
  this.params.metrics?.recordCounter("cex_deposit_poller_polls_total", 1, { exchange: target.exchangeId, outcome });
317594
+ this.#recordCoverage(target, outcome, observation ?? this.#observation("error", attemptedAt, {
317595
+ errorReason: "poll aborted before completion"
317596
+ }));
317512
317597
  }
317513
317598
  }
317514
- async#pollTarget(target) {
317599
+ #observation(disposition, attemptedAt, detail = {}) {
317600
+ if (disposition === "success" && detail.completedAt !== undefined && detail.completedAt < attemptedAt) {
317601
+ return this.#observation("error", attemptedAt, {
317602
+ requestSinceMs: detail.requestSinceMs,
317603
+ errorReason: `source clock invalid: completed ${detail.completedAt} before attempted ${attemptedAt}`
317604
+ });
317605
+ }
317606
+ return {
317607
+ version: "1",
317608
+ disposition,
317609
+ attempted_at: attemptedAt,
317610
+ completed_at: disposition === "success" ? detail.completedAt ?? null : null,
317611
+ poll_interval_ms: String(this.#config.pollIntervalMs),
317612
+ fetch_timeout_ms: String(this.#config.fetchTimeoutMs),
317613
+ deposits_limit: String(this.#config.depositsLimit),
317614
+ request_since_ms: decimal(detail.requestSinceMs),
317615
+ next_cursor_ms: disposition === "success" ? decimal(detail.nextCursorMs) : null,
317616
+ response_truncated: disposition === "success" ? detail.responseTruncated ?? null : null,
317617
+ malformed_count: String(detail.malformedCount ?? 0),
317618
+ error_reason: disposition === "error" ? detail.errorReason?.trim() || DEFAULT_FAILURE_REASON : "",
317619
+ observed_deposits: disposition === "success" ? detail.observedDeposits ?? [] : []
317620
+ };
317621
+ }
317622
+ #recordCoverage(target, outcome, observation) {
317623
+ const publisher = this.params.coveragePublisher;
317624
+ if (!publisher)
317625
+ return;
317626
+ const key = this.#targetKey(target);
317627
+ const previous = this.#coverage.get(key);
317628
+ const now3 = observation.completed_at ?? new Date().toISOString();
317629
+ const state = outcome === "ok" ? "connected" : "error";
317630
+ const base2 = previous?.snapshot;
317631
+ const stateChangedAt = base2 && base2.state === state ? base2.stateChangedAt : now3;
317632
+ const attempts = BigInt(base2?.connectAttemptCount ?? "0") + 1n;
317633
+ const errors = BigInt(base2?.errorCount ?? "0") + (outcome === "error" ? 1n : 0n);
317634
+ const reconnects = BigInt(base2?.reconnectCount ?? "0") + (outcome === "ok" && previous?.lastOutcome === "error" ? 1n : 0n);
317635
+ const snapshot = {
317636
+ exchange: target.exchangeId,
317637
+ accountSelector: target.account.label,
317638
+ accountRole: target.account.role,
317639
+ streamKind: "deposit_poller",
317640
+ accountScope: "spot",
317641
+ registryStatus: "active",
317642
+ retiredAt: null,
317643
+ state,
317644
+ stateChangedAt,
317645
+ lastConnectedAt: outcome === "ok" ? observation.completed_at : base2?.lastConnectedAt ?? null,
317646
+ lastAuthenticatedAt: null,
317647
+ lastReceivedAt: outcome === "ok" ? observation.completed_at : base2?.lastReceivedAt ?? null,
317648
+ connectAttemptCount: attempts.toString(),
317649
+ reconnectCount: reconnects.toString(),
317650
+ errorCount: errors.toString(),
317651
+ lastFailureKind: outcome === "ok" ? "none" : outcome === "unsupported" ? "unsupported_connector" : "transport_error",
317652
+ lastFailureReason: outcome === "ok" ? "" : outcome === "unsupported" ? "fetchDeposits unsupported" : observation.error_reason,
317653
+ trafficMode: "continuous",
317654
+ sourceWatermark: null,
317655
+ pollObservation: observation
317656
+ };
317657
+ this.#coverage.set(key, { snapshot, lastOutcome: outcome });
317658
+ publisher.publish(this.#coverageSnapshots());
317659
+ }
317660
+ #coverageSnapshots() {
317661
+ return [...this.#coverage.values()].map((entry) => entry.snapshot);
317662
+ }
317663
+ async#pollTarget(target, attemptedAt) {
317515
317664
  const exchange = target.account.exchange;
317516
317665
  const key = this.#targetKey(target);
317517
317666
  if (typeof exchange.fetchDeposits !== "function" || exchange.has?.fetchDeposits === false) {
@@ -317522,7 +317671,10 @@ class DepositArchivePoller {
317522
317671
  account: target.account.label
317523
317672
  });
317524
317673
  }
317525
- return "unsupported";
317674
+ return {
317675
+ outcome: "unsupported",
317676
+ observation: this.#observation("unsupported", attemptedAt)
317677
+ };
317526
317678
  }
317527
317679
  const since = this.#cursors.get(key) ?? Date.now() - this.#config.lookbackMs;
317528
317680
  let deposits;
@@ -317535,15 +317687,48 @@ class DepositArchivePoller {
317535
317687
  account: target.account.label,
317536
317688
  error
317537
317689
  });
317538
- return "error";
317690
+ return {
317691
+ outcome: "error",
317692
+ observation: this.#observation("error", attemptedAt, {
317693
+ requestSinceMs: since,
317694
+ errorReason: failureReason(error, exchange)
317695
+ })
317696
+ };
317539
317697
  }
317540
- if (!Array.isArray(deposits) || deposits.length === 0) {
317541
- return "ok";
317698
+ if (!Array.isArray(deposits)) {
317699
+ return {
317700
+ outcome: "error",
317701
+ observation: this.#observation("error", attemptedAt, {
317702
+ requestSinceMs: since,
317703
+ errorReason: "fetchDeposits returned a non-array response"
317704
+ })
317705
+ };
317706
+ }
317707
+ if (deposits.length > this.#config.depositsLimit) {
317708
+ return {
317709
+ outcome: "error",
317710
+ observation: this.#observation("error", attemptedAt, {
317711
+ requestSinceMs: since,
317712
+ errorReason: `fetchDeposits returned ${deposits.length} rows for a limit of ${this.#config.depositsLimit}`
317713
+ })
317714
+ };
317715
+ }
317716
+ if (deposits.length === 0) {
317717
+ return this.#completed(this.#observation("success", attemptedAt, {
317718
+ completedAt: new Date().toISOString(),
317719
+ requestSinceMs: since,
317720
+ nextCursorMs: this.#cursors.get(key),
317721
+ responseTruncated: false,
317722
+ observedDeposits: []
317723
+ }));
317542
317724
  }
317543
317725
  let archived = 0;
317726
+ let malformed = 0;
317727
+ const observed = [];
317544
317728
  for (const deposit of deposits) {
317545
317729
  const record = asRecord(deposit);
317546
317730
  if (!record) {
317731
+ malformed += 1;
317547
317732
  continue;
317548
317733
  }
317549
317734
  const info = asRecord(record.info);
@@ -317568,6 +317753,16 @@ class DepositArchivePoller {
317568
317753
  });
317569
317754
  const lastArchived = identity === undefined ? undefined : this.#lastArchivedByTarget.get(key)?.get(identity);
317570
317755
  const classification = classifyDeposit(target.exchangeId, record, lastArchived);
317756
+ const depositTimestampMs = depositTimestamp(record);
317757
+ observed.push({
317758
+ coin: assetSymbol === undefined ? "" : String(assetSymbol),
317759
+ network: network === undefined ? "" : String(network),
317760
+ external_id: depositTxid ?? "",
317761
+ txid: depositTxid ?? "",
317762
+ deposit_timestamp_ms: decimal(depositTimestampMs),
317763
+ status: classification.archiveStatus,
317764
+ progress: observedProgress(classification.unlockProgress)
317765
+ });
317571
317766
  if (lastArchived && lastArchived.status === classification.archiveStatus && lastArchived.progressKey === classification.progressKey) {
317572
317767
  continue;
317573
317768
  }
@@ -317608,7 +317803,7 @@ class DepositArchivePoller {
317608
317803
  }
317609
317804
  targetDeposits2.set(identity, {
317610
317805
  status: classification.archiveStatus,
317611
- timestamp: depositTimestamp(record),
317806
+ timestamp: depositTimestampMs,
317612
317807
  progressKey: classification.progressKey,
317613
317808
  highWatermark: classification.highWatermark
317614
317809
  });
@@ -317631,7 +317826,20 @@ class DepositArchivePoller {
317631
317826
  this.#lastArchivedByTarget.delete(key);
317632
317827
  }
317633
317828
  }
317634
- return "ok";
317829
+ return this.#completed(this.#observation("success", attemptedAt, {
317830
+ completedAt: new Date().toISOString(),
317831
+ requestSinceMs: since,
317832
+ nextCursorMs: nextCursor,
317833
+ responseTruncated: deposits.length >= this.#config.depositsLimit,
317834
+ malformedCount: malformed,
317835
+ observedDeposits: observed
317836
+ }));
317837
+ }
317838
+ #completed(observation) {
317839
+ return {
317840
+ outcome: observation.disposition === "success" ? "ok" : "error",
317841
+ observation
317842
+ };
317635
317843
  }
317636
317844
  #targetKey(target) {
317637
317845
  return `${target.exchangeId}|${target.account.label}|${target.code}`;
@@ -317778,10 +317986,7 @@ import { createHash as createHash4 } from "node:crypto";
317778
317986
  var MARKET_CAPTURE_SCHEMA_VERSION = "1.0.0";
317779
317987
  var CHECKSUM_ALGORITHM = "sha256-canonical-json-v1";
317780
317988
  var ARCHIVE_SOURCES = ["broker_read", "broker_write"];
317781
- var MARKET_ARCHIVE_SOURCES = [
317782
- ...ARCHIVE_SOURCES,
317783
- "external_backfill"
317784
- ];
317989
+ var MARKET_ARCHIVE_SOURCES = ARCHIVE_SOURCES;
317785
317990
  var CAPTURE_FEEDS = [
317786
317991
  "ORDERBOOK",
317787
317992
  "TICKER",
@@ -317795,15 +318000,12 @@ var SOURCE_MODES = [
317795
318000
  "broker_bootstrap_fetch_v1",
317796
318001
  "external_ccxt_fallback_v1",
317797
318002
  "external_hummingbot_fallback_v1",
317798
- "legacy_migration_v1",
317799
- "historical_vendor_orderbook_v1",
317800
- "vendor_historical_backfill_v1"
318003
+ "legacy_migration_v1"
317801
318004
  ];
317802
318005
  var RAW_CAPTURE_SCOPES = [
317803
318006
  "ccxt_normalized_object",
317804
318007
  "broker_visible_payload",
317805
- "exchange_wire_frame",
317806
- "vendor_normalized_dataset_file"
318008
+ "exchange_wire_frame"
317807
318009
  ];
317808
318010
  var CHECKSUM_FIELDS = new Set([
317809
318011
  "normalized_row_checksum",
@@ -317992,6 +318194,9 @@ function createRawCapture(context2, input) {
317992
318194
  checksumAlgorithm: context2.checksumAlgorithm
317993
318195
  };
317994
318196
  }
318197
+ function projectRawCapturePayload(rawCapture, redactedPayload) {
318198
+ return { ...rawCapture, redactedPayload };
318199
+ }
317995
318200
  function captureCoreFields(context2, rawCapture) {
317996
318201
  assertCaptureContext(context2);
317997
318202
  return {
@@ -318568,14 +318773,20 @@ function resolveConservativeOrderBookAcquisitionProfile(input) {
318568
318773
  id: `${exchange}:conservative:default`,
318569
318774
  upstreamLimit: undefined,
318570
318775
  guaranteedRetainedDepth: undefined,
318571
- coalescingSupported: false
318776
+ coalescingSupported: false,
318777
+ bidExhaustionEvidence: false,
318778
+ askExhaustionEvidence: false,
318779
+ exhaustionEvidenceValidated: true
318572
318780
  };
318573
318781
  }
318574
318782
  return {
318575
318783
  id: `${exchange}:conservative:limit:${input.requestedDepth}`,
318576
318784
  upstreamLimit: input.requestedDepth,
318577
318785
  guaranteedRetainedDepth: input.requestedDepth,
318578
- coalescingSupported: false
318786
+ coalescingSupported: false,
318787
+ bidExhaustionEvidence: false,
318788
+ askExhaustionEvidence: false,
318789
+ exhaustionEvidenceValidated: true
318579
318790
  };
318580
318791
  }
318581
318792
  function resolveOrderBookAcquisitionProfile(input) {
@@ -318587,7 +318798,10 @@ function resolveOrderBookAcquisitionProfile(input) {
318587
318798
  id: candidateProfileId,
318588
318799
  upstreamLimit: VERIFIED_PROFILE_DEPTH,
318589
318800
  guaranteedRetainedDepth: VERIFIED_PROFILE_DEPTH,
318590
- coalescingSupported: true
318801
+ coalescingSupported: true,
318802
+ bidExhaustionEvidence: false,
318803
+ askExhaustionEvidence: false,
318804
+ exhaustionEvidenceValidated: true
318591
318805
  };
318592
318806
  }
318593
318807
  return resolveConservativeOrderBookAcquisitionProfile(input);
@@ -318737,7 +318951,63 @@ class PublicFeedSubscriberBuffer {
318737
318951
  this.#bytes = 0;
318738
318952
  }
318739
318953
  }
318954
+ // src/helpers/market-data-archive/orderbook-depth.ts
318955
+ var DEFAULT_ORDERBOOK_ARCHIVE_DEPTH_LIMIT = 25;
318956
+ var MAX_ORDERBOOK_ARCHIVE_DEPTH_LIMIT = 500;
318957
+ var MAX_ORDERBOOK_MEASUREMENT_BANDS = 64;
318958
+ var MAX_ORDERBOOK_MEASUREMENT_BAND_BPS = 1e4;
318959
+ var DEFAULT_ORDERBOOK_MEASUREMENT_BANDS_BPS = [10, 25, 50, 100];
318960
+ function parseBoundedDepth(raw, field) {
318961
+ if (!/^[1-9]\d*$/.test(raw)) {
318962
+ throw new Error(`${field} must be an integer between 1 and 500`);
318963
+ }
318964
+ const parsed = Number(raw);
318965
+ if (!Number.isSafeInteger(parsed) || parsed > MAX_ORDERBOOK_ARCHIVE_DEPTH_LIMIT) {
318966
+ throw new Error(`${field} must be an integer between 1 and 500`);
318967
+ }
318968
+ return parsed;
318969
+ }
318970
+ function getOrderbookArchiveDepthLimit() {
318971
+ const raw = process.env.CEX_BROKER_ORDERBOOK_ARCHIVE_DEPTH_LIMIT?.trim();
318972
+ if (!raw) {
318973
+ return DEFAULT_ORDERBOOK_ARCHIVE_DEPTH_LIMIT;
318974
+ }
318975
+ return parseBoundedDepth(raw, "CEX_BROKER_ORDERBOOK_ARCHIVE_DEPTH_LIMIT");
318976
+ }
318977
+ function normalizeOrderbookMeasurementBandsBps(input) {
318978
+ const bands = input ?? DEFAULT_ORDERBOOK_MEASUREMENT_BANDS_BPS;
318979
+ if (bands.length === 0) {
318980
+ throw new Error("ORDERBOOK measurement bands must not be empty");
318981
+ }
318982
+ if (bands.length > MAX_ORDERBOOK_MEASUREMENT_BANDS) {
318983
+ throw new Error(`ORDERBOOK measurement bands must contain at most ${MAX_ORDERBOOK_MEASUREMENT_BANDS} entries`);
318984
+ }
318985
+ for (const band of bands) {
318986
+ if (!Number.isSafeInteger(band) || band <= 0 || band > MAX_ORDERBOOK_MEASUREMENT_BAND_BPS) {
318987
+ throw new Error("ORDERBOOK measurement bands must be integer basis points in 1..10000");
318988
+ }
318989
+ }
318990
+ const normalized = [...new Set(bands)].sort((left, right) => left - right);
318991
+ if (normalized.length > MAX_ORDERBOOK_MEASUREMENT_BANDS) {
318992
+ throw new Error(`ORDERBOOK measurement bands must contain at most ${MAX_ORDERBOOK_MEASUREMENT_BANDS} unique entries`);
318993
+ }
318994
+ return normalized;
318995
+ }
318996
+ function getOrderbookMeasurementBandsBps() {
318997
+ const raw = process.env.CEX_BROKER_ORDERBOOK_MEASUREMENT_BANDS_BPS?.trim();
318998
+ if (!raw) {
318999
+ return [...DEFAULT_ORDERBOOK_MEASUREMENT_BANDS_BPS];
319000
+ }
319001
+ const parts = raw.split(",").map((entry) => entry.trim());
319002
+ if (parts.some((entry) => !/^[1-9]\d*$/.test(entry))) {
319003
+ throw new Error("CEX_BROKER_ORDERBOOK_MEASUREMENT_BANDS_BPS must be a comma-separated list of positive integers");
319004
+ }
319005
+ return normalizeOrderbookMeasurementBandsBps(parts.map(Number));
319006
+ }
319007
+
318740
319008
  // src/helpers/market-data-archive/canonical-orderbook.ts
319009
+ var ORDERBOOK_SUMMARY_SCHEMA_VERSION = "2.0.0";
319010
+
318741
319011
  class OrderBookValidationError extends Error {
318742
319012
  reason;
318743
319013
  constructor(reason) {
@@ -318763,38 +319033,70 @@ function parseSequence(value) {
318763
319033
  }
318764
319034
  return parsed.toString(10);
318765
319035
  }
318766
- function validateSide(side, levels, depthLimit) {
319036
+ function validatePositiveInteger(value, field, maximum = Number.MAX_SAFE_INTEGER) {
319037
+ if (!Number.isSafeInteger(value) || value <= 0 || value > maximum) {
319038
+ throw new OrderBookValidationError(`${field} must be a positive integer${maximum === 500 ? " between 1 and 500" : ""}`);
319039
+ }
319040
+ }
319041
+ function validateSide(side, levels) {
318767
319042
  if (levels.length === 0) {
318768
319043
  throw new OrderBookValidationError(`${side} side is missing`);
318769
319044
  }
318770
- const retained = levels.slice(0, depthLimit);
318771
- for (let index2 = 0;index2 < retained.length; index2 += 1) {
318772
- const entry = retained[index2];
319045
+ const validated = [];
319046
+ for (let index2 = 0;index2 < levels.length; index2 += 1) {
319047
+ const entry = levels[index2];
318773
319048
  const price = entry?.[0];
318774
319049
  const amount = entry?.[1];
318775
319050
  if (!Array.isArray(entry) || entry.length < 2 || price === undefined || amount === undefined || !Number.isFinite(price) || !Number.isFinite(amount) || price <= 0 || amount <= 0) {
318776
319051
  throw new OrderBookValidationError(`${side} level ${index2} has a non-positive or non-finite price/amount`);
318777
319052
  }
318778
319053
  if (index2 > 0) {
318779
- const previous = retained[index2 - 1]?.[0];
319054
+ const previous = validated[index2 - 1]?.price;
318780
319055
  if (previous === undefined || (side === "bid" ? price >= previous : price <= previous)) {
318781
319056
  throw new OrderBookValidationError(`${side} levels are not strictly ${side === "bid" ? "descending" : "ascending"}`);
318782
319057
  }
318783
319058
  }
319059
+ validated.push({ price, amount });
318784
319060
  }
318785
- return retained.map(([price, amount]) => ({
318786
- price,
318787
- amount
318788
- }));
319061
+ return validated;
318789
319062
  }
318790
- function normalizedBands(input) {
318791
- const bands = input ?? [10, 25, 50, 100];
318792
- for (const band of bands) {
318793
- if (!Number.isFinite(band) || band <= 0 || !Number.isInteger(band)) {
318794
- throw new OrderBookValidationError("measurement bands must be positive integer basis points");
319063
+ function validateMetadata(metadata, observedBids, observedAsks) {
319064
+ if (!metadata.captureProfileId.trim()) {
319065
+ throw new OrderBookValidationError("capture_profile_id must not be empty");
319066
+ }
319067
+ validatePositiveInteger(metadata.effectiveCadenceMs, "effective_cadence_ms");
319068
+ if (metadata.requestedUpstreamDepth !== null) {
319069
+ validatePositiveInteger(metadata.requestedUpstreamDepth, "requested_upstream_depth", 500);
319070
+ }
319071
+ validatePositiveInteger(metadata.observedBidCount, "observed_bid_count");
319072
+ validatePositiveInteger(metadata.observedAskCount, "observed_ask_count");
319073
+ if (metadata.observedBidCount !== observedBids.length) {
319074
+ throw new OrderBookValidationError("observed_bid_count does not match the complete observation");
319075
+ }
319076
+ if (metadata.observedAskCount !== observedAsks.length) {
319077
+ throw new OrderBookValidationError("observed_ask_count does not match the complete observation");
319078
+ }
319079
+ const farthestBid = observedBids.at(-1)?.price;
319080
+ const farthestAsk = observedAsks.at(-1)?.price;
319081
+ if (!Number.isFinite(metadata.observedFarthestBid) || metadata.observedFarthestBid <= 0 || metadata.observedFarthestBid !== farthestBid) {
319082
+ throw new OrderBookValidationError("observed_farthest_bid does not match the complete observation");
319083
+ }
319084
+ if (!Number.isFinite(metadata.observedFarthestAsk) || metadata.observedFarthestAsk <= 0 || metadata.observedFarthestAsk !== farthestAsk) {
319085
+ throw new OrderBookValidationError("observed_farthest_ask does not match the complete observation");
319086
+ }
319087
+ for (const [side, evidence] of [
319088
+ ["bid", metadata.exhaustionEvidence.bid],
319089
+ ["ask", metadata.exhaustionEvidence.ask]
319090
+ ]) {
319091
+ if (evidence.validated !== true || typeof evidence.exhausted !== "boolean" || !evidence.source.trim()) {
319092
+ throw new OrderBookValidationError(`${side} exhaustion evidence must be validated and source-bound`);
318795
319093
  }
318796
319094
  }
318797
- return [...new Set(bands)].sort((left, right) => left - right);
319095
+ try {
319096
+ return normalizeOrderbookMeasurementBandsBps(metadata.measurementBandsBps);
319097
+ } catch (error) {
319098
+ throw new OrderBookValidationError(error instanceof Error ? error.message : "invalid measurement bands");
319099
+ }
318798
319100
  }
318799
319101
  function checksumRow(row) {
318800
319102
  return { ...row, normalized_row_checksum: sha256Canonical(row) };
@@ -318805,20 +319107,33 @@ function commonEvidenceFields(context2, rawCapture, input) {
318805
319107
  source_time_ms: input.eventTimeMs,
318806
319108
  received_time_ms: input.receivedTimeMs,
318807
319109
  snapshot_id: input.snapshotId,
318808
- construction_mode: input.constructionMode,
319110
+ construction_mode: "sampled_top_n_snapshot",
318809
319111
  gap_policy: "record_gap",
318810
319112
  depth_limit: input.depthLimit,
318811
319113
  sequence: input.sequence,
318812
319114
  exact_l2_reconstruction_complete: 0
318813
319115
  };
318814
319116
  }
319117
+ function sideBandEvidence(input) {
319118
+ const farthest = input.levels.at(-1)?.price;
319119
+ const depths = input.boundaries.map((boundary) => input.levels.filter(({ price }) => input.side === "bid" ? price >= boundary : price <= boundary).reduce((sum3, { amount }) => sum3 + amount, 0));
319120
+ const statuses = input.boundaries.map((boundary) => {
319121
+ const boundaryReached = input.side === "bid" ? farthest <= boundary : farthest >= boundary;
319122
+ return boundaryReached || input.exhausted ? "exact" : "censored";
319123
+ });
319124
+ return { depths, statuses };
319125
+ }
318815
319126
  function buildCanonicalOrderBookRows(input) {
318816
- if (!Number.isSafeInteger(input.depthLimit) || input.depthLimit <= 0 || input.depthLimit > 500) {
318817
- throw new OrderBookValidationError("depth limit must be an integer between 1 and 500");
318818
- }
319127
+ validatePositiveInteger(input.depthLimit, "depth limit", 500);
318819
319128
  if (input.context.feed !== "ORDERBOOK") {
318820
319129
  throw new OrderBookValidationError("capture context feed is not ORDERBOOK");
318821
319130
  }
319131
+ if (input.context.schemaVersion !== "1.0.0") {
319132
+ throw new OrderBookValidationError("ORDERBOOK raw and level capture schema must be 1.0.0");
319133
+ }
319134
+ if (!input.context.provenanceComplete) {
319135
+ throw new OrderBookValidationError("summary v2 requires complete live provenance");
319136
+ }
318822
319137
  if (input.constructionMode === "exact_l2_reconstruction") {
318823
319138
  throw new OrderBookValidationError("exact L2 requires a complete continuity proof and is unsupported");
318824
319139
  }
@@ -318833,13 +319148,18 @@ function buildCanonicalOrderBookRows(input) {
318833
319148
  if (receivedTimeMs < eventTimeMs) {
318834
319149
  throw new OrderBookValidationError("received timestamp precedes source timestamp");
318835
319150
  }
318836
- const bids = validateSide("bid", input.snapshot.bids, input.depthLimit);
318837
- const asks = validateSide("ask", input.snapshot.asks, input.depthLimit);
318838
- const bestBid = bids[0];
318839
- const bestAsk = asks[0];
319151
+ const observedBids = validateSide("bid", input.snapshot.bids);
319152
+ const observedAsks = validateSide("ask", input.snapshot.asks);
319153
+ const bestBid = observedBids[0];
319154
+ const bestAsk = observedAsks[0];
318840
319155
  if (bestBid.price >= bestAsk.price) {
318841
319156
  throw new OrderBookValidationError("book is crossed or locked");
318842
319157
  }
319158
+ const bands = validateMetadata(input.archiveMetadata, observedBids, observedAsks);
319159
+ const bids = observedBids.slice(0, input.depthLimit);
319160
+ const asks = observedAsks.slice(0, input.depthLimit);
319161
+ const retainedFarthestBid = bids.at(-1);
319162
+ const retainedFarthestAsk = asks.at(-1);
318843
319163
  const sequence = parseSequence(input.snapshot.sequence);
318844
319164
  const midPrice = (bestBid.price + bestAsk.price) / 2;
318845
319165
  const spread2 = bestAsk.price - bestBid.price;
@@ -318859,8 +319179,7 @@ function buildCanonicalOrderBookRows(input) {
318859
319179
  sequence,
318860
319180
  depthLimit: input.depthLimit,
318861
319181
  eventTimeMs,
318862
- receivedTimeMs,
318863
- constructionMode: input.constructionMode ?? "sampled_top_n_snapshot"
319182
+ receivedTimeMs
318864
319183
  });
318865
319184
  const levels = [
318866
319185
  ["bid", bids],
@@ -318878,17 +319197,34 @@ function buildCanonicalOrderBookRows(input) {
318878
319197
  });
318879
319198
  return { table: "market_data.cex_order_book_levels", row };
318880
319199
  }));
318881
- const bands = normalizedBands(input.measurementBandsBps);
318882
- const bidDepth = bands.map((band) => {
318883
- const minimumPrice = bestBid.price * (1 - band / 1e4);
318884
- return bids.filter(({ price }) => price >= minimumPrice).reduce((sum3, { amount }) => sum3 + amount, 0);
319200
+ const bidBoundaries = bands.map((band) => midPrice * (1 - band / 1e4));
319201
+ const askBoundaries = bands.map((band) => midPrice * (1 + band / 1e4));
319202
+ const bidEvidence = sideBandEvidence({
319203
+ levels: observedBids,
319204
+ boundaries: bidBoundaries,
319205
+ side: "bid",
319206
+ exhausted: input.archiveMetadata.exhaustionEvidence.bid.exhausted
318885
319207
  });
318886
- const askDepth = bands.map((band) => {
318887
- const maximumPrice = bestAsk.price * (1 + band / 1e4);
318888
- return asks.filter(({ price }) => price <= maximumPrice).reduce((sum3, { amount }) => sum3 + amount, 0);
319208
+ const askEvidence = sideBandEvidence({
319209
+ levels: observedAsks,
319210
+ boundaries: askBoundaries,
319211
+ side: "ask",
319212
+ exhausted: input.archiveMetadata.exhaustionEvidence.ask.exhausted
318889
319213
  });
318890
319214
  const summaryRow = checksumRow({
318891
319215
  ...common,
319216
+ schema_version: ORDERBOOK_SUMMARY_SCHEMA_VERSION,
319217
+ capture_profile_id: input.archiveMetadata.captureProfileId,
319218
+ effective_cadence_ms: input.archiveMetadata.effectiveCadenceMs,
319219
+ requested_upstream_depth: input.archiveMetadata.requestedUpstreamDepth,
319220
+ observed_bid_count: input.archiveMetadata.observedBidCount,
319221
+ observed_ask_count: input.archiveMetadata.observedAskCount,
319222
+ observed_farthest_bid: input.archiveMetadata.observedFarthestBid,
319223
+ observed_farthest_ask: input.archiveMetadata.observedFarthestAsk,
319224
+ retained_farthest_bid: retainedFarthestBid.price,
319225
+ retained_farthest_ask: retainedFarthestAsk.price,
319226
+ bid_exhausted: input.archiveMetadata.exhaustionEvidence.bid.exhausted ? 1 : 0,
319227
+ ask_exhausted: input.archiveMetadata.exhaustionEvidence.ask.exhausted ? 1 : 0,
318892
319228
  best_bid: bestBid.price,
318893
319229
  best_ask: bestAsk.price,
318894
319230
  best_bid_amount: bestBid.amount,
@@ -318900,8 +319236,12 @@ function buildCanonicalOrderBookRows(input) {
318900
319236
  bid_level_count: bids.length,
318901
319237
  ask_level_count: asks.length,
318902
319238
  measurement_bands_bps: bands,
318903
- bid_depth_by_band: bidDepth,
318904
- ask_depth_by_band: askDepth
319239
+ bid_boundary_price_by_band: bidBoundaries,
319240
+ ask_boundary_price_by_band: askBoundaries,
319241
+ bid_depth_by_band: bidEvidence.depths,
319242
+ ask_depth_by_band: askEvidence.depths,
319243
+ bid_status_by_band: bidEvidence.statuses,
319244
+ ask_status_by_band: askEvidence.statuses
318905
319245
  });
318906
319246
  return {
318907
319247
  snapshotId,
@@ -319031,21 +319371,6 @@ class OhlcvBarTracker {
319031
319371
  }
319032
319372
  }
319033
319373
 
319034
- // src/helpers/market-data-archive/orderbook-depth.ts
319035
- var DEFAULT_ORDERBOOK_ARCHIVE_DEPTH_LIMIT = 25;
319036
- var MAX_ORDERBOOK_ARCHIVE_DEPTH_LIMIT = 500;
319037
- function getOrderbookArchiveDepthLimit() {
319038
- const raw = process.env.CEX_BROKER_ORDERBOOK_ARCHIVE_DEPTH_LIMIT;
319039
- if (!raw) {
319040
- return DEFAULT_ORDERBOOK_ARCHIVE_DEPTH_LIMIT;
319041
- }
319042
- const parsed = Number.parseInt(raw, 10);
319043
- if (!Number.isFinite(parsed) || parsed <= 0) {
319044
- return DEFAULT_ORDERBOOK_ARCHIVE_DEPTH_LIMIT;
319045
- }
319046
- return Math.min(parsed, MAX_ORDERBOOK_ARCHIVE_DEPTH_LIMIT);
319047
- }
319048
-
319049
319374
  // src/helpers/market-data-archive/parse-stream.ts
319050
319375
  function isFiniteNumber2(value) {
319051
319376
  return typeof value === "number" && Number.isFinite(value);
@@ -319169,13 +319494,17 @@ function legacyMarketFields(context2, rawCapture) {
319169
319494
  function legacyDecimal8(value) {
319170
319495
  return value === undefined ? undefined : Number(value.toFixed(8));
319171
319496
  }
319172
- function buildCanonicalCexStreamEventRow(context2, rawCapture) {
319497
+ function buildCanonicalCexStreamEventRow(context2, rawCapture, options) {
319498
+ const payloadEncoding = options?.payloadEncoding ?? "canonical_json_v1";
319499
+ if (payloadEncoding === "orderbook_metadata_only_v1" && context2.feed !== "ORDERBOOK") {
319500
+ throw new Error("Metadata-only raw encoding requires an ORDERBOOK context");
319501
+ }
319173
319502
  const row = withNormalizedChecksum({
319174
319503
  ...captureCoreFields(context2, rawCapture),
319175
319504
  ...legacyMarketFields(context2, rawCapture),
319176
319505
  stream_type: context2.feed,
319177
319506
  event_time_ms: rawCapture.eventTimeMs,
319178
- payload_encoding: "canonical_json_v1",
319507
+ payload_encoding: payloadEncoding,
319179
319508
  payload_json: canonicalSerialize(rawCapture.redactedPayload)
319180
319509
  });
319181
319510
  return { table: "market_data.cex_stream_events", row };
@@ -319319,7 +319648,13 @@ function archiveOrderbookInBackground(archiver, otelMetrics, input, options) {
319319
319648
  queueMicrotask(() => {
319320
319649
  try {
319321
319650
  const context2 = resolveCaptureContext(archiver, input, "ORDERBOOK", options?.sourceMode ?? "broker_live_sampling_v1");
319322
- const rawCapture = createRawCapture(context2, {
319651
+ const depthLimit = options?.depthLimit ?? getOrderbookArchiveDepthLimit();
319652
+ const measurementBandsBps = normalizeOrderbookMeasurementBandsBps(input.archiveMetadata.measurementBandsBps);
319653
+ const archiveMetadata = {
319654
+ ...input.archiveMetadata,
319655
+ measurementBandsBps
319656
+ };
319657
+ const completeRawCapture = createRawCapture(context2, {
319323
319658
  payload: input.snapshot,
319324
319659
  eventTimeMs: input.snapshot.timestamp,
319325
319660
  receivedTimeMs: input.snapshot.receivedTimestamp,
@@ -319328,10 +319663,29 @@ function archiveOrderbookInBackground(archiver, otelMetrics, input, options) {
319328
319663
  const canonical = buildCanonicalOrderBookRows({
319329
319664
  context: context2,
319330
319665
  snapshot: input.snapshot,
319331
- rawCapture,
319332
- depthLimit: options?.depthLimit ?? getOrderbookArchiveDepthLimit()
319666
+ rawCapture: completeRawCapture,
319667
+ depthLimit,
319668
+ archiveMetadata
319333
319669
  });
319334
- archiver.enqueue(buildCanonicalCexStreamEventRow(context2, rawCapture));
319670
+ const rawMetadata = {
319671
+ capture_profile_id: archiveMetadata.captureProfileId,
319672
+ effective_cadence_ms: archiveMetadata.effectiveCadenceMs,
319673
+ requested_upstream_depth: archiveMetadata.requestedUpstreamDepth,
319674
+ archive_depth_limit: depthLimit,
319675
+ observed_bid_count: archiveMetadata.observedBidCount,
319676
+ observed_ask_count: archiveMetadata.observedAskCount,
319677
+ observed_farthest_bid: canonicalDecimal(archiveMetadata.observedFarthestBid),
319678
+ observed_farthest_ask: canonicalDecimal(archiveMetadata.observedFarthestAsk),
319679
+ bid_exhausted: archiveMetadata.exhaustionEvidence.bid.exhausted,
319680
+ ask_exhausted: archiveMetadata.exhaustionEvidence.ask.exhausted,
319681
+ retained_bid_count: canonical.levels.filter(({ row }) => row.side === "bid").length,
319682
+ retained_ask_count: canonical.levels.filter(({ row }) => row.side === "ask").length,
319683
+ measurement_bands_bps: measurementBandsBps
319684
+ };
319685
+ const storedRawCapture = projectRawCapturePayload(completeRawCapture, rawMetadata);
319686
+ archiver.enqueue(buildCanonicalCexStreamEventRow(context2, storedRawCapture, {
319687
+ payloadEncoding: "orderbook_metadata_only_v1"
319688
+ }));
319335
319689
  for (const row of canonical.levels)
319336
319690
  archiver.enqueue(row);
319337
319691
  archiver.enqueue(canonical.summary);
@@ -319453,6 +319807,9 @@ function archiveTickerInBackground(archiver, otelMetrics, input) {
319453
319807
  });
319454
319808
  }
319455
319809
  function archiveCexStreamEventInBackground(archiver, otelMetrics, input) {
319810
+ if (input.streamType === "ORDERBOOK") {
319811
+ throw new Error("Generic ORDERBOOK archive events are unsupported; use archiveOrderbookInBackground");
319812
+ }
319456
319813
  archiveMarketRowsInBackground(archiver, otelMetrics, "stream", input, input.streamType, () => [
319457
319814
  buildCexStreamEventRow({
319458
319815
  ...input,
@@ -319477,6 +319834,82 @@ function resolveOhlcvBootstrapLimit(optionValue) {
319477
319834
  }
319478
319835
  return Math.min(parsed, MAX_OHLCV_BOOTSTRAP_LIMIT);
319479
319836
  }
319837
+ // src/helpers/market-data-archive/summary-v2-conformance.ts
319838
+ var ORDERBOOK_SUMMARY_V2_SUPPORTED_VIEW_FIELDS = [
319839
+ ["source", "String", false],
319840
+ ["deployment_id", "String", false],
319841
+ ["capture_bundle_id", "String", false],
319842
+ ["exchange", "String", false],
319843
+ ["symbol", "String", false],
319844
+ ["trading_pair", "String", false],
319845
+ ["source_symbol", "String", false],
319846
+ ["asset_type", "String", false],
319847
+ ["feed", "String", false],
319848
+ ["provider", "String", false],
319849
+ ["source_mode", "String", false],
319850
+ ["source_time_ms", "UInt64", false],
319851
+ ["received_time_ms", "UInt64", false],
319852
+ ["raw_capture_id", "String", false],
319853
+ ["raw_capture_scope", "String", false],
319854
+ ["schema_version", "String", false],
319855
+ ["checksum_algorithm", "String", false],
319856
+ ["raw_checksum", "String", false],
319857
+ ["provenance_complete", "UInt8", false],
319858
+ ["snapshot_id", "String", false],
319859
+ ["construction_mode", "String", false],
319860
+ ["gap_policy", "String", false],
319861
+ ["depth_limit", "UInt16", false],
319862
+ ["sequence", "UInt64", true],
319863
+ ["exact_l2_reconstruction_complete", "UInt8", false],
319864
+ ["capture_profile_id", "String", false],
319865
+ ["effective_cadence_ms", "UInt32", false],
319866
+ ["requested_upstream_depth", "UInt16", true],
319867
+ ["observed_bid_count", "UInt32", false],
319868
+ ["observed_ask_count", "UInt32", false],
319869
+ ["observed_farthest_bid", "Decimal(38,18)", false],
319870
+ ["observed_farthest_ask", "Decimal(38,18)", false],
319871
+ ["retained_farthest_bid", "Decimal(38,18)", false],
319872
+ ["retained_farthest_ask", "Decimal(38,18)", false],
319873
+ ["bid_exhausted", "UInt8", false],
319874
+ ["ask_exhausted", "UInt8", false],
319875
+ ["best_bid", "Decimal(38,18)", false],
319876
+ ["best_ask", "Decimal(38,18)", false],
319877
+ ["best_bid_amount", "Decimal(38,18)", false],
319878
+ ["best_ask_amount", "Decimal(38,18)", false],
319879
+ ["mid_price", "Decimal(38,18)", false],
319880
+ ["spread", "Decimal(38,18)", false],
319881
+ ["spread_bps", "Float64", false],
319882
+ ["staleness_ms", "UInt64", false],
319883
+ ["bid_level_count", "UInt16", false],
319884
+ ["ask_level_count", "UInt16", false],
319885
+ ["measurement_bands_bps", "Array(UInt32)", false],
319886
+ ["bid_boundary_price_by_band", "Array(Decimal(38,18))", false],
319887
+ ["ask_boundary_price_by_band", "Array(Decimal(38,18))", false],
319888
+ ["bid_depth_by_band", "Array(Decimal(38,18))", false],
319889
+ ["ask_depth_by_band", "Array(Decimal(38,18))", false],
319890
+ ["bid_status_by_band", "Array(Enum8('exact'=1,'censored'=2))", false],
319891
+ ["ask_status_by_band", "Array(Enum8('exact'=1,'censored'=2))", false],
319892
+ ["normalized_row_checksum", "String", false]
319893
+ ];
319894
+ var ORDERBOOK_SUMMARY_V2_SUPPORTED_VIEW_FIELD_NAMES = ORDERBOOK_SUMMARY_V2_SUPPORTED_VIEW_FIELDS.map(([name]) => name);
319895
+ var DECIMAL_38_18_FIELDS = new Set([
319896
+ "observed_farthest_bid",
319897
+ "observed_farthest_ask",
319898
+ "retained_farthest_bid",
319899
+ "retained_farthest_ask",
319900
+ "best_bid",
319901
+ "best_ask",
319902
+ "best_bid_amount",
319903
+ "best_ask_amount",
319904
+ "mid_price",
319905
+ "spread"
319906
+ ]);
319907
+ var DECIMAL_38_18_ARRAY_FIELDS = new Set([
319908
+ "bid_boundary_price_by_band",
319909
+ "ask_boundary_price_by_band",
319910
+ "bid_depth_by_band",
319911
+ "ask_depth_by_band"
319912
+ ]);
319480
319913
  // src/helpers/order-book.ts
319481
319914
  var ORDER_BOOK_CALL_METHODS = {
319482
319915
  FETCH_CAPABILITY: "fetch_order_book_capability",
@@ -319828,7 +320261,7 @@ class PublicFeedWorker {
319828
320261
  if (this.options.profile) {
319829
320262
  this.options.metrics?.recordCounter("public_feed_acquisition_profiles_total", 1, {
319830
320263
  ...this.#metricLabels(),
319831
- profile: this.options.profile.id
320264
+ profile_class: this.options.profile.coalescingSupported ? "coalesced" : "conservative"
319832
320265
  });
319833
320266
  }
319834
320267
  this.#run = this.#watchLoop();
@@ -319896,7 +320329,34 @@ class PublicFeedWorker {
319896
320329
  depthLimit: retainedDepth,
319897
320330
  receivedTimestamp
319898
320331
  });
319899
- this.options.archiveSink.orderbook({ ...context2, snapshot }, { sampledOut: !this.#sampler.shouldEmit(receivedTimestamp) });
320332
+ const observedBidCount = snapshot.bids.length;
320333
+ const observedAskCount = snapshot.asks.length;
320334
+ this.options.archiveSink.orderbook({
320335
+ ...context2,
320336
+ snapshot,
320337
+ archiveMetadata: {
320338
+ captureProfileId: this.options.profile?.id ?? `${this.options.exchangeName}:conservative:default`,
320339
+ effectiveCadenceMs: getOrderbookIntervalMs(),
320340
+ requestedUpstreamDepth: this.options.profile?.upstreamLimit ?? null,
320341
+ observedBidCount,
320342
+ observedAskCount,
320343
+ observedFarthestBid: snapshot.bids.at(-1)?.[0] ?? Number.NaN,
320344
+ observedFarthestAsk: snapshot.asks.at(-1)?.[0] ?? Number.NaN,
320345
+ exhaustionEvidence: {
320346
+ bid: {
320347
+ exhausted: this.options.profile?.bidExhaustionEvidence ?? false,
320348
+ validated: this.options.profile?.exhaustionEvidenceValidated ?? true,
320349
+ source: this.options.profile?.id ?? "broker:conservative"
320350
+ },
320351
+ ask: {
320352
+ exhausted: this.options.profile?.askExhaustionEvidence ?? false,
320353
+ validated: this.options.profile?.exhaustionEvidenceValidated ?? true,
320354
+ source: this.options.profile?.id ?? "broker:conservative"
320355
+ }
320356
+ },
320357
+ measurementBandsBps: getOrderbookMeasurementBandsBps()
320358
+ }
320359
+ }, { sampledOut: !this.#sampler.shouldEmit(receivedTimestamp) });
319900
320360
  this.#recordCounter("public_feed_archive_decisions_total");
319901
320361
  this.options.observer?.archiveDecision?.(this.#observation());
319902
320362
  this.#fanout((subscriber) => this.#frame(projectOrderBookSnapshot(snapshot, subscriber.depthLimit), receivedTimestamp));
@@ -320069,7 +320529,7 @@ class PublicFeedWorker {
320069
320529
  }
320070
320530
  #metricLabels() {
320071
320531
  return {
320072
- exchange: this.options.exchangeName,
320532
+ exchange_bucket: this.options.exchangeOwned ? "dynamic" : "configured",
320073
320533
  feed: this.options.feed,
320074
320534
  market_type: this.options.marketType
320075
320535
  };
@@ -320257,7 +320717,8 @@ import { request as httpsRequest3 } from "node:https";
320257
320717
  import { dirname as dirname2 } from "node:path";
320258
320718
  var SOURCE = "broker_write";
320259
320719
  var TABLE = "broker_stream_health.snapshots";
320260
- var PRODUCER_ID = "cex-broker-user-data";
320720
+ var USER_DATA_STREAM_HEALTH_PRODUCER_ID = "cex-broker-user-data";
320721
+ var DEPOSIT_POLLER_STREAM_HEALTH_PRODUCER_ID = "cex-broker-deposit-poller";
320261
320722
  var STATE_VERSION = 1;
320262
320723
  var HEARTBEAT_MS = 30000;
320263
320724
  var FORWARDER_TIMEOUT_MS = 3000;
@@ -320292,11 +320753,11 @@ function registryRevision(snapshots) {
320292
320753
  })).sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
320293
320754
  return createHash5("sha256").update(JSON.stringify(rows)).digest("hex");
320294
320755
  }
320295
- function validState(value) {
320756
+ function validState(value, producerId) {
320296
320757
  if (!value || typeof value !== "object" || Array.isArray(value))
320297
320758
  return false;
320298
320759
  const state = value;
320299
- 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");
320760
+ 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");
320300
320761
  }
320301
320762
  function forwarderPost(url2, body, authToken, timeoutMs) {
320302
320763
  const request = url2.protocol === "http:" ? httpRequest3 : httpsRequest3;
@@ -320326,6 +320787,7 @@ function forwarderPost(url2, body, authToken, timeoutMs) {
320326
320787
  }
320327
320788
 
320328
320789
  class StreamHealthPublisher {
320790
+ #producerId;
320329
320791
  #deploymentId;
320330
320792
  #statePath;
320331
320793
  #heartbeatMs;
@@ -320340,6 +320802,7 @@ class StreamHealthPublisher {
320340
320802
  #retry = null;
320341
320803
  #retryAttempt = 0;
320342
320804
  constructor(options) {
320805
+ this.#producerId = identifier(options.producerId, "producer_id");
320343
320806
  this.#deploymentId = identifier(options.deploymentId, "deployment_id");
320344
320807
  this.#statePath = options.statePath.trim();
320345
320808
  if (!this.#statePath) {
@@ -320364,7 +320827,7 @@ class StreamHealthPublisher {
320364
320827
  const loaded = this.#read();
320365
320828
  this.#state = loaded ?? {
320366
320829
  version: STATE_VERSION,
320367
- producerId: PRODUCER_ID,
320830
+ producerId: this.#producerId,
320368
320831
  producerEpoch: "1",
320369
320832
  runId: randomUUID3(),
320370
320833
  nextBatchSequence: "1",
@@ -320477,7 +320940,7 @@ class StreamHealthPublisher {
320477
320940
  return {
320478
320941
  table: TABLE,
320479
320942
  row: {
320480
- producer_id: PRODUCER_ID,
320943
+ producer_id: this.#producerId,
320481
320944
  producer_epoch: this.#state.producerEpoch,
320482
320945
  run_id: this.#state.runId,
320483
320946
  batch_sequence: batchSequence,
@@ -320504,7 +320967,8 @@ class StreamHealthPublisher {
320504
320967
  last_failure_kind: snapshot.lastFailureKind,
320505
320968
  last_failure_reason: snapshot.lastFailureReason,
320506
320969
  traffic_mode: snapshot.trafficMode,
320507
- source_watermark: snapshot.sourceWatermark
320970
+ source_watermark: snapshot.sourceWatermark,
320971
+ ...snapshot.streamKind === "deposit_poller" ? { poll_observation: snapshot.pollObservation } : {}
320508
320972
  }
320509
320973
  };
320510
320974
  });
@@ -320532,7 +320996,7 @@ class StreamHealthPublisher {
320532
320996
  #read() {
320533
320997
  try {
320534
320998
  const parsed = JSON.parse(readFileSync2(this.#statePath, "utf8"));
320535
- if (!validState(parsed))
320999
+ if (!validState(parsed, this.#producerId))
320536
321000
  throw new Error("invalid state shape");
320537
321001
  return parsed;
320538
321002
  } catch (error) {
@@ -320595,6 +321059,14 @@ function streamHealthPublisherConfigFromEnv(env = process.env) {
320595
321059
  forwarderAuthToken: env.CEX_BROKER_ARCHIVE_FORWARDER_TOKEN?.trim() || undefined
320596
321060
  };
320597
321061
  }
321062
+ function depositPollerStreamHealthPublisherConfigFromEnv(env = process.env) {
321063
+ const base2 = streamHealthPublisherConfigFromEnv(env);
321064
+ return {
321065
+ ...base2,
321066
+ producerId: DEPOSIT_POLLER_STREAM_HEALTH_PRODUCER_ID,
321067
+ statePath: `${base2.statePath}.deposit-poller`
321068
+ };
321069
+ }
320598
321070
 
320599
321071
  // src/helpers/user-asset-archive-poller.ts
320600
321072
  var DEFAULT_CONFIG4 = {
@@ -321165,7 +321637,7 @@ class AccountWorker {
321165
321637
  for (const subscriber of [...this.#subscribers])
321166
321638
  subscriber.close();
321167
321639
  }
321168
- #transition(state, failureKind, failureReason) {
321640
+ #transition(state, failureKind, failureReason2) {
321169
321641
  const timestamp = now3();
321170
321642
  if (this.#snapshot.state !== state) {
321171
321643
  this.#snapshot.state = state;
@@ -321173,7 +321645,7 @@ class AccountWorker {
321173
321645
  }
321174
321646
  if (failureKind) {
321175
321647
  this.#snapshot.lastFailureKind = failureKind;
321176
- this.#snapshot.lastFailureReason = failureReason ?? "";
321648
+ this.#snapshot.lastFailureReason = failureReason2 ?? "";
321177
321649
  }
321178
321650
  this.onChange();
321179
321651
  }
@@ -321297,7 +321769,7 @@ class UserDataStreamSupervisor {
321297
321769
  }
321298
321770
 
321299
321771
  // src/server.ts
321300
- var grpc14 = __toESM(require_src3(), 1);
321772
+ var grpc16 = __toESM(require_src3(), 1);
321301
321773
 
321302
321774
  // src/handlers/execute-action/deposit.ts
321303
321775
  var grpc3 = __toESM(require_src3(), 1);
@@ -334949,7 +335421,10 @@ config(en_default());
334949
335421
  // src/schemas/action-payloads.ts
334950
335422
  var parseJsonString = (value) => {
334951
335423
  if (typeof value !== "string") {
334952
- return value;
335424
+ if (value === null || value === undefined || typeof value === "number" || typeof value === "boolean" || typeof value === "object") {
335425
+ return value;
335426
+ }
335427
+ return String(value);
334953
335428
  }
334954
335429
  try {
334955
335430
  return JSON.parse(value);
@@ -334958,19 +335433,6 @@ var parseJsonString = (value) => {
334958
335433
  }
334959
335434
  };
334960
335435
  var stringNumberRecordSchema = exports_external.record(exports_external.string(), exports_external.union([exports_external.string(), exports_external.number()]));
334961
- var booleanLikeSchema = exports_external.preprocess((value) => {
334962
- if (typeof value !== "string") {
334963
- return value;
334964
- }
334965
- const normalized = value.trim().toLowerCase();
334966
- if (["true", "1", "yes"].includes(normalized)) {
334967
- return true;
334968
- }
334969
- if (["false", "0", "no"].includes(normalized)) {
334970
- return false;
334971
- }
334972
- return value;
334973
- }, exports_external.boolean());
334974
335436
  var DepositPayloadSchema = exports_external.object({
334975
335437
  recipientAddress: exports_external.string().min(1),
334976
335438
  amount: exports_external.coerce.number().positive(),
@@ -335031,10 +335493,22 @@ var CancelOrderPayloadSchema = exports_external.object({
335031
335493
  orderId: exports_external.string().min(1),
335032
335494
  params: exports_external.preprocess(parseJsonString, stringNumberRecordSchema).default({})
335033
335495
  });
335034
- var FetchFeesPayloadSchema = exports_external.object({
335035
- includeAllFees: booleanLikeSchema.optional().default(false),
335036
- includeFundingFees: booleanLikeSchema.optional()
335037
- });
335496
+ var EmptyActionPayloadSchema = exports_external.object({}).strict();
335497
+ var FetchFeesPayloadSchema = EmptyActionPayloadSchema;
335498
+ var FetchCurrencyPayloadSchema = exports_external.object({
335499
+ network: exports_external.string().trim().min(1)
335500
+ }).strict();
335501
+ var MAX_BATCH_CHILDREN = 32;
335502
+ var MAX_BATCH_REQUEST_BYTES = 256 * 1024;
335503
+ var BatchChildRequestSchema = exports_external.object({
335504
+ id: exports_external.string().trim().min(1),
335505
+ action: exports_external.number().int().nonnegative(),
335506
+ symbol: exports_external.string(),
335507
+ payload: exports_external.record(exports_external.string(), exports_external.string())
335508
+ }).strict();
335509
+ var BatchPayloadSchema = exports_external.object({
335510
+ requests: exports_external.preprocess(parseJsonString, exports_external.array(BatchChildRequestSchema).min(1).max(MAX_BATCH_CHILDREN))
335511
+ }).strict();
335038
335512
 
335039
335513
  // src/helpers/grpc/callbacks.ts
335040
335514
  var grpc = __toESM(require_src3(), 1);
@@ -335083,7 +335557,7 @@ function stableGrpcErrorCode(message) {
335083
335557
  if (message.startsWith("AuthenticationError:")) {
335084
335558
  return grpc2.status.UNAUTHENTICATED;
335085
335559
  }
335086
- if (message.startsWith("InsufficientFunds:")) {
335560
+ if (message.startsWith("InsufficientFunds:") || message.startsWith("fee_unavailable:")) {
335087
335561
  return grpc2.status.FAILED_PRECONDITION;
335088
335562
  }
335089
335563
  if (message.startsWith("venue_discovery_unavailable:")) {
@@ -335143,6 +335617,13 @@ function resolveGrpcError(error48, message) {
335143
335617
  }
335144
335618
 
335145
335619
  // src/handlers/execute-action/context.ts
335620
+ function requireSymbol(ctx, message = "ValidationError: Symbol required") {
335621
+ if (!ctx.symbol) {
335622
+ ctx.wrappedCallback(invalidArgumentError(message), null);
335623
+ return false;
335624
+ }
335625
+ return true;
335626
+ }
335146
335627
  function parsePayloadForAction(ctx, schema) {
335147
335628
  return parseActionPayload(schema, ctx.call.request.payload, ctx.wrappedCallback);
335148
335629
  }
@@ -335165,6 +335646,12 @@ function rejectWithGrpcError(ctx, error48, options) {
335165
335646
  }
335166
335647
  ctx.wrappedCallback({ code, message: finalMessage }, null);
335167
335648
  }
335649
+ function successWithProof(ctx, result) {
335650
+ ctx.wrappedCallback(null, {
335651
+ proof: ctx.verity.proof,
335652
+ result: JSON.stringify(result)
335653
+ });
335654
+ }
335168
335655
 
335169
335656
  // src/handlers/execute-action/deposit.ts
335170
335657
  async function handleDeposit(ctx) {
@@ -335342,7 +335829,7 @@ async function handleDeposit(ctx) {
335342
335829
  }
335343
335830
  }
335344
335831
  // src/handlers/execute-action/handler.ts
335345
- var grpc12 = __toESM(require_src3(), 1);
335832
+ var grpc14 = __toESM(require_src3(), 1);
335346
335833
 
335347
335834
  // src/helpers/grpc/broker.ts
335348
335835
  function selectBrokerAccountForCex(normalizedCex, brokers, metadata) {
@@ -335436,7 +335923,29 @@ async function handleOrderBookCall(ctx) {
335436
335923
  assetType: "spot",
335437
335924
  accountSelector: ctx.selectedBrokerAccount?.label,
335438
335925
  deploymentId: ctx.brokerArchiver?.getDeploymentId() ?? "unarchived",
335439
- snapshot
335926
+ snapshot,
335927
+ archiveMetadata: {
335928
+ captureProfileId: `${orderBookPayload.exchange.trim().toLowerCase()}:current-snapshot:requested:${orderBookPayload.depthLimit}`,
335929
+ effectiveCadenceMs: 1,
335930
+ requestedUpstreamDepth: orderBookPayload.depthLimit,
335931
+ observedBidCount: snapshot.bids.length,
335932
+ observedAskCount: snapshot.asks.length,
335933
+ observedFarthestBid: snapshot.bids.at(-1)?.[0] ?? Number.NaN,
335934
+ observedFarthestAsk: snapshot.asks.at(-1)?.[0] ?? Number.NaN,
335935
+ exhaustionEvidence: {
335936
+ bid: {
335937
+ exhausted: false,
335938
+ validated: true,
335939
+ source: "broker:current-snapshot"
335940
+ },
335941
+ ask: {
335942
+ exhausted: false,
335943
+ validated: true,
335944
+ source: "broker:current-snapshot"
335945
+ }
335946
+ },
335947
+ measurementBandsBps: getOrderbookMeasurementBandsBps()
335948
+ }
335440
335949
  }, {
335441
335950
  sourceMode: "broker_current_snapshot_v1",
335442
335951
  depthLimit: orderBookPayload.depthLimit
@@ -335452,10 +335961,458 @@ async function handleOrderBookCall(ctx) {
335452
335961
  }
335453
335962
 
335454
335963
  // src/handlers/execute-action/registry.ts
335455
- var grpc11 = __toESM(require_src3(), 1);
335964
+ var grpc13 = __toESM(require_src3(), 1);
335456
335965
 
335457
- // src/handlers/execute-action/internal-transfer.ts
335966
+ // src/handlers/execute-action/batch.ts
335458
335967
  var grpc5 = __toESM(require_src3(), 1);
335968
+
335969
+ // src/helpers/venue-evidence.ts
335970
+ var DECIMAL_PATTERN = /^\+?(\d+)(?:\.(\d*))?(?:[eE]([+-]?\d+))?$/;
335971
+ function exchangeSecretLiterals(broker) {
335972
+ const record2 = broker;
335973
+ return [
335974
+ record2.apiKey,
335975
+ record2.secret,
335976
+ record2.password,
335977
+ record2.privateKey
335978
+ ].filter((value) => typeof value === "string" && value.length > 0);
335979
+ }
335980
+ function sanitizeVenueError(error48, broker) {
335981
+ return redactSecretLiterals(sanitizeErrorDetail(error48), exchangeSecretLiterals(broker));
335982
+ }
335983
+ function resolveEvidenceAccountScope(selectedBrokerAccount, metadata) {
335984
+ return {
335985
+ accountSelector: selectedBrokerAccount?.label ?? getCurrentBrokerSelector(metadata),
335986
+ credentialSource: selectedBrokerAccount ? "configured_pool" : "request_metadata"
335987
+ };
335988
+ }
335989
+ function canonicalNonnegativeDecimal(value, field) {
335990
+ const text = typeof value === "number" ? Number.isFinite(value) ? String(value) : "" : typeof value === "string" ? value.trim() : "";
335991
+ const match = text.match(DECIMAL_PATTERN);
335992
+ if (!match) {
335993
+ throw new Error(`venue_discovery_unavailable: ${field} must be decimal`);
335994
+ }
335995
+ const integer2 = match[1] ?? "0";
335996
+ const fraction = match[2] ?? "";
335997
+ const exponent = Number.parseInt(match[3] ?? "0", 10);
335998
+ if (!Number.isSafeInteger(exponent)) {
335999
+ throw new Error(`venue_discovery_unavailable: ${field} exponent is invalid`);
336000
+ }
336001
+ const digits = `${integer2}${fraction}`;
336002
+ const decimalIndex = integer2.length + exponent;
336003
+ let rendered;
336004
+ if (decimalIndex <= 0) {
336005
+ rendered = `0.${"0".repeat(-decimalIndex)}${digits}`;
336006
+ } else if (decimalIndex >= digits.length) {
336007
+ rendered = `${digits}${"0".repeat(decimalIndex - digits.length)}`;
336008
+ } else {
336009
+ rendered = `${digits.slice(0, decimalIndex)}.${digits.slice(decimalIndex)}`;
336010
+ }
336011
+ const [whole = "0", decimals = ""] = rendered.split(".");
336012
+ const normalizedWhole = whole.replace(/^0+(?=\d)/, "") || "0";
336013
+ const normalizedDecimals = decimals.replace(/0+$/, "");
336014
+ return normalizedDecimals ? `${normalizedWhole}.${normalizedDecimals}` : normalizedWhole;
336015
+ }
336016
+ function decimalFractionToBasisPoints(value) {
336017
+ const canonical = canonicalNonnegativeDecimal(value, "fee rate");
336018
+ return canonicalNonnegativeDecimal(`${canonical}e4`, "fee basis points");
336019
+ }
336020
+ function canonicalOptionalDecimal(value, field) {
336021
+ return value === undefined || value === null ? undefined : canonicalNonnegativeDecimal(value, field);
336022
+ }
336023
+ function precisionIncrement(value, precisionMode, field) {
336024
+ if ((precisionMode === 2 || precisionMode === "DECIMAL_PLACES") && (typeof value === "number" || typeof value === "string")) {
336025
+ const decimalPlaces = Number(value);
336026
+ if (Number.isInteger(decimalPlaces) && decimalPlaces >= 0) {
336027
+ return canonicalNonnegativeDecimal(`1e-${decimalPlaces}`, field);
336028
+ }
336029
+ }
336030
+ return canonicalNonnegativeDecimal(value, field);
336031
+ }
336032
+ function evidenceSourceDigest(input) {
336033
+ const source = removeSecretMaterial(input.source, exchangeSecretLiterals(input.broker));
336034
+ return sha256Canonical({
336035
+ action: input.action,
336036
+ exchange: input.exchange,
336037
+ requestedKey: input.requestedKey,
336038
+ accountSelector: input.accountSelector,
336039
+ sourceMethod: input.sourceMethod,
336040
+ source
336041
+ });
336042
+ }
336043
+ async function resolveSpotMarketIdentity(broker, symbol2) {
336044
+ const requestedSymbol = symbol2.trim().toUpperCase();
336045
+ if (!/^[^/\s]+\/[^/\s]+$/.test(requestedSymbol)) {
336046
+ throw new Error("venue_discovery_unavailable: symbol must be a slash-delimited spot pair");
336047
+ }
336048
+ await broker.loadMarkets();
336049
+ const market = broker.market(requestedSymbol);
336050
+ if (!isRecord(market)) {
336051
+ throw new Error(`venue_discovery_unavailable: market not found for ${requestedSymbol}`);
336052
+ }
336053
+ const unifiedSymbol = typeof market.symbol === "string" ? market.symbol.trim().toUpperCase() : requestedSymbol;
336054
+ const baseAsset = typeof market.base === "string" ? market.base.trim().toUpperCase() : "";
336055
+ const quoteAsset = typeof market.quote === "string" ? market.quote.trim().toUpperCase() : "";
336056
+ const sourceSymbol = typeof market.id === "string" ? market.id.trim() : "";
336057
+ const isSpot = market.spot === true || market.type === "spot";
336058
+ if (unifiedSymbol !== requestedSymbol || !baseAsset || !quoteAsset || !sourceSymbol || !isSpot || market.active !== true) {
336059
+ throw new Error(`venue_discovery_unavailable: active spot market identity unavailable for ${requestedSymbol}`);
336060
+ }
336061
+ return {
336062
+ market,
336063
+ canonicalPair: `${baseAsset}-${quoteAsset}`,
336064
+ unifiedSymbol,
336065
+ sourceSymbol,
336066
+ baseAsset,
336067
+ quoteAsset
336068
+ };
336069
+ }
336070
+ function extractTradingFeeRates(response) {
336071
+ if (!isRecord(response)) {
336072
+ throw new Error("fee_unavailable: trading-fee response is not an object");
336073
+ }
336074
+ const info = isRecord(response.info) ? response.info : undefined;
336075
+ const nestedData = isRecord(info?.data) ? info.data : isRecord(response.data) ? response.data : undefined;
336076
+ const maker = [
336077
+ response.maker,
336078
+ response.makerCommission,
336079
+ nestedData?.maker,
336080
+ nestedData?.makerCommission
336081
+ ].find((value) => value !== undefined && value !== null);
336082
+ const taker = [
336083
+ response.taker,
336084
+ response.takerCommission,
336085
+ nestedData?.taker,
336086
+ nestedData?.takerCommission
336087
+ ].find((value) => value !== undefined && value !== null);
336088
+ try {
336089
+ return {
336090
+ makerRate: canonicalNonnegativeDecimal(maker, "maker commission"),
336091
+ takerRate: canonicalNonnegativeDecimal(taker, "taker commission")
336092
+ };
336093
+ } catch (error48) {
336094
+ throw new Error(`fee_unavailable: ${sanitizeErrorDetail(error48)}`, {
336095
+ cause: error48
336096
+ });
336097
+ }
336098
+ }
336099
+ function evidenceExchange(broker) {
336100
+ return broker;
336101
+ }
336102
+
336103
+ // src/schemas/action-evidence.ts
336104
+ var EVIDENCE_DIGEST_ALGORITHM = "sha256-canonical-json-v1";
336105
+ var CanonicalDecimalStringSchema = exports_external.string().regex(/^(?:0|[1-9]\d*)(?:\.\d*[1-9])?$/);
336106
+ var accountScopeShape = {
336107
+ accountSelector: exports_external.string().min(1),
336108
+ credentialSource: exports_external.enum(["configured_pool", "request_metadata"])
336109
+ };
336110
+ var evidenceSourceShape = {
336111
+ observedAt: exports_external.string().datetime({ offset: true }),
336112
+ digestAlgorithm: exports_external.literal(EVIDENCE_DIGEST_ALGORITHM),
336113
+ sourceDigest: exports_external.string().regex(/^[a-f0-9]{64}$/)
336114
+ };
336115
+ var TradingFeeEvidenceSchema = exports_external.object({
336116
+ schemaVersion: exports_external.literal("cex-trading-fee-evidence/v1"),
336117
+ exchange: exports_external.string().min(1),
336118
+ marketType: exports_external.literal("spot"),
336119
+ canonicalPair: exports_external.string().regex(/^[^-\s]+-[^-\s]+$/),
336120
+ unifiedSymbol: exports_external.string().regex(/^[^/\s]+\/[^/\s]+$/),
336121
+ sourceSymbol: exports_external.string().min(1),
336122
+ ...accountScopeShape,
336123
+ ...evidenceSourceShape,
336124
+ sourceMethod: exports_external.literal("ccxt.fetchTradingFee"),
336125
+ makerRate: CanonicalDecimalStringSchema,
336126
+ takerRate: CanonicalDecimalStringSchema,
336127
+ rateUnit: exports_external.literal("decimal_fraction"),
336128
+ makerBasisPoints: CanonicalDecimalStringSchema,
336129
+ takerBasisPoints: CanonicalDecimalStringSchema,
336130
+ basisPointsUnit: exports_external.literal("basis_points")
336131
+ }).strict();
336132
+ var MarketRuleEvidenceSchema = exports_external.object({
336133
+ schemaVersion: exports_external.literal("cex-market-rule-evidence/v1"),
336134
+ exchange: exports_external.string().min(1),
336135
+ marketType: exports_external.literal("spot"),
336136
+ canonicalPair: exports_external.string().regex(/^[^-\s]+-[^-\s]+$/),
336137
+ unifiedSymbol: exports_external.string().regex(/^[^/\s]+\/[^/\s]+$/),
336138
+ sourceSymbol: exports_external.string().min(1),
336139
+ baseAsset: exports_external.string().min(1),
336140
+ quoteAsset: exports_external.string().min(1),
336141
+ active: exports_external.literal(true),
336142
+ precisionMode: exports_external.union([exports_external.string().min(1), exports_external.number().int()]),
336143
+ priceIncrement: CanonicalDecimalStringSchema,
336144
+ amountIncrement: CanonicalDecimalStringSchema,
336145
+ minimumAmount: CanonicalDecimalStringSchema,
336146
+ minimumNotional: CanonicalDecimalStringSchema,
336147
+ maximumAmount: CanonicalDecimalStringSchema.optional(),
336148
+ maximumPrice: CanonicalDecimalStringSchema.optional(),
336149
+ maximumNotional: CanonicalDecimalStringSchema.optional(),
336150
+ ...accountScopeShape,
336151
+ ...evidenceSourceShape,
336152
+ sourceMethod: exports_external.literal("ccxt.loadMarkets")
336153
+ }).strict();
336154
+ var TransferNetworkEvidenceSchema = exports_external.object({
336155
+ schemaVersion: exports_external.literal("cex-transfer-network-evidence/v1"),
336156
+ exchange: exports_external.string().min(1),
336157
+ asset: exports_external.string().min(1),
336158
+ operatorNetworkAlias: exports_external.string().min(1),
336159
+ brokerNetworkId: exports_external.string().min(1),
336160
+ exchangeNetworkId: exports_external.string().min(1),
336161
+ depositAvailable: exports_external.boolean(),
336162
+ withdrawalAvailable: exports_external.boolean(),
336163
+ withdrawalFee: CanonicalDecimalStringSchema.nullable(),
336164
+ withdrawalLimits: exports_external.object({
336165
+ minimum: CanonicalDecimalStringSchema.nullable(),
336166
+ maximum: CanonicalDecimalStringSchema.nullable()
336167
+ }).strict(),
336168
+ ...accountScopeShape,
336169
+ ...evidenceSourceShape,
336170
+ sourceMethod: exports_external.literal("ccxt.fetchCurrencies")
336171
+ }).strict();
336172
+ var BatchSuccessEntrySchema = exports_external.object({
336173
+ id: exports_external.string().min(1),
336174
+ action: exports_external.number().int().nonnegative(),
336175
+ symbol: exports_external.string(),
336176
+ response: exports_external.object({
336177
+ result: exports_external.string(),
336178
+ proof: exports_external.string()
336179
+ }).strict(),
336180
+ error: exports_external.null()
336181
+ }).strict();
336182
+ var BatchErrorEntrySchema = exports_external.object({
336183
+ id: exports_external.string().min(1),
336184
+ action: exports_external.number().int().nonnegative(),
336185
+ symbol: exports_external.string(),
336186
+ response: exports_external.null(),
336187
+ error: exports_external.object({
336188
+ code: exports_external.string().min(1),
336189
+ grpcStatus: exports_external.number().int().nonnegative(),
336190
+ message: exports_external.string()
336191
+ }).strict()
336192
+ }).strict();
336193
+ var BatchResponseEntrySchema = exports_external.union([
336194
+ BatchSuccessEntrySchema,
336195
+ BatchErrorEntrySchema
336196
+ ]);
336197
+ var BatchResponseEnvelopeSchema = exports_external.object({
336198
+ schemaVersion: exports_external.literal("cex-broker-action-batch/v1"),
336199
+ responses: exports_external.array(BatchResponseEntrySchema).max(32)
336200
+ }).strict();
336201
+
336202
+ // src/handlers/execute-action/batch.ts
336203
+ var FORBIDDEN_ROUTING_KEYS = new Set([
336204
+ "account",
336205
+ "accountid",
336206
+ "accountselector",
336207
+ "apikey",
336208
+ "apisecret",
336209
+ "auth",
336210
+ "authorization",
336211
+ "cex",
336212
+ "credential",
336213
+ "credentials",
336214
+ "exchange",
336215
+ "metadata",
336216
+ "password",
336217
+ "secret",
336218
+ "signature",
336219
+ "usesecondarykey"
336220
+ ]);
336221
+ function normalizeRoutingKey(key2) {
336222
+ return key2.replace(/[-_]/g, "").toLowerCase();
336223
+ }
336224
+ function hasForbiddenRoutingKey(value) {
336225
+ if (Array.isArray(value)) {
336226
+ return value.some(hasForbiddenRoutingKey);
336227
+ }
336228
+ if (value === null || typeof value !== "object") {
336229
+ return false;
336230
+ }
336231
+ for (const [key2, entry] of Object.entries(value)) {
336232
+ if (FORBIDDEN_ROUTING_KEYS.has(normalizeRoutingKey(key2))) {
336233
+ return true;
336234
+ }
336235
+ if (hasForbiddenRoutingKey(entry)) {
336236
+ return true;
336237
+ }
336238
+ }
336239
+ return false;
336240
+ }
336241
+ function childContainsRoutingOverride(child) {
336242
+ for (const [key2, value] of Object.entries(child.payload)) {
336243
+ if (FORBIDDEN_ROUTING_KEYS.has(normalizeRoutingKey(key2))) {
336244
+ return true;
336245
+ }
336246
+ try {
336247
+ if (hasForbiddenRoutingKey(JSON.parse(value))) {
336248
+ return true;
336249
+ }
336250
+ } catch {}
336251
+ }
336252
+ return false;
336253
+ }
336254
+ function stableBatchErrorCode(message, grpcStatus) {
336255
+ const prefix = message.match(/^([A-Za-z][A-Za-z0-9_]*):/)?.[1];
336256
+ if (prefix) {
336257
+ return prefix;
336258
+ }
336259
+ return grpc5.status[grpcStatus] ?? "UNKNOWN";
336260
+ }
336261
+ function batchErrorEntry(child, error48, broker) {
336262
+ const resolved = resolveGrpcError(error48);
336263
+ const errorRecord = error48 !== null && typeof error48 === "object" ? error48 : undefined;
336264
+ const grpcStatus = typeof errorRecord?.code === "number" ? errorRecord.code : resolved.code;
336265
+ const rawMessage = typeof errorRecord?.message === "string" ? errorRecord.message : resolved.message;
336266
+ const sanitizedMessage = redactSecretLiterals(sanitizeErrorDetail(rawMessage), exchangeSecretLiterals(broker));
336267
+ return {
336268
+ id: child.id,
336269
+ action: child.action,
336270
+ symbol: child.symbol,
336271
+ response: null,
336272
+ error: {
336273
+ code: stableBatchErrorCode(rawMessage, grpcStatus),
336274
+ grpcStatus,
336275
+ message: sanitizedMessage
336276
+ }
336277
+ };
336278
+ }
336279
+ function childCall(ctx, request) {
336280
+ return {
336281
+ ...ctx.call,
336282
+ request
336283
+ };
336284
+ }
336285
+ async function executeChild(ctx, child, descriptor) {
336286
+ const action = resolveAction(child.action);
336287
+ if (action === undefined) {
336288
+ return batchErrorEntry(child, new Error(`ValidationError: invalid action ${child.action}`), ctx.broker);
336289
+ }
336290
+ const proofState = { proof: "" };
336291
+ let completion;
336292
+ const localCallback = (error48, response) => {
336293
+ if (completion === undefined) {
336294
+ completion = { error: error48, response: response ?? null };
336295
+ }
336296
+ };
336297
+ const request = {
336298
+ action,
336299
+ cex: ctx.cex,
336300
+ symbol: child.symbol,
336301
+ payload: child.payload
336302
+ };
336303
+ const childContext = {
336304
+ ...ctx,
336305
+ call: childCall(ctx, request),
336306
+ wrappedCallback: localCallback,
336307
+ action,
336308
+ symbol: child.symbol,
336309
+ verity: proofState,
336310
+ applyVerityToBroker: (target) => ctx.applyVerityToBroker(target, proofState)
336311
+ };
336312
+ try {
336313
+ childContext.applyVerityToBroker(ctx.broker);
336314
+ await descriptor.handler(childContext);
336315
+ } catch (error48) {
336316
+ completion ??= {
336317
+ error: resolveGrpcError(error48),
336318
+ response: null
336319
+ };
336320
+ }
336321
+ if (completion === undefined) {
336322
+ return batchErrorEntry(child, new Error(`INTERNAL: ${getActionName(action)} completed without a callback`), ctx.broker);
336323
+ }
336324
+ if (completion.error || !completion.response) {
336325
+ return batchErrorEntry(child, completion.error ?? new Error("INTERNAL: child returned no response"), ctx.broker);
336326
+ }
336327
+ return {
336328
+ id: child.id,
336329
+ action: child.action,
336330
+ symbol: child.symbol,
336331
+ response: {
336332
+ result: completion.response.result,
336333
+ proof: completion.response.proof ?? proofState.proof
336334
+ },
336335
+ error: null
336336
+ };
336337
+ }
336338
+ async function handleBatch(ctx, lookupDescriptor) {
336339
+ if (ctx.symbol?.trim()) {
336340
+ return ctx.wrappedCallback({
336341
+ code: grpc5.status.INVALID_ARGUMENT,
336342
+ message: "ValidationError: Batch symbol must be empty"
336343
+ }, null);
336344
+ }
336345
+ const encodedRequests = ctx.call.request.payload?.requests;
336346
+ if (typeof encodedRequests === "string" && Buffer.byteLength(encodedRequests, "utf8") > MAX_BATCH_REQUEST_BYTES) {
336347
+ return ctx.wrappedCallback({
336348
+ code: grpc5.status.INVALID_ARGUMENT,
336349
+ message: `ValidationError: Batch requests exceed ${MAX_BATCH_REQUEST_BYTES} bytes`
336350
+ }, null);
336351
+ }
336352
+ const payload = parsePayloadForAction(ctx, BatchPayloadSchema);
336353
+ if (payload === null) {
336354
+ return;
336355
+ }
336356
+ const seenIds = new Set;
336357
+ const prepared = [];
336358
+ for (const child of payload.requests) {
336359
+ if (seenIds.has(child.id)) {
336360
+ return ctx.wrappedCallback({
336361
+ code: grpc5.status.INVALID_ARGUMENT,
336362
+ message: `ValidationError: duplicate batch child id '${child.id}'`
336363
+ }, null);
336364
+ }
336365
+ seenIds.add(child.id);
336366
+ if (childContainsRoutingOverride(child)) {
336367
+ return ctx.wrappedCallback({
336368
+ code: grpc5.status.INVALID_ARGUMENT,
336369
+ message: `ValidationError: batch child '${child.id}' contains a routing override`
336370
+ }, null);
336371
+ }
336372
+ const action = resolveAction(child.action);
336373
+ const descriptor = action === undefined ? undefined : lookupDescriptor(action);
336374
+ if (!descriptor || descriptor.access !== "read" || !descriptor.batchable) {
336375
+ return ctx.wrappedCallback({
336376
+ code: grpc5.status.INVALID_ARGUMENT,
336377
+ message: `ValidationError: batch child '${child.id}' action ${child.action} is not batchable`
336378
+ }, null);
336379
+ }
336380
+ const validation = descriptor.validateBatchRequest?.({
336381
+ action,
336382
+ cex: ctx.cex,
336383
+ symbol: child.symbol,
336384
+ payload: child.payload
336385
+ });
336386
+ if (validation && !validation.valid) {
336387
+ return ctx.wrappedCallback({
336388
+ code: grpc5.status.INVALID_ARGUMENT,
336389
+ message: `ValidationError: batch child '${child.id}': ${validation.message}`
336390
+ }, null);
336391
+ }
336392
+ prepared.push({ child, descriptor });
336393
+ }
336394
+ const responses = [];
336395
+ for (const { child, descriptor } of prepared) {
336396
+ const response = await executeChild(ctx, child, descriptor);
336397
+ responses.push(response);
336398
+ ctx.otelMetrics?.recordCounter("execute_action_batch_items_total", 1, {
336399
+ action: getActionName(child.action),
336400
+ cex: ctx.normalizedCex,
336401
+ outcome: response.error ? "error" : "success"
336402
+ });
336403
+ }
336404
+ const envelope = BatchResponseEnvelopeSchema.parse({
336405
+ schemaVersion: "cex-broker-action-batch/v1",
336406
+ responses
336407
+ });
336408
+ ctx.wrappedCallback(null, {
336409
+ result: JSON.stringify(envelope),
336410
+ proof: ""
336411
+ });
336412
+ }
336413
+
336414
+ // src/handlers/execute-action/internal-transfer.ts
336415
+ var grpc6 = __toESM(require_src3(), 1);
335459
336416
  async function handleInternalTransfer(ctx) {
335460
336417
  const {
335461
336418
  brokers,
@@ -335469,7 +336426,7 @@ async function handleInternalTransfer(ctx) {
335469
336426
  } = ctx;
335470
336427
  if (!symbol2) {
335471
336428
  return ctx.wrappedCallback({
335472
- code: grpc5.status.INVALID_ARGUMENT,
336429
+ code: grpc6.status.INVALID_ARGUMENT,
335473
336430
  message: `ValidationError: Symbol required`
335474
336431
  }, null);
335475
336432
  }
@@ -335478,14 +336435,14 @@ async function handleInternalTransfer(ctx) {
335478
336435
  return;
335479
336436
  if (normalizedCex !== "binance") {
335480
336437
  return ctx.wrappedCallback({
335481
- code: grpc5.status.UNIMPLEMENTED,
336438
+ code: grpc6.status.UNIMPLEMENTED,
335482
336439
  message: `InternalTransfer is only supported for Binance`
335483
336440
  }, null);
335484
336441
  }
335485
336442
  const pool = brokers[normalizedCex];
335486
336443
  if (!pool) {
335487
336444
  return ctx.wrappedCallback({
335488
- code: grpc5.status.FAILED_PRECONDITION,
336445
+ code: grpc6.status.FAILED_PRECONDITION,
335489
336446
  message: `No broker accounts configured for ${normalizedCex}`
335490
336447
  }, null);
335491
336448
  }
@@ -335494,14 +336451,14 @@ async function handleInternalTransfer(ctx) {
335494
336451
  const sourceAccount = resolveBrokerAccount(pool, fromSelector);
335495
336452
  if (!sourceAccount) {
335496
336453
  return ctx.wrappedCallback({
335497
- code: grpc5.status.INVALID_ARGUMENT,
336454
+ code: grpc6.status.INVALID_ARGUMENT,
335498
336455
  message: `Source account "${fromSelector}" is not configured`
335499
336456
  }, null);
335500
336457
  }
335501
336458
  const destAccount = resolveBrokerAccount(pool, toSelector);
335502
336459
  if (!destAccount) {
335503
336460
  return ctx.wrappedCallback({
335504
- code: grpc5.status.INVALID_ARGUMENT,
336461
+ code: grpc6.status.INVALID_ARGUMENT,
335505
336462
  message: `Destination account "${toSelector}" is not configured`
335506
336463
  }, null);
335507
336464
  }
@@ -335535,18 +336492,18 @@ async function handleInternalTransfer(ctx) {
335535
336492
  safeLogError("InternalTransfer failed", error48);
335536
336493
  if (error48 instanceof BrokerAccountPreconditionError) {
335537
336494
  return ctx.wrappedCallback({
335538
- code: grpc5.status.FAILED_PRECONDITION,
336495
+ code: grpc6.status.FAILED_PRECONDITION,
335539
336496
  message: getErrorMessage(error48)
335540
336497
  }, null);
335541
336498
  }
335542
336499
  const msg = getErrorMessage(error48);
335543
336500
  let code;
335544
336501
  if (msg.includes("Unsupported transfer direction")) {
335545
- code = grpc5.status.INVALID_ARGUMENT;
336502
+ code = grpc6.status.INVALID_ARGUMENT;
335546
336503
  } else if (msg.includes("unavailable in this CCXT build")) {
335547
- code = grpc5.status.UNIMPLEMENTED;
336504
+ code = grpc6.status.UNIMPLEMENTED;
335548
336505
  } else {
335549
- code = mapCcxtErrorToGrpcStatus(error48) ?? grpc5.status.INTERNAL;
336506
+ code = mapCcxtErrorToGrpcStatus(error48) ?? grpc6.status.INTERNAL;
335550
336507
  }
335551
336508
  ctx.wrappedCallback({
335552
336509
  code,
@@ -335556,7 +336513,7 @@ async function handleInternalTransfer(ctx) {
335556
336513
  }
335557
336514
 
335558
336515
  // src/handlers/execute-action/orders.ts
335559
- var grpc6 = __toESM(require_src3(), 1);
336516
+ var grpc7 = __toESM(require_src3(), 1);
335560
336517
 
335561
336518
  // src/helpers/passive-order.ts
335562
336519
  var PASSIVE_ORDER_ERROR_CODES = {
@@ -335617,7 +336574,7 @@ async function handleCreateOrder(ctx) {
335617
336574
  const isPassiveOrder = orderValue.orderIntent === "passive_only";
335618
336575
  if (isPassiveOrder && orderValue.orderType !== "limit") {
335619
336576
  return ctx.wrappedCallback({
335620
- code: grpc6.status.INVALID_ARGUMENT,
336577
+ code: grpc7.status.INVALID_ARGUMENT,
335621
336578
  message: "ValidationError: passive_only order intent requires a limit order"
335622
336579
  }, null);
335623
336580
  }
@@ -335634,14 +336591,14 @@ async function handleCreateOrder(ctx) {
335634
336591
  try {
335635
336592
  if (!broker) {
335636
336593
  return ctx.wrappedCallback({
335637
- code: grpc6.status.INVALID_ARGUMENT,
336594
+ code: grpc7.status.INVALID_ARGUMENT,
335638
336595
  message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
335639
336596
  }, null);
335640
336597
  }
335641
336598
  const resolution = await resolveOrderExecution(policy, broker, cex3, orderValue.fromToken, orderValue.toToken, orderValue.amount, orderValue.price, orderValue.marketType);
335642
336599
  if (!resolution.valid || !resolution.symbol || !resolution.side) {
335643
336600
  return ctx.wrappedCallback({
335644
- code: grpc6.status.INVALID_ARGUMENT,
336601
+ code: grpc7.status.INVALID_ARGUMENT,
335645
336602
  message: resolution.error ?? "Order rejected by policy: market or limits not satisfied"
335646
336603
  }, null);
335647
336604
  }
@@ -335714,7 +336671,7 @@ async function handleCreateOrder(ctx) {
335714
336671
  });
335715
336672
  }
335716
336673
  ctx.wrappedCallback({
335717
- code: grpc6.status.INTERNAL,
336674
+ code: grpc7.status.INTERNAL,
335718
336675
  message: `Order Creation failed: ${sanitizeErrorDetail(error48)}`
335719
336676
  }, null);
335720
336677
  }
@@ -335746,7 +336703,7 @@ async function handleGetOrderDetails(ctx) {
335746
336703
  try {
335747
336704
  if (!broker) {
335748
336705
  return ctx.wrappedCallback({
335749
- code: grpc6.status.INVALID_ARGUMENT,
336706
+ code: grpc7.status.INVALID_ARGUMENT,
335750
336707
  message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
335751
336708
  }, null);
335752
336709
  }
@@ -335787,7 +336744,7 @@ async function handleGetOrderDetails(ctx) {
335787
336744
  emitOrderExecutionTelemetryInBackground(otelMetrics, failedGetOrderContext, undefined, error48);
335788
336745
  archiveOrderExecutionInBackground(brokerArchiver, failedGetOrderContext, undefined, error48);
335789
336746
  ctx.wrappedCallback({
335790
- code: grpc6.status.INTERNAL,
336747
+ code: grpc7.status.INTERNAL,
335791
336748
  message: `Failed to fetch order details from ${cex3}: ${sanitizeErrorDetail(error48)}`
335792
336749
  }, null);
335793
336750
  }
@@ -335819,7 +336776,7 @@ async function handleCancelOrder(ctx) {
335819
336776
  try {
335820
336777
  if (!broker) {
335821
336778
  return ctx.wrappedCallback({
335822
- code: grpc6.status.INVALID_ARGUMENT,
336779
+ code: grpc7.status.INVALID_ARGUMENT,
335823
336780
  message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
335824
336781
  }, null);
335825
336782
  }
@@ -335851,7 +336808,7 @@ async function handleCancelOrder(ctx) {
335851
336808
  emitOrderExecutionTelemetryInBackground(otelMetrics, failedCancelContext, undefined, error48);
335852
336809
  archiveOrderExecutionInBackground(brokerArchiver, failedCancelContext, undefined, error48);
335853
336810
  ctx.wrappedCallback({
335854
- code: grpc6.status.INTERNAL,
336811
+ code: grpc7.status.INTERNAL,
335855
336812
  message: `Failed to cancel order from ${cex3}: ${sanitizeErrorDetail(error48)}`
335856
336813
  }, null);
335857
336814
  }
@@ -335866,261 +336823,254 @@ async function handleOrders(ctx) {
335866
336823
  }
335867
336824
 
335868
336825
  // src/handlers/execute-action/pass-through.ts
335869
- var grpc7 = __toESM(require_src3(), 1);
335870
- async function handleFetchCurrency(ctx) {
335871
- const {
335872
- call,
335873
- wrappedCallback,
335874
- policy,
335875
- brokers,
335876
- metadata,
335877
- normalizedCex,
335878
- cex: cex3,
335879
- symbol: symbol2,
335880
- selectedBrokerAccount,
335881
- broker,
335882
- verity,
335883
- applyVerityToBroker,
335884
- useVerity,
335885
- verityProverUrl,
335886
- otelMetrics
335887
- } = ctx;
335888
- const verityProof = verity.proof;
335889
- if (!symbol2) {
336826
+ var grpc9 = __toESM(require_src3(), 1);
336827
+
336828
+ // src/handlers/execute-action/venue-evidence.ts
336829
+ var grpc8 = __toESM(require_src3(), 1);
336830
+ function failVenueDiscovery(ctx, error48, operation) {
336831
+ safeLogRedactedError(`${operation} failed`, error48);
336832
+ const sanitized = sanitizeVenueError(error48, ctx.broker);
336833
+ const message = sanitized.startsWith("venue_discovery_unavailable:") ? sanitized : `venue_discovery_unavailable: ${sanitized}`;
336834
+ ctx.wrappedCallback({
336835
+ code: stableGrpcErrorCode(message) ?? mapCcxtErrorToGrpcStatus(error48) ?? grpc8.status.UNIMPLEMENTED,
336836
+ message
336837
+ }, null);
336838
+ }
336839
+ function optionalDecimalFields(values2) {
336840
+ const result = {};
336841
+ for (const [key2, value, field] of values2) {
336842
+ const normalized = canonicalOptionalDecimal(value, field);
336843
+ if (normalized !== undefined) {
336844
+ result[key2] = normalized;
336845
+ }
336846
+ }
336847
+ return result;
336848
+ }
336849
+ async function handleFetchFeesEvidence(ctx) {
336850
+ if (!requireSymbol(ctx, "ValidationError: symbol must be a slash-delimited spot pair")) {
336851
+ return;
336852
+ }
336853
+ if (!/^[^/\s]+\/[^/\s]+$/.test(ctx.symbol.trim())) {
335890
336854
  return ctx.wrappedCallback({
335891
- code: grpc7.status.INVALID_ARGUMENT,
335892
- message: `ValidationError: Symbol required`
336855
+ code: grpc8.status.INVALID_ARGUMENT,
336856
+ message: "ValidationError: symbol must be a slash-delimited spot pair"
335893
336857
  }, null);
335894
336858
  }
336859
+ if (parsePayloadForAction(ctx, FetchFeesPayloadSchema) === null) {
336860
+ return;
336861
+ }
335895
336862
  try {
335896
- const assetCode = symbol2.trim().toUpperCase();
335897
- const currencyInfo = await fetchCurrencyMetadata(broker, assetCode);
335898
- if (!currencyInfo) {
335899
- return ctx.wrappedCallback({
335900
- code: grpc7.status.NOT_FOUND,
335901
- message: `venue_discovery_unavailable: currency not found for ${assetCode}`
335902
- }, null);
335903
- }
335904
- const networkEvidence = buildTransferNetworkEvidence(currencyInfo);
335905
- ctx.wrappedCallback(null, {
335906
- proof: ctx.verity.proof,
335907
- result: JSON.stringify({
335908
- ...currencyInfo,
335909
- exchange: normalizedCex,
335910
- asset: assetCode,
335911
- code: currencyInfo.code ?? assetCode,
335912
- id: currencyInfo.id ?? null,
335913
- networks: networkEvidence.networks,
335914
- networkAliases: networkEvidence.aliases,
335915
- raw: currencyInfo
336863
+ const identity2 = await resolveSpotMarketIdentity(ctx.broker, ctx.symbol);
336864
+ const exchange = evidenceExchange(ctx.broker);
336865
+ if (exchange.has?.fetchTradingFee === false || typeof exchange.fetchTradingFee !== "function") {
336866
+ throw new Error(`fee_unavailable: ${ctx.normalizedCex} does not support fetchTradingFee`);
336867
+ }
336868
+ const sourceResponse = await exchange.fetchTradingFee(identity2.unifiedSymbol);
336869
+ if (isRecord(sourceResponse) && typeof sourceResponse.symbol === "string" && sourceResponse.symbol.trim().toUpperCase() !== identity2.unifiedSymbol) {
336870
+ throw new Error(`fee_unavailable: trading-fee response symbol does not match ${identity2.unifiedSymbol}`);
336871
+ }
336872
+ const { makerRate, takerRate } = extractTradingFeeRates(sourceResponse);
336873
+ const accountScope = resolveEvidenceAccountScope(ctx.selectedBrokerAccount, ctx.metadata);
336874
+ const evidence = TradingFeeEvidenceSchema.parse({
336875
+ schemaVersion: "cex-trading-fee-evidence/v1",
336876
+ exchange: ctx.normalizedCex,
336877
+ marketType: "spot",
336878
+ canonicalPair: identity2.canonicalPair,
336879
+ unifiedSymbol: identity2.unifiedSymbol,
336880
+ sourceSymbol: identity2.sourceSymbol,
336881
+ ...accountScope,
336882
+ observedAt: new Date().toISOString(),
336883
+ sourceMethod: "ccxt.fetchTradingFee",
336884
+ makerRate,
336885
+ takerRate,
336886
+ rateUnit: "decimal_fraction",
336887
+ makerBasisPoints: decimalFractionToBasisPoints(makerRate),
336888
+ takerBasisPoints: decimalFractionToBasisPoints(takerRate),
336889
+ basisPointsUnit: "basis_points",
336890
+ digestAlgorithm: "sha256-canonical-json-v1",
336891
+ sourceDigest: evidenceSourceDigest({
336892
+ action: "FetchFees",
336893
+ exchange: ctx.normalizedCex,
336894
+ requestedKey: identity2.canonicalPair,
336895
+ accountSelector: accountScope.accountSelector,
336896
+ sourceMethod: "ccxt.fetchTradingFee",
336897
+ source: sourceResponse,
336898
+ broker: ctx.broker
335916
336899
  })
335917
336900
  });
336901
+ successWithProof(ctx, evidence);
335918
336902
  } catch (error48) {
335919
- safeLogError(`Error fetching currency ${symbol2} from ${cex3}`, error48);
335920
- const message = getErrorMessage(error48);
336903
+ safeLogRedactedError(`FetchFees failed for ${ctx.normalizedCex}/${ctx.symbol}`, error48);
336904
+ const sanitized = sanitizeVenueError(error48, ctx.broker);
336905
+ const message = sanitized.startsWith("fee_unavailable:") ? sanitized : `fee_unavailable: ${sanitized}`;
335921
336906
  ctx.wrappedCallback({
335922
- code: stableGrpcErrorCode(message) ?? mapCcxtErrorToGrpcStatus(error48) ?? grpc7.status.INTERNAL,
335923
- message: message.startsWith("venue_discovery_unavailable:") ? message : `venue_discovery_unavailable: ${message}`
336907
+ code: grpc8.status.FAILED_PRECONDITION,
336908
+ message
335924
336909
  }, null);
335925
336910
  }
335926
336911
  }
335927
- async function handleFetchAccountId(ctx) {
335928
- const {
335929
- call,
335930
- wrappedCallback,
335931
- policy,
335932
- brokers,
335933
- metadata,
335934
- normalizedCex,
335935
- cex: cex3,
335936
- symbol: symbol2,
335937
- selectedBrokerAccount,
335938
- broker,
335939
- verity,
335940
- applyVerityToBroker,
335941
- useVerity,
335942
- verityProverUrl,
335943
- otelMetrics
335944
- } = ctx;
335945
- const verityProof = verity.proof;
336912
+ async function handleFetchMarketRulesEvidence(ctx) {
336913
+ if (!requireSymbol(ctx, "ValidationError: symbol must be a slash-delimited spot pair")) {
336914
+ return;
336915
+ }
336916
+ if (!/^[^/\s]+\/[^/\s]+$/.test(ctx.symbol.trim())) {
336917
+ return ctx.wrappedCallback({
336918
+ code: grpc8.status.INVALID_ARGUMENT,
336919
+ message: "ValidationError: symbol must be a slash-delimited spot pair"
336920
+ }, null);
336921
+ }
336922
+ if (parsePayloadForAction(ctx, EmptyActionPayloadSchema) === null) {
336923
+ return;
336924
+ }
335946
336925
  try {
335947
- const accountId = await broker.fetchAccountId();
335948
- return ctx.wrappedCallback(null, {
335949
- proof: ctx.verity.proof,
335950
- result: JSON.stringify({ accountId })
336926
+ const identity2 = await resolveSpotMarketIdentity(ctx.broker, ctx.symbol);
336927
+ const precision = isRecord(identity2.market.precision) ? identity2.market.precision : {};
336928
+ const limits = isRecord(identity2.market.limits) ? identity2.market.limits : {};
336929
+ const amountLimits = isRecord(limits.amount) ? limits.amount : {};
336930
+ const priceLimits = isRecord(limits.price) ? limits.price : {};
336931
+ const costLimits = isRecord(limits.cost) ? limits.cost : {};
336932
+ const precisionMode = evidenceExchange(ctx.broker).precisionMode;
336933
+ if (precisionMode === undefined || precisionMode === null) {
336934
+ throw new Error("venue_discovery_unavailable: precision mode is unavailable");
336935
+ }
336936
+ const accountScope = resolveEvidenceAccountScope(ctx.selectedBrokerAccount, ctx.metadata);
336937
+ const evidence = MarketRuleEvidenceSchema.parse({
336938
+ schemaVersion: "cex-market-rule-evidence/v1",
336939
+ exchange: ctx.normalizedCex,
336940
+ marketType: "spot",
336941
+ canonicalPair: identity2.canonicalPair,
336942
+ unifiedSymbol: identity2.unifiedSymbol,
336943
+ sourceSymbol: identity2.sourceSymbol,
336944
+ baseAsset: identity2.baseAsset,
336945
+ quoteAsset: identity2.quoteAsset,
336946
+ active: true,
336947
+ precisionMode,
336948
+ priceIncrement: precisionIncrement(precision.price, precisionMode, "price increment"),
336949
+ amountIncrement: precisionIncrement(precision.amount, precisionMode, "amount increment"),
336950
+ minimumAmount: canonicalNonnegativeDecimal(amountLimits.min, "minimum amount"),
336951
+ minimumNotional: canonicalNonnegativeDecimal(costLimits.min, "minimum notional"),
336952
+ ...optionalDecimalFields([
336953
+ ["maximumAmount", amountLimits.max, "maximum amount"],
336954
+ ["maximumPrice", priceLimits.max, "maximum price"],
336955
+ ["maximumNotional", costLimits.max, "maximum notional"]
336956
+ ]),
336957
+ ...accountScope,
336958
+ observedAt: new Date().toISOString(),
336959
+ sourceMethod: "ccxt.loadMarkets",
336960
+ digestAlgorithm: "sha256-canonical-json-v1",
336961
+ sourceDigest: evidenceSourceDigest({
336962
+ action: "FetchMarketRules",
336963
+ exchange: ctx.normalizedCex,
336964
+ requestedKey: identity2.canonicalPair,
336965
+ accountSelector: accountScope.accountSelector,
336966
+ sourceMethod: "ccxt.loadMarkets",
336967
+ source: identity2.market,
336968
+ broker: ctx.broker
336969
+ })
335951
336970
  });
336971
+ successWithProof(ctx, evidence);
335952
336972
  } catch (error48) {
335953
- safeLogError(`Error fetching account ID ${cex3}`, error48);
335954
- ctx.wrappedCallback({
335955
- code: grpc7.status.INTERNAL,
335956
- message: `Error fetching account ID from ${cex3}`
335957
- }, null);
336973
+ failVenueDiscovery(ctx, error48, `FetchMarketRules ${ctx.normalizedCex}/${ctx.symbol}`);
335958
336974
  }
335959
336975
  }
335960
- async function handleFetchFees(ctx) {
335961
- const {
335962
- call,
335963
- wrappedCallback,
335964
- policy,
335965
- brokers,
335966
- metadata,
335967
- normalizedCex,
335968
- cex: cex3,
335969
- symbol: symbol2,
335970
- selectedBrokerAccount,
335971
- broker,
335972
- verity,
335973
- applyVerityToBroker,
335974
- useVerity,
335975
- verityProverUrl,
335976
- otelMetrics
335977
- } = ctx;
335978
- const verityProof = verity.proof;
335979
- if (!symbol2) {
335980
- return ctx.wrappedCallback({
335981
- code: grpc7.status.INVALID_ARGUMENT,
335982
- message: `ValidationError: Symbol required`
335983
- }, null);
336976
+ async function handleFetchCurrencyEvidence(ctx) {
336977
+ if (!requireSymbol(ctx)) {
336978
+ return;
335984
336979
  }
335985
- const feesPayload = parsePayloadForAction(ctx, FetchFeesPayloadSchema);
335986
- if (feesPayload === null)
336980
+ const payload = parsePayloadForAction(ctx, FetchCurrencyPayloadSchema);
336981
+ if (payload === null) {
335987
336982
  return;
335988
- const includeAllFees = feesPayload.includeAllFees || feesPayload.includeFundingFees === true;
336983
+ }
336984
+ const asset = ctx.symbol.trim().toUpperCase();
336985
+ const requestedAlias = payload.network.trim().toUpperCase();
335989
336986
  try {
335990
- await broker.loadMarkets();
335991
- const fetchFundingFees = async (currencyCodes) => {
335992
- let fundingFeeSource2 = "unavailable";
335993
- const fundingFeesByCurrency2 = {};
335994
- if (broker.has.fetchDepositWithdrawFees) {
335995
- try {
335996
- const feeMap = await broker.fetchDepositWithdrawFees(currencyCodes);
335997
- for (const code of currencyCodes) {
335998
- const feeInfo = feeMap[code];
335999
- if (!feeInfo) {
336000
- continue;
336001
- }
336002
- const fallbackFee = feeInfo.fee !== undefined || feeInfo.percentage !== undefined ? {
336003
- fee: feeInfo.fee ?? null,
336004
- percentage: feeInfo.percentage ?? null
336005
- } : null;
336006
- fundingFeesByCurrency2[code] = {
336007
- deposit: feeInfo.deposit ?? fallbackFee,
336008
- withdraw: feeInfo.withdraw ?? fallbackFee,
336009
- networks: feeInfo.networks ?? {}
336010
- };
336011
- }
336012
- if (Object.keys(fundingFeesByCurrency2).length > 0) {
336013
- fundingFeeSource2 = "fetchDepositWithdrawFees";
336014
- }
336015
- } catch (error48) {
336016
- safeLogError(`Error fetching deposit/withdraw fee map for ${symbol2} from ${cex3}`, error48);
336017
- }
336018
- }
336019
- if (fundingFeeSource2 === "unavailable") {
336020
- try {
336021
- const currencies = await broker.fetchCurrencies();
336022
- for (const code of currencyCodes) {
336023
- const currency = currencies[code];
336024
- if (!currency) {
336025
- continue;
336026
- }
336027
- fundingFeesByCurrency2[code] = {
336028
- deposit: {
336029
- enabled: currency.deposit ?? null
336030
- },
336031
- withdraw: {
336032
- enabled: currency.withdraw ?? null,
336033
- fee: currency.fee ?? null,
336034
- limits: currency.limits?.withdraw ?? null
336035
- },
336036
- networks: currency.networks ?? {}
336037
- };
336038
- }
336039
- if (Object.keys(fundingFeesByCurrency2).length > 0) {
336040
- fundingFeeSource2 = "currencies";
336041
- }
336042
- } catch (error48) {
336043
- safeLogError(`Error fetching currency metadata for fees for ${symbol2} from ${cex3}`, error48);
336044
- }
336045
- }
336046
- return { fundingFeeSource: fundingFeeSource2, fundingFeesByCurrency: fundingFeesByCurrency2 };
336047
- };
336048
- const isMarketSymbol = symbol2.includes("/");
336049
- if (isMarketSymbol) {
336050
- const market = await broker.market(symbol2);
336051
- const generalFee = broker.fees ?? null;
336052
- const feeStatus = broker.fees ? "available" : "unknown";
336053
- if (!broker.fees) {
336054
- log.warn(`Fee metadata unavailable for ${cex3}`, { symbol: symbol2 });
336055
- }
336056
- if (!includeAllFees) {
336057
- return ctx.wrappedCallback(null, {
336058
- proof: ctx.verity.proof,
336059
- result: JSON.stringify({
336060
- feeScope: "market",
336061
- generalFee,
336062
- feeStatus,
336063
- market
336064
- })
336065
- });
336066
- }
336067
- const currencyCodes = Array.from(new Set([market.base, market.quote]));
336068
- const { fundingFeeSource: fundingFeeSource2, fundingFeesByCurrency: fundingFeesByCurrency2 } = await fetchFundingFees(currencyCodes);
336069
- return ctx.wrappedCallback(null, {
336070
- proof: ctx.verity.proof,
336071
- result: JSON.stringify({
336072
- feeScope: "market+funding",
336073
- generalFee,
336074
- feeStatus,
336075
- market,
336076
- fundingFeeSource: fundingFeeSource2,
336077
- fundingFeesByCurrency: fundingFeesByCurrency2
336078
- })
336079
- });
336080
- }
336081
- const tokenCode = symbol2.toUpperCase();
336082
- const { fundingFeeSource, fundingFeesByCurrency } = await fetchFundingFees([
336083
- tokenCode
336084
- ]);
336987
+ const exchange = evidenceExchange(ctx.broker);
336988
+ if (exchange.has?.fetchCurrencies === false || typeof exchange.fetchCurrencies !== "function") {
336989
+ throw new Error(`venue_discovery_unavailable: fetchCurrencies unavailable for ${asset}`);
336990
+ }
336991
+ const currencies = await exchange.fetchCurrencies();
336992
+ const currency = currencies[asset];
336993
+ if (!isRecord(currency)) {
336994
+ throw new Error(`venue_discovery_unavailable: currency not found for ${asset}`);
336995
+ }
336996
+ const networkEvidence = buildTransferNetworkEvidence(currency);
336997
+ const brokerNetworkId = normalizeBrokerNetworkId(requestedAlias);
336998
+ const resolution = networkEvidence.aliases[requestedAlias] ?? networkEvidence.aliases[brokerNetworkId];
336999
+ if (!resolution?.networkKey) {
337000
+ throw new Error(`network_alias_unresolved: ${asset}/${requestedAlias} is not available in discovered transfer networks`);
337001
+ }
337002
+ const networks = isRecord(currency.networks) ? currency.networks : {};
337003
+ const networkCandidate = networks[resolution.networkKey];
337004
+ if (!isRecord(networkCandidate) || typeof networkCandidate.deposit !== "boolean" || typeof networkCandidate.withdraw !== "boolean") {
337005
+ throw new Error(`venue_discovery_unavailable: transfer availability is incomplete for ${asset}/${requestedAlias}`);
337006
+ }
337007
+ const network = networkCandidate;
337008
+ const limits = isRecord(network.limits) ? network.limits : {};
337009
+ const withdrawalLimits = isRecord(limits.withdraw) ? limits.withdraw : {};
337010
+ const accountScope = resolveEvidenceAccountScope(ctx.selectedBrokerAccount, ctx.metadata);
337011
+ const evidence = TransferNetworkEvidenceSchema.parse({
337012
+ schemaVersion: "cex-transfer-network-evidence/v1",
337013
+ exchange: ctx.normalizedCex,
337014
+ asset,
337015
+ operatorNetworkAlias: requestedAlias,
337016
+ brokerNetworkId,
337017
+ exchangeNetworkId: resolution.exchangeNetworkId,
337018
+ depositAvailable: network.deposit,
337019
+ withdrawalAvailable: network.withdraw,
337020
+ withdrawalFee: canonicalOptionalDecimal(network.fee, "withdrawal fee") ?? null,
337021
+ withdrawalLimits: {
337022
+ minimum: canonicalOptionalDecimal(withdrawalLimits.min, "minimum withdrawal") ?? null,
337023
+ maximum: canonicalOptionalDecimal(withdrawalLimits.max, "maximum withdrawal") ?? null
337024
+ },
337025
+ ...accountScope,
337026
+ observedAt: new Date().toISOString(),
337027
+ sourceMethod: "ccxt.fetchCurrencies",
337028
+ digestAlgorithm: "sha256-canonical-json-v1",
337029
+ sourceDigest: evidenceSourceDigest({
337030
+ action: "FetchCurrency",
337031
+ exchange: ctx.normalizedCex,
337032
+ requestedKey: `${asset}/${requestedAlias}`,
337033
+ accountSelector: accountScope.accountSelector,
337034
+ sourceMethod: "ccxt.fetchCurrencies",
337035
+ source: network,
337036
+ broker: ctx.broker
337037
+ })
337038
+ });
337039
+ successWithProof(ctx, evidence);
337040
+ } catch (error48) {
337041
+ safeLogRedactedError(`FetchCurrency failed for ${ctx.normalizedCex}/${asset}/${requestedAlias}`, error48);
337042
+ const sanitized = sanitizeVenueError(error48, ctx.broker);
337043
+ const isAliasError = sanitized.startsWith("network_alias_unresolved:");
337044
+ const message = isAliasError || sanitized.startsWith("venue_discovery_unavailable:") ? sanitized : `venue_discovery_unavailable: ${sanitized}`;
337045
+ ctx.wrappedCallback({
337046
+ code: stableGrpcErrorCode(message) ?? mapCcxtErrorToGrpcStatus(error48) ?? grpc8.status.UNIMPLEMENTED,
337047
+ message
337048
+ }, null);
337049
+ }
337050
+ }
337051
+
337052
+ // src/handlers/execute-action/pass-through.ts
337053
+ async function handleFetchAccountId(ctx) {
337054
+ const { cex: cex3, broker } = ctx;
337055
+ try {
337056
+ const accountId = await broker.fetchAccountId();
336085
337057
  return ctx.wrappedCallback(null, {
336086
337058
  proof: ctx.verity.proof,
336087
- result: JSON.stringify({
336088
- feeScope: "token",
336089
- symbol: tokenCode,
336090
- fundingFeeSource,
336091
- fundingFeesByCurrency
336092
- })
337059
+ result: JSON.stringify({ accountId })
336093
337060
  });
336094
337061
  } catch (error48) {
336095
- safeLogError(`Error fetching fees for ${symbol2} from ${cex3}`, error48);
337062
+ safeLogError(`Error fetching account ID ${cex3}`, error48);
336096
337063
  ctx.wrappedCallback({
336097
- code: grpc7.status.INTERNAL,
336098
- message: `Error fetching fees from ${cex3}`
337064
+ code: grpc9.status.INTERNAL,
337065
+ message: `Error fetching account ID from ${cex3}`
336099
337066
  }, null);
336100
337067
  }
336101
337068
  }
336102
337069
  async function handleFetchDepositAddresses(ctx) {
336103
- const {
336104
- call,
336105
- wrappedCallback,
336106
- policy,
336107
- brokers,
336108
- metadata,
336109
- normalizedCex,
336110
- cex: cex3,
336111
- symbol: symbol2,
336112
- selectedBrokerAccount,
336113
- broker,
336114
- verity,
336115
- applyVerityToBroker,
336116
- useVerity,
336117
- verityProverUrl,
336118
- otelMetrics
336119
- } = ctx;
336120
- const verityProof = verity.proof;
337070
+ const { policy, cex: cex3, symbol: symbol2, broker } = ctx;
336121
337071
  if (!symbol2) {
336122
337072
  return ctx.wrappedCallback({
336123
- code: grpc7.status.INVALID_ARGUMENT,
337073
+ code: grpc9.status.INVALID_ARGUMENT,
336124
337074
  message: `ValidationError: Symbol required`
336125
337075
  }, null);
336126
337076
  }
@@ -336133,14 +337083,14 @@ async function handleFetchDepositAddresses(ctx) {
336133
337083
  } catch (error48) {
336134
337084
  const message = getErrorMessage(error48);
336135
337085
  return ctx.wrappedCallback({
336136
- code: stableGrpcErrorCode(message) ?? grpc7.status.INVALID_ARGUMENT,
337086
+ code: stableGrpcErrorCode(message) ?? grpc9.status.INVALID_ARGUMENT,
336137
337087
  message
336138
337088
  }, null);
336139
337089
  }
336140
337090
  const depositValidation = validateDeposit(policy, cex3, depositNetwork.brokerNetworkId, symbol2);
336141
337091
  if (!depositValidation.valid) {
336142
337092
  return ctx.wrappedCallback({
336143
- code: grpc7.status.PERMISSION_DENIED,
337093
+ code: grpc9.status.PERMISSION_DENIED,
336144
337094
  message: `policy_deposit_denied: ${depositValidation.error}`
336145
337095
  }, null);
336146
337096
  }
@@ -336166,45 +337116,30 @@ async function handleFetchDepositAddresses(ctx) {
336166
337116
  });
336167
337117
  }
336168
337118
  ctx.wrappedCallback({
336169
- code: grpc7.status.INTERNAL,
337119
+ code: grpc9.status.INTERNAL,
336170
337120
  message: "Deposit confirmation failed"
336171
337121
  }, null);
336172
337122
  } catch (error48) {
336173
337123
  safeLogError("Fetch Deposit Addresses confirmation failed", error48);
336174
337124
  const message = getErrorMessage(error48);
336175
337125
  ctx.wrappedCallback({
336176
- code: grpc7.status.INTERNAL,
337126
+ code: grpc9.status.INTERNAL,
336177
337127
  message: "Fetch Deposit Addresses confirmation failed: " + message
336178
337128
  }, null);
336179
337129
  }
336180
337130
  }
336181
337131
  async function handleFetchBalances(ctx) {
336182
- const {
336183
- call,
336184
- wrappedCallback,
336185
- policy,
336186
- brokers,
336187
- metadata,
336188
- normalizedCex,
336189
- cex: cex3,
336190
- symbol: symbol2,
336191
- selectedBrokerAccount,
336192
- broker,
336193
- verity,
336194
- applyVerityToBroker,
336195
- useVerity,
336196
- verityProverUrl,
336197
- otelMetrics
336198
- } = ctx;
336199
- const verityProof = verity.proof;
337132
+ const { call, cex: cex3, symbol: symbol2, broker } = ctx;
336200
337133
  try {
336201
- const payload = call.request.payload || {};
336202
- const providedBalanceType = payload.balanceType;
337134
+ const payload = {
337135
+ ...call.request.payload ?? {}
337136
+ };
337137
+ const providedBalanceType = typeof payload.balanceType === "string" ? payload.balanceType : undefined;
336203
337138
  const balanceType = (providedBalanceType ?? "total").toString();
336204
337139
  const validBalanceTypes = new Set(["free", "used", "total"]);
336205
337140
  if (!validBalanceTypes.has(balanceType)) {
336206
337141
  return ctx.wrappedCallback({
336207
- code: grpc7.status.INVALID_ARGUMENT,
337142
+ code: grpc9.status.INVALID_ARGUMENT,
336208
337143
  message: `ValidationError: invalid balanceType '${providedBalanceType}'. Expected one of: free | used | total`
336209
337144
  }, null);
336210
337145
  }
@@ -336245,33 +337180,16 @@ async function handleFetchBalances(ctx) {
336245
337180
  } catch (error48) {
336246
337181
  safeLogError(`Error fetching balance from ${cex3}`, error48);
336247
337182
  ctx.wrappedCallback({
336248
- code: grpc7.status.INTERNAL,
337183
+ code: grpc9.status.INTERNAL,
336249
337184
  message: `Failed to fetch balance from ${cex3}`
336250
337185
  }, null);
336251
337186
  }
336252
337187
  }
336253
337188
  async function handleFetchTicker(ctx) {
336254
- const {
336255
- call,
336256
- wrappedCallback,
336257
- policy,
336258
- brokers,
336259
- metadata,
336260
- normalizedCex,
336261
- cex: cex3,
336262
- symbol: symbol2,
336263
- selectedBrokerAccount,
336264
- broker,
336265
- verity,
336266
- applyVerityToBroker,
336267
- useVerity,
336268
- verityProverUrl,
336269
- otelMetrics
336270
- } = ctx;
336271
- const verityProof = verity.proof;
337189
+ const { cex: cex3, symbol: symbol2, broker } = ctx;
336272
337190
  if (!symbol2) {
336273
337191
  return ctx.wrappedCallback({
336274
- code: grpc7.status.INVALID_ARGUMENT,
337192
+ code: grpc9.status.INVALID_ARGUMENT,
336275
337193
  message: `ValidationError: Symbol required`
336276
337194
  }, null);
336277
337195
  }
@@ -336284,18 +337202,20 @@ async function handleFetchTicker(ctx) {
336284
337202
  } catch (error48) {
336285
337203
  safeLogError(`Error fetching ticker from ${cex3}`, error48);
336286
337204
  ctx.wrappedCallback({
336287
- code: grpc7.status.INTERNAL,
337205
+ code: grpc9.status.INTERNAL,
336288
337206
  message: `Failed to fetch ticker from ${cex3}`
336289
337207
  }, null);
336290
337208
  }
336291
337209
  }
336292
337210
  async function handlePassThrough(ctx) {
336293
337211
  if (ctx.action === Action.FetchCurrency)
336294
- return handleFetchCurrency(ctx);
337212
+ return handleFetchCurrencyEvidence(ctx);
336295
337213
  if (ctx.action === Action.FetchAccountId)
336296
337214
  return handleFetchAccountId(ctx);
336297
337215
  if (ctx.action === Action.FetchFees)
336298
- return handleFetchFees(ctx);
337216
+ return handleFetchFeesEvidence(ctx);
337217
+ if (ctx.action === Action.FetchMarketRules)
337218
+ return handleFetchMarketRulesEvidence(ctx);
336299
337219
  if (ctx.action === Action.FetchDepositAddresses)
336300
337220
  return handleFetchDepositAddresses(ctx);
336301
337221
  if (ctx.action === Action.FetchBalances)
@@ -336305,7 +337225,7 @@ async function handlePassThrough(ctx) {
336305
337225
  }
336306
337226
 
336307
337227
  // src/handlers/execute-action/perp-config.ts
336308
- var grpc8 = __toESM(require_src3(), 1);
337228
+ var grpc10 = __toESM(require_src3(), 1);
336309
337229
  function exchangeSupports(broker, capability) {
336310
337230
  return broker.has?.[capability] === true;
336311
337231
  }
@@ -336324,14 +337244,14 @@ async function handleGetPerpConfigState(ctx) {
336324
337244
  }
336325
337245
  if (!broker) {
336326
337246
  return wrappedCallback({
336327
- code: grpc8.status.INVALID_ARGUMENT,
337247
+ code: grpc10.status.INVALID_ARGUMENT,
336328
337248
  message: `Invalid CEX key: ${cex3}`
336329
337249
  }, null);
336330
337250
  }
336331
337251
  const exchange = broker;
336332
337252
  if (!exchangeSupports(exchange, "fetchPositions")) {
336333
337253
  return wrappedCallback({
336334
- code: grpc8.status.UNIMPLEMENTED,
337254
+ code: grpc10.status.UNIMPLEMENTED,
336335
337255
  message: `${normalizedCex} does not support fetchPositions`
336336
337256
  }, null);
336337
337257
  }
@@ -336348,7 +337268,7 @@ async function handleGetPerpConfigState(ctx) {
336348
337268
  } catch (error48) {
336349
337269
  safeLogError(`GetPerpConfigState failed for ${cex3}`, error48);
336350
337270
  ctx.wrappedCallback({
336351
- code: grpc8.status.INTERNAL,
337271
+ code: grpc10.status.INTERNAL,
336352
337272
  message: `GetPerpConfigState failed: ${sanitizeErrorDetail(error48)}`
336353
337273
  }, null);
336354
337274
  }
@@ -336361,14 +337281,14 @@ async function handleSetPerpConfigState(ctx) {
336361
337281
  }
336362
337282
  if (!broker) {
336363
337283
  return wrappedCallback({
336364
- code: grpc8.status.INVALID_ARGUMENT,
337284
+ code: grpc10.status.INVALID_ARGUMENT,
336365
337285
  message: `Invalid CEX key: ${cex3}`
336366
337286
  }, null);
336367
337287
  }
336368
337288
  const exchange = broker;
336369
337289
  if (!exchangeSupports(exchange, "setLeverage")) {
336370
337290
  return wrappedCallback({
336371
- code: grpc8.status.UNIMPLEMENTED,
337291
+ code: grpc10.status.UNIMPLEMENTED,
336372
337292
  message: `${normalizedCex} does not support setLeverage`
336373
337293
  }, null);
336374
337294
  }
@@ -336389,7 +337309,7 @@ async function handleSetPerpConfigState(ctx) {
336389
337309
  } catch (error48) {
336390
337310
  safeLogError(`SetPerpConfigState failed for ${cex3}`, error48);
336391
337311
  ctx.wrappedCallback({
336392
- code: grpc8.status.INTERNAL,
337312
+ code: grpc10.status.INTERNAL,
336393
337313
  message: `SetPerpConfigState failed: ${sanitizeErrorDetail(error48)}`
336394
337314
  }, null);
336395
337315
  }
@@ -336404,7 +337324,7 @@ async function handlePerpConfig(ctx) {
336404
337324
  }
336405
337325
 
336406
337326
  // src/handlers/execute-action/treasury-call.ts
336407
- var grpc9 = __toESM(require_src3(), 1);
337327
+ var grpc11 = __toESM(require_src3(), 1);
336408
337328
  async function handleTreasuryCall(ctx) {
336409
337329
  const { broker } = ctx;
336410
337330
  const callValue = parsePayloadForAction(ctx, CallPayloadSchema);
@@ -336415,7 +337335,7 @@ async function handleTreasuryCall(ctx) {
336415
337335
  try {
336416
337336
  if (callValue.functionName.startsWith("_") || callValue.functionName.includes("constructor") || callValue.functionName.includes("prototype")) {
336417
337337
  return ctx.wrappedCallback({
336418
- code: grpc9.status.PERMISSION_DENIED,
337338
+ code: grpc11.status.PERMISSION_DENIED,
336419
337339
  message: "Access to the requested function is denied"
336420
337340
  }, null);
336421
337341
  }
@@ -336430,7 +337350,7 @@ async function handleTreasuryCall(ctx) {
336430
337350
  const fn = broker[callValue.functionName];
336431
337351
  if (typeof fn !== "function" || broker.has?.[callValue.functionName] === false) {
336432
337352
  return ctx.wrappedCallback({
336433
- code: grpc9.status.INVALID_ARGUMENT,
337353
+ code: grpc11.status.INVALID_ARGUMENT,
336434
337354
  message: `Function not found on broker: ${callValue.functionName}`
336435
337355
  }, null);
336436
337356
  }
@@ -336509,7 +337429,7 @@ function asFiniteNumber(value) {
336509
337429
  }
336510
337430
 
336511
337431
  // src/handlers/execute-action/withdraw.ts
336512
- var grpc10 = __toESM(require_src3(), 1);
337432
+ var grpc12 = __toESM(require_src3(), 1);
336513
337433
  async function handleWithdraw(ctx) {
336514
337434
  const {
336515
337435
  call,
@@ -336532,7 +337452,7 @@ async function handleWithdraw(ctx) {
336532
337452
  const verityProof = verity.proof;
336533
337453
  if (!symbol2) {
336534
337454
  return ctx.wrappedCallback({
336535
- code: grpc10.status.INVALID_ARGUMENT,
337455
+ code: grpc12.status.INVALID_ARGUMENT,
336536
337456
  message: `ValidationError: Symbol required`
336537
337457
  }, null);
336538
337458
  }
@@ -336545,21 +337465,21 @@ async function handleWithdraw(ctx) {
336545
337465
  } catch (error48) {
336546
337466
  const message = getErrorMessage(error48);
336547
337467
  return ctx.wrappedCallback({
336548
- code: stableGrpcErrorCode(message) ?? grpc10.status.INVALID_ARGUMENT,
337468
+ code: stableGrpcErrorCode(message) ?? grpc12.status.INVALID_ARGUMENT,
336549
337469
  message
336550
337470
  }, null);
336551
337471
  }
336552
337472
  const transferValidation = validateWithdraw(policy, cex3, withdrawNetwork.brokerNetworkId, transferValue.recipientAddress, transferValue.amount, symbol2);
336553
337473
  if (!transferValidation.valid) {
336554
337474
  return ctx.wrappedCallback({
336555
- code: grpc10.status.PERMISSION_DENIED,
337475
+ code: grpc12.status.PERMISSION_DENIED,
336556
337476
  message: `policy_withdrawal_denied: ${transferValidation.error}`
336557
337477
  }, null);
336558
337478
  }
336559
337479
  const travelRule = resolveTravelRuleDecision(policy, cex3, transferValue.recipientAddress);
336560
337480
  if (travelRule.mode === "denied") {
336561
337481
  return ctx.wrappedCallback({
336562
- code: grpc10.status.FAILED_PRECONDITION,
337482
+ code: grpc12.status.FAILED_PRECONDITION,
336563
337483
  message: `travel_rule_denied: ${travelRule.error}`
336564
337484
  }, null);
336565
337485
  }
@@ -336626,7 +337546,7 @@ async function handleWithdraw(ctx) {
336626
337546
  payload: { recipientAddress: transferValue.recipientAddress }
336627
337547
  }
336628
337548
  });
336629
- const code = mapCcxtErrorToGrpcStatus(error48) ?? grpc10.status.INTERNAL;
337549
+ const code = mapCcxtErrorToGrpcStatus(error48) ?? grpc12.status.INTERNAL;
336630
337550
  ctx.wrappedCallback({
336631
337551
  code,
336632
337552
  message: `Withdraw failed: ${sanitizeErrorDetail(error48)}`
@@ -336635,33 +337555,152 @@ async function handleWithdraw(ctx) {
336635
337555
  }
336636
337556
 
336637
337557
  // src/handlers/execute-action/registry.ts
336638
- var ACTION_HANDLERS = {
336639
- [Action.Deposit]: handleDeposit,
336640
- [Action.Withdraw]: handleWithdraw,
336641
- [Action.Call]: handleTreasuryCall,
336642
- [Action.InternalTransfer]: handleInternalTransfer,
336643
- [Action.CreateOrder]: handleOrders,
336644
- [Action.GetOrderDetails]: handleOrders,
336645
- [Action.CancelOrder]: handleOrders,
336646
- [Action.FetchCurrency]: handlePassThrough,
336647
- [Action.FetchAccountId]: handlePassThrough,
336648
- [Action.FetchFees]: handlePassThrough,
336649
- [Action.FetchDepositAddresses]: handlePassThrough,
336650
- [Action.FetchBalances]: handlePassThrough,
336651
- [Action.FetchTicker]: handlePassThrough,
336652
- [Action.GetPerpConfigState]: handlePerpConfig,
336653
- [Action.SetPerpConfigState]: handlePerpConfig
337558
+ function valid() {
337559
+ return { valid: true };
337560
+ }
337561
+ function requireSymbol2(request) {
337562
+ return request.symbol?.trim() ? valid() : { valid: false, message: "symbol is required" };
337563
+ }
337564
+ function requireSpotSymbol(request) {
337565
+ const symbol2 = request.symbol?.trim() ?? "";
337566
+ return /^[^/\s]+\/[^/\s]+$/.test(symbol2) ? valid() : {
337567
+ valid: false,
337568
+ message: "symbol must be a slash-delimited spot pair"
337569
+ };
337570
+ }
337571
+ function validatePayload(schema, request) {
337572
+ const parsed = parsePayload(schema, request.payload);
337573
+ return parsed.success ? valid() : { valid: false, message: parsed.message };
337574
+ }
337575
+ function validateSymbolAndPayload(schema) {
337576
+ return (request) => {
337577
+ const symbolValidation = requireSymbol2(request);
337578
+ return symbolValidation.valid ? validatePayload(schema, request) : symbolValidation;
337579
+ };
337580
+ }
337581
+ function validateSpotSymbolAndPayload(schema) {
337582
+ return (request) => {
337583
+ const symbolValidation = requireSpotSymbol(request);
337584
+ return symbolValidation.valid ? validatePayload(schema, request) : symbolValidation;
337585
+ };
337586
+ }
337587
+ function validateFetchBalances(request) {
337588
+ const balanceType = request.payload?.balanceType;
337589
+ if (balanceType !== undefined && !new Set(["free", "used", "total"]).has(balanceType)) {
337590
+ return {
337591
+ valid: false,
337592
+ message: "balanceType must be free, used, or total"
337593
+ };
337594
+ }
337595
+ return valid();
337596
+ }
337597
+ var ACTION_DESCRIPTORS = {
337598
+ [Action.Deposit]: {
337599
+ handler: handleDeposit,
337600
+ access: "write",
337601
+ batchable: false
337602
+ },
337603
+ [Action.Withdraw]: {
337604
+ handler: handleWithdraw,
337605
+ access: "write",
337606
+ batchable: false
337607
+ },
337608
+ [Action.Call]: {
337609
+ handler: handleTreasuryCall,
337610
+ access: "write",
337611
+ batchable: false
337612
+ },
337613
+ [Action.InternalTransfer]: {
337614
+ handler: handleInternalTransfer,
337615
+ access: "write",
337616
+ batchable: false
337617
+ },
337618
+ [Action.CreateOrder]: {
337619
+ handler: handleOrders,
337620
+ access: "write",
337621
+ batchable: false
337622
+ },
337623
+ [Action.GetOrderDetails]: {
337624
+ handler: handleOrders,
337625
+ access: "read",
337626
+ batchable: false
337627
+ },
337628
+ [Action.CancelOrder]: {
337629
+ handler: handleOrders,
337630
+ access: "write",
337631
+ batchable: false
337632
+ },
337633
+ [Action.FetchCurrency]: {
337634
+ handler: handlePassThrough,
337635
+ access: "read",
337636
+ batchable: true,
337637
+ validateBatchRequest: validateSymbolAndPayload(FetchCurrencyPayloadSchema)
337638
+ },
337639
+ [Action.FetchAccountId]: {
337640
+ handler: handlePassThrough,
337641
+ access: "read",
337642
+ batchable: true,
337643
+ validateBatchRequest: (request) => validatePayload(EmptyActionPayloadSchema, request)
337644
+ },
337645
+ [Action.FetchFees]: {
337646
+ handler: handlePassThrough,
337647
+ access: "read",
337648
+ batchable: true,
337649
+ validateBatchRequest: validateSpotSymbolAndPayload(FetchFeesPayloadSchema)
337650
+ },
337651
+ [Action.FetchDepositAddresses]: {
337652
+ handler: handlePassThrough,
337653
+ access: "read",
337654
+ batchable: false
337655
+ },
337656
+ [Action.FetchBalances]: {
337657
+ handler: handlePassThrough,
337658
+ access: "read",
337659
+ batchable: true,
337660
+ validateBatchRequest: validateFetchBalances
337661
+ },
337662
+ [Action.FetchTicker]: {
337663
+ handler: handlePassThrough,
337664
+ access: "read",
337665
+ batchable: true,
337666
+ validateBatchRequest: validateSymbolAndPayload(EmptyActionPayloadSchema)
337667
+ },
337668
+ [Action.GetPerpConfigState]: {
337669
+ handler: handlePerpConfig,
337670
+ access: "read",
337671
+ batchable: true,
337672
+ validateBatchRequest: (request) => validatePayload(GetPerpConfigStatePayloadSchema, request)
337673
+ },
337674
+ [Action.SetPerpConfigState]: {
337675
+ handler: handlePerpConfig,
337676
+ access: "write",
337677
+ batchable: false
337678
+ },
337679
+ [Action.FetchMarketRules]: {
337680
+ handler: handlePassThrough,
337681
+ access: "read",
337682
+ batchable: true,
337683
+ validateBatchRequest: validateSpotSymbolAndPayload(EmptyActionPayloadSchema)
337684
+ },
337685
+ [Action.Batch]: {
337686
+ handler: (ctx) => handleBatch(ctx, getActionDescriptor),
337687
+ access: "read",
337688
+ batchable: false
337689
+ }
336654
337690
  };
337691
+ function getActionDescriptor(action) {
337692
+ return ACTION_DESCRIPTORS[action];
337693
+ }
336655
337694
  async function dispatchExecuteAction(ctx) {
336656
- const handler = ACTION_HANDLERS[ctx.action];
336657
- if (!handler) {
337695
+ const descriptor = getActionDescriptor(ctx.action);
337696
+ if (!descriptor) {
336658
337697
  ctx.wrappedCallback({
336659
- code: grpc11.status.INVALID_ARGUMENT,
337698
+ code: grpc13.status.INVALID_ARGUMENT,
336660
337699
  message: "Invalid Action"
336661
337700
  }, null);
336662
337701
  return;
336663
337702
  }
336664
- await handler(ctx);
337703
+ await descriptor.handler(ctx);
336665
337704
  }
336666
337705
 
336667
337706
  // src/handlers/execute-action/handler.ts
@@ -336677,7 +337716,7 @@ function grpcStatusName(error48) {
336677
337716
  if (typeof error48.code !== "number") {
336678
337717
  return "UNKNOWN";
336679
337718
  }
336680
- return grpc12.status[error48.code] ?? "UNKNOWN";
337719
+ return grpc14.status[error48.code] ?? "UNKNOWN";
336681
337720
  }
336682
337721
  function createExecuteActionHandler(deps) {
336683
337722
  const {
@@ -336725,7 +337764,7 @@ function createExecuteActionHandler(deps) {
336725
337764
  otelMetrics?.recordCounter("execute_action_errors_total", 1, {
336726
337765
  action: actionName,
336727
337766
  cex: cex3 || "unknown",
336728
- error_type: error48.code ? grpc12.status[error48.code] || "unknown" : "unknown"
337767
+ error_type: error48.code ? grpc14.status[error48.code] || "unknown" : "unknown"
336729
337768
  });
336730
337769
  } else {
336731
337770
  otelMetrics?.recordCounter("execute_action_success_total", 1, {
@@ -336748,13 +337787,13 @@ function createExecuteActionHandler(deps) {
336748
337787
  });
336749
337788
  if (!authenticateRequest(call, whitelistIps)) {
336750
337789
  return wrappedCallback({
336751
- code: grpc12.status.PERMISSION_DENIED,
337790
+ code: grpc14.status.PERMISSION_DENIED,
336752
337791
  message: "Access denied: Unauthorized IP"
336753
337792
  }, null);
336754
337793
  }
336755
337794
  if (!action || !cex3) {
336756
337795
  return wrappedCallback({
336757
- code: grpc12.status.INVALID_ARGUMENT,
337796
+ code: grpc14.status.INVALID_ARGUMENT,
336758
337797
  message: "`action` AND `cex` fields are required"
336759
337798
  }, null);
336760
337799
  }
@@ -336764,17 +337803,20 @@ function createExecuteActionHandler(deps) {
336764
337803
  const broker = selectedBrokerAccount?.exchange ?? createBroker(normalizedCex, call.metadata) ?? (isPublicMarketDataAction(action, call.request.payload) ? createPublicBroker(normalizedCex) : null);
336765
337804
  if (!broker) {
336766
337805
  return wrappedCallback({
336767
- code: grpc12.status.UNAUTHENTICATED,
337806
+ code: grpc14.status.UNAUTHENTICATED,
336768
337807
  message: `This Exchange is not registered and No API metadata was found`
336769
337808
  }, null);
336770
337809
  }
336771
337810
  const verity = { proof: "" };
336772
- const applyVerityToBroker = (targetBroker) => {
337811
+ const applyVerityToBroker = (targetBroker, proofState = verity) => {
336773
337812
  if (!useVerity)
336774
337813
  return;
336775
337814
  const override = buildHttpClientOverrideFromMetadata(metadata, verityProverUrl, (proof, notaryPubKey) => {
336776
- verity.proof = proof;
336777
- log.debug(`Verity proof:`, { proof, notaryPubKey });
337815
+ proofState.proof = proof;
337816
+ log.debug(`Verity proof received`, {
337817
+ has_proof: proof.length > 0,
337818
+ has_notary_public_key: Boolean(notaryPubKey)
337819
+ });
336778
337820
  });
336779
337821
  targetBroker.setHttpClientOverride(override, verityHttpClientOverridePredicate);
336780
337822
  };
@@ -336810,7 +337852,7 @@ function createExecuteActionHandler(deps) {
336810
337852
  } catch (error48) {
336811
337853
  safeLogError("ExecuteAction unhandled error", error48);
336812
337854
  return wrappedCallback({
336813
- code: grpc12.status.INTERNAL,
337855
+ code: grpc14.status.INTERNAL,
336814
337856
  message: "ExecuteAction failed unexpectedly"
336815
337857
  }, null);
336816
337858
  }
@@ -336870,7 +337912,7 @@ class SubscribeBrokerLifecycle {
336870
337912
  }
336871
337913
  }
336872
337914
  // src/handlers/subscribe/handler.ts
336873
- var grpc13 = __toESM(require_src3(), 1);
337915
+ var grpc15 = __toESM(require_src3(), 1);
336874
337916
 
336875
337917
  // src/helpers/binance-user-data-normalization.ts
336876
337918
  function requireQuantity(entry, key2) {
@@ -336981,8 +338023,8 @@ async function writeSubscribeError(call, isClosed, frame) {
336981
338023
  call.end();
336982
338024
  }
336983
338025
  }
336984
- function grpcStatusName2(status14) {
336985
- return typeof status14 === "number" ? grpc13.status[status14] ?? "UNKNOWN" : "UNKNOWN";
338026
+ function grpcStatusName2(status16) {
338027
+ return typeof status16 === "number" ? grpc15.status[status16] ?? "UNKNOWN" : "UNKNOWN";
336986
338028
  }
336987
338029
  function getBinanceEventMarketId(event) {
336988
338030
  const value = event.s;
@@ -337175,9 +338217,9 @@ function createSubscribeHandler(deps) {
337175
338217
  log.withMetadata(fields).info("Subscribe ended");
337176
338218
  }
337177
338219
  };
337178
- const writeTerminalError = async (frame, status14 = grpc13.status.UNKNOWN) => {
338220
+ const writeTerminalError = async (frame, status16 = grpc15.status.UNKNOWN) => {
337179
338221
  terminalOutcome = "error";
337180
- terminalGrpcStatus = grpcStatusName2(status14);
338222
+ terminalGrpcStatus = grpcStatusName2(status16);
337181
338223
  await writeSubscribeError(call, isStreamClosed, frame);
337182
338224
  };
337183
338225
  log.withMetadata(operationalFields).info("Subscribe started");
@@ -337213,7 +338255,7 @@ function createSubscribeHandler(deps) {
337213
338255
  error_type: "permission_denied"
337214
338256
  });
337215
338257
  call.emit("error", {
337216
- code: grpc13.status.PERMISSION_DENIED,
338258
+ code: grpc15.status.PERMISSION_DENIED,
337217
338259
  message: "Access denied: Unauthorized IP"
337218
338260
  }, null);
337219
338261
  call.destroy(new Error("Access denied: Unauthorized IP"));
@@ -337234,7 +338276,7 @@ function createSubscribeHandler(deps) {
337234
338276
  timestamp: Date.now(),
337235
338277
  symbol: symbol2 || "",
337236
338278
  type: subscriptionType2
337237
- }, grpc13.status.INVALID_ARGUMENT);
338279
+ }, grpc15.status.INVALID_ARGUMENT);
337238
338280
  return;
337239
338281
  }
337240
338282
  if (isPublicMarketDataSubscription(subscriptionType2)) {
@@ -337291,7 +338333,7 @@ function createSubscribeHandler(deps) {
337291
338333
  timestamp: Date.now(),
337292
338334
  symbol: symbol2,
337293
338335
  type: subscriptionType2
337294
- }, grpc13.status.NOT_FOUND);
338336
+ }, grpc15.status.NOT_FOUND);
337295
338337
  return;
337296
338338
  }
337297
338339
  if (!selectedBrokerAccount) {
@@ -337326,7 +338368,7 @@ function createSubscribeHandler(deps) {
337326
338368
  timestamp: Date.now(),
337327
338369
  symbol: resolvedSymbol,
337328
338370
  type: subscriptionType2
337329
- }, grpc13.status.FAILED_PRECONDITION);
338371
+ }, grpc15.status.FAILED_PRECONDITION);
337330
338372
  return;
337331
338373
  }
337332
338374
  const marketId = subscriptionType2 === SubscriptionType.ORDERS ? await getBinanceMarketId(accountBroker, resolvedSymbol) : undefined;
@@ -337340,7 +338382,7 @@ function createSubscribeHandler(deps) {
337340
338382
  timestamp: Date.now(),
337341
338383
  symbol: resolvedSymbol,
337342
338384
  type: subscriptionType2
337343
- }, grpc13.status.FAILED_PRECONDITION);
338385
+ }, grpc15.status.FAILED_PRECONDITION);
337344
338386
  return;
337345
338387
  }
337346
338388
  userDataSource = userDataStreamSupervisor.subscribe({
@@ -337398,7 +338440,7 @@ function createSubscribeHandler(deps) {
337398
338440
  timestamp: Date.now(),
337399
338441
  symbol: symbol2,
337400
338442
  type: subscriptionType2
337401
- }, grpc13.status.INVALID_ARGUMENT);
338443
+ }, grpc15.status.INVALID_ARGUMENT);
337402
338444
  }
337403
338445
  } catch (error48) {
337404
338446
  log.error("Error in Subscribe stream:", error48);
@@ -337408,7 +338450,7 @@ function createSubscribeHandler(deps) {
337408
338450
  timestamp: Date.now(),
337409
338451
  symbol: "",
337410
338452
  type: subscriptionType2
337411
- }, grpc13.status.INTERNAL);
338453
+ }, grpc15.status.INTERNAL);
337412
338454
  } finally {
337413
338455
  call.off("cancelled", closeOwnedBrokerOnCallEnd);
337414
338456
  call.off("error", closeOwnedBrokerOnCallEnd);
@@ -337540,7 +338582,9 @@ var descriptor = {
337540
338582
  FetchFees: 12,
337541
338583
  InternalTransfer: 13,
337542
338584
  GetPerpConfigState: 14,
337543
- SetPerpConfigState: 15
338585
+ SetPerpConfigState: 15,
338586
+ FetchMarketRules: 16,
338587
+ Batch: 17
337544
338588
  }
337545
338589
  }
337546
338590
  }
@@ -337562,10 +338606,10 @@ var PROTO_LOADER_OPTIONS = {
337562
338606
  var CEX_BROKER_PACKAGE_DEFINITION = protoLoader.fromJSON(node_descriptor_default, PROTO_LOADER_OPTIONS);
337563
338607
 
337564
338608
  // src/server.ts
337565
- var grpcObj = grpc14.loadPackageDefinition(CEX_BROKER_PACKAGE_DEFINITION);
338609
+ var grpcObj = grpc16.loadPackageDefinition(CEX_BROKER_PACKAGE_DEFINITION);
337566
338610
  var cexNode = grpcObj.cex_broker;
337567
338611
  function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, otelMetrics, brokerArchiver, orderActivityTracker, withdrawalObservationTracker, subscribeBrokerLifecycle, userDataStreamSupervisor, publicMarketDataFeedSupervisor) {
337568
- const server = new grpc14.Server;
338612
+ const server = new grpc16.Server;
337569
338613
  server.addService(cexNode.cex_service.service, {
337570
338614
  ExecuteAction: createExecuteActionHandler({
337571
338615
  policy,
@@ -337954,7 +338998,10 @@ class CEXBroker {
337954
338998
  await this.otelMetrics.initialize();
337955
338999
  }
337956
339000
  if (!this.userDataStreamSupervisor && Object.keys(this.brokers).length > 0) {
337957
- const publisher = new StreamHealthPublisher(streamHealthPublisherConfigFromEnv());
339001
+ const publisher = new StreamHealthPublisher({
339002
+ ...streamHealthPublisherConfigFromEnv(),
339003
+ producerId: USER_DATA_STREAM_HEALTH_PRODUCER_ID
339004
+ });
337958
339005
  this.userDataStreamSupervisor = new UserDataStreamSupervisor({
337959
339006
  brokers: this.brokers,
337960
339007
  publisher
@@ -337967,7 +339014,7 @@ class CEXBroker {
337967
339014
  otelMetrics: this.otelMetrics
337968
339015
  });
337969
339016
  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);
337970
- this.server.bindAsync(`0.0.0.0:${this.port}`, grpc15.ServerCredentials.createInsecure(), (err2, port) => {
339017
+ this.server.bindAsync(`0.0.0.0:${this.port}`, grpc17.ServerCredentials.createInsecure(), (err2, port) => {
337971
339018
  if (err2) {
337972
339019
  log.error(err2);
337973
339020
  return;
@@ -337992,7 +339039,8 @@ class CEXBroker {
337992
339039
  this.depositArchivePoller = new DepositArchivePoller({
337993
339040
  brokers: this.brokers,
337994
339041
  archiver: this.brokerArchiver,
337995
- metrics: this.otelMetrics
339042
+ metrics: this.otelMetrics,
339043
+ coveragePublisher: Object.keys(this.brokers).length > 0 ? new StreamHealthPublisher(depositPollerStreamHealthPublisherConfigFromEnv()) : undefined
337996
339044
  });
337997
339045
  this.depositArchivePoller.start();
337998
339046
  if (this.userDataStreamSupervisor) {