@usherlabs/cex-broker 0.3.0 → 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.
package/dist/index.js CHANGED
@@ -50010,7 +50010,7 @@ var require_src13 = __commonJS((exports) => {
50010
50010
  });
50011
50011
 
50012
50012
  // src/index.ts
50013
- import * as grpc15 from "@grpc/grpc-js";
50013
+ import * as grpc17 from "@grpc/grpc-js";
50014
50014
 
50015
50015
  // node_modules/@usherlabs/ccxt/js/src/base/functions.js
50016
50016
  var exports_functions = {};
@@ -290094,7 +290094,9 @@ var Action = {
290094
290094
  FetchFees: 12,
290095
290095
  InternalTransfer: 13,
290096
290096
  GetPerpConfigState: 14,
290097
- SetPerpConfigState: 15
290097
+ SetPerpConfigState: 15,
290098
+ FetchMarketRules: 16,
290099
+ Batch: 17
290098
290100
  };
290099
290101
  var SubscriptionType = {
290100
290102
  NO_ACTION: 0,
@@ -290642,6 +290644,30 @@ function redactUnknownValue(value, secretLiterals) {
290642
290644
  }
290643
290645
  return String(value);
290644
290646
  }
290647
+ function removeSecretMaterial(value, secretLiterals = []) {
290648
+ if (value === null || value === undefined) {
290649
+ return value;
290650
+ }
290651
+ if (typeof value === "string") {
290652
+ return redactSecretLiterals(value, secretLiterals);
290653
+ }
290654
+ if (typeof value === "number" || typeof value === "boolean") {
290655
+ return value;
290656
+ }
290657
+ if (Array.isArray(value)) {
290658
+ return value.map((entry) => removeSecretMaterial(entry, secretLiterals));
290659
+ }
290660
+ if (typeof value === "object") {
290661
+ const clean = {};
290662
+ for (const [key, entry] of Object.entries(value)) {
290663
+ if (!SECRET_KEY_PATTERN.test(key)) {
290664
+ clean[key] = removeSecretMaterial(entry, secretLiterals);
290665
+ }
290666
+ }
290667
+ return clean;
290668
+ }
290669
+ return String(value);
290670
+ }
290645
290671
  function redactStreamPayload(payload, secretLiterals = []) {
290646
290672
  if (Array.isArray(payload)) {
290647
290673
  return {
@@ -292744,6 +292770,17 @@ function withTimeout(promise, timeoutMs, label) {
292744
292770
  return Promise.race([promise, expiry]).finally(() => clearTimeout(timer));
292745
292771
  }
292746
292772
  var ALL_CURRENCIES_CODE = "*";
292773
+ var MAX_FAILURE_REASON_CHARS = 256;
292774
+ var DEFAULT_FAILURE_REASON = "fetchDeposits failed";
292775
+ function decimal(value) {
292776
+ return value === null || value === undefined ? null : String(value);
292777
+ }
292778
+ function failureReason(error, exchange) {
292779
+ const text = error instanceof Error ? error.message : typeof error === "string" ? error : "";
292780
+ const secrets = [exchange.apiKey, exchange.secret].filter((value) => typeof value === "string" && value.length > 0);
292781
+ const trimmed = redactSecretLiterals(text, secrets).replace(/\s+/g, " ").trim().slice(0, MAX_FAILURE_REASON_CHARS);
292782
+ return trimmed.length > 0 ? trimmed : DEFAULT_FAILURE_REASON;
292783
+ }
292747
292784
  var BINANCE_UNLOCK_PROGRESS_SOURCE = {
292748
292785
  venue: "binance",
292749
292786
  endpoint: "GET /sapi/v1/capital/deposit/hisrec",
@@ -292754,6 +292791,20 @@ var BINANCE_UNLOCK_PROGRESS_SOURCE = {
292754
292791
  completeTime: "info.completeTime"
292755
292792
  }
292756
292793
  };
292794
+ function observedProgress(progress) {
292795
+ if (progress === undefined)
292796
+ return null;
292797
+ return {
292798
+ state: progress.state,
292799
+ progress_state: progress.progress_state,
292800
+ reason: progress.reason,
292801
+ native_status: decimal(progress.native_status),
292802
+ current: decimal(progress.current),
292803
+ credit_required: decimal(progress.credit_required),
292804
+ unlock_required: decimal(progress.unlock_required),
292805
+ complete_time: decimal(progress.complete_time)
292806
+ };
292807
+ }
292757
292808
  function depositTimestamp(record) {
292758
292809
  const observedAt = depositField(record, [
292759
292810
  "timestamp",
@@ -292983,6 +293034,7 @@ class DepositArchivePoller {
292983
293034
  #cursors = new Map;
292984
293035
  #lastArchivedByTarget = new Map;
292985
293036
  #unsupportedLogged = new Set;
293037
+ #coverage = new Map;
292986
293038
  #config;
292987
293039
  constructor(params) {
292988
293040
  this.params = params;
@@ -292993,6 +293045,7 @@ class DepositArchivePoller {
292993
293045
  return;
292994
293046
  }
292995
293047
  log.info("\uD83D\uDCE5 Deposit archive poller started");
293048
+ this.params.coveragePublisher?.start();
292996
293049
  this.#schedule(0);
292997
293050
  }
292998
293051
  async stop() {
@@ -293002,6 +293055,7 @@ class DepositArchivePoller {
293002
293055
  this.#timer = null;
293003
293056
  }
293004
293057
  await this.#running;
293058
+ await this.params.coveragePublisher?.close(this.#coverageSnapshots());
293005
293059
  }
293006
293060
  async pollAllOnce() {
293007
293061
  if (this.#stopped || this.#running || !this.params.archiver.isEnabled()) {
@@ -293037,14 +293091,90 @@ class DepositArchivePoller {
293037
293091
  return true;
293038
293092
  }
293039
293093
  async#pollOne(target) {
293094
+ const attemptedAt = new Date().toISOString();
293040
293095
  let outcome = "error";
293096
+ let observation;
293041
293097
  try {
293042
- outcome = await this.#pollTarget(target);
293098
+ const result = await this.#pollTarget(target, attemptedAt);
293099
+ outcome = result.outcome;
293100
+ observation = result.observation;
293101
+ } catch (error) {
293102
+ observation = this.#observation("error", attemptedAt, {
293103
+ errorReason: failureReason(error, target.account.exchange)
293104
+ });
293105
+ throw error;
293043
293106
  } finally {
293044
293107
  this.params.metrics?.recordCounter("cex_deposit_poller_polls_total", 1, { exchange: target.exchangeId, outcome });
293108
+ this.#recordCoverage(target, outcome, observation ?? this.#observation("error", attemptedAt, {
293109
+ errorReason: "poll aborted before completion"
293110
+ }));
293045
293111
  }
293046
293112
  }
293047
- async#pollTarget(target) {
293113
+ #observation(disposition, attemptedAt, detail = {}) {
293114
+ if (disposition === "success" && detail.completedAt !== undefined && detail.completedAt < attemptedAt) {
293115
+ return this.#observation("error", attemptedAt, {
293116
+ requestSinceMs: detail.requestSinceMs,
293117
+ errorReason: `source clock invalid: completed ${detail.completedAt} before attempted ${attemptedAt}`
293118
+ });
293119
+ }
293120
+ return {
293121
+ version: "1",
293122
+ disposition,
293123
+ attempted_at: attemptedAt,
293124
+ completed_at: disposition === "success" ? detail.completedAt ?? null : null,
293125
+ poll_interval_ms: String(this.#config.pollIntervalMs),
293126
+ fetch_timeout_ms: String(this.#config.fetchTimeoutMs),
293127
+ deposits_limit: String(this.#config.depositsLimit),
293128
+ request_since_ms: decimal(detail.requestSinceMs),
293129
+ next_cursor_ms: disposition === "success" ? decimal(detail.nextCursorMs) : null,
293130
+ response_truncated: disposition === "success" ? detail.responseTruncated ?? null : null,
293131
+ malformed_count: String(detail.malformedCount ?? 0),
293132
+ error_reason: disposition === "error" ? detail.errorReason?.trim() || DEFAULT_FAILURE_REASON : "",
293133
+ observed_deposits: disposition === "success" ? detail.observedDeposits ?? [] : []
293134
+ };
293135
+ }
293136
+ #recordCoverage(target, outcome, observation) {
293137
+ const publisher = this.params.coveragePublisher;
293138
+ if (!publisher)
293139
+ return;
293140
+ const key = this.#targetKey(target);
293141
+ const previous = this.#coverage.get(key);
293142
+ const now3 = observation.completed_at ?? new Date().toISOString();
293143
+ const state = outcome === "ok" ? "connected" : "error";
293144
+ const base2 = previous?.snapshot;
293145
+ const stateChangedAt = base2 && base2.state === state ? base2.stateChangedAt : now3;
293146
+ const attempts = BigInt(base2?.connectAttemptCount ?? "0") + 1n;
293147
+ const errors = BigInt(base2?.errorCount ?? "0") + (outcome === "error" ? 1n : 0n);
293148
+ const reconnects = BigInt(base2?.reconnectCount ?? "0") + (outcome === "ok" && previous?.lastOutcome === "error" ? 1n : 0n);
293149
+ const snapshot = {
293150
+ exchange: target.exchangeId,
293151
+ accountSelector: target.account.label,
293152
+ accountRole: target.account.role,
293153
+ streamKind: "deposit_poller",
293154
+ accountScope: "spot",
293155
+ registryStatus: "active",
293156
+ retiredAt: null,
293157
+ state,
293158
+ stateChangedAt,
293159
+ lastConnectedAt: outcome === "ok" ? observation.completed_at : base2?.lastConnectedAt ?? null,
293160
+ lastAuthenticatedAt: null,
293161
+ lastReceivedAt: outcome === "ok" ? observation.completed_at : base2?.lastReceivedAt ?? null,
293162
+ connectAttemptCount: attempts.toString(),
293163
+ reconnectCount: reconnects.toString(),
293164
+ errorCount: errors.toString(),
293165
+ lastFailureKind: outcome === "ok" ? "none" : outcome === "unsupported" ? "unsupported_connector" : "transport_error",
293166
+ lastFailureReason: outcome === "ok" ? "" : outcome === "unsupported" ? "fetchDeposits unsupported" : observation.error_reason,
293167
+ trafficMode: "continuous",
293168
+ sourceWatermark: null,
293169
+ pollObservation: observation
293170
+ };
293171
+ this.#coverage.set(key, { snapshot, lastOutcome: outcome });
293172
+ publisher.publish(this.#coverageSnapshots());
293173
+ }
293174
+ #coverageSnapshots() {
293175
+ return [...this.#coverage.values()].map((entry) => entry.snapshot);
293176
+ }
293177
+ async#pollTarget(target, attemptedAt) {
293048
293178
  const exchange = target.account.exchange;
293049
293179
  const key = this.#targetKey(target);
293050
293180
  if (typeof exchange.fetchDeposits !== "function" || exchange.has?.fetchDeposits === false) {
@@ -293055,7 +293185,10 @@ class DepositArchivePoller {
293055
293185
  account: target.account.label
293056
293186
  });
293057
293187
  }
293058
- return "unsupported";
293188
+ return {
293189
+ outcome: "unsupported",
293190
+ observation: this.#observation("unsupported", attemptedAt)
293191
+ };
293059
293192
  }
293060
293193
  const since = this.#cursors.get(key) ?? Date.now() - this.#config.lookbackMs;
293061
293194
  let deposits;
@@ -293068,15 +293201,48 @@ class DepositArchivePoller {
293068
293201
  account: target.account.label,
293069
293202
  error
293070
293203
  });
293071
- return "error";
293204
+ return {
293205
+ outcome: "error",
293206
+ observation: this.#observation("error", attemptedAt, {
293207
+ requestSinceMs: since,
293208
+ errorReason: failureReason(error, exchange)
293209
+ })
293210
+ };
293072
293211
  }
293073
- if (!Array.isArray(deposits) || deposits.length === 0) {
293074
- return "ok";
293212
+ if (!Array.isArray(deposits)) {
293213
+ return {
293214
+ outcome: "error",
293215
+ observation: this.#observation("error", attemptedAt, {
293216
+ requestSinceMs: since,
293217
+ errorReason: "fetchDeposits returned a non-array response"
293218
+ })
293219
+ };
293220
+ }
293221
+ if (deposits.length > this.#config.depositsLimit) {
293222
+ return {
293223
+ outcome: "error",
293224
+ observation: this.#observation("error", attemptedAt, {
293225
+ requestSinceMs: since,
293226
+ errorReason: `fetchDeposits returned ${deposits.length} rows for a limit of ${this.#config.depositsLimit}`
293227
+ })
293228
+ };
293229
+ }
293230
+ if (deposits.length === 0) {
293231
+ return this.#completed(this.#observation("success", attemptedAt, {
293232
+ completedAt: new Date().toISOString(),
293233
+ requestSinceMs: since,
293234
+ nextCursorMs: this.#cursors.get(key),
293235
+ responseTruncated: false,
293236
+ observedDeposits: []
293237
+ }));
293075
293238
  }
293076
293239
  let archived = 0;
293240
+ let malformed = 0;
293241
+ const observed = [];
293077
293242
  for (const deposit of deposits) {
293078
293243
  const record = asRecord(deposit);
293079
293244
  if (!record) {
293245
+ malformed += 1;
293080
293246
  continue;
293081
293247
  }
293082
293248
  const info = asRecord(record.info);
@@ -293101,6 +293267,16 @@ class DepositArchivePoller {
293101
293267
  });
293102
293268
  const lastArchived = identity === undefined ? undefined : this.#lastArchivedByTarget.get(key)?.get(identity);
293103
293269
  const classification = classifyDeposit(target.exchangeId, record, lastArchived);
293270
+ const depositTimestampMs = depositTimestamp(record);
293271
+ observed.push({
293272
+ coin: assetSymbol === undefined ? "" : String(assetSymbol),
293273
+ network: network === undefined ? "" : String(network),
293274
+ external_id: depositTxid ?? "",
293275
+ txid: depositTxid ?? "",
293276
+ deposit_timestamp_ms: decimal(depositTimestampMs),
293277
+ status: classification.archiveStatus,
293278
+ progress: observedProgress(classification.unlockProgress)
293279
+ });
293104
293280
  if (lastArchived && lastArchived.status === classification.archiveStatus && lastArchived.progressKey === classification.progressKey) {
293105
293281
  continue;
293106
293282
  }
@@ -293141,7 +293317,7 @@ class DepositArchivePoller {
293141
293317
  }
293142
293318
  targetDeposits2.set(identity, {
293143
293319
  status: classification.archiveStatus,
293144
- timestamp: depositTimestamp(record),
293320
+ timestamp: depositTimestampMs,
293145
293321
  progressKey: classification.progressKey,
293146
293322
  highWatermark: classification.highWatermark
293147
293323
  });
@@ -293164,7 +293340,20 @@ class DepositArchivePoller {
293164
293340
  this.#lastArchivedByTarget.delete(key);
293165
293341
  }
293166
293342
  }
293167
- return "ok";
293343
+ return this.#completed(this.#observation("success", attemptedAt, {
293344
+ completedAt: new Date().toISOString(),
293345
+ requestSinceMs: since,
293346
+ nextCursorMs: nextCursor,
293347
+ responseTruncated: deposits.length >= this.#config.depositsLimit,
293348
+ malformedCount: malformed,
293349
+ observedDeposits: observed
293350
+ }));
293351
+ }
293352
+ #completed(observation) {
293353
+ return {
293354
+ outcome: observation.disposition === "success" ? "ok" : "error",
293355
+ observation
293356
+ };
293168
293357
  }
293169
293358
  #targetKey(target) {
293170
293359
  return `${target.exchangeId}|${target.account.label}|${target.code}`;
@@ -296042,7 +296231,8 @@ import { request as httpsRequest3 } from "node:https";
296042
296231
  import { dirname as dirname2 } from "node:path";
296043
296232
  var SOURCE = "broker_write";
296044
296233
  var TABLE = "broker_stream_health.snapshots";
296045
- var PRODUCER_ID = "cex-broker-user-data";
296234
+ var USER_DATA_STREAM_HEALTH_PRODUCER_ID = "cex-broker-user-data";
296235
+ var DEPOSIT_POLLER_STREAM_HEALTH_PRODUCER_ID = "cex-broker-deposit-poller";
296046
296236
  var STATE_VERSION = 1;
296047
296237
  var HEARTBEAT_MS = 30000;
296048
296238
  var FORWARDER_TIMEOUT_MS = 3000;
@@ -296077,11 +296267,11 @@ function registryRevision(snapshots) {
296077
296267
  })).sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
296078
296268
  return createHash5("sha256").update(JSON.stringify(rows)).digest("hex");
296079
296269
  }
296080
- function validState(value) {
296270
+ function validState(value, producerId) {
296081
296271
  if (!value || typeof value !== "object" || Array.isArray(value))
296082
296272
  return false;
296083
296273
  const state = value;
296084
- return state.version === STATE_VERSION && state.producerId === PRODUCER_ID && typeof state.producerEpoch === "string" && typeof state.runId === "string" && typeof state.nextBatchSequence === "string" && state.nextStreamSequences !== null && typeof state.nextStreamSequences === "object" && (state.pendingBody === undefined || typeof state.pendingBody === "string");
296274
+ return state.version === STATE_VERSION && state.producerId === producerId && typeof state.producerEpoch === "string" && typeof state.runId === "string" && typeof state.nextBatchSequence === "string" && state.nextStreamSequences !== null && typeof state.nextStreamSequences === "object" && (state.pendingBody === undefined || typeof state.pendingBody === "string");
296085
296275
  }
296086
296276
  function forwarderPost(url2, body, authToken, timeoutMs) {
296087
296277
  const request = url2.protocol === "http:" ? httpRequest3 : httpsRequest3;
@@ -296111,6 +296301,7 @@ function forwarderPost(url2, body, authToken, timeoutMs) {
296111
296301
  }
296112
296302
 
296113
296303
  class StreamHealthPublisher {
296304
+ #producerId;
296114
296305
  #deploymentId;
296115
296306
  #statePath;
296116
296307
  #heartbeatMs;
@@ -296125,6 +296316,7 @@ class StreamHealthPublisher {
296125
296316
  #retry = null;
296126
296317
  #retryAttempt = 0;
296127
296318
  constructor(options) {
296319
+ this.#producerId = identifier(options.producerId, "producer_id");
296128
296320
  this.#deploymentId = identifier(options.deploymentId, "deployment_id");
296129
296321
  this.#statePath = options.statePath.trim();
296130
296322
  if (!this.#statePath) {
@@ -296149,7 +296341,7 @@ class StreamHealthPublisher {
296149
296341
  const loaded = this.#read();
296150
296342
  this.#state = loaded ?? {
296151
296343
  version: STATE_VERSION,
296152
- producerId: PRODUCER_ID,
296344
+ producerId: this.#producerId,
296153
296345
  producerEpoch: "1",
296154
296346
  runId: randomUUID3(),
296155
296347
  nextBatchSequence: "1",
@@ -296262,7 +296454,7 @@ class StreamHealthPublisher {
296262
296454
  return {
296263
296455
  table: TABLE,
296264
296456
  row: {
296265
- producer_id: PRODUCER_ID,
296457
+ producer_id: this.#producerId,
296266
296458
  producer_epoch: this.#state.producerEpoch,
296267
296459
  run_id: this.#state.runId,
296268
296460
  batch_sequence: batchSequence,
@@ -296289,7 +296481,8 @@ class StreamHealthPublisher {
296289
296481
  last_failure_kind: snapshot.lastFailureKind,
296290
296482
  last_failure_reason: snapshot.lastFailureReason,
296291
296483
  traffic_mode: snapshot.trafficMode,
296292
- source_watermark: snapshot.sourceWatermark
296484
+ source_watermark: snapshot.sourceWatermark,
296485
+ ...snapshot.streamKind === "deposit_poller" ? { poll_observation: snapshot.pollObservation } : {}
296293
296486
  }
296294
296487
  };
296295
296488
  });
@@ -296317,7 +296510,7 @@ class StreamHealthPublisher {
296317
296510
  #read() {
296318
296511
  try {
296319
296512
  const parsed = JSON.parse(readFileSync2(this.#statePath, "utf8"));
296320
- if (!validState(parsed))
296513
+ if (!validState(parsed, this.#producerId))
296321
296514
  throw new Error("invalid state shape");
296322
296515
  return parsed;
296323
296516
  } catch (error) {
@@ -296380,6 +296573,14 @@ function streamHealthPublisherConfigFromEnv(env = process.env) {
296380
296573
  forwarderAuthToken: env.CEX_BROKER_ARCHIVE_FORWARDER_TOKEN?.trim() || undefined
296381
296574
  };
296382
296575
  }
296576
+ function depositPollerStreamHealthPublisherConfigFromEnv(env = process.env) {
296577
+ const base2 = streamHealthPublisherConfigFromEnv(env);
296578
+ return {
296579
+ ...base2,
296580
+ producerId: DEPOSIT_POLLER_STREAM_HEALTH_PRODUCER_ID,
296581
+ statePath: `${base2.statePath}.deposit-poller`
296582
+ };
296583
+ }
296383
296584
 
296384
296585
  // src/helpers/user-asset-archive-poller.ts
296385
296586
  var DEFAULT_CONFIG4 = {
@@ -296950,7 +297151,7 @@ class AccountWorker {
296950
297151
  for (const subscriber of [...this.#subscribers])
296951
297152
  subscriber.close();
296952
297153
  }
296953
- #transition(state, failureKind, failureReason) {
297154
+ #transition(state, failureKind, failureReason2) {
296954
297155
  const timestamp = now3();
296955
297156
  if (this.#snapshot.state !== state) {
296956
297157
  this.#snapshot.state = state;
@@ -296958,7 +297159,7 @@ class AccountWorker {
296958
297159
  }
296959
297160
  if (failureKind) {
296960
297161
  this.#snapshot.lastFailureKind = failureKind;
296961
- this.#snapshot.lastFailureReason = failureReason ?? "";
297162
+ this.#snapshot.lastFailureReason = failureReason2 ?? "";
296962
297163
  }
296963
297164
  this.onChange();
296964
297165
  }
@@ -297082,7 +297283,7 @@ class UserDataStreamSupervisor {
297082
297283
  }
297083
297284
 
297084
297285
  // src/server.ts
297085
- import * as grpc14 from "@grpc/grpc-js";
297286
+ import * as grpc16 from "@grpc/grpc-js";
297086
297287
 
297087
297288
  // src/handlers/execute-action/deposit.ts
297088
297289
  import * as grpc3 from "@grpc/grpc-js";
@@ -310734,7 +310935,10 @@ config(en_default());
310734
310935
  // src/schemas/action-payloads.ts
310735
310936
  var parseJsonString = (value) => {
310736
310937
  if (typeof value !== "string") {
310737
- return value;
310938
+ if (value === null || value === undefined || typeof value === "number" || typeof value === "boolean" || typeof value === "object") {
310939
+ return value;
310940
+ }
310941
+ return String(value);
310738
310942
  }
310739
310943
  try {
310740
310944
  return JSON.parse(value);
@@ -310743,19 +310947,6 @@ var parseJsonString = (value) => {
310743
310947
  }
310744
310948
  };
310745
310949
  var stringNumberRecordSchema = exports_external.record(exports_external.string(), exports_external.union([exports_external.string(), exports_external.number()]));
310746
- var booleanLikeSchema = exports_external.preprocess((value) => {
310747
- if (typeof value !== "string") {
310748
- return value;
310749
- }
310750
- const normalized = value.trim().toLowerCase();
310751
- if (["true", "1", "yes"].includes(normalized)) {
310752
- return true;
310753
- }
310754
- if (["false", "0", "no"].includes(normalized)) {
310755
- return false;
310756
- }
310757
- return value;
310758
- }, exports_external.boolean());
310759
310950
  var DepositPayloadSchema = exports_external.object({
310760
310951
  recipientAddress: exports_external.string().min(1),
310761
310952
  amount: exports_external.coerce.number().positive(),
@@ -310816,10 +311007,22 @@ var CancelOrderPayloadSchema = exports_external.object({
310816
311007
  orderId: exports_external.string().min(1),
310817
311008
  params: exports_external.preprocess(parseJsonString, stringNumberRecordSchema).default({})
310818
311009
  });
310819
- var FetchFeesPayloadSchema = exports_external.object({
310820
- includeAllFees: booleanLikeSchema.optional().default(false),
310821
- includeFundingFees: booleanLikeSchema.optional()
310822
- });
311010
+ var EmptyActionPayloadSchema = exports_external.object({}).strict();
311011
+ var FetchFeesPayloadSchema = EmptyActionPayloadSchema;
311012
+ var FetchCurrencyPayloadSchema = exports_external.object({
311013
+ network: exports_external.string().trim().min(1)
311014
+ }).strict();
311015
+ var MAX_BATCH_CHILDREN = 32;
311016
+ var MAX_BATCH_REQUEST_BYTES = 256 * 1024;
311017
+ var BatchChildRequestSchema = exports_external.object({
311018
+ id: exports_external.string().trim().min(1),
311019
+ action: exports_external.number().int().nonnegative(),
311020
+ symbol: exports_external.string(),
311021
+ payload: exports_external.record(exports_external.string(), exports_external.string())
311022
+ }).strict();
311023
+ var BatchPayloadSchema = exports_external.object({
311024
+ requests: exports_external.preprocess(parseJsonString, exports_external.array(BatchChildRequestSchema).min(1).max(MAX_BATCH_CHILDREN))
311025
+ }).strict();
310823
311026
 
310824
311027
  // src/helpers/grpc/callbacks.ts
310825
311028
  import * as grpc from "@grpc/grpc-js";
@@ -310868,7 +311071,7 @@ function stableGrpcErrorCode(message) {
310868
311071
  if (message.startsWith("AuthenticationError:")) {
310869
311072
  return grpc2.status.UNAUTHENTICATED;
310870
311073
  }
310871
- if (message.startsWith("InsufficientFunds:")) {
311074
+ if (message.startsWith("InsufficientFunds:") || message.startsWith("fee_unavailable:")) {
310872
311075
  return grpc2.status.FAILED_PRECONDITION;
310873
311076
  }
310874
311077
  if (message.startsWith("venue_discovery_unavailable:")) {
@@ -310928,6 +311131,13 @@ function resolveGrpcError(error48, message) {
310928
311131
  }
310929
311132
 
310930
311133
  // src/handlers/execute-action/context.ts
311134
+ function requireSymbol(ctx, message = "ValidationError: Symbol required") {
311135
+ if (!ctx.symbol) {
311136
+ ctx.wrappedCallback(invalidArgumentError(message), null);
311137
+ return false;
311138
+ }
311139
+ return true;
311140
+ }
310931
311141
  function parsePayloadForAction(ctx, schema) {
310932
311142
  return parseActionPayload(schema, ctx.call.request.payload, ctx.wrappedCallback);
310933
311143
  }
@@ -310950,6 +311160,12 @@ function rejectWithGrpcError(ctx, error48, options) {
310950
311160
  }
310951
311161
  ctx.wrappedCallback({ code, message: finalMessage }, null);
310952
311162
  }
311163
+ function successWithProof(ctx, result) {
311164
+ ctx.wrappedCallback(null, {
311165
+ proof: ctx.verity.proof,
311166
+ result: JSON.stringify(result)
311167
+ });
311168
+ }
310953
311169
 
310954
311170
  // src/handlers/execute-action/deposit.ts
310955
311171
  async function handleDeposit(ctx) {
@@ -311127,7 +311343,7 @@ async function handleDeposit(ctx) {
311127
311343
  }
311128
311344
  }
311129
311345
  // src/handlers/execute-action/handler.ts
311130
- import * as grpc12 from "@grpc/grpc-js";
311346
+ import * as grpc14 from "@grpc/grpc-js";
311131
311347
 
311132
311348
  // src/helpers/grpc/broker.ts
311133
311349
  function selectBrokerAccountForCex(normalizedCex, brokers, metadata) {
@@ -311259,10 +311475,458 @@ async function handleOrderBookCall(ctx) {
311259
311475
  }
311260
311476
 
311261
311477
  // src/handlers/execute-action/registry.ts
311262
- import * as grpc11 from "@grpc/grpc-js";
311478
+ import * as grpc13 from "@grpc/grpc-js";
311263
311479
 
311264
- // src/handlers/execute-action/internal-transfer.ts
311480
+ // src/handlers/execute-action/batch.ts
311265
311481
  import * as grpc5 from "@grpc/grpc-js";
311482
+
311483
+ // src/helpers/venue-evidence.ts
311484
+ var DECIMAL_PATTERN = /^\+?(\d+)(?:\.(\d*))?(?:[eE]([+-]?\d+))?$/;
311485
+ function exchangeSecretLiterals(broker) {
311486
+ const record2 = broker;
311487
+ return [
311488
+ record2.apiKey,
311489
+ record2.secret,
311490
+ record2.password,
311491
+ record2.privateKey
311492
+ ].filter((value) => typeof value === "string" && value.length > 0);
311493
+ }
311494
+ function sanitizeVenueError(error48, broker) {
311495
+ return redactSecretLiterals(sanitizeErrorDetail(error48), exchangeSecretLiterals(broker));
311496
+ }
311497
+ function resolveEvidenceAccountScope(selectedBrokerAccount, metadata) {
311498
+ return {
311499
+ accountSelector: selectedBrokerAccount?.label ?? getCurrentBrokerSelector(metadata),
311500
+ credentialSource: selectedBrokerAccount ? "configured_pool" : "request_metadata"
311501
+ };
311502
+ }
311503
+ function canonicalNonnegativeDecimal(value, field) {
311504
+ const text = typeof value === "number" ? Number.isFinite(value) ? String(value) : "" : typeof value === "string" ? value.trim() : "";
311505
+ const match = text.match(DECIMAL_PATTERN);
311506
+ if (!match) {
311507
+ throw new Error(`venue_discovery_unavailable: ${field} must be decimal`);
311508
+ }
311509
+ const integer2 = match[1] ?? "0";
311510
+ const fraction = match[2] ?? "";
311511
+ const exponent = Number.parseInt(match[3] ?? "0", 10);
311512
+ if (!Number.isSafeInteger(exponent)) {
311513
+ throw new Error(`venue_discovery_unavailable: ${field} exponent is invalid`);
311514
+ }
311515
+ const digits = `${integer2}${fraction}`;
311516
+ const decimalIndex = integer2.length + exponent;
311517
+ let rendered;
311518
+ if (decimalIndex <= 0) {
311519
+ rendered = `0.${"0".repeat(-decimalIndex)}${digits}`;
311520
+ } else if (decimalIndex >= digits.length) {
311521
+ rendered = `${digits}${"0".repeat(decimalIndex - digits.length)}`;
311522
+ } else {
311523
+ rendered = `${digits.slice(0, decimalIndex)}.${digits.slice(decimalIndex)}`;
311524
+ }
311525
+ const [whole = "0", decimals = ""] = rendered.split(".");
311526
+ const normalizedWhole = whole.replace(/^0+(?=\d)/, "") || "0";
311527
+ const normalizedDecimals = decimals.replace(/0+$/, "");
311528
+ return normalizedDecimals ? `${normalizedWhole}.${normalizedDecimals}` : normalizedWhole;
311529
+ }
311530
+ function decimalFractionToBasisPoints(value) {
311531
+ const canonical = canonicalNonnegativeDecimal(value, "fee rate");
311532
+ return canonicalNonnegativeDecimal(`${canonical}e4`, "fee basis points");
311533
+ }
311534
+ function canonicalOptionalDecimal(value, field) {
311535
+ return value === undefined || value === null ? undefined : canonicalNonnegativeDecimal(value, field);
311536
+ }
311537
+ function precisionIncrement(value, precisionMode, field) {
311538
+ if ((precisionMode === 2 || precisionMode === "DECIMAL_PLACES") && (typeof value === "number" || typeof value === "string")) {
311539
+ const decimalPlaces = Number(value);
311540
+ if (Number.isInteger(decimalPlaces) && decimalPlaces >= 0) {
311541
+ return canonicalNonnegativeDecimal(`1e-${decimalPlaces}`, field);
311542
+ }
311543
+ }
311544
+ return canonicalNonnegativeDecimal(value, field);
311545
+ }
311546
+ function evidenceSourceDigest(input) {
311547
+ const source = removeSecretMaterial(input.source, exchangeSecretLiterals(input.broker));
311548
+ return sha256Canonical({
311549
+ action: input.action,
311550
+ exchange: input.exchange,
311551
+ requestedKey: input.requestedKey,
311552
+ accountSelector: input.accountSelector,
311553
+ sourceMethod: input.sourceMethod,
311554
+ source
311555
+ });
311556
+ }
311557
+ async function resolveSpotMarketIdentity(broker, symbol2) {
311558
+ const requestedSymbol = symbol2.trim().toUpperCase();
311559
+ if (!/^[^/\s]+\/[^/\s]+$/.test(requestedSymbol)) {
311560
+ throw new Error("venue_discovery_unavailable: symbol must be a slash-delimited spot pair");
311561
+ }
311562
+ await broker.loadMarkets();
311563
+ const market = broker.market(requestedSymbol);
311564
+ if (!isRecord(market)) {
311565
+ throw new Error(`venue_discovery_unavailable: market not found for ${requestedSymbol}`);
311566
+ }
311567
+ const unifiedSymbol = typeof market.symbol === "string" ? market.symbol.trim().toUpperCase() : requestedSymbol;
311568
+ const baseAsset = typeof market.base === "string" ? market.base.trim().toUpperCase() : "";
311569
+ const quoteAsset = typeof market.quote === "string" ? market.quote.trim().toUpperCase() : "";
311570
+ const sourceSymbol = typeof market.id === "string" ? market.id.trim() : "";
311571
+ const isSpot = market.spot === true || market.type === "spot";
311572
+ if (unifiedSymbol !== requestedSymbol || !baseAsset || !quoteAsset || !sourceSymbol || !isSpot || market.active !== true) {
311573
+ throw new Error(`venue_discovery_unavailable: active spot market identity unavailable for ${requestedSymbol}`);
311574
+ }
311575
+ return {
311576
+ market,
311577
+ canonicalPair: `${baseAsset}-${quoteAsset}`,
311578
+ unifiedSymbol,
311579
+ sourceSymbol,
311580
+ baseAsset,
311581
+ quoteAsset
311582
+ };
311583
+ }
311584
+ function extractTradingFeeRates(response) {
311585
+ if (!isRecord(response)) {
311586
+ throw new Error("fee_unavailable: trading-fee response is not an object");
311587
+ }
311588
+ const info = isRecord(response.info) ? response.info : undefined;
311589
+ const nestedData = isRecord(info?.data) ? info.data : isRecord(response.data) ? response.data : undefined;
311590
+ const maker = [
311591
+ response.maker,
311592
+ response.makerCommission,
311593
+ nestedData?.maker,
311594
+ nestedData?.makerCommission
311595
+ ].find((value) => value !== undefined && value !== null);
311596
+ const taker = [
311597
+ response.taker,
311598
+ response.takerCommission,
311599
+ nestedData?.taker,
311600
+ nestedData?.takerCommission
311601
+ ].find((value) => value !== undefined && value !== null);
311602
+ try {
311603
+ return {
311604
+ makerRate: canonicalNonnegativeDecimal(maker, "maker commission"),
311605
+ takerRate: canonicalNonnegativeDecimal(taker, "taker commission")
311606
+ };
311607
+ } catch (error48) {
311608
+ throw new Error(`fee_unavailable: ${sanitizeErrorDetail(error48)}`, {
311609
+ cause: error48
311610
+ });
311611
+ }
311612
+ }
311613
+ function evidenceExchange(broker) {
311614
+ return broker;
311615
+ }
311616
+
311617
+ // src/schemas/action-evidence.ts
311618
+ var EVIDENCE_DIGEST_ALGORITHM = "sha256-canonical-json-v1";
311619
+ var CanonicalDecimalStringSchema = exports_external.string().regex(/^(?:0|[1-9]\d*)(?:\.\d*[1-9])?$/);
311620
+ var accountScopeShape = {
311621
+ accountSelector: exports_external.string().min(1),
311622
+ credentialSource: exports_external.enum(["configured_pool", "request_metadata"])
311623
+ };
311624
+ var evidenceSourceShape = {
311625
+ observedAt: exports_external.string().datetime({ offset: true }),
311626
+ digestAlgorithm: exports_external.literal(EVIDENCE_DIGEST_ALGORITHM),
311627
+ sourceDigest: exports_external.string().regex(/^[a-f0-9]{64}$/)
311628
+ };
311629
+ var TradingFeeEvidenceSchema = exports_external.object({
311630
+ schemaVersion: exports_external.literal("cex-trading-fee-evidence/v1"),
311631
+ exchange: exports_external.string().min(1),
311632
+ marketType: exports_external.literal("spot"),
311633
+ canonicalPair: exports_external.string().regex(/^[^-\s]+-[^-\s]+$/),
311634
+ unifiedSymbol: exports_external.string().regex(/^[^/\s]+\/[^/\s]+$/),
311635
+ sourceSymbol: exports_external.string().min(1),
311636
+ ...accountScopeShape,
311637
+ ...evidenceSourceShape,
311638
+ sourceMethod: exports_external.literal("ccxt.fetchTradingFee"),
311639
+ makerRate: CanonicalDecimalStringSchema,
311640
+ takerRate: CanonicalDecimalStringSchema,
311641
+ rateUnit: exports_external.literal("decimal_fraction"),
311642
+ makerBasisPoints: CanonicalDecimalStringSchema,
311643
+ takerBasisPoints: CanonicalDecimalStringSchema,
311644
+ basisPointsUnit: exports_external.literal("basis_points")
311645
+ }).strict();
311646
+ var MarketRuleEvidenceSchema = exports_external.object({
311647
+ schemaVersion: exports_external.literal("cex-market-rule-evidence/v1"),
311648
+ exchange: exports_external.string().min(1),
311649
+ marketType: exports_external.literal("spot"),
311650
+ canonicalPair: exports_external.string().regex(/^[^-\s]+-[^-\s]+$/),
311651
+ unifiedSymbol: exports_external.string().regex(/^[^/\s]+\/[^/\s]+$/),
311652
+ sourceSymbol: exports_external.string().min(1),
311653
+ baseAsset: exports_external.string().min(1),
311654
+ quoteAsset: exports_external.string().min(1),
311655
+ active: exports_external.literal(true),
311656
+ precisionMode: exports_external.union([exports_external.string().min(1), exports_external.number().int()]),
311657
+ priceIncrement: CanonicalDecimalStringSchema,
311658
+ amountIncrement: CanonicalDecimalStringSchema,
311659
+ minimumAmount: CanonicalDecimalStringSchema,
311660
+ minimumNotional: CanonicalDecimalStringSchema,
311661
+ maximumAmount: CanonicalDecimalStringSchema.optional(),
311662
+ maximumPrice: CanonicalDecimalStringSchema.optional(),
311663
+ maximumNotional: CanonicalDecimalStringSchema.optional(),
311664
+ ...accountScopeShape,
311665
+ ...evidenceSourceShape,
311666
+ sourceMethod: exports_external.literal("ccxt.loadMarkets")
311667
+ }).strict();
311668
+ var TransferNetworkEvidenceSchema = exports_external.object({
311669
+ schemaVersion: exports_external.literal("cex-transfer-network-evidence/v1"),
311670
+ exchange: exports_external.string().min(1),
311671
+ asset: exports_external.string().min(1),
311672
+ operatorNetworkAlias: exports_external.string().min(1),
311673
+ brokerNetworkId: exports_external.string().min(1),
311674
+ exchangeNetworkId: exports_external.string().min(1),
311675
+ depositAvailable: exports_external.boolean(),
311676
+ withdrawalAvailable: exports_external.boolean(),
311677
+ withdrawalFee: CanonicalDecimalStringSchema.nullable(),
311678
+ withdrawalLimits: exports_external.object({
311679
+ minimum: CanonicalDecimalStringSchema.nullable(),
311680
+ maximum: CanonicalDecimalStringSchema.nullable()
311681
+ }).strict(),
311682
+ ...accountScopeShape,
311683
+ ...evidenceSourceShape,
311684
+ sourceMethod: exports_external.literal("ccxt.fetchCurrencies")
311685
+ }).strict();
311686
+ var BatchSuccessEntrySchema = exports_external.object({
311687
+ id: exports_external.string().min(1),
311688
+ action: exports_external.number().int().nonnegative(),
311689
+ symbol: exports_external.string(),
311690
+ response: exports_external.object({
311691
+ result: exports_external.string(),
311692
+ proof: exports_external.string()
311693
+ }).strict(),
311694
+ error: exports_external.null()
311695
+ }).strict();
311696
+ var BatchErrorEntrySchema = exports_external.object({
311697
+ id: exports_external.string().min(1),
311698
+ action: exports_external.number().int().nonnegative(),
311699
+ symbol: exports_external.string(),
311700
+ response: exports_external.null(),
311701
+ error: exports_external.object({
311702
+ code: exports_external.string().min(1),
311703
+ grpcStatus: exports_external.number().int().nonnegative(),
311704
+ message: exports_external.string()
311705
+ }).strict()
311706
+ }).strict();
311707
+ var BatchResponseEntrySchema = exports_external.union([
311708
+ BatchSuccessEntrySchema,
311709
+ BatchErrorEntrySchema
311710
+ ]);
311711
+ var BatchResponseEnvelopeSchema = exports_external.object({
311712
+ schemaVersion: exports_external.literal("cex-broker-action-batch/v1"),
311713
+ responses: exports_external.array(BatchResponseEntrySchema).max(32)
311714
+ }).strict();
311715
+
311716
+ // src/handlers/execute-action/batch.ts
311717
+ var FORBIDDEN_ROUTING_KEYS = new Set([
311718
+ "account",
311719
+ "accountid",
311720
+ "accountselector",
311721
+ "apikey",
311722
+ "apisecret",
311723
+ "auth",
311724
+ "authorization",
311725
+ "cex",
311726
+ "credential",
311727
+ "credentials",
311728
+ "exchange",
311729
+ "metadata",
311730
+ "password",
311731
+ "secret",
311732
+ "signature",
311733
+ "usesecondarykey"
311734
+ ]);
311735
+ function normalizeRoutingKey(key2) {
311736
+ return key2.replace(/[-_]/g, "").toLowerCase();
311737
+ }
311738
+ function hasForbiddenRoutingKey(value) {
311739
+ if (Array.isArray(value)) {
311740
+ return value.some(hasForbiddenRoutingKey);
311741
+ }
311742
+ if (value === null || typeof value !== "object") {
311743
+ return false;
311744
+ }
311745
+ for (const [key2, entry] of Object.entries(value)) {
311746
+ if (FORBIDDEN_ROUTING_KEYS.has(normalizeRoutingKey(key2))) {
311747
+ return true;
311748
+ }
311749
+ if (hasForbiddenRoutingKey(entry)) {
311750
+ return true;
311751
+ }
311752
+ }
311753
+ return false;
311754
+ }
311755
+ function childContainsRoutingOverride(child) {
311756
+ for (const [key2, value] of Object.entries(child.payload)) {
311757
+ if (FORBIDDEN_ROUTING_KEYS.has(normalizeRoutingKey(key2))) {
311758
+ return true;
311759
+ }
311760
+ try {
311761
+ if (hasForbiddenRoutingKey(JSON.parse(value))) {
311762
+ return true;
311763
+ }
311764
+ } catch {}
311765
+ }
311766
+ return false;
311767
+ }
311768
+ function stableBatchErrorCode(message, grpcStatus) {
311769
+ const prefix = message.match(/^([A-Za-z][A-Za-z0-9_]*):/)?.[1];
311770
+ if (prefix) {
311771
+ return prefix;
311772
+ }
311773
+ return grpc5.status[grpcStatus] ?? "UNKNOWN";
311774
+ }
311775
+ function batchErrorEntry(child, error48, broker) {
311776
+ const resolved = resolveGrpcError(error48);
311777
+ const errorRecord = error48 !== null && typeof error48 === "object" ? error48 : undefined;
311778
+ const grpcStatus = typeof errorRecord?.code === "number" ? errorRecord.code : resolved.code;
311779
+ const rawMessage = typeof errorRecord?.message === "string" ? errorRecord.message : resolved.message;
311780
+ const sanitizedMessage = redactSecretLiterals(sanitizeErrorDetail(rawMessage), exchangeSecretLiterals(broker));
311781
+ return {
311782
+ id: child.id,
311783
+ action: child.action,
311784
+ symbol: child.symbol,
311785
+ response: null,
311786
+ error: {
311787
+ code: stableBatchErrorCode(rawMessage, grpcStatus),
311788
+ grpcStatus,
311789
+ message: sanitizedMessage
311790
+ }
311791
+ };
311792
+ }
311793
+ function childCall(ctx, request) {
311794
+ return {
311795
+ ...ctx.call,
311796
+ request
311797
+ };
311798
+ }
311799
+ async function executeChild(ctx, child, descriptor) {
311800
+ const action = resolveAction(child.action);
311801
+ if (action === undefined) {
311802
+ return batchErrorEntry(child, new Error(`ValidationError: invalid action ${child.action}`), ctx.broker);
311803
+ }
311804
+ const proofState = { proof: "" };
311805
+ let completion;
311806
+ const localCallback = (error48, response) => {
311807
+ if (completion === undefined) {
311808
+ completion = { error: error48, response: response ?? null };
311809
+ }
311810
+ };
311811
+ const request = {
311812
+ action,
311813
+ cex: ctx.cex,
311814
+ symbol: child.symbol,
311815
+ payload: child.payload
311816
+ };
311817
+ const childContext = {
311818
+ ...ctx,
311819
+ call: childCall(ctx, request),
311820
+ wrappedCallback: localCallback,
311821
+ action,
311822
+ symbol: child.symbol,
311823
+ verity: proofState,
311824
+ applyVerityToBroker: (target) => ctx.applyVerityToBroker(target, proofState)
311825
+ };
311826
+ try {
311827
+ childContext.applyVerityToBroker(ctx.broker);
311828
+ await descriptor.handler(childContext);
311829
+ } catch (error48) {
311830
+ completion ??= {
311831
+ error: resolveGrpcError(error48),
311832
+ response: null
311833
+ };
311834
+ }
311835
+ if (completion === undefined) {
311836
+ return batchErrorEntry(child, new Error(`INTERNAL: ${getActionName(action)} completed without a callback`), ctx.broker);
311837
+ }
311838
+ if (completion.error || !completion.response) {
311839
+ return batchErrorEntry(child, completion.error ?? new Error("INTERNAL: child returned no response"), ctx.broker);
311840
+ }
311841
+ return {
311842
+ id: child.id,
311843
+ action: child.action,
311844
+ symbol: child.symbol,
311845
+ response: {
311846
+ result: completion.response.result,
311847
+ proof: completion.response.proof ?? proofState.proof
311848
+ },
311849
+ error: null
311850
+ };
311851
+ }
311852
+ async function handleBatch(ctx, lookupDescriptor) {
311853
+ if (ctx.symbol?.trim()) {
311854
+ return ctx.wrappedCallback({
311855
+ code: grpc5.status.INVALID_ARGUMENT,
311856
+ message: "ValidationError: Batch symbol must be empty"
311857
+ }, null);
311858
+ }
311859
+ const encodedRequests = ctx.call.request.payload?.requests;
311860
+ if (typeof encodedRequests === "string" && Buffer.byteLength(encodedRequests, "utf8") > MAX_BATCH_REQUEST_BYTES) {
311861
+ return ctx.wrappedCallback({
311862
+ code: grpc5.status.INVALID_ARGUMENT,
311863
+ message: `ValidationError: Batch requests exceed ${MAX_BATCH_REQUEST_BYTES} bytes`
311864
+ }, null);
311865
+ }
311866
+ const payload = parsePayloadForAction(ctx, BatchPayloadSchema);
311867
+ if (payload === null) {
311868
+ return;
311869
+ }
311870
+ const seenIds = new Set;
311871
+ const prepared = [];
311872
+ for (const child of payload.requests) {
311873
+ if (seenIds.has(child.id)) {
311874
+ return ctx.wrappedCallback({
311875
+ code: grpc5.status.INVALID_ARGUMENT,
311876
+ message: `ValidationError: duplicate batch child id '${child.id}'`
311877
+ }, null);
311878
+ }
311879
+ seenIds.add(child.id);
311880
+ if (childContainsRoutingOverride(child)) {
311881
+ return ctx.wrappedCallback({
311882
+ code: grpc5.status.INVALID_ARGUMENT,
311883
+ message: `ValidationError: batch child '${child.id}' contains a routing override`
311884
+ }, null);
311885
+ }
311886
+ const action = resolveAction(child.action);
311887
+ const descriptor = action === undefined ? undefined : lookupDescriptor(action);
311888
+ if (!descriptor || descriptor.access !== "read" || !descriptor.batchable) {
311889
+ return ctx.wrappedCallback({
311890
+ code: grpc5.status.INVALID_ARGUMENT,
311891
+ message: `ValidationError: batch child '${child.id}' action ${child.action} is not batchable`
311892
+ }, null);
311893
+ }
311894
+ const validation = descriptor.validateBatchRequest?.({
311895
+ action,
311896
+ cex: ctx.cex,
311897
+ symbol: child.symbol,
311898
+ payload: child.payload
311899
+ });
311900
+ if (validation && !validation.valid) {
311901
+ return ctx.wrappedCallback({
311902
+ code: grpc5.status.INVALID_ARGUMENT,
311903
+ message: `ValidationError: batch child '${child.id}': ${validation.message}`
311904
+ }, null);
311905
+ }
311906
+ prepared.push({ child, descriptor });
311907
+ }
311908
+ const responses = [];
311909
+ for (const { child, descriptor } of prepared) {
311910
+ const response = await executeChild(ctx, child, descriptor);
311911
+ responses.push(response);
311912
+ ctx.otelMetrics?.recordCounter("execute_action_batch_items_total", 1, {
311913
+ action: getActionName(child.action),
311914
+ cex: ctx.normalizedCex,
311915
+ outcome: response.error ? "error" : "success"
311916
+ });
311917
+ }
311918
+ const envelope = BatchResponseEnvelopeSchema.parse({
311919
+ schemaVersion: "cex-broker-action-batch/v1",
311920
+ responses
311921
+ });
311922
+ ctx.wrappedCallback(null, {
311923
+ result: JSON.stringify(envelope),
311924
+ proof: ""
311925
+ });
311926
+ }
311927
+
311928
+ // src/handlers/execute-action/internal-transfer.ts
311929
+ import * as grpc6 from "@grpc/grpc-js";
311266
311930
  async function handleInternalTransfer(ctx) {
311267
311931
  const {
311268
311932
  brokers,
@@ -311276,7 +311940,7 @@ async function handleInternalTransfer(ctx) {
311276
311940
  } = ctx;
311277
311941
  if (!symbol2) {
311278
311942
  return ctx.wrappedCallback({
311279
- code: grpc5.status.INVALID_ARGUMENT,
311943
+ code: grpc6.status.INVALID_ARGUMENT,
311280
311944
  message: `ValidationError: Symbol required`
311281
311945
  }, null);
311282
311946
  }
@@ -311285,14 +311949,14 @@ async function handleInternalTransfer(ctx) {
311285
311949
  return;
311286
311950
  if (normalizedCex !== "binance") {
311287
311951
  return ctx.wrappedCallback({
311288
- code: grpc5.status.UNIMPLEMENTED,
311952
+ code: grpc6.status.UNIMPLEMENTED,
311289
311953
  message: `InternalTransfer is only supported for Binance`
311290
311954
  }, null);
311291
311955
  }
311292
311956
  const pool = brokers[normalizedCex];
311293
311957
  if (!pool) {
311294
311958
  return ctx.wrappedCallback({
311295
- code: grpc5.status.FAILED_PRECONDITION,
311959
+ code: grpc6.status.FAILED_PRECONDITION,
311296
311960
  message: `No broker accounts configured for ${normalizedCex}`
311297
311961
  }, null);
311298
311962
  }
@@ -311301,14 +311965,14 @@ async function handleInternalTransfer(ctx) {
311301
311965
  const sourceAccount = resolveBrokerAccount(pool, fromSelector);
311302
311966
  if (!sourceAccount) {
311303
311967
  return ctx.wrappedCallback({
311304
- code: grpc5.status.INVALID_ARGUMENT,
311968
+ code: grpc6.status.INVALID_ARGUMENT,
311305
311969
  message: `Source account "${fromSelector}" is not configured`
311306
311970
  }, null);
311307
311971
  }
311308
311972
  const destAccount = resolveBrokerAccount(pool, toSelector);
311309
311973
  if (!destAccount) {
311310
311974
  return ctx.wrappedCallback({
311311
- code: grpc5.status.INVALID_ARGUMENT,
311975
+ code: grpc6.status.INVALID_ARGUMENT,
311312
311976
  message: `Destination account "${toSelector}" is not configured`
311313
311977
  }, null);
311314
311978
  }
@@ -311342,18 +312006,18 @@ async function handleInternalTransfer(ctx) {
311342
312006
  safeLogError("InternalTransfer failed", error48);
311343
312007
  if (error48 instanceof BrokerAccountPreconditionError) {
311344
312008
  return ctx.wrappedCallback({
311345
- code: grpc5.status.FAILED_PRECONDITION,
312009
+ code: grpc6.status.FAILED_PRECONDITION,
311346
312010
  message: getErrorMessage(error48)
311347
312011
  }, null);
311348
312012
  }
311349
312013
  const msg = getErrorMessage(error48);
311350
312014
  let code;
311351
312015
  if (msg.includes("Unsupported transfer direction")) {
311352
- code = grpc5.status.INVALID_ARGUMENT;
312016
+ code = grpc6.status.INVALID_ARGUMENT;
311353
312017
  } else if (msg.includes("unavailable in this CCXT build")) {
311354
- code = grpc5.status.UNIMPLEMENTED;
312018
+ code = grpc6.status.UNIMPLEMENTED;
311355
312019
  } else {
311356
- code = mapCcxtErrorToGrpcStatus(error48) ?? grpc5.status.INTERNAL;
312020
+ code = mapCcxtErrorToGrpcStatus(error48) ?? grpc6.status.INTERNAL;
311357
312021
  }
311358
312022
  ctx.wrappedCallback({
311359
312023
  code,
@@ -311363,7 +312027,7 @@ async function handleInternalTransfer(ctx) {
311363
312027
  }
311364
312028
 
311365
312029
  // src/handlers/execute-action/orders.ts
311366
- import * as grpc6 from "@grpc/grpc-js";
312030
+ import * as grpc7 from "@grpc/grpc-js";
311367
312031
 
311368
312032
  // src/helpers/passive-order.ts
311369
312033
  var PASSIVE_ORDER_ERROR_CODES = {
@@ -311424,7 +312088,7 @@ async function handleCreateOrder(ctx) {
311424
312088
  const isPassiveOrder = orderValue.orderIntent === "passive_only";
311425
312089
  if (isPassiveOrder && orderValue.orderType !== "limit") {
311426
312090
  return ctx.wrappedCallback({
311427
- code: grpc6.status.INVALID_ARGUMENT,
312091
+ code: grpc7.status.INVALID_ARGUMENT,
311428
312092
  message: "ValidationError: passive_only order intent requires a limit order"
311429
312093
  }, null);
311430
312094
  }
@@ -311441,14 +312105,14 @@ async function handleCreateOrder(ctx) {
311441
312105
  try {
311442
312106
  if (!broker) {
311443
312107
  return ctx.wrappedCallback({
311444
- code: grpc6.status.INVALID_ARGUMENT,
312108
+ code: grpc7.status.INVALID_ARGUMENT,
311445
312109
  message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
311446
312110
  }, null);
311447
312111
  }
311448
312112
  const resolution = await resolveOrderExecution(policy, broker, cex3, orderValue.fromToken, orderValue.toToken, orderValue.amount, orderValue.price, orderValue.marketType);
311449
312113
  if (!resolution.valid || !resolution.symbol || !resolution.side) {
311450
312114
  return ctx.wrappedCallback({
311451
- code: grpc6.status.INVALID_ARGUMENT,
312115
+ code: grpc7.status.INVALID_ARGUMENT,
311452
312116
  message: resolution.error ?? "Order rejected by policy: market or limits not satisfied"
311453
312117
  }, null);
311454
312118
  }
@@ -311521,7 +312185,7 @@ async function handleCreateOrder(ctx) {
311521
312185
  });
311522
312186
  }
311523
312187
  ctx.wrappedCallback({
311524
- code: grpc6.status.INTERNAL,
312188
+ code: grpc7.status.INTERNAL,
311525
312189
  message: `Order Creation failed: ${sanitizeErrorDetail(error48)}`
311526
312190
  }, null);
311527
312191
  }
@@ -311553,7 +312217,7 @@ async function handleGetOrderDetails(ctx) {
311553
312217
  try {
311554
312218
  if (!broker) {
311555
312219
  return ctx.wrappedCallback({
311556
- code: grpc6.status.INVALID_ARGUMENT,
312220
+ code: grpc7.status.INVALID_ARGUMENT,
311557
312221
  message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
311558
312222
  }, null);
311559
312223
  }
@@ -311594,7 +312258,7 @@ async function handleGetOrderDetails(ctx) {
311594
312258
  emitOrderExecutionTelemetryInBackground(otelMetrics, failedGetOrderContext, undefined, error48);
311595
312259
  archiveOrderExecutionInBackground(brokerArchiver, failedGetOrderContext, undefined, error48);
311596
312260
  ctx.wrappedCallback({
311597
- code: grpc6.status.INTERNAL,
312261
+ code: grpc7.status.INTERNAL,
311598
312262
  message: `Failed to fetch order details from ${cex3}: ${sanitizeErrorDetail(error48)}`
311599
312263
  }, null);
311600
312264
  }
@@ -311626,7 +312290,7 @@ async function handleCancelOrder(ctx) {
311626
312290
  try {
311627
312291
  if (!broker) {
311628
312292
  return ctx.wrappedCallback({
311629
- code: grpc6.status.INVALID_ARGUMENT,
312293
+ code: grpc7.status.INVALID_ARGUMENT,
311630
312294
  message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
311631
312295
  }, null);
311632
312296
  }
@@ -311658,7 +312322,7 @@ async function handleCancelOrder(ctx) {
311658
312322
  emitOrderExecutionTelemetryInBackground(otelMetrics, failedCancelContext, undefined, error48);
311659
312323
  archiveOrderExecutionInBackground(brokerArchiver, failedCancelContext, undefined, error48);
311660
312324
  ctx.wrappedCallback({
311661
- code: grpc6.status.INTERNAL,
312325
+ code: grpc7.status.INTERNAL,
311662
312326
  message: `Failed to cancel order from ${cex3}: ${sanitizeErrorDetail(error48)}`
311663
312327
  }, null);
311664
312328
  }
@@ -311673,261 +312337,254 @@ async function handleOrders(ctx) {
311673
312337
  }
311674
312338
 
311675
312339
  // src/handlers/execute-action/pass-through.ts
311676
- import * as grpc7 from "@grpc/grpc-js";
311677
- async function handleFetchCurrency(ctx) {
311678
- const {
311679
- call,
311680
- wrappedCallback,
311681
- policy,
311682
- brokers,
311683
- metadata,
311684
- normalizedCex,
311685
- cex: cex3,
311686
- symbol: symbol2,
311687
- selectedBrokerAccount,
311688
- broker,
311689
- verity,
311690
- applyVerityToBroker,
311691
- useVerity,
311692
- verityProverUrl,
311693
- otelMetrics
311694
- } = ctx;
311695
- const verityProof = verity.proof;
311696
- if (!symbol2) {
312340
+ import * as grpc9 from "@grpc/grpc-js";
312341
+
312342
+ // src/handlers/execute-action/venue-evidence.ts
312343
+ import * as grpc8 from "@grpc/grpc-js";
312344
+ function failVenueDiscovery(ctx, error48, operation) {
312345
+ safeLogRedactedError(`${operation} failed`, error48);
312346
+ const sanitized = sanitizeVenueError(error48, ctx.broker);
312347
+ const message = sanitized.startsWith("venue_discovery_unavailable:") ? sanitized : `venue_discovery_unavailable: ${sanitized}`;
312348
+ ctx.wrappedCallback({
312349
+ code: stableGrpcErrorCode(message) ?? mapCcxtErrorToGrpcStatus(error48) ?? grpc8.status.UNIMPLEMENTED,
312350
+ message
312351
+ }, null);
312352
+ }
312353
+ function optionalDecimalFields(values2) {
312354
+ const result = {};
312355
+ for (const [key2, value, field] of values2) {
312356
+ const normalized = canonicalOptionalDecimal(value, field);
312357
+ if (normalized !== undefined) {
312358
+ result[key2] = normalized;
312359
+ }
312360
+ }
312361
+ return result;
312362
+ }
312363
+ async function handleFetchFeesEvidence(ctx) {
312364
+ if (!requireSymbol(ctx, "ValidationError: symbol must be a slash-delimited spot pair")) {
312365
+ return;
312366
+ }
312367
+ if (!/^[^/\s]+\/[^/\s]+$/.test(ctx.symbol.trim())) {
311697
312368
  return ctx.wrappedCallback({
311698
- code: grpc7.status.INVALID_ARGUMENT,
311699
- message: `ValidationError: Symbol required`
312369
+ code: grpc8.status.INVALID_ARGUMENT,
312370
+ message: "ValidationError: symbol must be a slash-delimited spot pair"
311700
312371
  }, null);
311701
312372
  }
312373
+ if (parsePayloadForAction(ctx, FetchFeesPayloadSchema) === null) {
312374
+ return;
312375
+ }
311702
312376
  try {
311703
- const assetCode = symbol2.trim().toUpperCase();
311704
- const currencyInfo = await fetchCurrencyMetadata(broker, assetCode);
311705
- if (!currencyInfo) {
311706
- return ctx.wrappedCallback({
311707
- code: grpc7.status.NOT_FOUND,
311708
- message: `venue_discovery_unavailable: currency not found for ${assetCode}`
311709
- }, null);
311710
- }
311711
- const networkEvidence = buildTransferNetworkEvidence(currencyInfo);
311712
- ctx.wrappedCallback(null, {
311713
- proof: ctx.verity.proof,
311714
- result: JSON.stringify({
311715
- ...currencyInfo,
311716
- exchange: normalizedCex,
311717
- asset: assetCode,
311718
- code: currencyInfo.code ?? assetCode,
311719
- id: currencyInfo.id ?? null,
311720
- networks: networkEvidence.networks,
311721
- networkAliases: networkEvidence.aliases,
311722
- raw: currencyInfo
312377
+ const identity2 = await resolveSpotMarketIdentity(ctx.broker, ctx.symbol);
312378
+ const exchange = evidenceExchange(ctx.broker);
312379
+ if (exchange.has?.fetchTradingFee === false || typeof exchange.fetchTradingFee !== "function") {
312380
+ throw new Error(`fee_unavailable: ${ctx.normalizedCex} does not support fetchTradingFee`);
312381
+ }
312382
+ const sourceResponse = await exchange.fetchTradingFee(identity2.unifiedSymbol);
312383
+ if (isRecord(sourceResponse) && typeof sourceResponse.symbol === "string" && sourceResponse.symbol.trim().toUpperCase() !== identity2.unifiedSymbol) {
312384
+ throw new Error(`fee_unavailable: trading-fee response symbol does not match ${identity2.unifiedSymbol}`);
312385
+ }
312386
+ const { makerRate, takerRate } = extractTradingFeeRates(sourceResponse);
312387
+ const accountScope = resolveEvidenceAccountScope(ctx.selectedBrokerAccount, ctx.metadata);
312388
+ const evidence = TradingFeeEvidenceSchema.parse({
312389
+ schemaVersion: "cex-trading-fee-evidence/v1",
312390
+ exchange: ctx.normalizedCex,
312391
+ marketType: "spot",
312392
+ canonicalPair: identity2.canonicalPair,
312393
+ unifiedSymbol: identity2.unifiedSymbol,
312394
+ sourceSymbol: identity2.sourceSymbol,
312395
+ ...accountScope,
312396
+ observedAt: new Date().toISOString(),
312397
+ sourceMethod: "ccxt.fetchTradingFee",
312398
+ makerRate,
312399
+ takerRate,
312400
+ rateUnit: "decimal_fraction",
312401
+ makerBasisPoints: decimalFractionToBasisPoints(makerRate),
312402
+ takerBasisPoints: decimalFractionToBasisPoints(takerRate),
312403
+ basisPointsUnit: "basis_points",
312404
+ digestAlgorithm: "sha256-canonical-json-v1",
312405
+ sourceDigest: evidenceSourceDigest({
312406
+ action: "FetchFees",
312407
+ exchange: ctx.normalizedCex,
312408
+ requestedKey: identity2.canonicalPair,
312409
+ accountSelector: accountScope.accountSelector,
312410
+ sourceMethod: "ccxt.fetchTradingFee",
312411
+ source: sourceResponse,
312412
+ broker: ctx.broker
311723
312413
  })
311724
312414
  });
312415
+ successWithProof(ctx, evidence);
311725
312416
  } catch (error48) {
311726
- safeLogError(`Error fetching currency ${symbol2} from ${cex3}`, error48);
311727
- const message = getErrorMessage(error48);
312417
+ safeLogRedactedError(`FetchFees failed for ${ctx.normalizedCex}/${ctx.symbol}`, error48);
312418
+ const sanitized = sanitizeVenueError(error48, ctx.broker);
312419
+ const message = sanitized.startsWith("fee_unavailable:") ? sanitized : `fee_unavailable: ${sanitized}`;
311728
312420
  ctx.wrappedCallback({
311729
- code: stableGrpcErrorCode(message) ?? mapCcxtErrorToGrpcStatus(error48) ?? grpc7.status.INTERNAL,
311730
- message: message.startsWith("venue_discovery_unavailable:") ? message : `venue_discovery_unavailable: ${message}`
312421
+ code: grpc8.status.FAILED_PRECONDITION,
312422
+ message
311731
312423
  }, null);
311732
312424
  }
311733
312425
  }
311734
- async function handleFetchAccountId(ctx) {
311735
- const {
311736
- call,
311737
- wrappedCallback,
311738
- policy,
311739
- brokers,
311740
- metadata,
311741
- normalizedCex,
311742
- cex: cex3,
311743
- symbol: symbol2,
311744
- selectedBrokerAccount,
311745
- broker,
311746
- verity,
311747
- applyVerityToBroker,
311748
- useVerity,
311749
- verityProverUrl,
311750
- otelMetrics
311751
- } = ctx;
311752
- const verityProof = verity.proof;
312426
+ async function handleFetchMarketRulesEvidence(ctx) {
312427
+ if (!requireSymbol(ctx, "ValidationError: symbol must be a slash-delimited spot pair")) {
312428
+ return;
312429
+ }
312430
+ if (!/^[^/\s]+\/[^/\s]+$/.test(ctx.symbol.trim())) {
312431
+ return ctx.wrappedCallback({
312432
+ code: grpc8.status.INVALID_ARGUMENT,
312433
+ message: "ValidationError: symbol must be a slash-delimited spot pair"
312434
+ }, null);
312435
+ }
312436
+ if (parsePayloadForAction(ctx, EmptyActionPayloadSchema) === null) {
312437
+ return;
312438
+ }
311753
312439
  try {
311754
- const accountId = await broker.fetchAccountId();
311755
- return ctx.wrappedCallback(null, {
311756
- proof: ctx.verity.proof,
311757
- result: JSON.stringify({ accountId })
312440
+ const identity2 = await resolveSpotMarketIdentity(ctx.broker, ctx.symbol);
312441
+ const precision = isRecord(identity2.market.precision) ? identity2.market.precision : {};
312442
+ const limits = isRecord(identity2.market.limits) ? identity2.market.limits : {};
312443
+ const amountLimits = isRecord(limits.amount) ? limits.amount : {};
312444
+ const priceLimits = isRecord(limits.price) ? limits.price : {};
312445
+ const costLimits = isRecord(limits.cost) ? limits.cost : {};
312446
+ const precisionMode = evidenceExchange(ctx.broker).precisionMode;
312447
+ if (precisionMode === undefined || precisionMode === null) {
312448
+ throw new Error("venue_discovery_unavailable: precision mode is unavailable");
312449
+ }
312450
+ const accountScope = resolveEvidenceAccountScope(ctx.selectedBrokerAccount, ctx.metadata);
312451
+ const evidence = MarketRuleEvidenceSchema.parse({
312452
+ schemaVersion: "cex-market-rule-evidence/v1",
312453
+ exchange: ctx.normalizedCex,
312454
+ marketType: "spot",
312455
+ canonicalPair: identity2.canonicalPair,
312456
+ unifiedSymbol: identity2.unifiedSymbol,
312457
+ sourceSymbol: identity2.sourceSymbol,
312458
+ baseAsset: identity2.baseAsset,
312459
+ quoteAsset: identity2.quoteAsset,
312460
+ active: true,
312461
+ precisionMode,
312462
+ priceIncrement: precisionIncrement(precision.price, precisionMode, "price increment"),
312463
+ amountIncrement: precisionIncrement(precision.amount, precisionMode, "amount increment"),
312464
+ minimumAmount: canonicalNonnegativeDecimal(amountLimits.min, "minimum amount"),
312465
+ minimumNotional: canonicalNonnegativeDecimal(costLimits.min, "minimum notional"),
312466
+ ...optionalDecimalFields([
312467
+ ["maximumAmount", amountLimits.max, "maximum amount"],
312468
+ ["maximumPrice", priceLimits.max, "maximum price"],
312469
+ ["maximumNotional", costLimits.max, "maximum notional"]
312470
+ ]),
312471
+ ...accountScope,
312472
+ observedAt: new Date().toISOString(),
312473
+ sourceMethod: "ccxt.loadMarkets",
312474
+ digestAlgorithm: "sha256-canonical-json-v1",
312475
+ sourceDigest: evidenceSourceDigest({
312476
+ action: "FetchMarketRules",
312477
+ exchange: ctx.normalizedCex,
312478
+ requestedKey: identity2.canonicalPair,
312479
+ accountSelector: accountScope.accountSelector,
312480
+ sourceMethod: "ccxt.loadMarkets",
312481
+ source: identity2.market,
312482
+ broker: ctx.broker
312483
+ })
311758
312484
  });
312485
+ successWithProof(ctx, evidence);
311759
312486
  } catch (error48) {
311760
- safeLogError(`Error fetching account ID ${cex3}`, error48);
311761
- ctx.wrappedCallback({
311762
- code: grpc7.status.INTERNAL,
311763
- message: `Error fetching account ID from ${cex3}`
311764
- }, null);
312487
+ failVenueDiscovery(ctx, error48, `FetchMarketRules ${ctx.normalizedCex}/${ctx.symbol}`);
311765
312488
  }
311766
312489
  }
311767
- async function handleFetchFees(ctx) {
311768
- const {
311769
- call,
311770
- wrappedCallback,
311771
- policy,
311772
- brokers,
311773
- metadata,
311774
- normalizedCex,
311775
- cex: cex3,
311776
- symbol: symbol2,
311777
- selectedBrokerAccount,
311778
- broker,
311779
- verity,
311780
- applyVerityToBroker,
311781
- useVerity,
311782
- verityProverUrl,
311783
- otelMetrics
311784
- } = ctx;
311785
- const verityProof = verity.proof;
311786
- if (!symbol2) {
311787
- return ctx.wrappedCallback({
311788
- code: grpc7.status.INVALID_ARGUMENT,
311789
- message: `ValidationError: Symbol required`
311790
- }, null);
312490
+ async function handleFetchCurrencyEvidence(ctx) {
312491
+ if (!requireSymbol(ctx)) {
312492
+ return;
311791
312493
  }
311792
- const feesPayload = parsePayloadForAction(ctx, FetchFeesPayloadSchema);
311793
- if (feesPayload === null)
312494
+ const payload = parsePayloadForAction(ctx, FetchCurrencyPayloadSchema);
312495
+ if (payload === null) {
311794
312496
  return;
311795
- const includeAllFees = feesPayload.includeAllFees || feesPayload.includeFundingFees === true;
312497
+ }
312498
+ const asset = ctx.symbol.trim().toUpperCase();
312499
+ const requestedAlias = payload.network.trim().toUpperCase();
311796
312500
  try {
311797
- await broker.loadMarkets();
311798
- const fetchFundingFees = async (currencyCodes) => {
311799
- let fundingFeeSource2 = "unavailable";
311800
- const fundingFeesByCurrency2 = {};
311801
- if (broker.has.fetchDepositWithdrawFees) {
311802
- try {
311803
- const feeMap = await broker.fetchDepositWithdrawFees(currencyCodes);
311804
- for (const code of currencyCodes) {
311805
- const feeInfo = feeMap[code];
311806
- if (!feeInfo) {
311807
- continue;
311808
- }
311809
- const fallbackFee = feeInfo.fee !== undefined || feeInfo.percentage !== undefined ? {
311810
- fee: feeInfo.fee ?? null,
311811
- percentage: feeInfo.percentage ?? null
311812
- } : null;
311813
- fundingFeesByCurrency2[code] = {
311814
- deposit: feeInfo.deposit ?? fallbackFee,
311815
- withdraw: feeInfo.withdraw ?? fallbackFee,
311816
- networks: feeInfo.networks ?? {}
311817
- };
311818
- }
311819
- if (Object.keys(fundingFeesByCurrency2).length > 0) {
311820
- fundingFeeSource2 = "fetchDepositWithdrawFees";
311821
- }
311822
- } catch (error48) {
311823
- safeLogError(`Error fetching deposit/withdraw fee map for ${symbol2} from ${cex3}`, error48);
311824
- }
311825
- }
311826
- if (fundingFeeSource2 === "unavailable") {
311827
- try {
311828
- const currencies = await broker.fetchCurrencies();
311829
- for (const code of currencyCodes) {
311830
- const currency = currencies[code];
311831
- if (!currency) {
311832
- continue;
311833
- }
311834
- fundingFeesByCurrency2[code] = {
311835
- deposit: {
311836
- enabled: currency.deposit ?? null
311837
- },
311838
- withdraw: {
311839
- enabled: currency.withdraw ?? null,
311840
- fee: currency.fee ?? null,
311841
- limits: currency.limits?.withdraw ?? null
311842
- },
311843
- networks: currency.networks ?? {}
311844
- };
311845
- }
311846
- if (Object.keys(fundingFeesByCurrency2).length > 0) {
311847
- fundingFeeSource2 = "currencies";
311848
- }
311849
- } catch (error48) {
311850
- safeLogError(`Error fetching currency metadata for fees for ${symbol2} from ${cex3}`, error48);
311851
- }
311852
- }
311853
- return { fundingFeeSource: fundingFeeSource2, fundingFeesByCurrency: fundingFeesByCurrency2 };
311854
- };
311855
- const isMarketSymbol = symbol2.includes("/");
311856
- if (isMarketSymbol) {
311857
- const market = await broker.market(symbol2);
311858
- const generalFee = broker.fees ?? null;
311859
- const feeStatus = broker.fees ? "available" : "unknown";
311860
- if (!broker.fees) {
311861
- log.warn(`Fee metadata unavailable for ${cex3}`, { symbol: symbol2 });
311862
- }
311863
- if (!includeAllFees) {
311864
- return ctx.wrappedCallback(null, {
311865
- proof: ctx.verity.proof,
311866
- result: JSON.stringify({
311867
- feeScope: "market",
311868
- generalFee,
311869
- feeStatus,
311870
- market
311871
- })
311872
- });
311873
- }
311874
- const currencyCodes = Array.from(new Set([market.base, market.quote]));
311875
- const { fundingFeeSource: fundingFeeSource2, fundingFeesByCurrency: fundingFeesByCurrency2 } = await fetchFundingFees(currencyCodes);
311876
- return ctx.wrappedCallback(null, {
311877
- proof: ctx.verity.proof,
311878
- result: JSON.stringify({
311879
- feeScope: "market+funding",
311880
- generalFee,
311881
- feeStatus,
311882
- market,
311883
- fundingFeeSource: fundingFeeSource2,
311884
- fundingFeesByCurrency: fundingFeesByCurrency2
311885
- })
311886
- });
311887
- }
311888
- const tokenCode = symbol2.toUpperCase();
311889
- const { fundingFeeSource, fundingFeesByCurrency } = await fetchFundingFees([
311890
- tokenCode
311891
- ]);
312501
+ const exchange = evidenceExchange(ctx.broker);
312502
+ if (exchange.has?.fetchCurrencies === false || typeof exchange.fetchCurrencies !== "function") {
312503
+ throw new Error(`venue_discovery_unavailable: fetchCurrencies unavailable for ${asset}`);
312504
+ }
312505
+ const currencies = await exchange.fetchCurrencies();
312506
+ const currency = currencies[asset];
312507
+ if (!isRecord(currency)) {
312508
+ throw new Error(`venue_discovery_unavailable: currency not found for ${asset}`);
312509
+ }
312510
+ const networkEvidence = buildTransferNetworkEvidence(currency);
312511
+ const brokerNetworkId = normalizeBrokerNetworkId(requestedAlias);
312512
+ const resolution = networkEvidence.aliases[requestedAlias] ?? networkEvidence.aliases[brokerNetworkId];
312513
+ if (!resolution?.networkKey) {
312514
+ throw new Error(`network_alias_unresolved: ${asset}/${requestedAlias} is not available in discovered transfer networks`);
312515
+ }
312516
+ const networks = isRecord(currency.networks) ? currency.networks : {};
312517
+ const networkCandidate = networks[resolution.networkKey];
312518
+ if (!isRecord(networkCandidate) || typeof networkCandidate.deposit !== "boolean" || typeof networkCandidate.withdraw !== "boolean") {
312519
+ throw new Error(`venue_discovery_unavailable: transfer availability is incomplete for ${asset}/${requestedAlias}`);
312520
+ }
312521
+ const network = networkCandidate;
312522
+ const limits = isRecord(network.limits) ? network.limits : {};
312523
+ const withdrawalLimits = isRecord(limits.withdraw) ? limits.withdraw : {};
312524
+ const accountScope = resolveEvidenceAccountScope(ctx.selectedBrokerAccount, ctx.metadata);
312525
+ const evidence = TransferNetworkEvidenceSchema.parse({
312526
+ schemaVersion: "cex-transfer-network-evidence/v1",
312527
+ exchange: ctx.normalizedCex,
312528
+ asset,
312529
+ operatorNetworkAlias: requestedAlias,
312530
+ brokerNetworkId,
312531
+ exchangeNetworkId: resolution.exchangeNetworkId,
312532
+ depositAvailable: network.deposit,
312533
+ withdrawalAvailable: network.withdraw,
312534
+ withdrawalFee: canonicalOptionalDecimal(network.fee, "withdrawal fee") ?? null,
312535
+ withdrawalLimits: {
312536
+ minimum: canonicalOptionalDecimal(withdrawalLimits.min, "minimum withdrawal") ?? null,
312537
+ maximum: canonicalOptionalDecimal(withdrawalLimits.max, "maximum withdrawal") ?? null
312538
+ },
312539
+ ...accountScope,
312540
+ observedAt: new Date().toISOString(),
312541
+ sourceMethod: "ccxt.fetchCurrencies",
312542
+ digestAlgorithm: "sha256-canonical-json-v1",
312543
+ sourceDigest: evidenceSourceDigest({
312544
+ action: "FetchCurrency",
312545
+ exchange: ctx.normalizedCex,
312546
+ requestedKey: `${asset}/${requestedAlias}`,
312547
+ accountSelector: accountScope.accountSelector,
312548
+ sourceMethod: "ccxt.fetchCurrencies",
312549
+ source: network,
312550
+ broker: ctx.broker
312551
+ })
312552
+ });
312553
+ successWithProof(ctx, evidence);
312554
+ } catch (error48) {
312555
+ safeLogRedactedError(`FetchCurrency failed for ${ctx.normalizedCex}/${asset}/${requestedAlias}`, error48);
312556
+ const sanitized = sanitizeVenueError(error48, ctx.broker);
312557
+ const isAliasError = sanitized.startsWith("network_alias_unresolved:");
312558
+ const message = isAliasError || sanitized.startsWith("venue_discovery_unavailable:") ? sanitized : `venue_discovery_unavailable: ${sanitized}`;
312559
+ ctx.wrappedCallback({
312560
+ code: stableGrpcErrorCode(message) ?? mapCcxtErrorToGrpcStatus(error48) ?? grpc8.status.UNIMPLEMENTED,
312561
+ message
312562
+ }, null);
312563
+ }
312564
+ }
312565
+
312566
+ // src/handlers/execute-action/pass-through.ts
312567
+ async function handleFetchAccountId(ctx) {
312568
+ const { cex: cex3, broker } = ctx;
312569
+ try {
312570
+ const accountId = await broker.fetchAccountId();
311892
312571
  return ctx.wrappedCallback(null, {
311893
312572
  proof: ctx.verity.proof,
311894
- result: JSON.stringify({
311895
- feeScope: "token",
311896
- symbol: tokenCode,
311897
- fundingFeeSource,
311898
- fundingFeesByCurrency
311899
- })
312573
+ result: JSON.stringify({ accountId })
311900
312574
  });
311901
312575
  } catch (error48) {
311902
- safeLogError(`Error fetching fees for ${symbol2} from ${cex3}`, error48);
312576
+ safeLogError(`Error fetching account ID ${cex3}`, error48);
311903
312577
  ctx.wrappedCallback({
311904
- code: grpc7.status.INTERNAL,
311905
- message: `Error fetching fees from ${cex3}`
312578
+ code: grpc9.status.INTERNAL,
312579
+ message: `Error fetching account ID from ${cex3}`
311906
312580
  }, null);
311907
312581
  }
311908
312582
  }
311909
312583
  async function handleFetchDepositAddresses(ctx) {
311910
- const {
311911
- call,
311912
- wrappedCallback,
311913
- policy,
311914
- brokers,
311915
- metadata,
311916
- normalizedCex,
311917
- cex: cex3,
311918
- symbol: symbol2,
311919
- selectedBrokerAccount,
311920
- broker,
311921
- verity,
311922
- applyVerityToBroker,
311923
- useVerity,
311924
- verityProverUrl,
311925
- otelMetrics
311926
- } = ctx;
311927
- const verityProof = verity.proof;
312584
+ const { policy, cex: cex3, symbol: symbol2, broker } = ctx;
311928
312585
  if (!symbol2) {
311929
312586
  return ctx.wrappedCallback({
311930
- code: grpc7.status.INVALID_ARGUMENT,
312587
+ code: grpc9.status.INVALID_ARGUMENT,
311931
312588
  message: `ValidationError: Symbol required`
311932
312589
  }, null);
311933
312590
  }
@@ -311940,14 +312597,14 @@ async function handleFetchDepositAddresses(ctx) {
311940
312597
  } catch (error48) {
311941
312598
  const message = getErrorMessage(error48);
311942
312599
  return ctx.wrappedCallback({
311943
- code: stableGrpcErrorCode(message) ?? grpc7.status.INVALID_ARGUMENT,
312600
+ code: stableGrpcErrorCode(message) ?? grpc9.status.INVALID_ARGUMENT,
311944
312601
  message
311945
312602
  }, null);
311946
312603
  }
311947
312604
  const depositValidation = validateDeposit(policy, cex3, depositNetwork.brokerNetworkId, symbol2);
311948
312605
  if (!depositValidation.valid) {
311949
312606
  return ctx.wrappedCallback({
311950
- code: grpc7.status.PERMISSION_DENIED,
312607
+ code: grpc9.status.PERMISSION_DENIED,
311951
312608
  message: `policy_deposit_denied: ${depositValidation.error}`
311952
312609
  }, null);
311953
312610
  }
@@ -311973,45 +312630,30 @@ async function handleFetchDepositAddresses(ctx) {
311973
312630
  });
311974
312631
  }
311975
312632
  ctx.wrappedCallback({
311976
- code: grpc7.status.INTERNAL,
312633
+ code: grpc9.status.INTERNAL,
311977
312634
  message: "Deposit confirmation failed"
311978
312635
  }, null);
311979
312636
  } catch (error48) {
311980
312637
  safeLogError("Fetch Deposit Addresses confirmation failed", error48);
311981
312638
  const message = getErrorMessage(error48);
311982
312639
  ctx.wrappedCallback({
311983
- code: grpc7.status.INTERNAL,
312640
+ code: grpc9.status.INTERNAL,
311984
312641
  message: "Fetch Deposit Addresses confirmation failed: " + message
311985
312642
  }, null);
311986
312643
  }
311987
312644
  }
311988
312645
  async function handleFetchBalances(ctx) {
311989
- const {
311990
- call,
311991
- wrappedCallback,
311992
- policy,
311993
- brokers,
311994
- metadata,
311995
- normalizedCex,
311996
- cex: cex3,
311997
- symbol: symbol2,
311998
- selectedBrokerAccount,
311999
- broker,
312000
- verity,
312001
- applyVerityToBroker,
312002
- useVerity,
312003
- verityProverUrl,
312004
- otelMetrics
312005
- } = ctx;
312006
- const verityProof = verity.proof;
312646
+ const { call, cex: cex3, symbol: symbol2, broker } = ctx;
312007
312647
  try {
312008
- const payload = call.request.payload || {};
312009
- const providedBalanceType = payload.balanceType;
312648
+ const payload = {
312649
+ ...call.request.payload ?? {}
312650
+ };
312651
+ const providedBalanceType = typeof payload.balanceType === "string" ? payload.balanceType : undefined;
312010
312652
  const balanceType = (providedBalanceType ?? "total").toString();
312011
312653
  const validBalanceTypes = new Set(["free", "used", "total"]);
312012
312654
  if (!validBalanceTypes.has(balanceType)) {
312013
312655
  return ctx.wrappedCallback({
312014
- code: grpc7.status.INVALID_ARGUMENT,
312656
+ code: grpc9.status.INVALID_ARGUMENT,
312015
312657
  message: `ValidationError: invalid balanceType '${providedBalanceType}'. Expected one of: free | used | total`
312016
312658
  }, null);
312017
312659
  }
@@ -312052,33 +312694,16 @@ async function handleFetchBalances(ctx) {
312052
312694
  } catch (error48) {
312053
312695
  safeLogError(`Error fetching balance from ${cex3}`, error48);
312054
312696
  ctx.wrappedCallback({
312055
- code: grpc7.status.INTERNAL,
312697
+ code: grpc9.status.INTERNAL,
312056
312698
  message: `Failed to fetch balance from ${cex3}`
312057
312699
  }, null);
312058
312700
  }
312059
312701
  }
312060
312702
  async function handleFetchTicker(ctx) {
312061
- const {
312062
- call,
312063
- wrappedCallback,
312064
- policy,
312065
- brokers,
312066
- metadata,
312067
- normalizedCex,
312068
- cex: cex3,
312069
- symbol: symbol2,
312070
- selectedBrokerAccount,
312071
- broker,
312072
- verity,
312073
- applyVerityToBroker,
312074
- useVerity,
312075
- verityProverUrl,
312076
- otelMetrics
312077
- } = ctx;
312078
- const verityProof = verity.proof;
312703
+ const { cex: cex3, symbol: symbol2, broker } = ctx;
312079
312704
  if (!symbol2) {
312080
312705
  return ctx.wrappedCallback({
312081
- code: grpc7.status.INVALID_ARGUMENT,
312706
+ code: grpc9.status.INVALID_ARGUMENT,
312082
312707
  message: `ValidationError: Symbol required`
312083
312708
  }, null);
312084
312709
  }
@@ -312091,18 +312716,20 @@ async function handleFetchTicker(ctx) {
312091
312716
  } catch (error48) {
312092
312717
  safeLogError(`Error fetching ticker from ${cex3}`, error48);
312093
312718
  ctx.wrappedCallback({
312094
- code: grpc7.status.INTERNAL,
312719
+ code: grpc9.status.INTERNAL,
312095
312720
  message: `Failed to fetch ticker from ${cex3}`
312096
312721
  }, null);
312097
312722
  }
312098
312723
  }
312099
312724
  async function handlePassThrough(ctx) {
312100
312725
  if (ctx.action === Action.FetchCurrency)
312101
- return handleFetchCurrency(ctx);
312726
+ return handleFetchCurrencyEvidence(ctx);
312102
312727
  if (ctx.action === Action.FetchAccountId)
312103
312728
  return handleFetchAccountId(ctx);
312104
312729
  if (ctx.action === Action.FetchFees)
312105
- return handleFetchFees(ctx);
312730
+ return handleFetchFeesEvidence(ctx);
312731
+ if (ctx.action === Action.FetchMarketRules)
312732
+ return handleFetchMarketRulesEvidence(ctx);
312106
312733
  if (ctx.action === Action.FetchDepositAddresses)
312107
312734
  return handleFetchDepositAddresses(ctx);
312108
312735
  if (ctx.action === Action.FetchBalances)
@@ -312112,7 +312739,7 @@ async function handlePassThrough(ctx) {
312112
312739
  }
312113
312740
 
312114
312741
  // src/handlers/execute-action/perp-config.ts
312115
- import * as grpc8 from "@grpc/grpc-js";
312742
+ import * as grpc10 from "@grpc/grpc-js";
312116
312743
  function exchangeSupports(broker, capability) {
312117
312744
  return broker.has?.[capability] === true;
312118
312745
  }
@@ -312131,14 +312758,14 @@ async function handleGetPerpConfigState(ctx) {
312131
312758
  }
312132
312759
  if (!broker) {
312133
312760
  return wrappedCallback({
312134
- code: grpc8.status.INVALID_ARGUMENT,
312761
+ code: grpc10.status.INVALID_ARGUMENT,
312135
312762
  message: `Invalid CEX key: ${cex3}`
312136
312763
  }, null);
312137
312764
  }
312138
312765
  const exchange = broker;
312139
312766
  if (!exchangeSupports(exchange, "fetchPositions")) {
312140
312767
  return wrappedCallback({
312141
- code: grpc8.status.UNIMPLEMENTED,
312768
+ code: grpc10.status.UNIMPLEMENTED,
312142
312769
  message: `${normalizedCex} does not support fetchPositions`
312143
312770
  }, null);
312144
312771
  }
@@ -312155,7 +312782,7 @@ async function handleGetPerpConfigState(ctx) {
312155
312782
  } catch (error48) {
312156
312783
  safeLogError(`GetPerpConfigState failed for ${cex3}`, error48);
312157
312784
  ctx.wrappedCallback({
312158
- code: grpc8.status.INTERNAL,
312785
+ code: grpc10.status.INTERNAL,
312159
312786
  message: `GetPerpConfigState failed: ${sanitizeErrorDetail(error48)}`
312160
312787
  }, null);
312161
312788
  }
@@ -312168,14 +312795,14 @@ async function handleSetPerpConfigState(ctx) {
312168
312795
  }
312169
312796
  if (!broker) {
312170
312797
  return wrappedCallback({
312171
- code: grpc8.status.INVALID_ARGUMENT,
312798
+ code: grpc10.status.INVALID_ARGUMENT,
312172
312799
  message: `Invalid CEX key: ${cex3}`
312173
312800
  }, null);
312174
312801
  }
312175
312802
  const exchange = broker;
312176
312803
  if (!exchangeSupports(exchange, "setLeverage")) {
312177
312804
  return wrappedCallback({
312178
- code: grpc8.status.UNIMPLEMENTED,
312805
+ code: grpc10.status.UNIMPLEMENTED,
312179
312806
  message: `${normalizedCex} does not support setLeverage`
312180
312807
  }, null);
312181
312808
  }
@@ -312196,7 +312823,7 @@ async function handleSetPerpConfigState(ctx) {
312196
312823
  } catch (error48) {
312197
312824
  safeLogError(`SetPerpConfigState failed for ${cex3}`, error48);
312198
312825
  ctx.wrappedCallback({
312199
- code: grpc8.status.INTERNAL,
312826
+ code: grpc10.status.INTERNAL,
312200
312827
  message: `SetPerpConfigState failed: ${sanitizeErrorDetail(error48)}`
312201
312828
  }, null);
312202
312829
  }
@@ -312211,7 +312838,7 @@ async function handlePerpConfig(ctx) {
312211
312838
  }
312212
312839
 
312213
312840
  // src/handlers/execute-action/treasury-call.ts
312214
- import * as grpc9 from "@grpc/grpc-js";
312841
+ import * as grpc11 from "@grpc/grpc-js";
312215
312842
  async function handleTreasuryCall(ctx) {
312216
312843
  const { broker } = ctx;
312217
312844
  const callValue = parsePayloadForAction(ctx, CallPayloadSchema);
@@ -312222,7 +312849,7 @@ async function handleTreasuryCall(ctx) {
312222
312849
  try {
312223
312850
  if (callValue.functionName.startsWith("_") || callValue.functionName.includes("constructor") || callValue.functionName.includes("prototype")) {
312224
312851
  return ctx.wrappedCallback({
312225
- code: grpc9.status.PERMISSION_DENIED,
312852
+ code: grpc11.status.PERMISSION_DENIED,
312226
312853
  message: "Access to the requested function is denied"
312227
312854
  }, null);
312228
312855
  }
@@ -312237,7 +312864,7 @@ async function handleTreasuryCall(ctx) {
312237
312864
  const fn = broker[callValue.functionName];
312238
312865
  if (typeof fn !== "function" || broker.has?.[callValue.functionName] === false) {
312239
312866
  return ctx.wrappedCallback({
312240
- code: grpc9.status.INVALID_ARGUMENT,
312867
+ code: grpc11.status.INVALID_ARGUMENT,
312241
312868
  message: `Function not found on broker: ${callValue.functionName}`
312242
312869
  }, null);
312243
312870
  }
@@ -312316,7 +312943,7 @@ function asFiniteNumber(value) {
312316
312943
  }
312317
312944
 
312318
312945
  // src/handlers/execute-action/withdraw.ts
312319
- import * as grpc10 from "@grpc/grpc-js";
312946
+ import * as grpc12 from "@grpc/grpc-js";
312320
312947
  async function handleWithdraw(ctx) {
312321
312948
  const {
312322
312949
  call,
@@ -312339,7 +312966,7 @@ async function handleWithdraw(ctx) {
312339
312966
  const verityProof = verity.proof;
312340
312967
  if (!symbol2) {
312341
312968
  return ctx.wrappedCallback({
312342
- code: grpc10.status.INVALID_ARGUMENT,
312969
+ code: grpc12.status.INVALID_ARGUMENT,
312343
312970
  message: `ValidationError: Symbol required`
312344
312971
  }, null);
312345
312972
  }
@@ -312352,21 +312979,21 @@ async function handleWithdraw(ctx) {
312352
312979
  } catch (error48) {
312353
312980
  const message = getErrorMessage(error48);
312354
312981
  return ctx.wrappedCallback({
312355
- code: stableGrpcErrorCode(message) ?? grpc10.status.INVALID_ARGUMENT,
312982
+ code: stableGrpcErrorCode(message) ?? grpc12.status.INVALID_ARGUMENT,
312356
312983
  message
312357
312984
  }, null);
312358
312985
  }
312359
312986
  const transferValidation = validateWithdraw(policy, cex3, withdrawNetwork.brokerNetworkId, transferValue.recipientAddress, transferValue.amount, symbol2);
312360
312987
  if (!transferValidation.valid) {
312361
312988
  return ctx.wrappedCallback({
312362
- code: grpc10.status.PERMISSION_DENIED,
312989
+ code: grpc12.status.PERMISSION_DENIED,
312363
312990
  message: `policy_withdrawal_denied: ${transferValidation.error}`
312364
312991
  }, null);
312365
312992
  }
312366
312993
  const travelRule = resolveTravelRuleDecision(policy, cex3, transferValue.recipientAddress);
312367
312994
  if (travelRule.mode === "denied") {
312368
312995
  return ctx.wrappedCallback({
312369
- code: grpc10.status.FAILED_PRECONDITION,
312996
+ code: grpc12.status.FAILED_PRECONDITION,
312370
312997
  message: `travel_rule_denied: ${travelRule.error}`
312371
312998
  }, null);
312372
312999
  }
@@ -312433,7 +313060,7 @@ async function handleWithdraw(ctx) {
312433
313060
  payload: { recipientAddress: transferValue.recipientAddress }
312434
313061
  }
312435
313062
  });
312436
- const code = mapCcxtErrorToGrpcStatus(error48) ?? grpc10.status.INTERNAL;
313063
+ const code = mapCcxtErrorToGrpcStatus(error48) ?? grpc12.status.INTERNAL;
312437
313064
  ctx.wrappedCallback({
312438
313065
  code,
312439
313066
  message: `Withdraw failed: ${sanitizeErrorDetail(error48)}`
@@ -312442,33 +313069,152 @@ async function handleWithdraw(ctx) {
312442
313069
  }
312443
313070
 
312444
313071
  // src/handlers/execute-action/registry.ts
312445
- var ACTION_HANDLERS = {
312446
- [Action.Deposit]: handleDeposit,
312447
- [Action.Withdraw]: handleWithdraw,
312448
- [Action.Call]: handleTreasuryCall,
312449
- [Action.InternalTransfer]: handleInternalTransfer,
312450
- [Action.CreateOrder]: handleOrders,
312451
- [Action.GetOrderDetails]: handleOrders,
312452
- [Action.CancelOrder]: handleOrders,
312453
- [Action.FetchCurrency]: handlePassThrough,
312454
- [Action.FetchAccountId]: handlePassThrough,
312455
- [Action.FetchFees]: handlePassThrough,
312456
- [Action.FetchDepositAddresses]: handlePassThrough,
312457
- [Action.FetchBalances]: handlePassThrough,
312458
- [Action.FetchTicker]: handlePassThrough,
312459
- [Action.GetPerpConfigState]: handlePerpConfig,
312460
- [Action.SetPerpConfigState]: handlePerpConfig
313072
+ function valid() {
313073
+ return { valid: true };
313074
+ }
313075
+ function requireSymbol2(request) {
313076
+ return request.symbol?.trim() ? valid() : { valid: false, message: "symbol is required" };
313077
+ }
313078
+ function requireSpotSymbol(request) {
313079
+ const symbol2 = request.symbol?.trim() ?? "";
313080
+ return /^[^/\s]+\/[^/\s]+$/.test(symbol2) ? valid() : {
313081
+ valid: false,
313082
+ message: "symbol must be a slash-delimited spot pair"
313083
+ };
313084
+ }
313085
+ function validatePayload(schema, request) {
313086
+ const parsed = parsePayload(schema, request.payload);
313087
+ return parsed.success ? valid() : { valid: false, message: parsed.message };
313088
+ }
313089
+ function validateSymbolAndPayload(schema) {
313090
+ return (request) => {
313091
+ const symbolValidation = requireSymbol2(request);
313092
+ return symbolValidation.valid ? validatePayload(schema, request) : symbolValidation;
313093
+ };
313094
+ }
313095
+ function validateSpotSymbolAndPayload(schema) {
313096
+ return (request) => {
313097
+ const symbolValidation = requireSpotSymbol(request);
313098
+ return symbolValidation.valid ? validatePayload(schema, request) : symbolValidation;
313099
+ };
313100
+ }
313101
+ function validateFetchBalances(request) {
313102
+ const balanceType = request.payload?.balanceType;
313103
+ if (balanceType !== undefined && !new Set(["free", "used", "total"]).has(balanceType)) {
313104
+ return {
313105
+ valid: false,
313106
+ message: "balanceType must be free, used, or total"
313107
+ };
313108
+ }
313109
+ return valid();
313110
+ }
313111
+ var ACTION_DESCRIPTORS = {
313112
+ [Action.Deposit]: {
313113
+ handler: handleDeposit,
313114
+ access: "write",
313115
+ batchable: false
313116
+ },
313117
+ [Action.Withdraw]: {
313118
+ handler: handleWithdraw,
313119
+ access: "write",
313120
+ batchable: false
313121
+ },
313122
+ [Action.Call]: {
313123
+ handler: handleTreasuryCall,
313124
+ access: "write",
313125
+ batchable: false
313126
+ },
313127
+ [Action.InternalTransfer]: {
313128
+ handler: handleInternalTransfer,
313129
+ access: "write",
313130
+ batchable: false
313131
+ },
313132
+ [Action.CreateOrder]: {
313133
+ handler: handleOrders,
313134
+ access: "write",
313135
+ batchable: false
313136
+ },
313137
+ [Action.GetOrderDetails]: {
313138
+ handler: handleOrders,
313139
+ access: "read",
313140
+ batchable: false
313141
+ },
313142
+ [Action.CancelOrder]: {
313143
+ handler: handleOrders,
313144
+ access: "write",
313145
+ batchable: false
313146
+ },
313147
+ [Action.FetchCurrency]: {
313148
+ handler: handlePassThrough,
313149
+ access: "read",
313150
+ batchable: true,
313151
+ validateBatchRequest: validateSymbolAndPayload(FetchCurrencyPayloadSchema)
313152
+ },
313153
+ [Action.FetchAccountId]: {
313154
+ handler: handlePassThrough,
313155
+ access: "read",
313156
+ batchable: true,
313157
+ validateBatchRequest: (request) => validatePayload(EmptyActionPayloadSchema, request)
313158
+ },
313159
+ [Action.FetchFees]: {
313160
+ handler: handlePassThrough,
313161
+ access: "read",
313162
+ batchable: true,
313163
+ validateBatchRequest: validateSpotSymbolAndPayload(FetchFeesPayloadSchema)
313164
+ },
313165
+ [Action.FetchDepositAddresses]: {
313166
+ handler: handlePassThrough,
313167
+ access: "read",
313168
+ batchable: false
313169
+ },
313170
+ [Action.FetchBalances]: {
313171
+ handler: handlePassThrough,
313172
+ access: "read",
313173
+ batchable: true,
313174
+ validateBatchRequest: validateFetchBalances
313175
+ },
313176
+ [Action.FetchTicker]: {
313177
+ handler: handlePassThrough,
313178
+ access: "read",
313179
+ batchable: true,
313180
+ validateBatchRequest: validateSymbolAndPayload(EmptyActionPayloadSchema)
313181
+ },
313182
+ [Action.GetPerpConfigState]: {
313183
+ handler: handlePerpConfig,
313184
+ access: "read",
313185
+ batchable: true,
313186
+ validateBatchRequest: (request) => validatePayload(GetPerpConfigStatePayloadSchema, request)
313187
+ },
313188
+ [Action.SetPerpConfigState]: {
313189
+ handler: handlePerpConfig,
313190
+ access: "write",
313191
+ batchable: false
313192
+ },
313193
+ [Action.FetchMarketRules]: {
313194
+ handler: handlePassThrough,
313195
+ access: "read",
313196
+ batchable: true,
313197
+ validateBatchRequest: validateSpotSymbolAndPayload(EmptyActionPayloadSchema)
313198
+ },
313199
+ [Action.Batch]: {
313200
+ handler: (ctx) => handleBatch(ctx, getActionDescriptor),
313201
+ access: "read",
313202
+ batchable: false
313203
+ }
312461
313204
  };
313205
+ function getActionDescriptor(action) {
313206
+ return ACTION_DESCRIPTORS[action];
313207
+ }
312462
313208
  async function dispatchExecuteAction(ctx) {
312463
- const handler = ACTION_HANDLERS[ctx.action];
312464
- if (!handler) {
313209
+ const descriptor = getActionDescriptor(ctx.action);
313210
+ if (!descriptor) {
312465
313211
  ctx.wrappedCallback({
312466
- code: grpc11.status.INVALID_ARGUMENT,
313212
+ code: grpc13.status.INVALID_ARGUMENT,
312467
313213
  message: "Invalid Action"
312468
313214
  }, null);
312469
313215
  return;
312470
313216
  }
312471
- await handler(ctx);
313217
+ await descriptor.handler(ctx);
312472
313218
  }
312473
313219
 
312474
313220
  // src/handlers/execute-action/handler.ts
@@ -312484,7 +313230,7 @@ function grpcStatusName(error48) {
312484
313230
  if (typeof error48.code !== "number") {
312485
313231
  return "UNKNOWN";
312486
313232
  }
312487
- return grpc12.status[error48.code] ?? "UNKNOWN";
313233
+ return grpc14.status[error48.code] ?? "UNKNOWN";
312488
313234
  }
312489
313235
  function createExecuteActionHandler(deps) {
312490
313236
  const {
@@ -312532,7 +313278,7 @@ function createExecuteActionHandler(deps) {
312532
313278
  otelMetrics?.recordCounter("execute_action_errors_total", 1, {
312533
313279
  action: actionName,
312534
313280
  cex: cex3 || "unknown",
312535
- error_type: error48.code ? grpc12.status[error48.code] || "unknown" : "unknown"
313281
+ error_type: error48.code ? grpc14.status[error48.code] || "unknown" : "unknown"
312536
313282
  });
312537
313283
  } else {
312538
313284
  otelMetrics?.recordCounter("execute_action_success_total", 1, {
@@ -312555,13 +313301,13 @@ function createExecuteActionHandler(deps) {
312555
313301
  });
312556
313302
  if (!authenticateRequest(call, whitelistIps)) {
312557
313303
  return wrappedCallback({
312558
- code: grpc12.status.PERMISSION_DENIED,
313304
+ code: grpc14.status.PERMISSION_DENIED,
312559
313305
  message: "Access denied: Unauthorized IP"
312560
313306
  }, null);
312561
313307
  }
312562
313308
  if (!action || !cex3) {
312563
313309
  return wrappedCallback({
312564
- code: grpc12.status.INVALID_ARGUMENT,
313310
+ code: grpc14.status.INVALID_ARGUMENT,
312565
313311
  message: "`action` AND `cex` fields are required"
312566
313312
  }, null);
312567
313313
  }
@@ -312571,17 +313317,20 @@ function createExecuteActionHandler(deps) {
312571
313317
  const broker = selectedBrokerAccount?.exchange ?? createBroker(normalizedCex, call.metadata) ?? (isPublicMarketDataAction(action, call.request.payload) ? createPublicBroker(normalizedCex) : null);
312572
313318
  if (!broker) {
312573
313319
  return wrappedCallback({
312574
- code: grpc12.status.UNAUTHENTICATED,
313320
+ code: grpc14.status.UNAUTHENTICATED,
312575
313321
  message: `This Exchange is not registered and No API metadata was found`
312576
313322
  }, null);
312577
313323
  }
312578
313324
  const verity = { proof: "" };
312579
- const applyVerityToBroker = (targetBroker) => {
313325
+ const applyVerityToBroker = (targetBroker, proofState = verity) => {
312580
313326
  if (!useVerity)
312581
313327
  return;
312582
313328
  const override = buildHttpClientOverrideFromMetadata(metadata, verityProverUrl, (proof, notaryPubKey) => {
312583
- verity.proof = proof;
312584
- log.debug(`Verity proof:`, { proof, notaryPubKey });
313329
+ proofState.proof = proof;
313330
+ log.debug(`Verity proof received`, {
313331
+ has_proof: proof.length > 0,
313332
+ has_notary_public_key: Boolean(notaryPubKey)
313333
+ });
312585
313334
  });
312586
313335
  targetBroker.setHttpClientOverride(override, verityHttpClientOverridePredicate);
312587
313336
  };
@@ -312617,7 +313366,7 @@ function createExecuteActionHandler(deps) {
312617
313366
  } catch (error48) {
312618
313367
  safeLogError("ExecuteAction unhandled error", error48);
312619
313368
  return wrappedCallback({
312620
- code: grpc12.status.INTERNAL,
313369
+ code: grpc14.status.INTERNAL,
312621
313370
  message: "ExecuteAction failed unexpectedly"
312622
313371
  }, null);
312623
313372
  }
@@ -312677,7 +313426,7 @@ class SubscribeBrokerLifecycle {
312677
313426
  }
312678
313427
  }
312679
313428
  // src/handlers/subscribe/handler.ts
312680
- import * as grpc13 from "@grpc/grpc-js";
313429
+ import * as grpc15 from "@grpc/grpc-js";
312681
313430
 
312682
313431
  // src/helpers/binance-user-data-normalization.ts
312683
313432
  function requireQuantity(entry, key2) {
@@ -312788,8 +313537,8 @@ async function writeSubscribeError(call, isClosed, frame) {
312788
313537
  call.end();
312789
313538
  }
312790
313539
  }
312791
- function grpcStatusName2(status14) {
312792
- return typeof status14 === "number" ? grpc13.status[status14] ?? "UNKNOWN" : "UNKNOWN";
313540
+ function grpcStatusName2(status16) {
313541
+ return typeof status16 === "number" ? grpc15.status[status16] ?? "UNKNOWN" : "UNKNOWN";
312793
313542
  }
312794
313543
  function getBinanceEventMarketId(event) {
312795
313544
  const value = event.s;
@@ -312982,9 +313731,9 @@ function createSubscribeHandler(deps) {
312982
313731
  log.withMetadata(fields).info("Subscribe ended");
312983
313732
  }
312984
313733
  };
312985
- const writeTerminalError = async (frame, status14 = grpc13.status.UNKNOWN) => {
313734
+ const writeTerminalError = async (frame, status16 = grpc15.status.UNKNOWN) => {
312986
313735
  terminalOutcome = "error";
312987
- terminalGrpcStatus = grpcStatusName2(status14);
313736
+ terminalGrpcStatus = grpcStatusName2(status16);
312988
313737
  await writeSubscribeError(call, isStreamClosed, frame);
312989
313738
  };
312990
313739
  log.withMetadata(operationalFields).info("Subscribe started");
@@ -313020,7 +313769,7 @@ function createSubscribeHandler(deps) {
313020
313769
  error_type: "permission_denied"
313021
313770
  });
313022
313771
  call.emit("error", {
313023
- code: grpc13.status.PERMISSION_DENIED,
313772
+ code: grpc15.status.PERMISSION_DENIED,
313024
313773
  message: "Access denied: Unauthorized IP"
313025
313774
  }, null);
313026
313775
  call.destroy(new Error("Access denied: Unauthorized IP"));
@@ -313041,7 +313790,7 @@ function createSubscribeHandler(deps) {
313041
313790
  timestamp: Date.now(),
313042
313791
  symbol: symbol2 || "",
313043
313792
  type: subscriptionType2
313044
- }, grpc13.status.INVALID_ARGUMENT);
313793
+ }, grpc15.status.INVALID_ARGUMENT);
313045
313794
  return;
313046
313795
  }
313047
313796
  if (isPublicMarketDataSubscription(subscriptionType2)) {
@@ -313098,7 +313847,7 @@ function createSubscribeHandler(deps) {
313098
313847
  timestamp: Date.now(),
313099
313848
  symbol: symbol2,
313100
313849
  type: subscriptionType2
313101
- }, grpc13.status.NOT_FOUND);
313850
+ }, grpc15.status.NOT_FOUND);
313102
313851
  return;
313103
313852
  }
313104
313853
  if (!selectedBrokerAccount) {
@@ -313133,7 +313882,7 @@ function createSubscribeHandler(deps) {
313133
313882
  timestamp: Date.now(),
313134
313883
  symbol: resolvedSymbol,
313135
313884
  type: subscriptionType2
313136
- }, grpc13.status.FAILED_PRECONDITION);
313885
+ }, grpc15.status.FAILED_PRECONDITION);
313137
313886
  return;
313138
313887
  }
313139
313888
  const marketId = subscriptionType2 === SubscriptionType.ORDERS ? await getBinanceMarketId(accountBroker, resolvedSymbol) : undefined;
@@ -313147,7 +313896,7 @@ function createSubscribeHandler(deps) {
313147
313896
  timestamp: Date.now(),
313148
313897
  symbol: resolvedSymbol,
313149
313898
  type: subscriptionType2
313150
- }, grpc13.status.FAILED_PRECONDITION);
313899
+ }, grpc15.status.FAILED_PRECONDITION);
313151
313900
  return;
313152
313901
  }
313153
313902
  userDataSource = userDataStreamSupervisor.subscribe({
@@ -313205,7 +313954,7 @@ function createSubscribeHandler(deps) {
313205
313954
  timestamp: Date.now(),
313206
313955
  symbol: symbol2,
313207
313956
  type: subscriptionType2
313208
- }, grpc13.status.INVALID_ARGUMENT);
313957
+ }, grpc15.status.INVALID_ARGUMENT);
313209
313958
  }
313210
313959
  } catch (error48) {
313211
313960
  log.error("Error in Subscribe stream:", error48);
@@ -313215,7 +313964,7 @@ function createSubscribeHandler(deps) {
313215
313964
  timestamp: Date.now(),
313216
313965
  symbol: "",
313217
313966
  type: subscriptionType2
313218
- }, grpc13.status.INTERNAL);
313967
+ }, grpc15.status.INTERNAL);
313219
313968
  } finally {
313220
313969
  call.off("cancelled", closeOwnedBrokerOnCallEnd);
313221
313970
  call.off("error", closeOwnedBrokerOnCallEnd);
@@ -313347,7 +314096,9 @@ var descriptor = {
313347
314096
  FetchFees: 12,
313348
314097
  InternalTransfer: 13,
313349
314098
  GetPerpConfigState: 14,
313350
- SetPerpConfigState: 15
314099
+ SetPerpConfigState: 15,
314100
+ FetchMarketRules: 16,
314101
+ Batch: 17
313351
314102
  }
313352
314103
  }
313353
314104
  }
@@ -313369,10 +314120,10 @@ var PROTO_LOADER_OPTIONS = {
313369
314120
  var CEX_BROKER_PACKAGE_DEFINITION = protoLoader.fromJSON(node_descriptor_default, PROTO_LOADER_OPTIONS);
313370
314121
 
313371
314122
  // src/server.ts
313372
- var grpcObj = grpc14.loadPackageDefinition(CEX_BROKER_PACKAGE_DEFINITION);
314123
+ var grpcObj = grpc16.loadPackageDefinition(CEX_BROKER_PACKAGE_DEFINITION);
313373
314124
  var cexNode = grpcObj.cex_broker;
313374
314125
  function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, otelMetrics, brokerArchiver, orderActivityTracker, withdrawalObservationTracker, subscribeBrokerLifecycle, userDataStreamSupervisor, publicMarketDataFeedSupervisor) {
313375
- const server = new grpc14.Server;
314126
+ const server = new grpc16.Server;
313376
314127
  server.addService(cexNode.cex_service.service, {
313377
314128
  ExecuteAction: createExecuteActionHandler({
313378
314129
  policy,
@@ -313761,7 +314512,10 @@ class CEXBroker {
313761
314512
  await this.otelMetrics.initialize();
313762
314513
  }
313763
314514
  if (!this.userDataStreamSupervisor && Object.keys(this.brokers).length > 0) {
313764
- const publisher = new StreamHealthPublisher(streamHealthPublisherConfigFromEnv());
314515
+ const publisher = new StreamHealthPublisher({
314516
+ ...streamHealthPublisherConfigFromEnv(),
314517
+ producerId: USER_DATA_STREAM_HEALTH_PRODUCER_ID
314518
+ });
313765
314519
  this.userDataStreamSupervisor = new UserDataStreamSupervisor({
313766
314520
  brokers: this.brokers,
313767
314521
  publisher
@@ -313774,7 +314528,7 @@ class CEXBroker {
313774
314528
  otelMetrics: this.otelMetrics
313775
314529
  });
313776
314530
  this.server = getServer(this.policy, this.brokers, this.whitelistIps, this.useVerity, this.#verityProverUrl, this.otelMetrics, this.brokerArchiver, this.orderActivityTracker, this.withdrawalObservationTracker, undefined, this.userDataStreamSupervisor, this.publicMarketDataFeedSupervisor);
313777
- this.server.bindAsync(`0.0.0.0:${this.port}`, grpc15.ServerCredentials.createInsecure(), (err2, port) => {
314531
+ this.server.bindAsync(`0.0.0.0:${this.port}`, grpc17.ServerCredentials.createInsecure(), (err2, port) => {
313778
314532
  if (err2) {
313779
314533
  log.error(err2);
313780
314534
  return;
@@ -313799,7 +314553,8 @@ class CEXBroker {
313799
314553
  this.depositArchivePoller = new DepositArchivePoller({
313800
314554
  brokers: this.brokers,
313801
314555
  archiver: this.brokerArchiver,
313802
- metrics: this.otelMetrics
314556
+ metrics: this.otelMetrics,
314557
+ coveragePublisher: Object.keys(this.brokers).length > 0 ? new StreamHealthPublisher(depositPollerStreamHealthPublisherConfigFromEnv()) : undefined
313803
314558
  });
313804
314559
  this.depositArchivePoller.start();
313805
314560
  if (this.userDataStreamSupervisor) {
@@ -313832,4 +314587,4 @@ export {
313832
314587
  CEXBroker as default
313833
314588
  };
313834
314589
 
313835
- //# debugId=46EFCE207D029E0F64756E2164756E21
314590
+ //# debugId=9C0805B96506F17064756E2164756E21