@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.
@@ -74496,7 +74496,7 @@ var {
74496
74496
  } = import__.default;
74497
74497
 
74498
74498
  // src/index.ts
74499
- var grpc15 = __toESM(require_src3(), 1);
74499
+ var grpc17 = __toESM(require_src3(), 1);
74500
74500
 
74501
74501
  // node_modules/@usherlabs/ccxt/js/src/base/functions.js
74502
74502
  var exports_functions = {};
@@ -314580,7 +314580,9 @@ var Action = {
314580
314580
  FetchFees: 12,
314581
314581
  InternalTransfer: 13,
314582
314582
  GetPerpConfigState: 14,
314583
- SetPerpConfigState: 15
314583
+ SetPerpConfigState: 15,
314584
+ FetchMarketRules: 16,
314585
+ Batch: 17
314584
314586
  };
314585
314587
  var SubscriptionType = {
314586
314588
  NO_ACTION: 0,
@@ -315128,6 +315130,30 @@ function redactUnknownValue(value, secretLiterals) {
315128
315130
  }
315129
315131
  return String(value);
315130
315132
  }
315133
+ function removeSecretMaterial(value, secretLiterals = []) {
315134
+ if (value === null || value === undefined) {
315135
+ return value;
315136
+ }
315137
+ if (typeof value === "string") {
315138
+ return redactSecretLiterals(value, secretLiterals);
315139
+ }
315140
+ if (typeof value === "number" || typeof value === "boolean") {
315141
+ return value;
315142
+ }
315143
+ if (Array.isArray(value)) {
315144
+ return value.map((entry) => removeSecretMaterial(entry, secretLiterals));
315145
+ }
315146
+ if (typeof value === "object") {
315147
+ const clean = {};
315148
+ for (const [key, entry] of Object.entries(value)) {
315149
+ if (!SECRET_KEY_PATTERN.test(key)) {
315150
+ clean[key] = removeSecretMaterial(entry, secretLiterals);
315151
+ }
315152
+ }
315153
+ return clean;
315154
+ }
315155
+ return String(value);
315156
+ }
315131
315157
  function redactStreamPayload(payload, secretLiterals = []) {
315132
315158
  if (Array.isArray(payload)) {
315133
315159
  return {
@@ -317230,6 +317256,17 @@ function withTimeout(promise, timeoutMs, label) {
317230
317256
  return Promise.race([promise, expiry]).finally(() => clearTimeout(timer));
317231
317257
  }
317232
317258
  var ALL_CURRENCIES_CODE = "*";
317259
+ var MAX_FAILURE_REASON_CHARS = 256;
317260
+ var DEFAULT_FAILURE_REASON = "fetchDeposits failed";
317261
+ function decimal(value) {
317262
+ return value === null || value === undefined ? null : String(value);
317263
+ }
317264
+ function failureReason(error, exchange) {
317265
+ const text = error instanceof Error ? error.message : typeof error === "string" ? error : "";
317266
+ const secrets = [exchange.apiKey, exchange.secret].filter((value) => typeof value === "string" && value.length > 0);
317267
+ const trimmed = redactSecretLiterals(text, secrets).replace(/\s+/g, " ").trim().slice(0, MAX_FAILURE_REASON_CHARS);
317268
+ return trimmed.length > 0 ? trimmed : DEFAULT_FAILURE_REASON;
317269
+ }
317233
317270
  var BINANCE_UNLOCK_PROGRESS_SOURCE = {
317234
317271
  venue: "binance",
317235
317272
  endpoint: "GET /sapi/v1/capital/deposit/hisrec",
@@ -317240,6 +317277,20 @@ var BINANCE_UNLOCK_PROGRESS_SOURCE = {
317240
317277
  completeTime: "info.completeTime"
317241
317278
  }
317242
317279
  };
317280
+ function observedProgress(progress) {
317281
+ if (progress === undefined)
317282
+ return null;
317283
+ return {
317284
+ state: progress.state,
317285
+ progress_state: progress.progress_state,
317286
+ reason: progress.reason,
317287
+ native_status: decimal(progress.native_status),
317288
+ current: decimal(progress.current),
317289
+ credit_required: decimal(progress.credit_required),
317290
+ unlock_required: decimal(progress.unlock_required),
317291
+ complete_time: decimal(progress.complete_time)
317292
+ };
317293
+ }
317243
317294
  function depositTimestamp(record) {
317244
317295
  const observedAt = depositField(record, [
317245
317296
  "timestamp",
@@ -317469,6 +317520,7 @@ class DepositArchivePoller {
317469
317520
  #cursors = new Map;
317470
317521
  #lastArchivedByTarget = new Map;
317471
317522
  #unsupportedLogged = new Set;
317523
+ #coverage = new Map;
317472
317524
  #config;
317473
317525
  constructor(params) {
317474
317526
  this.params = params;
@@ -317479,6 +317531,7 @@ class DepositArchivePoller {
317479
317531
  return;
317480
317532
  }
317481
317533
  log.info("\uD83D\uDCE5 Deposit archive poller started");
317534
+ this.params.coveragePublisher?.start();
317482
317535
  this.#schedule(0);
317483
317536
  }
317484
317537
  async stop() {
@@ -317488,6 +317541,7 @@ class DepositArchivePoller {
317488
317541
  this.#timer = null;
317489
317542
  }
317490
317543
  await this.#running;
317544
+ await this.params.coveragePublisher?.close(this.#coverageSnapshots());
317491
317545
  }
317492
317546
  async pollAllOnce() {
317493
317547
  if (this.#stopped || this.#running || !this.params.archiver.isEnabled()) {
@@ -317523,14 +317577,90 @@ class DepositArchivePoller {
317523
317577
  return true;
317524
317578
  }
317525
317579
  async#pollOne(target) {
317580
+ const attemptedAt = new Date().toISOString();
317526
317581
  let outcome = "error";
317582
+ let observation;
317527
317583
  try {
317528
- outcome = await this.#pollTarget(target);
317584
+ const result = await this.#pollTarget(target, attemptedAt);
317585
+ outcome = result.outcome;
317586
+ observation = result.observation;
317587
+ } catch (error) {
317588
+ observation = this.#observation("error", attemptedAt, {
317589
+ errorReason: failureReason(error, target.account.exchange)
317590
+ });
317591
+ throw error;
317529
317592
  } finally {
317530
317593
  this.params.metrics?.recordCounter("cex_deposit_poller_polls_total", 1, { exchange: target.exchangeId, outcome });
317594
+ this.#recordCoverage(target, outcome, observation ?? this.#observation("error", attemptedAt, {
317595
+ errorReason: "poll aborted before completion"
317596
+ }));
317531
317597
  }
317532
317598
  }
317533
- async#pollTarget(target) {
317599
+ #observation(disposition, attemptedAt, detail = {}) {
317600
+ if (disposition === "success" && detail.completedAt !== undefined && detail.completedAt < attemptedAt) {
317601
+ return this.#observation("error", attemptedAt, {
317602
+ requestSinceMs: detail.requestSinceMs,
317603
+ errorReason: `source clock invalid: completed ${detail.completedAt} before attempted ${attemptedAt}`
317604
+ });
317605
+ }
317606
+ return {
317607
+ version: "1",
317608
+ disposition,
317609
+ attempted_at: attemptedAt,
317610
+ completed_at: disposition === "success" ? detail.completedAt ?? null : null,
317611
+ poll_interval_ms: String(this.#config.pollIntervalMs),
317612
+ fetch_timeout_ms: String(this.#config.fetchTimeoutMs),
317613
+ deposits_limit: String(this.#config.depositsLimit),
317614
+ request_since_ms: decimal(detail.requestSinceMs),
317615
+ next_cursor_ms: disposition === "success" ? decimal(detail.nextCursorMs) : null,
317616
+ response_truncated: disposition === "success" ? detail.responseTruncated ?? null : null,
317617
+ malformed_count: String(detail.malformedCount ?? 0),
317618
+ error_reason: disposition === "error" ? detail.errorReason?.trim() || DEFAULT_FAILURE_REASON : "",
317619
+ observed_deposits: disposition === "success" ? detail.observedDeposits ?? [] : []
317620
+ };
317621
+ }
317622
+ #recordCoverage(target, outcome, observation) {
317623
+ const publisher = this.params.coveragePublisher;
317624
+ if (!publisher)
317625
+ return;
317626
+ const key = this.#targetKey(target);
317627
+ const previous = this.#coverage.get(key);
317628
+ const now3 = observation.completed_at ?? new Date().toISOString();
317629
+ const state = outcome === "ok" ? "connected" : "error";
317630
+ const base2 = previous?.snapshot;
317631
+ const stateChangedAt = base2 && base2.state === state ? base2.stateChangedAt : now3;
317632
+ const attempts = BigInt(base2?.connectAttemptCount ?? "0") + 1n;
317633
+ const errors = BigInt(base2?.errorCount ?? "0") + (outcome === "error" ? 1n : 0n);
317634
+ const reconnects = BigInt(base2?.reconnectCount ?? "0") + (outcome === "ok" && previous?.lastOutcome === "error" ? 1n : 0n);
317635
+ const snapshot = {
317636
+ exchange: target.exchangeId,
317637
+ accountSelector: target.account.label,
317638
+ accountRole: target.account.role,
317639
+ streamKind: "deposit_poller",
317640
+ accountScope: "spot",
317641
+ registryStatus: "active",
317642
+ retiredAt: null,
317643
+ state,
317644
+ stateChangedAt,
317645
+ lastConnectedAt: outcome === "ok" ? observation.completed_at : base2?.lastConnectedAt ?? null,
317646
+ lastAuthenticatedAt: null,
317647
+ lastReceivedAt: outcome === "ok" ? observation.completed_at : base2?.lastReceivedAt ?? null,
317648
+ connectAttemptCount: attempts.toString(),
317649
+ reconnectCount: reconnects.toString(),
317650
+ errorCount: errors.toString(),
317651
+ lastFailureKind: outcome === "ok" ? "none" : outcome === "unsupported" ? "unsupported_connector" : "transport_error",
317652
+ lastFailureReason: outcome === "ok" ? "" : outcome === "unsupported" ? "fetchDeposits unsupported" : observation.error_reason,
317653
+ trafficMode: "continuous",
317654
+ sourceWatermark: null,
317655
+ pollObservation: observation
317656
+ };
317657
+ this.#coverage.set(key, { snapshot, lastOutcome: outcome });
317658
+ publisher.publish(this.#coverageSnapshots());
317659
+ }
317660
+ #coverageSnapshots() {
317661
+ return [...this.#coverage.values()].map((entry) => entry.snapshot);
317662
+ }
317663
+ async#pollTarget(target, attemptedAt) {
317534
317664
  const exchange = target.account.exchange;
317535
317665
  const key = this.#targetKey(target);
317536
317666
  if (typeof exchange.fetchDeposits !== "function" || exchange.has?.fetchDeposits === false) {
@@ -317541,7 +317671,10 @@ class DepositArchivePoller {
317541
317671
  account: target.account.label
317542
317672
  });
317543
317673
  }
317544
- return "unsupported";
317674
+ return {
317675
+ outcome: "unsupported",
317676
+ observation: this.#observation("unsupported", attemptedAt)
317677
+ };
317545
317678
  }
317546
317679
  const since = this.#cursors.get(key) ?? Date.now() - this.#config.lookbackMs;
317547
317680
  let deposits;
@@ -317554,15 +317687,48 @@ class DepositArchivePoller {
317554
317687
  account: target.account.label,
317555
317688
  error
317556
317689
  });
317557
- return "error";
317690
+ return {
317691
+ outcome: "error",
317692
+ observation: this.#observation("error", attemptedAt, {
317693
+ requestSinceMs: since,
317694
+ errorReason: failureReason(error, exchange)
317695
+ })
317696
+ };
317558
317697
  }
317559
- if (!Array.isArray(deposits) || deposits.length === 0) {
317560
- return "ok";
317698
+ if (!Array.isArray(deposits)) {
317699
+ return {
317700
+ outcome: "error",
317701
+ observation: this.#observation("error", attemptedAt, {
317702
+ requestSinceMs: since,
317703
+ errorReason: "fetchDeposits returned a non-array response"
317704
+ })
317705
+ };
317706
+ }
317707
+ if (deposits.length > this.#config.depositsLimit) {
317708
+ return {
317709
+ outcome: "error",
317710
+ observation: this.#observation("error", attemptedAt, {
317711
+ requestSinceMs: since,
317712
+ errorReason: `fetchDeposits returned ${deposits.length} rows for a limit of ${this.#config.depositsLimit}`
317713
+ })
317714
+ };
317715
+ }
317716
+ if (deposits.length === 0) {
317717
+ return this.#completed(this.#observation("success", attemptedAt, {
317718
+ completedAt: new Date().toISOString(),
317719
+ requestSinceMs: since,
317720
+ nextCursorMs: this.#cursors.get(key),
317721
+ responseTruncated: false,
317722
+ observedDeposits: []
317723
+ }));
317561
317724
  }
317562
317725
  let archived = 0;
317726
+ let malformed = 0;
317727
+ const observed = [];
317563
317728
  for (const deposit of deposits) {
317564
317729
  const record = asRecord(deposit);
317565
317730
  if (!record) {
317731
+ malformed += 1;
317566
317732
  continue;
317567
317733
  }
317568
317734
  const info = asRecord(record.info);
@@ -317587,6 +317753,16 @@ class DepositArchivePoller {
317587
317753
  });
317588
317754
  const lastArchived = identity === undefined ? undefined : this.#lastArchivedByTarget.get(key)?.get(identity);
317589
317755
  const classification = classifyDeposit(target.exchangeId, record, lastArchived);
317756
+ const depositTimestampMs = depositTimestamp(record);
317757
+ observed.push({
317758
+ coin: assetSymbol === undefined ? "" : String(assetSymbol),
317759
+ network: network === undefined ? "" : String(network),
317760
+ external_id: depositTxid ?? "",
317761
+ txid: depositTxid ?? "",
317762
+ deposit_timestamp_ms: decimal(depositTimestampMs),
317763
+ status: classification.archiveStatus,
317764
+ progress: observedProgress(classification.unlockProgress)
317765
+ });
317590
317766
  if (lastArchived && lastArchived.status === classification.archiveStatus && lastArchived.progressKey === classification.progressKey) {
317591
317767
  continue;
317592
317768
  }
@@ -317627,7 +317803,7 @@ class DepositArchivePoller {
317627
317803
  }
317628
317804
  targetDeposits2.set(identity, {
317629
317805
  status: classification.archiveStatus,
317630
- timestamp: depositTimestamp(record),
317806
+ timestamp: depositTimestampMs,
317631
317807
  progressKey: classification.progressKey,
317632
317808
  highWatermark: classification.highWatermark
317633
317809
  });
@@ -317650,7 +317826,20 @@ class DepositArchivePoller {
317650
317826
  this.#lastArchivedByTarget.delete(key);
317651
317827
  }
317652
317828
  }
317653
- return "ok";
317829
+ return this.#completed(this.#observation("success", attemptedAt, {
317830
+ completedAt: new Date().toISOString(),
317831
+ requestSinceMs: since,
317832
+ nextCursorMs: nextCursor,
317833
+ responseTruncated: deposits.length >= this.#config.depositsLimit,
317834
+ malformedCount: malformed,
317835
+ observedDeposits: observed
317836
+ }));
317837
+ }
317838
+ #completed(observation) {
317839
+ return {
317840
+ outcome: observation.disposition === "success" ? "ok" : "error",
317841
+ observation
317842
+ };
317654
317843
  }
317655
317844
  #targetKey(target) {
317656
317845
  return `${target.exchangeId}|${target.account.label}|${target.code}`;
@@ -320528,7 +320717,8 @@ import { request as httpsRequest3 } from "node:https";
320528
320717
  import { dirname as dirname2 } from "node:path";
320529
320718
  var SOURCE = "broker_write";
320530
320719
  var TABLE = "broker_stream_health.snapshots";
320531
- var PRODUCER_ID = "cex-broker-user-data";
320720
+ var USER_DATA_STREAM_HEALTH_PRODUCER_ID = "cex-broker-user-data";
320721
+ var DEPOSIT_POLLER_STREAM_HEALTH_PRODUCER_ID = "cex-broker-deposit-poller";
320532
320722
  var STATE_VERSION = 1;
320533
320723
  var HEARTBEAT_MS = 30000;
320534
320724
  var FORWARDER_TIMEOUT_MS = 3000;
@@ -320563,11 +320753,11 @@ function registryRevision(snapshots) {
320563
320753
  })).sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
320564
320754
  return createHash5("sha256").update(JSON.stringify(rows)).digest("hex");
320565
320755
  }
320566
- function validState(value) {
320756
+ function validState(value, producerId) {
320567
320757
  if (!value || typeof value !== "object" || Array.isArray(value))
320568
320758
  return false;
320569
320759
  const state = value;
320570
- return state.version === STATE_VERSION && state.producerId === PRODUCER_ID && typeof state.producerEpoch === "string" && typeof state.runId === "string" && typeof state.nextBatchSequence === "string" && state.nextStreamSequences !== null && typeof state.nextStreamSequences === "object" && (state.pendingBody === undefined || typeof state.pendingBody === "string");
320760
+ return state.version === STATE_VERSION && state.producerId === producerId && typeof state.producerEpoch === "string" && typeof state.runId === "string" && typeof state.nextBatchSequence === "string" && state.nextStreamSequences !== null && typeof state.nextStreamSequences === "object" && (state.pendingBody === undefined || typeof state.pendingBody === "string");
320571
320761
  }
320572
320762
  function forwarderPost(url2, body, authToken, timeoutMs) {
320573
320763
  const request = url2.protocol === "http:" ? httpRequest3 : httpsRequest3;
@@ -320597,6 +320787,7 @@ function forwarderPost(url2, body, authToken, timeoutMs) {
320597
320787
  }
320598
320788
 
320599
320789
  class StreamHealthPublisher {
320790
+ #producerId;
320600
320791
  #deploymentId;
320601
320792
  #statePath;
320602
320793
  #heartbeatMs;
@@ -320611,6 +320802,7 @@ class StreamHealthPublisher {
320611
320802
  #retry = null;
320612
320803
  #retryAttempt = 0;
320613
320804
  constructor(options) {
320805
+ this.#producerId = identifier(options.producerId, "producer_id");
320614
320806
  this.#deploymentId = identifier(options.deploymentId, "deployment_id");
320615
320807
  this.#statePath = options.statePath.trim();
320616
320808
  if (!this.#statePath) {
@@ -320635,7 +320827,7 @@ class StreamHealthPublisher {
320635
320827
  const loaded = this.#read();
320636
320828
  this.#state = loaded ?? {
320637
320829
  version: STATE_VERSION,
320638
- producerId: PRODUCER_ID,
320830
+ producerId: this.#producerId,
320639
320831
  producerEpoch: "1",
320640
320832
  runId: randomUUID3(),
320641
320833
  nextBatchSequence: "1",
@@ -320748,7 +320940,7 @@ class StreamHealthPublisher {
320748
320940
  return {
320749
320941
  table: TABLE,
320750
320942
  row: {
320751
- producer_id: PRODUCER_ID,
320943
+ producer_id: this.#producerId,
320752
320944
  producer_epoch: this.#state.producerEpoch,
320753
320945
  run_id: this.#state.runId,
320754
320946
  batch_sequence: batchSequence,
@@ -320775,7 +320967,8 @@ class StreamHealthPublisher {
320775
320967
  last_failure_kind: snapshot.lastFailureKind,
320776
320968
  last_failure_reason: snapshot.lastFailureReason,
320777
320969
  traffic_mode: snapshot.trafficMode,
320778
- source_watermark: snapshot.sourceWatermark
320970
+ source_watermark: snapshot.sourceWatermark,
320971
+ ...snapshot.streamKind === "deposit_poller" ? { poll_observation: snapshot.pollObservation } : {}
320779
320972
  }
320780
320973
  };
320781
320974
  });
@@ -320803,7 +320996,7 @@ class StreamHealthPublisher {
320803
320996
  #read() {
320804
320997
  try {
320805
320998
  const parsed = JSON.parse(readFileSync2(this.#statePath, "utf8"));
320806
- if (!validState(parsed))
320999
+ if (!validState(parsed, this.#producerId))
320807
321000
  throw new Error("invalid state shape");
320808
321001
  return parsed;
320809
321002
  } catch (error) {
@@ -320866,6 +321059,14 @@ function streamHealthPublisherConfigFromEnv(env = process.env) {
320866
321059
  forwarderAuthToken: env.CEX_BROKER_ARCHIVE_FORWARDER_TOKEN?.trim() || undefined
320867
321060
  };
320868
321061
  }
321062
+ function depositPollerStreamHealthPublisherConfigFromEnv(env = process.env) {
321063
+ const base2 = streamHealthPublisherConfigFromEnv(env);
321064
+ return {
321065
+ ...base2,
321066
+ producerId: DEPOSIT_POLLER_STREAM_HEALTH_PRODUCER_ID,
321067
+ statePath: `${base2.statePath}.deposit-poller`
321068
+ };
321069
+ }
320869
321070
 
320870
321071
  // src/helpers/user-asset-archive-poller.ts
320871
321072
  var DEFAULT_CONFIG4 = {
@@ -321436,7 +321637,7 @@ class AccountWorker {
321436
321637
  for (const subscriber of [...this.#subscribers])
321437
321638
  subscriber.close();
321438
321639
  }
321439
- #transition(state, failureKind, failureReason) {
321640
+ #transition(state, failureKind, failureReason2) {
321440
321641
  const timestamp = now3();
321441
321642
  if (this.#snapshot.state !== state) {
321442
321643
  this.#snapshot.state = state;
@@ -321444,7 +321645,7 @@ class AccountWorker {
321444
321645
  }
321445
321646
  if (failureKind) {
321446
321647
  this.#snapshot.lastFailureKind = failureKind;
321447
- this.#snapshot.lastFailureReason = failureReason ?? "";
321648
+ this.#snapshot.lastFailureReason = failureReason2 ?? "";
321448
321649
  }
321449
321650
  this.onChange();
321450
321651
  }
@@ -321568,7 +321769,7 @@ class UserDataStreamSupervisor {
321568
321769
  }
321569
321770
 
321570
321771
  // src/server.ts
321571
- var grpc14 = __toESM(require_src3(), 1);
321772
+ var grpc16 = __toESM(require_src3(), 1);
321572
321773
 
321573
321774
  // src/handlers/execute-action/deposit.ts
321574
321775
  var grpc3 = __toESM(require_src3(), 1);
@@ -335220,7 +335421,10 @@ config(en_default());
335220
335421
  // src/schemas/action-payloads.ts
335221
335422
  var parseJsonString = (value) => {
335222
335423
  if (typeof value !== "string") {
335223
- return value;
335424
+ if (value === null || value === undefined || typeof value === "number" || typeof value === "boolean" || typeof value === "object") {
335425
+ return value;
335426
+ }
335427
+ return String(value);
335224
335428
  }
335225
335429
  try {
335226
335430
  return JSON.parse(value);
@@ -335229,19 +335433,6 @@ var parseJsonString = (value) => {
335229
335433
  }
335230
335434
  };
335231
335435
  var stringNumberRecordSchema = exports_external.record(exports_external.string(), exports_external.union([exports_external.string(), exports_external.number()]));
335232
- var booleanLikeSchema = exports_external.preprocess((value) => {
335233
- if (typeof value !== "string") {
335234
- return value;
335235
- }
335236
- const normalized = value.trim().toLowerCase();
335237
- if (["true", "1", "yes"].includes(normalized)) {
335238
- return true;
335239
- }
335240
- if (["false", "0", "no"].includes(normalized)) {
335241
- return false;
335242
- }
335243
- return value;
335244
- }, exports_external.boolean());
335245
335436
  var DepositPayloadSchema = exports_external.object({
335246
335437
  recipientAddress: exports_external.string().min(1),
335247
335438
  amount: exports_external.coerce.number().positive(),
@@ -335302,10 +335493,22 @@ var CancelOrderPayloadSchema = exports_external.object({
335302
335493
  orderId: exports_external.string().min(1),
335303
335494
  params: exports_external.preprocess(parseJsonString, stringNumberRecordSchema).default({})
335304
335495
  });
335305
- var FetchFeesPayloadSchema = exports_external.object({
335306
- includeAllFees: booleanLikeSchema.optional().default(false),
335307
- includeFundingFees: booleanLikeSchema.optional()
335308
- });
335496
+ var EmptyActionPayloadSchema = exports_external.object({}).strict();
335497
+ var FetchFeesPayloadSchema = EmptyActionPayloadSchema;
335498
+ var FetchCurrencyPayloadSchema = exports_external.object({
335499
+ network: exports_external.string().trim().min(1)
335500
+ }).strict();
335501
+ var MAX_BATCH_CHILDREN = 32;
335502
+ var MAX_BATCH_REQUEST_BYTES = 256 * 1024;
335503
+ var BatchChildRequestSchema = exports_external.object({
335504
+ id: exports_external.string().trim().min(1),
335505
+ action: exports_external.number().int().nonnegative(),
335506
+ symbol: exports_external.string(),
335507
+ payload: exports_external.record(exports_external.string(), exports_external.string())
335508
+ }).strict();
335509
+ var BatchPayloadSchema = exports_external.object({
335510
+ requests: exports_external.preprocess(parseJsonString, exports_external.array(BatchChildRequestSchema).min(1).max(MAX_BATCH_CHILDREN))
335511
+ }).strict();
335309
335512
 
335310
335513
  // src/helpers/grpc/callbacks.ts
335311
335514
  var grpc = __toESM(require_src3(), 1);
@@ -335354,7 +335557,7 @@ function stableGrpcErrorCode(message) {
335354
335557
  if (message.startsWith("AuthenticationError:")) {
335355
335558
  return grpc2.status.UNAUTHENTICATED;
335356
335559
  }
335357
- if (message.startsWith("InsufficientFunds:")) {
335560
+ if (message.startsWith("InsufficientFunds:") || message.startsWith("fee_unavailable:")) {
335358
335561
  return grpc2.status.FAILED_PRECONDITION;
335359
335562
  }
335360
335563
  if (message.startsWith("venue_discovery_unavailable:")) {
@@ -335414,6 +335617,13 @@ function resolveGrpcError(error48, message) {
335414
335617
  }
335415
335618
 
335416
335619
  // src/handlers/execute-action/context.ts
335620
+ function requireSymbol(ctx, message = "ValidationError: Symbol required") {
335621
+ if (!ctx.symbol) {
335622
+ ctx.wrappedCallback(invalidArgumentError(message), null);
335623
+ return false;
335624
+ }
335625
+ return true;
335626
+ }
335417
335627
  function parsePayloadForAction(ctx, schema) {
335418
335628
  return parseActionPayload(schema, ctx.call.request.payload, ctx.wrappedCallback);
335419
335629
  }
@@ -335436,6 +335646,12 @@ function rejectWithGrpcError(ctx, error48, options) {
335436
335646
  }
335437
335647
  ctx.wrappedCallback({ code, message: finalMessage }, null);
335438
335648
  }
335649
+ function successWithProof(ctx, result) {
335650
+ ctx.wrappedCallback(null, {
335651
+ proof: ctx.verity.proof,
335652
+ result: JSON.stringify(result)
335653
+ });
335654
+ }
335439
335655
 
335440
335656
  // src/handlers/execute-action/deposit.ts
335441
335657
  async function handleDeposit(ctx) {
@@ -335613,7 +335829,7 @@ async function handleDeposit(ctx) {
335613
335829
  }
335614
335830
  }
335615
335831
  // src/handlers/execute-action/handler.ts
335616
- var grpc12 = __toESM(require_src3(), 1);
335832
+ var grpc14 = __toESM(require_src3(), 1);
335617
335833
 
335618
335834
  // src/helpers/grpc/broker.ts
335619
335835
  function selectBrokerAccountForCex(normalizedCex, brokers, metadata) {
@@ -335745,10 +335961,458 @@ async function handleOrderBookCall(ctx) {
335745
335961
  }
335746
335962
 
335747
335963
  // src/handlers/execute-action/registry.ts
335748
- var grpc11 = __toESM(require_src3(), 1);
335964
+ var grpc13 = __toESM(require_src3(), 1);
335749
335965
 
335750
- // src/handlers/execute-action/internal-transfer.ts
335966
+ // src/handlers/execute-action/batch.ts
335751
335967
  var grpc5 = __toESM(require_src3(), 1);
335968
+
335969
+ // src/helpers/venue-evidence.ts
335970
+ var DECIMAL_PATTERN = /^\+?(\d+)(?:\.(\d*))?(?:[eE]([+-]?\d+))?$/;
335971
+ function exchangeSecretLiterals(broker) {
335972
+ const record2 = broker;
335973
+ return [
335974
+ record2.apiKey,
335975
+ record2.secret,
335976
+ record2.password,
335977
+ record2.privateKey
335978
+ ].filter((value) => typeof value === "string" && value.length > 0);
335979
+ }
335980
+ function sanitizeVenueError(error48, broker) {
335981
+ return redactSecretLiterals(sanitizeErrorDetail(error48), exchangeSecretLiterals(broker));
335982
+ }
335983
+ function resolveEvidenceAccountScope(selectedBrokerAccount, metadata) {
335984
+ return {
335985
+ accountSelector: selectedBrokerAccount?.label ?? getCurrentBrokerSelector(metadata),
335986
+ credentialSource: selectedBrokerAccount ? "configured_pool" : "request_metadata"
335987
+ };
335988
+ }
335989
+ function canonicalNonnegativeDecimal(value, field) {
335990
+ const text = typeof value === "number" ? Number.isFinite(value) ? String(value) : "" : typeof value === "string" ? value.trim() : "";
335991
+ const match = text.match(DECIMAL_PATTERN);
335992
+ if (!match) {
335993
+ throw new Error(`venue_discovery_unavailable: ${field} must be decimal`);
335994
+ }
335995
+ const integer2 = match[1] ?? "0";
335996
+ const fraction = match[2] ?? "";
335997
+ const exponent = Number.parseInt(match[3] ?? "0", 10);
335998
+ if (!Number.isSafeInteger(exponent)) {
335999
+ throw new Error(`venue_discovery_unavailable: ${field} exponent is invalid`);
336000
+ }
336001
+ const digits = `${integer2}${fraction}`;
336002
+ const decimalIndex = integer2.length + exponent;
336003
+ let rendered;
336004
+ if (decimalIndex <= 0) {
336005
+ rendered = `0.${"0".repeat(-decimalIndex)}${digits}`;
336006
+ } else if (decimalIndex >= digits.length) {
336007
+ rendered = `${digits}${"0".repeat(decimalIndex - digits.length)}`;
336008
+ } else {
336009
+ rendered = `${digits.slice(0, decimalIndex)}.${digits.slice(decimalIndex)}`;
336010
+ }
336011
+ const [whole = "0", decimals = ""] = rendered.split(".");
336012
+ const normalizedWhole = whole.replace(/^0+(?=\d)/, "") || "0";
336013
+ const normalizedDecimals = decimals.replace(/0+$/, "");
336014
+ return normalizedDecimals ? `${normalizedWhole}.${normalizedDecimals}` : normalizedWhole;
336015
+ }
336016
+ function decimalFractionToBasisPoints(value) {
336017
+ const canonical = canonicalNonnegativeDecimal(value, "fee rate");
336018
+ return canonicalNonnegativeDecimal(`${canonical}e4`, "fee basis points");
336019
+ }
336020
+ function canonicalOptionalDecimal(value, field) {
336021
+ return value === undefined || value === null ? undefined : canonicalNonnegativeDecimal(value, field);
336022
+ }
336023
+ function precisionIncrement(value, precisionMode, field) {
336024
+ if ((precisionMode === 2 || precisionMode === "DECIMAL_PLACES") && (typeof value === "number" || typeof value === "string")) {
336025
+ const decimalPlaces = Number(value);
336026
+ if (Number.isInteger(decimalPlaces) && decimalPlaces >= 0) {
336027
+ return canonicalNonnegativeDecimal(`1e-${decimalPlaces}`, field);
336028
+ }
336029
+ }
336030
+ return canonicalNonnegativeDecimal(value, field);
336031
+ }
336032
+ function evidenceSourceDigest(input) {
336033
+ const source = removeSecretMaterial(input.source, exchangeSecretLiterals(input.broker));
336034
+ return sha256Canonical({
336035
+ action: input.action,
336036
+ exchange: input.exchange,
336037
+ requestedKey: input.requestedKey,
336038
+ accountSelector: input.accountSelector,
336039
+ sourceMethod: input.sourceMethod,
336040
+ source
336041
+ });
336042
+ }
336043
+ async function resolveSpotMarketIdentity(broker, symbol2) {
336044
+ const requestedSymbol = symbol2.trim().toUpperCase();
336045
+ if (!/^[^/\s]+\/[^/\s]+$/.test(requestedSymbol)) {
336046
+ throw new Error("venue_discovery_unavailable: symbol must be a slash-delimited spot pair");
336047
+ }
336048
+ await broker.loadMarkets();
336049
+ const market = broker.market(requestedSymbol);
336050
+ if (!isRecord(market)) {
336051
+ throw new Error(`venue_discovery_unavailable: market not found for ${requestedSymbol}`);
336052
+ }
336053
+ const unifiedSymbol = typeof market.symbol === "string" ? market.symbol.trim().toUpperCase() : requestedSymbol;
336054
+ const baseAsset = typeof market.base === "string" ? market.base.trim().toUpperCase() : "";
336055
+ const quoteAsset = typeof market.quote === "string" ? market.quote.trim().toUpperCase() : "";
336056
+ const sourceSymbol = typeof market.id === "string" ? market.id.trim() : "";
336057
+ const isSpot = market.spot === true || market.type === "spot";
336058
+ if (unifiedSymbol !== requestedSymbol || !baseAsset || !quoteAsset || !sourceSymbol || !isSpot || market.active !== true) {
336059
+ throw new Error(`venue_discovery_unavailable: active spot market identity unavailable for ${requestedSymbol}`);
336060
+ }
336061
+ return {
336062
+ market,
336063
+ canonicalPair: `${baseAsset}-${quoteAsset}`,
336064
+ unifiedSymbol,
336065
+ sourceSymbol,
336066
+ baseAsset,
336067
+ quoteAsset
336068
+ };
336069
+ }
336070
+ function extractTradingFeeRates(response) {
336071
+ if (!isRecord(response)) {
336072
+ throw new Error("fee_unavailable: trading-fee response is not an object");
336073
+ }
336074
+ const info = isRecord(response.info) ? response.info : undefined;
336075
+ const nestedData = isRecord(info?.data) ? info.data : isRecord(response.data) ? response.data : undefined;
336076
+ const maker = [
336077
+ response.maker,
336078
+ response.makerCommission,
336079
+ nestedData?.maker,
336080
+ nestedData?.makerCommission
336081
+ ].find((value) => value !== undefined && value !== null);
336082
+ const taker = [
336083
+ response.taker,
336084
+ response.takerCommission,
336085
+ nestedData?.taker,
336086
+ nestedData?.takerCommission
336087
+ ].find((value) => value !== undefined && value !== null);
336088
+ try {
336089
+ return {
336090
+ makerRate: canonicalNonnegativeDecimal(maker, "maker commission"),
336091
+ takerRate: canonicalNonnegativeDecimal(taker, "taker commission")
336092
+ };
336093
+ } catch (error48) {
336094
+ throw new Error(`fee_unavailable: ${sanitizeErrorDetail(error48)}`, {
336095
+ cause: error48
336096
+ });
336097
+ }
336098
+ }
336099
+ function evidenceExchange(broker) {
336100
+ return broker;
336101
+ }
336102
+
336103
+ // src/schemas/action-evidence.ts
336104
+ var EVIDENCE_DIGEST_ALGORITHM = "sha256-canonical-json-v1";
336105
+ var CanonicalDecimalStringSchema = exports_external.string().regex(/^(?:0|[1-9]\d*)(?:\.\d*[1-9])?$/);
336106
+ var accountScopeShape = {
336107
+ accountSelector: exports_external.string().min(1),
336108
+ credentialSource: exports_external.enum(["configured_pool", "request_metadata"])
336109
+ };
336110
+ var evidenceSourceShape = {
336111
+ observedAt: exports_external.string().datetime({ offset: true }),
336112
+ digestAlgorithm: exports_external.literal(EVIDENCE_DIGEST_ALGORITHM),
336113
+ sourceDigest: exports_external.string().regex(/^[a-f0-9]{64}$/)
336114
+ };
336115
+ var TradingFeeEvidenceSchema = exports_external.object({
336116
+ schemaVersion: exports_external.literal("cex-trading-fee-evidence/v1"),
336117
+ exchange: exports_external.string().min(1),
336118
+ marketType: exports_external.literal("spot"),
336119
+ canonicalPair: exports_external.string().regex(/^[^-\s]+-[^-\s]+$/),
336120
+ unifiedSymbol: exports_external.string().regex(/^[^/\s]+\/[^/\s]+$/),
336121
+ sourceSymbol: exports_external.string().min(1),
336122
+ ...accountScopeShape,
336123
+ ...evidenceSourceShape,
336124
+ sourceMethod: exports_external.literal("ccxt.fetchTradingFee"),
336125
+ makerRate: CanonicalDecimalStringSchema,
336126
+ takerRate: CanonicalDecimalStringSchema,
336127
+ rateUnit: exports_external.literal("decimal_fraction"),
336128
+ makerBasisPoints: CanonicalDecimalStringSchema,
336129
+ takerBasisPoints: CanonicalDecimalStringSchema,
336130
+ basisPointsUnit: exports_external.literal("basis_points")
336131
+ }).strict();
336132
+ var MarketRuleEvidenceSchema = exports_external.object({
336133
+ schemaVersion: exports_external.literal("cex-market-rule-evidence/v1"),
336134
+ exchange: exports_external.string().min(1),
336135
+ marketType: exports_external.literal("spot"),
336136
+ canonicalPair: exports_external.string().regex(/^[^-\s]+-[^-\s]+$/),
336137
+ unifiedSymbol: exports_external.string().regex(/^[^/\s]+\/[^/\s]+$/),
336138
+ sourceSymbol: exports_external.string().min(1),
336139
+ baseAsset: exports_external.string().min(1),
336140
+ quoteAsset: exports_external.string().min(1),
336141
+ active: exports_external.literal(true),
336142
+ precisionMode: exports_external.union([exports_external.string().min(1), exports_external.number().int()]),
336143
+ priceIncrement: CanonicalDecimalStringSchema,
336144
+ amountIncrement: CanonicalDecimalStringSchema,
336145
+ minimumAmount: CanonicalDecimalStringSchema,
336146
+ minimumNotional: CanonicalDecimalStringSchema,
336147
+ maximumAmount: CanonicalDecimalStringSchema.optional(),
336148
+ maximumPrice: CanonicalDecimalStringSchema.optional(),
336149
+ maximumNotional: CanonicalDecimalStringSchema.optional(),
336150
+ ...accountScopeShape,
336151
+ ...evidenceSourceShape,
336152
+ sourceMethod: exports_external.literal("ccxt.loadMarkets")
336153
+ }).strict();
336154
+ var TransferNetworkEvidenceSchema = exports_external.object({
336155
+ schemaVersion: exports_external.literal("cex-transfer-network-evidence/v1"),
336156
+ exchange: exports_external.string().min(1),
336157
+ asset: exports_external.string().min(1),
336158
+ operatorNetworkAlias: exports_external.string().min(1),
336159
+ brokerNetworkId: exports_external.string().min(1),
336160
+ exchangeNetworkId: exports_external.string().min(1),
336161
+ depositAvailable: exports_external.boolean(),
336162
+ withdrawalAvailable: exports_external.boolean(),
336163
+ withdrawalFee: CanonicalDecimalStringSchema.nullable(),
336164
+ withdrawalLimits: exports_external.object({
336165
+ minimum: CanonicalDecimalStringSchema.nullable(),
336166
+ maximum: CanonicalDecimalStringSchema.nullable()
336167
+ }).strict(),
336168
+ ...accountScopeShape,
336169
+ ...evidenceSourceShape,
336170
+ sourceMethod: exports_external.literal("ccxt.fetchCurrencies")
336171
+ }).strict();
336172
+ var BatchSuccessEntrySchema = exports_external.object({
336173
+ id: exports_external.string().min(1),
336174
+ action: exports_external.number().int().nonnegative(),
336175
+ symbol: exports_external.string(),
336176
+ response: exports_external.object({
336177
+ result: exports_external.string(),
336178
+ proof: exports_external.string()
336179
+ }).strict(),
336180
+ error: exports_external.null()
336181
+ }).strict();
336182
+ var BatchErrorEntrySchema = exports_external.object({
336183
+ id: exports_external.string().min(1),
336184
+ action: exports_external.number().int().nonnegative(),
336185
+ symbol: exports_external.string(),
336186
+ response: exports_external.null(),
336187
+ error: exports_external.object({
336188
+ code: exports_external.string().min(1),
336189
+ grpcStatus: exports_external.number().int().nonnegative(),
336190
+ message: exports_external.string()
336191
+ }).strict()
336192
+ }).strict();
336193
+ var BatchResponseEntrySchema = exports_external.union([
336194
+ BatchSuccessEntrySchema,
336195
+ BatchErrorEntrySchema
336196
+ ]);
336197
+ var BatchResponseEnvelopeSchema = exports_external.object({
336198
+ schemaVersion: exports_external.literal("cex-broker-action-batch/v1"),
336199
+ responses: exports_external.array(BatchResponseEntrySchema).max(32)
336200
+ }).strict();
336201
+
336202
+ // src/handlers/execute-action/batch.ts
336203
+ var FORBIDDEN_ROUTING_KEYS = new Set([
336204
+ "account",
336205
+ "accountid",
336206
+ "accountselector",
336207
+ "apikey",
336208
+ "apisecret",
336209
+ "auth",
336210
+ "authorization",
336211
+ "cex",
336212
+ "credential",
336213
+ "credentials",
336214
+ "exchange",
336215
+ "metadata",
336216
+ "password",
336217
+ "secret",
336218
+ "signature",
336219
+ "usesecondarykey"
336220
+ ]);
336221
+ function normalizeRoutingKey(key2) {
336222
+ return key2.replace(/[-_]/g, "").toLowerCase();
336223
+ }
336224
+ function hasForbiddenRoutingKey(value) {
336225
+ if (Array.isArray(value)) {
336226
+ return value.some(hasForbiddenRoutingKey);
336227
+ }
336228
+ if (value === null || typeof value !== "object") {
336229
+ return false;
336230
+ }
336231
+ for (const [key2, entry] of Object.entries(value)) {
336232
+ if (FORBIDDEN_ROUTING_KEYS.has(normalizeRoutingKey(key2))) {
336233
+ return true;
336234
+ }
336235
+ if (hasForbiddenRoutingKey(entry)) {
336236
+ return true;
336237
+ }
336238
+ }
336239
+ return false;
336240
+ }
336241
+ function childContainsRoutingOverride(child) {
336242
+ for (const [key2, value] of Object.entries(child.payload)) {
336243
+ if (FORBIDDEN_ROUTING_KEYS.has(normalizeRoutingKey(key2))) {
336244
+ return true;
336245
+ }
336246
+ try {
336247
+ if (hasForbiddenRoutingKey(JSON.parse(value))) {
336248
+ return true;
336249
+ }
336250
+ } catch {}
336251
+ }
336252
+ return false;
336253
+ }
336254
+ function stableBatchErrorCode(message, grpcStatus) {
336255
+ const prefix = message.match(/^([A-Za-z][A-Za-z0-9_]*):/)?.[1];
336256
+ if (prefix) {
336257
+ return prefix;
336258
+ }
336259
+ return grpc5.status[grpcStatus] ?? "UNKNOWN";
336260
+ }
336261
+ function batchErrorEntry(child, error48, broker) {
336262
+ const resolved = resolveGrpcError(error48);
336263
+ const errorRecord = error48 !== null && typeof error48 === "object" ? error48 : undefined;
336264
+ const grpcStatus = typeof errorRecord?.code === "number" ? errorRecord.code : resolved.code;
336265
+ const rawMessage = typeof errorRecord?.message === "string" ? errorRecord.message : resolved.message;
336266
+ const sanitizedMessage = redactSecretLiterals(sanitizeErrorDetail(rawMessage), exchangeSecretLiterals(broker));
336267
+ return {
336268
+ id: child.id,
336269
+ action: child.action,
336270
+ symbol: child.symbol,
336271
+ response: null,
336272
+ error: {
336273
+ code: stableBatchErrorCode(rawMessage, grpcStatus),
336274
+ grpcStatus,
336275
+ message: sanitizedMessage
336276
+ }
336277
+ };
336278
+ }
336279
+ function childCall(ctx, request) {
336280
+ return {
336281
+ ...ctx.call,
336282
+ request
336283
+ };
336284
+ }
336285
+ async function executeChild(ctx, child, descriptor) {
336286
+ const action = resolveAction(child.action);
336287
+ if (action === undefined) {
336288
+ return batchErrorEntry(child, new Error(`ValidationError: invalid action ${child.action}`), ctx.broker);
336289
+ }
336290
+ const proofState = { proof: "" };
336291
+ let completion;
336292
+ const localCallback = (error48, response) => {
336293
+ if (completion === undefined) {
336294
+ completion = { error: error48, response: response ?? null };
336295
+ }
336296
+ };
336297
+ const request = {
336298
+ action,
336299
+ cex: ctx.cex,
336300
+ symbol: child.symbol,
336301
+ payload: child.payload
336302
+ };
336303
+ const childContext = {
336304
+ ...ctx,
336305
+ call: childCall(ctx, request),
336306
+ wrappedCallback: localCallback,
336307
+ action,
336308
+ symbol: child.symbol,
336309
+ verity: proofState,
336310
+ applyVerityToBroker: (target) => ctx.applyVerityToBroker(target, proofState)
336311
+ };
336312
+ try {
336313
+ childContext.applyVerityToBroker(ctx.broker);
336314
+ await descriptor.handler(childContext);
336315
+ } catch (error48) {
336316
+ completion ??= {
336317
+ error: resolveGrpcError(error48),
336318
+ response: null
336319
+ };
336320
+ }
336321
+ if (completion === undefined) {
336322
+ return batchErrorEntry(child, new Error(`INTERNAL: ${getActionName(action)} completed without a callback`), ctx.broker);
336323
+ }
336324
+ if (completion.error || !completion.response) {
336325
+ return batchErrorEntry(child, completion.error ?? new Error("INTERNAL: child returned no response"), ctx.broker);
336326
+ }
336327
+ return {
336328
+ id: child.id,
336329
+ action: child.action,
336330
+ symbol: child.symbol,
336331
+ response: {
336332
+ result: completion.response.result,
336333
+ proof: completion.response.proof ?? proofState.proof
336334
+ },
336335
+ error: null
336336
+ };
336337
+ }
336338
+ async function handleBatch(ctx, lookupDescriptor) {
336339
+ if (ctx.symbol?.trim()) {
336340
+ return ctx.wrappedCallback({
336341
+ code: grpc5.status.INVALID_ARGUMENT,
336342
+ message: "ValidationError: Batch symbol must be empty"
336343
+ }, null);
336344
+ }
336345
+ const encodedRequests = ctx.call.request.payload?.requests;
336346
+ if (typeof encodedRequests === "string" && Buffer.byteLength(encodedRequests, "utf8") > MAX_BATCH_REQUEST_BYTES) {
336347
+ return ctx.wrappedCallback({
336348
+ code: grpc5.status.INVALID_ARGUMENT,
336349
+ message: `ValidationError: Batch requests exceed ${MAX_BATCH_REQUEST_BYTES} bytes`
336350
+ }, null);
336351
+ }
336352
+ const payload = parsePayloadForAction(ctx, BatchPayloadSchema);
336353
+ if (payload === null) {
336354
+ return;
336355
+ }
336356
+ const seenIds = new Set;
336357
+ const prepared = [];
336358
+ for (const child of payload.requests) {
336359
+ if (seenIds.has(child.id)) {
336360
+ return ctx.wrappedCallback({
336361
+ code: grpc5.status.INVALID_ARGUMENT,
336362
+ message: `ValidationError: duplicate batch child id '${child.id}'`
336363
+ }, null);
336364
+ }
336365
+ seenIds.add(child.id);
336366
+ if (childContainsRoutingOverride(child)) {
336367
+ return ctx.wrappedCallback({
336368
+ code: grpc5.status.INVALID_ARGUMENT,
336369
+ message: `ValidationError: batch child '${child.id}' contains a routing override`
336370
+ }, null);
336371
+ }
336372
+ const action = resolveAction(child.action);
336373
+ const descriptor = action === undefined ? undefined : lookupDescriptor(action);
336374
+ if (!descriptor || descriptor.access !== "read" || !descriptor.batchable) {
336375
+ return ctx.wrappedCallback({
336376
+ code: grpc5.status.INVALID_ARGUMENT,
336377
+ message: `ValidationError: batch child '${child.id}' action ${child.action} is not batchable`
336378
+ }, null);
336379
+ }
336380
+ const validation = descriptor.validateBatchRequest?.({
336381
+ action,
336382
+ cex: ctx.cex,
336383
+ symbol: child.symbol,
336384
+ payload: child.payload
336385
+ });
336386
+ if (validation && !validation.valid) {
336387
+ return ctx.wrappedCallback({
336388
+ code: grpc5.status.INVALID_ARGUMENT,
336389
+ message: `ValidationError: batch child '${child.id}': ${validation.message}`
336390
+ }, null);
336391
+ }
336392
+ prepared.push({ child, descriptor });
336393
+ }
336394
+ const responses = [];
336395
+ for (const { child, descriptor } of prepared) {
336396
+ const response = await executeChild(ctx, child, descriptor);
336397
+ responses.push(response);
336398
+ ctx.otelMetrics?.recordCounter("execute_action_batch_items_total", 1, {
336399
+ action: getActionName(child.action),
336400
+ cex: ctx.normalizedCex,
336401
+ outcome: response.error ? "error" : "success"
336402
+ });
336403
+ }
336404
+ const envelope = BatchResponseEnvelopeSchema.parse({
336405
+ schemaVersion: "cex-broker-action-batch/v1",
336406
+ responses
336407
+ });
336408
+ ctx.wrappedCallback(null, {
336409
+ result: JSON.stringify(envelope),
336410
+ proof: ""
336411
+ });
336412
+ }
336413
+
336414
+ // src/handlers/execute-action/internal-transfer.ts
336415
+ var grpc6 = __toESM(require_src3(), 1);
335752
336416
  async function handleInternalTransfer(ctx) {
335753
336417
  const {
335754
336418
  brokers,
@@ -335762,7 +336426,7 @@ async function handleInternalTransfer(ctx) {
335762
336426
  } = ctx;
335763
336427
  if (!symbol2) {
335764
336428
  return ctx.wrappedCallback({
335765
- code: grpc5.status.INVALID_ARGUMENT,
336429
+ code: grpc6.status.INVALID_ARGUMENT,
335766
336430
  message: `ValidationError: Symbol required`
335767
336431
  }, null);
335768
336432
  }
@@ -335771,14 +336435,14 @@ async function handleInternalTransfer(ctx) {
335771
336435
  return;
335772
336436
  if (normalizedCex !== "binance") {
335773
336437
  return ctx.wrappedCallback({
335774
- code: grpc5.status.UNIMPLEMENTED,
336438
+ code: grpc6.status.UNIMPLEMENTED,
335775
336439
  message: `InternalTransfer is only supported for Binance`
335776
336440
  }, null);
335777
336441
  }
335778
336442
  const pool = brokers[normalizedCex];
335779
336443
  if (!pool) {
335780
336444
  return ctx.wrappedCallback({
335781
- code: grpc5.status.FAILED_PRECONDITION,
336445
+ code: grpc6.status.FAILED_PRECONDITION,
335782
336446
  message: `No broker accounts configured for ${normalizedCex}`
335783
336447
  }, null);
335784
336448
  }
@@ -335787,14 +336451,14 @@ async function handleInternalTransfer(ctx) {
335787
336451
  const sourceAccount = resolveBrokerAccount(pool, fromSelector);
335788
336452
  if (!sourceAccount) {
335789
336453
  return ctx.wrappedCallback({
335790
- code: grpc5.status.INVALID_ARGUMENT,
336454
+ code: grpc6.status.INVALID_ARGUMENT,
335791
336455
  message: `Source account "${fromSelector}" is not configured`
335792
336456
  }, null);
335793
336457
  }
335794
336458
  const destAccount = resolveBrokerAccount(pool, toSelector);
335795
336459
  if (!destAccount) {
335796
336460
  return ctx.wrappedCallback({
335797
- code: grpc5.status.INVALID_ARGUMENT,
336461
+ code: grpc6.status.INVALID_ARGUMENT,
335798
336462
  message: `Destination account "${toSelector}" is not configured`
335799
336463
  }, null);
335800
336464
  }
@@ -335828,18 +336492,18 @@ async function handleInternalTransfer(ctx) {
335828
336492
  safeLogError("InternalTransfer failed", error48);
335829
336493
  if (error48 instanceof BrokerAccountPreconditionError) {
335830
336494
  return ctx.wrappedCallback({
335831
- code: grpc5.status.FAILED_PRECONDITION,
336495
+ code: grpc6.status.FAILED_PRECONDITION,
335832
336496
  message: getErrorMessage(error48)
335833
336497
  }, null);
335834
336498
  }
335835
336499
  const msg = getErrorMessage(error48);
335836
336500
  let code;
335837
336501
  if (msg.includes("Unsupported transfer direction")) {
335838
- code = grpc5.status.INVALID_ARGUMENT;
336502
+ code = grpc6.status.INVALID_ARGUMENT;
335839
336503
  } else if (msg.includes("unavailable in this CCXT build")) {
335840
- code = grpc5.status.UNIMPLEMENTED;
336504
+ code = grpc6.status.UNIMPLEMENTED;
335841
336505
  } else {
335842
- code = mapCcxtErrorToGrpcStatus(error48) ?? grpc5.status.INTERNAL;
336506
+ code = mapCcxtErrorToGrpcStatus(error48) ?? grpc6.status.INTERNAL;
335843
336507
  }
335844
336508
  ctx.wrappedCallback({
335845
336509
  code,
@@ -335849,7 +336513,7 @@ async function handleInternalTransfer(ctx) {
335849
336513
  }
335850
336514
 
335851
336515
  // src/handlers/execute-action/orders.ts
335852
- var grpc6 = __toESM(require_src3(), 1);
336516
+ var grpc7 = __toESM(require_src3(), 1);
335853
336517
 
335854
336518
  // src/helpers/passive-order.ts
335855
336519
  var PASSIVE_ORDER_ERROR_CODES = {
@@ -335910,7 +336574,7 @@ async function handleCreateOrder(ctx) {
335910
336574
  const isPassiveOrder = orderValue.orderIntent === "passive_only";
335911
336575
  if (isPassiveOrder && orderValue.orderType !== "limit") {
335912
336576
  return ctx.wrappedCallback({
335913
- code: grpc6.status.INVALID_ARGUMENT,
336577
+ code: grpc7.status.INVALID_ARGUMENT,
335914
336578
  message: "ValidationError: passive_only order intent requires a limit order"
335915
336579
  }, null);
335916
336580
  }
@@ -335927,14 +336591,14 @@ async function handleCreateOrder(ctx) {
335927
336591
  try {
335928
336592
  if (!broker) {
335929
336593
  return ctx.wrappedCallback({
335930
- code: grpc6.status.INVALID_ARGUMENT,
336594
+ code: grpc7.status.INVALID_ARGUMENT,
335931
336595
  message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
335932
336596
  }, null);
335933
336597
  }
335934
336598
  const resolution = await resolveOrderExecution(policy, broker, cex3, orderValue.fromToken, orderValue.toToken, orderValue.amount, orderValue.price, orderValue.marketType);
335935
336599
  if (!resolution.valid || !resolution.symbol || !resolution.side) {
335936
336600
  return ctx.wrappedCallback({
335937
- code: grpc6.status.INVALID_ARGUMENT,
336601
+ code: grpc7.status.INVALID_ARGUMENT,
335938
336602
  message: resolution.error ?? "Order rejected by policy: market or limits not satisfied"
335939
336603
  }, null);
335940
336604
  }
@@ -336007,7 +336671,7 @@ async function handleCreateOrder(ctx) {
336007
336671
  });
336008
336672
  }
336009
336673
  ctx.wrappedCallback({
336010
- code: grpc6.status.INTERNAL,
336674
+ code: grpc7.status.INTERNAL,
336011
336675
  message: `Order Creation failed: ${sanitizeErrorDetail(error48)}`
336012
336676
  }, null);
336013
336677
  }
@@ -336039,7 +336703,7 @@ async function handleGetOrderDetails(ctx) {
336039
336703
  try {
336040
336704
  if (!broker) {
336041
336705
  return ctx.wrappedCallback({
336042
- code: grpc6.status.INVALID_ARGUMENT,
336706
+ code: grpc7.status.INVALID_ARGUMENT,
336043
336707
  message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
336044
336708
  }, null);
336045
336709
  }
@@ -336080,7 +336744,7 @@ async function handleGetOrderDetails(ctx) {
336080
336744
  emitOrderExecutionTelemetryInBackground(otelMetrics, failedGetOrderContext, undefined, error48);
336081
336745
  archiveOrderExecutionInBackground(brokerArchiver, failedGetOrderContext, undefined, error48);
336082
336746
  ctx.wrappedCallback({
336083
- code: grpc6.status.INTERNAL,
336747
+ code: grpc7.status.INTERNAL,
336084
336748
  message: `Failed to fetch order details from ${cex3}: ${sanitizeErrorDetail(error48)}`
336085
336749
  }, null);
336086
336750
  }
@@ -336112,7 +336776,7 @@ async function handleCancelOrder(ctx) {
336112
336776
  try {
336113
336777
  if (!broker) {
336114
336778
  return ctx.wrappedCallback({
336115
- code: grpc6.status.INVALID_ARGUMENT,
336779
+ code: grpc7.status.INVALID_ARGUMENT,
336116
336780
  message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
336117
336781
  }, null);
336118
336782
  }
@@ -336144,7 +336808,7 @@ async function handleCancelOrder(ctx) {
336144
336808
  emitOrderExecutionTelemetryInBackground(otelMetrics, failedCancelContext, undefined, error48);
336145
336809
  archiveOrderExecutionInBackground(brokerArchiver, failedCancelContext, undefined, error48);
336146
336810
  ctx.wrappedCallback({
336147
- code: grpc6.status.INTERNAL,
336811
+ code: grpc7.status.INTERNAL,
336148
336812
  message: `Failed to cancel order from ${cex3}: ${sanitizeErrorDetail(error48)}`
336149
336813
  }, null);
336150
336814
  }
@@ -336159,261 +336823,254 @@ async function handleOrders(ctx) {
336159
336823
  }
336160
336824
 
336161
336825
  // src/handlers/execute-action/pass-through.ts
336162
- var grpc7 = __toESM(require_src3(), 1);
336163
- async function handleFetchCurrency(ctx) {
336164
- const {
336165
- call,
336166
- wrappedCallback,
336167
- policy,
336168
- brokers,
336169
- metadata,
336170
- normalizedCex,
336171
- cex: cex3,
336172
- symbol: symbol2,
336173
- selectedBrokerAccount,
336174
- broker,
336175
- verity,
336176
- applyVerityToBroker,
336177
- useVerity,
336178
- verityProverUrl,
336179
- otelMetrics
336180
- } = ctx;
336181
- const verityProof = verity.proof;
336182
- if (!symbol2) {
336826
+ var grpc9 = __toESM(require_src3(), 1);
336827
+
336828
+ // src/handlers/execute-action/venue-evidence.ts
336829
+ var grpc8 = __toESM(require_src3(), 1);
336830
+ function failVenueDiscovery(ctx, error48, operation) {
336831
+ safeLogRedactedError(`${operation} failed`, error48);
336832
+ const sanitized = sanitizeVenueError(error48, ctx.broker);
336833
+ const message = sanitized.startsWith("venue_discovery_unavailable:") ? sanitized : `venue_discovery_unavailable: ${sanitized}`;
336834
+ ctx.wrappedCallback({
336835
+ code: stableGrpcErrorCode(message) ?? mapCcxtErrorToGrpcStatus(error48) ?? grpc8.status.UNIMPLEMENTED,
336836
+ message
336837
+ }, null);
336838
+ }
336839
+ function optionalDecimalFields(values2) {
336840
+ const result = {};
336841
+ for (const [key2, value, field] of values2) {
336842
+ const normalized = canonicalOptionalDecimal(value, field);
336843
+ if (normalized !== undefined) {
336844
+ result[key2] = normalized;
336845
+ }
336846
+ }
336847
+ return result;
336848
+ }
336849
+ async function handleFetchFeesEvidence(ctx) {
336850
+ if (!requireSymbol(ctx, "ValidationError: symbol must be a slash-delimited spot pair")) {
336851
+ return;
336852
+ }
336853
+ if (!/^[^/\s]+\/[^/\s]+$/.test(ctx.symbol.trim())) {
336183
336854
  return ctx.wrappedCallback({
336184
- code: grpc7.status.INVALID_ARGUMENT,
336185
- message: `ValidationError: Symbol required`
336855
+ code: grpc8.status.INVALID_ARGUMENT,
336856
+ message: "ValidationError: symbol must be a slash-delimited spot pair"
336186
336857
  }, null);
336187
336858
  }
336859
+ if (parsePayloadForAction(ctx, FetchFeesPayloadSchema) === null) {
336860
+ return;
336861
+ }
336188
336862
  try {
336189
- const assetCode = symbol2.trim().toUpperCase();
336190
- const currencyInfo = await fetchCurrencyMetadata(broker, assetCode);
336191
- if (!currencyInfo) {
336192
- return ctx.wrappedCallback({
336193
- code: grpc7.status.NOT_FOUND,
336194
- message: `venue_discovery_unavailable: currency not found for ${assetCode}`
336195
- }, null);
336196
- }
336197
- const networkEvidence = buildTransferNetworkEvidence(currencyInfo);
336198
- ctx.wrappedCallback(null, {
336199
- proof: ctx.verity.proof,
336200
- result: JSON.stringify({
336201
- ...currencyInfo,
336202
- exchange: normalizedCex,
336203
- asset: assetCode,
336204
- code: currencyInfo.code ?? assetCode,
336205
- id: currencyInfo.id ?? null,
336206
- networks: networkEvidence.networks,
336207
- networkAliases: networkEvidence.aliases,
336208
- raw: currencyInfo
336863
+ const identity2 = await resolveSpotMarketIdentity(ctx.broker, ctx.symbol);
336864
+ const exchange = evidenceExchange(ctx.broker);
336865
+ if (exchange.has?.fetchTradingFee === false || typeof exchange.fetchTradingFee !== "function") {
336866
+ throw new Error(`fee_unavailable: ${ctx.normalizedCex} does not support fetchTradingFee`);
336867
+ }
336868
+ const sourceResponse = await exchange.fetchTradingFee(identity2.unifiedSymbol);
336869
+ if (isRecord(sourceResponse) && typeof sourceResponse.symbol === "string" && sourceResponse.symbol.trim().toUpperCase() !== identity2.unifiedSymbol) {
336870
+ throw new Error(`fee_unavailable: trading-fee response symbol does not match ${identity2.unifiedSymbol}`);
336871
+ }
336872
+ const { makerRate, takerRate } = extractTradingFeeRates(sourceResponse);
336873
+ const accountScope = resolveEvidenceAccountScope(ctx.selectedBrokerAccount, ctx.metadata);
336874
+ const evidence = TradingFeeEvidenceSchema.parse({
336875
+ schemaVersion: "cex-trading-fee-evidence/v1",
336876
+ exchange: ctx.normalizedCex,
336877
+ marketType: "spot",
336878
+ canonicalPair: identity2.canonicalPair,
336879
+ unifiedSymbol: identity2.unifiedSymbol,
336880
+ sourceSymbol: identity2.sourceSymbol,
336881
+ ...accountScope,
336882
+ observedAt: new Date().toISOString(),
336883
+ sourceMethod: "ccxt.fetchTradingFee",
336884
+ makerRate,
336885
+ takerRate,
336886
+ rateUnit: "decimal_fraction",
336887
+ makerBasisPoints: decimalFractionToBasisPoints(makerRate),
336888
+ takerBasisPoints: decimalFractionToBasisPoints(takerRate),
336889
+ basisPointsUnit: "basis_points",
336890
+ digestAlgorithm: "sha256-canonical-json-v1",
336891
+ sourceDigest: evidenceSourceDigest({
336892
+ action: "FetchFees",
336893
+ exchange: ctx.normalizedCex,
336894
+ requestedKey: identity2.canonicalPair,
336895
+ accountSelector: accountScope.accountSelector,
336896
+ sourceMethod: "ccxt.fetchTradingFee",
336897
+ source: sourceResponse,
336898
+ broker: ctx.broker
336209
336899
  })
336210
336900
  });
336901
+ successWithProof(ctx, evidence);
336211
336902
  } catch (error48) {
336212
- safeLogError(`Error fetching currency ${symbol2} from ${cex3}`, error48);
336213
- const message = getErrorMessage(error48);
336903
+ safeLogRedactedError(`FetchFees failed for ${ctx.normalizedCex}/${ctx.symbol}`, error48);
336904
+ const sanitized = sanitizeVenueError(error48, ctx.broker);
336905
+ const message = sanitized.startsWith("fee_unavailable:") ? sanitized : `fee_unavailable: ${sanitized}`;
336214
336906
  ctx.wrappedCallback({
336215
- code: stableGrpcErrorCode(message) ?? mapCcxtErrorToGrpcStatus(error48) ?? grpc7.status.INTERNAL,
336216
- message: message.startsWith("venue_discovery_unavailable:") ? message : `venue_discovery_unavailable: ${message}`
336907
+ code: grpc8.status.FAILED_PRECONDITION,
336908
+ message
336217
336909
  }, null);
336218
336910
  }
336219
336911
  }
336220
- async function handleFetchAccountId(ctx) {
336221
- const {
336222
- call,
336223
- wrappedCallback,
336224
- policy,
336225
- brokers,
336226
- metadata,
336227
- normalizedCex,
336228
- cex: cex3,
336229
- symbol: symbol2,
336230
- selectedBrokerAccount,
336231
- broker,
336232
- verity,
336233
- applyVerityToBroker,
336234
- useVerity,
336235
- verityProverUrl,
336236
- otelMetrics
336237
- } = ctx;
336238
- const verityProof = verity.proof;
336912
+ async function handleFetchMarketRulesEvidence(ctx) {
336913
+ if (!requireSymbol(ctx, "ValidationError: symbol must be a slash-delimited spot pair")) {
336914
+ return;
336915
+ }
336916
+ if (!/^[^/\s]+\/[^/\s]+$/.test(ctx.symbol.trim())) {
336917
+ return ctx.wrappedCallback({
336918
+ code: grpc8.status.INVALID_ARGUMENT,
336919
+ message: "ValidationError: symbol must be a slash-delimited spot pair"
336920
+ }, null);
336921
+ }
336922
+ if (parsePayloadForAction(ctx, EmptyActionPayloadSchema) === null) {
336923
+ return;
336924
+ }
336239
336925
  try {
336240
- const accountId = await broker.fetchAccountId();
336241
- return ctx.wrappedCallback(null, {
336242
- proof: ctx.verity.proof,
336243
- result: JSON.stringify({ accountId })
336926
+ const identity2 = await resolveSpotMarketIdentity(ctx.broker, ctx.symbol);
336927
+ const precision = isRecord(identity2.market.precision) ? identity2.market.precision : {};
336928
+ const limits = isRecord(identity2.market.limits) ? identity2.market.limits : {};
336929
+ const amountLimits = isRecord(limits.amount) ? limits.amount : {};
336930
+ const priceLimits = isRecord(limits.price) ? limits.price : {};
336931
+ const costLimits = isRecord(limits.cost) ? limits.cost : {};
336932
+ const precisionMode = evidenceExchange(ctx.broker).precisionMode;
336933
+ if (precisionMode === undefined || precisionMode === null) {
336934
+ throw new Error("venue_discovery_unavailable: precision mode is unavailable");
336935
+ }
336936
+ const accountScope = resolveEvidenceAccountScope(ctx.selectedBrokerAccount, ctx.metadata);
336937
+ const evidence = MarketRuleEvidenceSchema.parse({
336938
+ schemaVersion: "cex-market-rule-evidence/v1",
336939
+ exchange: ctx.normalizedCex,
336940
+ marketType: "spot",
336941
+ canonicalPair: identity2.canonicalPair,
336942
+ unifiedSymbol: identity2.unifiedSymbol,
336943
+ sourceSymbol: identity2.sourceSymbol,
336944
+ baseAsset: identity2.baseAsset,
336945
+ quoteAsset: identity2.quoteAsset,
336946
+ active: true,
336947
+ precisionMode,
336948
+ priceIncrement: precisionIncrement(precision.price, precisionMode, "price increment"),
336949
+ amountIncrement: precisionIncrement(precision.amount, precisionMode, "amount increment"),
336950
+ minimumAmount: canonicalNonnegativeDecimal(amountLimits.min, "minimum amount"),
336951
+ minimumNotional: canonicalNonnegativeDecimal(costLimits.min, "minimum notional"),
336952
+ ...optionalDecimalFields([
336953
+ ["maximumAmount", amountLimits.max, "maximum amount"],
336954
+ ["maximumPrice", priceLimits.max, "maximum price"],
336955
+ ["maximumNotional", costLimits.max, "maximum notional"]
336956
+ ]),
336957
+ ...accountScope,
336958
+ observedAt: new Date().toISOString(),
336959
+ sourceMethod: "ccxt.loadMarkets",
336960
+ digestAlgorithm: "sha256-canonical-json-v1",
336961
+ sourceDigest: evidenceSourceDigest({
336962
+ action: "FetchMarketRules",
336963
+ exchange: ctx.normalizedCex,
336964
+ requestedKey: identity2.canonicalPair,
336965
+ accountSelector: accountScope.accountSelector,
336966
+ sourceMethod: "ccxt.loadMarkets",
336967
+ source: identity2.market,
336968
+ broker: ctx.broker
336969
+ })
336244
336970
  });
336971
+ successWithProof(ctx, evidence);
336245
336972
  } catch (error48) {
336246
- safeLogError(`Error fetching account ID ${cex3}`, error48);
336247
- ctx.wrappedCallback({
336248
- code: grpc7.status.INTERNAL,
336249
- message: `Error fetching account ID from ${cex3}`
336250
- }, null);
336973
+ failVenueDiscovery(ctx, error48, `FetchMarketRules ${ctx.normalizedCex}/${ctx.symbol}`);
336251
336974
  }
336252
336975
  }
336253
- async function handleFetchFees(ctx) {
336254
- const {
336255
- call,
336256
- wrappedCallback,
336257
- policy,
336258
- brokers,
336259
- metadata,
336260
- normalizedCex,
336261
- cex: cex3,
336262
- symbol: symbol2,
336263
- selectedBrokerAccount,
336264
- broker,
336265
- verity,
336266
- applyVerityToBroker,
336267
- useVerity,
336268
- verityProverUrl,
336269
- otelMetrics
336270
- } = ctx;
336271
- const verityProof = verity.proof;
336272
- if (!symbol2) {
336273
- return ctx.wrappedCallback({
336274
- code: grpc7.status.INVALID_ARGUMENT,
336275
- message: `ValidationError: Symbol required`
336276
- }, null);
336976
+ async function handleFetchCurrencyEvidence(ctx) {
336977
+ if (!requireSymbol(ctx)) {
336978
+ return;
336277
336979
  }
336278
- const feesPayload = parsePayloadForAction(ctx, FetchFeesPayloadSchema);
336279
- if (feesPayload === null)
336980
+ const payload = parsePayloadForAction(ctx, FetchCurrencyPayloadSchema);
336981
+ if (payload === null) {
336280
336982
  return;
336281
- const includeAllFees = feesPayload.includeAllFees || feesPayload.includeFundingFees === true;
336983
+ }
336984
+ const asset = ctx.symbol.trim().toUpperCase();
336985
+ const requestedAlias = payload.network.trim().toUpperCase();
336282
336986
  try {
336283
- await broker.loadMarkets();
336284
- const fetchFundingFees = async (currencyCodes) => {
336285
- let fundingFeeSource2 = "unavailable";
336286
- const fundingFeesByCurrency2 = {};
336287
- if (broker.has.fetchDepositWithdrawFees) {
336288
- try {
336289
- const feeMap = await broker.fetchDepositWithdrawFees(currencyCodes);
336290
- for (const code of currencyCodes) {
336291
- const feeInfo = feeMap[code];
336292
- if (!feeInfo) {
336293
- continue;
336294
- }
336295
- const fallbackFee = feeInfo.fee !== undefined || feeInfo.percentage !== undefined ? {
336296
- fee: feeInfo.fee ?? null,
336297
- percentage: feeInfo.percentage ?? null
336298
- } : null;
336299
- fundingFeesByCurrency2[code] = {
336300
- deposit: feeInfo.deposit ?? fallbackFee,
336301
- withdraw: feeInfo.withdraw ?? fallbackFee,
336302
- networks: feeInfo.networks ?? {}
336303
- };
336304
- }
336305
- if (Object.keys(fundingFeesByCurrency2).length > 0) {
336306
- fundingFeeSource2 = "fetchDepositWithdrawFees";
336307
- }
336308
- } catch (error48) {
336309
- safeLogError(`Error fetching deposit/withdraw fee map for ${symbol2} from ${cex3}`, error48);
336310
- }
336311
- }
336312
- if (fundingFeeSource2 === "unavailable") {
336313
- try {
336314
- const currencies = await broker.fetchCurrencies();
336315
- for (const code of currencyCodes) {
336316
- const currency = currencies[code];
336317
- if (!currency) {
336318
- continue;
336319
- }
336320
- fundingFeesByCurrency2[code] = {
336321
- deposit: {
336322
- enabled: currency.deposit ?? null
336323
- },
336324
- withdraw: {
336325
- enabled: currency.withdraw ?? null,
336326
- fee: currency.fee ?? null,
336327
- limits: currency.limits?.withdraw ?? null
336328
- },
336329
- networks: currency.networks ?? {}
336330
- };
336331
- }
336332
- if (Object.keys(fundingFeesByCurrency2).length > 0) {
336333
- fundingFeeSource2 = "currencies";
336334
- }
336335
- } catch (error48) {
336336
- safeLogError(`Error fetching currency metadata for fees for ${symbol2} from ${cex3}`, error48);
336337
- }
336338
- }
336339
- return { fundingFeeSource: fundingFeeSource2, fundingFeesByCurrency: fundingFeesByCurrency2 };
336340
- };
336341
- const isMarketSymbol = symbol2.includes("/");
336342
- if (isMarketSymbol) {
336343
- const market = await broker.market(symbol2);
336344
- const generalFee = broker.fees ?? null;
336345
- const feeStatus = broker.fees ? "available" : "unknown";
336346
- if (!broker.fees) {
336347
- log.warn(`Fee metadata unavailable for ${cex3}`, { symbol: symbol2 });
336348
- }
336349
- if (!includeAllFees) {
336350
- return ctx.wrappedCallback(null, {
336351
- proof: ctx.verity.proof,
336352
- result: JSON.stringify({
336353
- feeScope: "market",
336354
- generalFee,
336355
- feeStatus,
336356
- market
336357
- })
336358
- });
336359
- }
336360
- const currencyCodes = Array.from(new Set([market.base, market.quote]));
336361
- const { fundingFeeSource: fundingFeeSource2, fundingFeesByCurrency: fundingFeesByCurrency2 } = await fetchFundingFees(currencyCodes);
336362
- return ctx.wrappedCallback(null, {
336363
- proof: ctx.verity.proof,
336364
- result: JSON.stringify({
336365
- feeScope: "market+funding",
336366
- generalFee,
336367
- feeStatus,
336368
- market,
336369
- fundingFeeSource: fundingFeeSource2,
336370
- fundingFeesByCurrency: fundingFeesByCurrency2
336371
- })
336372
- });
336373
- }
336374
- const tokenCode = symbol2.toUpperCase();
336375
- const { fundingFeeSource, fundingFeesByCurrency } = await fetchFundingFees([
336376
- tokenCode
336377
- ]);
336987
+ const exchange = evidenceExchange(ctx.broker);
336988
+ if (exchange.has?.fetchCurrencies === false || typeof exchange.fetchCurrencies !== "function") {
336989
+ throw new Error(`venue_discovery_unavailable: fetchCurrencies unavailable for ${asset}`);
336990
+ }
336991
+ const currencies = await exchange.fetchCurrencies();
336992
+ const currency = currencies[asset];
336993
+ if (!isRecord(currency)) {
336994
+ throw new Error(`venue_discovery_unavailable: currency not found for ${asset}`);
336995
+ }
336996
+ const networkEvidence = buildTransferNetworkEvidence(currency);
336997
+ const brokerNetworkId = normalizeBrokerNetworkId(requestedAlias);
336998
+ const resolution = networkEvidence.aliases[requestedAlias] ?? networkEvidence.aliases[brokerNetworkId];
336999
+ if (!resolution?.networkKey) {
337000
+ throw new Error(`network_alias_unresolved: ${asset}/${requestedAlias} is not available in discovered transfer networks`);
337001
+ }
337002
+ const networks = isRecord(currency.networks) ? currency.networks : {};
337003
+ const networkCandidate = networks[resolution.networkKey];
337004
+ if (!isRecord(networkCandidate) || typeof networkCandidate.deposit !== "boolean" || typeof networkCandidate.withdraw !== "boolean") {
337005
+ throw new Error(`venue_discovery_unavailable: transfer availability is incomplete for ${asset}/${requestedAlias}`);
337006
+ }
337007
+ const network = networkCandidate;
337008
+ const limits = isRecord(network.limits) ? network.limits : {};
337009
+ const withdrawalLimits = isRecord(limits.withdraw) ? limits.withdraw : {};
337010
+ const accountScope = resolveEvidenceAccountScope(ctx.selectedBrokerAccount, ctx.metadata);
337011
+ const evidence = TransferNetworkEvidenceSchema.parse({
337012
+ schemaVersion: "cex-transfer-network-evidence/v1",
337013
+ exchange: ctx.normalizedCex,
337014
+ asset,
337015
+ operatorNetworkAlias: requestedAlias,
337016
+ brokerNetworkId,
337017
+ exchangeNetworkId: resolution.exchangeNetworkId,
337018
+ depositAvailable: network.deposit,
337019
+ withdrawalAvailable: network.withdraw,
337020
+ withdrawalFee: canonicalOptionalDecimal(network.fee, "withdrawal fee") ?? null,
337021
+ withdrawalLimits: {
337022
+ minimum: canonicalOptionalDecimal(withdrawalLimits.min, "minimum withdrawal") ?? null,
337023
+ maximum: canonicalOptionalDecimal(withdrawalLimits.max, "maximum withdrawal") ?? null
337024
+ },
337025
+ ...accountScope,
337026
+ observedAt: new Date().toISOString(),
337027
+ sourceMethod: "ccxt.fetchCurrencies",
337028
+ digestAlgorithm: "sha256-canonical-json-v1",
337029
+ sourceDigest: evidenceSourceDigest({
337030
+ action: "FetchCurrency",
337031
+ exchange: ctx.normalizedCex,
337032
+ requestedKey: `${asset}/${requestedAlias}`,
337033
+ accountSelector: accountScope.accountSelector,
337034
+ sourceMethod: "ccxt.fetchCurrencies",
337035
+ source: network,
337036
+ broker: ctx.broker
337037
+ })
337038
+ });
337039
+ successWithProof(ctx, evidence);
337040
+ } catch (error48) {
337041
+ safeLogRedactedError(`FetchCurrency failed for ${ctx.normalizedCex}/${asset}/${requestedAlias}`, error48);
337042
+ const sanitized = sanitizeVenueError(error48, ctx.broker);
337043
+ const isAliasError = sanitized.startsWith("network_alias_unresolved:");
337044
+ const message = isAliasError || sanitized.startsWith("venue_discovery_unavailable:") ? sanitized : `venue_discovery_unavailable: ${sanitized}`;
337045
+ ctx.wrappedCallback({
337046
+ code: stableGrpcErrorCode(message) ?? mapCcxtErrorToGrpcStatus(error48) ?? grpc8.status.UNIMPLEMENTED,
337047
+ message
337048
+ }, null);
337049
+ }
337050
+ }
337051
+
337052
+ // src/handlers/execute-action/pass-through.ts
337053
+ async function handleFetchAccountId(ctx) {
337054
+ const { cex: cex3, broker } = ctx;
337055
+ try {
337056
+ const accountId = await broker.fetchAccountId();
336378
337057
  return ctx.wrappedCallback(null, {
336379
337058
  proof: ctx.verity.proof,
336380
- result: JSON.stringify({
336381
- feeScope: "token",
336382
- symbol: tokenCode,
336383
- fundingFeeSource,
336384
- fundingFeesByCurrency
336385
- })
337059
+ result: JSON.stringify({ accountId })
336386
337060
  });
336387
337061
  } catch (error48) {
336388
- safeLogError(`Error fetching fees for ${symbol2} from ${cex3}`, error48);
337062
+ safeLogError(`Error fetching account ID ${cex3}`, error48);
336389
337063
  ctx.wrappedCallback({
336390
- code: grpc7.status.INTERNAL,
336391
- message: `Error fetching fees from ${cex3}`
337064
+ code: grpc9.status.INTERNAL,
337065
+ message: `Error fetching account ID from ${cex3}`
336392
337066
  }, null);
336393
337067
  }
336394
337068
  }
336395
337069
  async function handleFetchDepositAddresses(ctx) {
336396
- const {
336397
- call,
336398
- wrappedCallback,
336399
- policy,
336400
- brokers,
336401
- metadata,
336402
- normalizedCex,
336403
- cex: cex3,
336404
- symbol: symbol2,
336405
- selectedBrokerAccount,
336406
- broker,
336407
- verity,
336408
- applyVerityToBroker,
336409
- useVerity,
336410
- verityProverUrl,
336411
- otelMetrics
336412
- } = ctx;
336413
- const verityProof = verity.proof;
337070
+ const { policy, cex: cex3, symbol: symbol2, broker } = ctx;
336414
337071
  if (!symbol2) {
336415
337072
  return ctx.wrappedCallback({
336416
- code: grpc7.status.INVALID_ARGUMENT,
337073
+ code: grpc9.status.INVALID_ARGUMENT,
336417
337074
  message: `ValidationError: Symbol required`
336418
337075
  }, null);
336419
337076
  }
@@ -336426,14 +337083,14 @@ async function handleFetchDepositAddresses(ctx) {
336426
337083
  } catch (error48) {
336427
337084
  const message = getErrorMessage(error48);
336428
337085
  return ctx.wrappedCallback({
336429
- code: stableGrpcErrorCode(message) ?? grpc7.status.INVALID_ARGUMENT,
337086
+ code: stableGrpcErrorCode(message) ?? grpc9.status.INVALID_ARGUMENT,
336430
337087
  message
336431
337088
  }, null);
336432
337089
  }
336433
337090
  const depositValidation = validateDeposit(policy, cex3, depositNetwork.brokerNetworkId, symbol2);
336434
337091
  if (!depositValidation.valid) {
336435
337092
  return ctx.wrappedCallback({
336436
- code: grpc7.status.PERMISSION_DENIED,
337093
+ code: grpc9.status.PERMISSION_DENIED,
336437
337094
  message: `policy_deposit_denied: ${depositValidation.error}`
336438
337095
  }, null);
336439
337096
  }
@@ -336459,45 +337116,30 @@ async function handleFetchDepositAddresses(ctx) {
336459
337116
  });
336460
337117
  }
336461
337118
  ctx.wrappedCallback({
336462
- code: grpc7.status.INTERNAL,
337119
+ code: grpc9.status.INTERNAL,
336463
337120
  message: "Deposit confirmation failed"
336464
337121
  }, null);
336465
337122
  } catch (error48) {
336466
337123
  safeLogError("Fetch Deposit Addresses confirmation failed", error48);
336467
337124
  const message = getErrorMessage(error48);
336468
337125
  ctx.wrappedCallback({
336469
- code: grpc7.status.INTERNAL,
337126
+ code: grpc9.status.INTERNAL,
336470
337127
  message: "Fetch Deposit Addresses confirmation failed: " + message
336471
337128
  }, null);
336472
337129
  }
336473
337130
  }
336474
337131
  async function handleFetchBalances(ctx) {
336475
- const {
336476
- call,
336477
- wrappedCallback,
336478
- policy,
336479
- brokers,
336480
- metadata,
336481
- normalizedCex,
336482
- cex: cex3,
336483
- symbol: symbol2,
336484
- selectedBrokerAccount,
336485
- broker,
336486
- verity,
336487
- applyVerityToBroker,
336488
- useVerity,
336489
- verityProverUrl,
336490
- otelMetrics
336491
- } = ctx;
336492
- const verityProof = verity.proof;
337132
+ const { call, cex: cex3, symbol: symbol2, broker } = ctx;
336493
337133
  try {
336494
- const payload = call.request.payload || {};
336495
- const providedBalanceType = payload.balanceType;
337134
+ const payload = {
337135
+ ...call.request.payload ?? {}
337136
+ };
337137
+ const providedBalanceType = typeof payload.balanceType === "string" ? payload.balanceType : undefined;
336496
337138
  const balanceType = (providedBalanceType ?? "total").toString();
336497
337139
  const validBalanceTypes = new Set(["free", "used", "total"]);
336498
337140
  if (!validBalanceTypes.has(balanceType)) {
336499
337141
  return ctx.wrappedCallback({
336500
- code: grpc7.status.INVALID_ARGUMENT,
337142
+ code: grpc9.status.INVALID_ARGUMENT,
336501
337143
  message: `ValidationError: invalid balanceType '${providedBalanceType}'. Expected one of: free | used | total`
336502
337144
  }, null);
336503
337145
  }
@@ -336538,33 +337180,16 @@ async function handleFetchBalances(ctx) {
336538
337180
  } catch (error48) {
336539
337181
  safeLogError(`Error fetching balance from ${cex3}`, error48);
336540
337182
  ctx.wrappedCallback({
336541
- code: grpc7.status.INTERNAL,
337183
+ code: grpc9.status.INTERNAL,
336542
337184
  message: `Failed to fetch balance from ${cex3}`
336543
337185
  }, null);
336544
337186
  }
336545
337187
  }
336546
337188
  async function handleFetchTicker(ctx) {
336547
- const {
336548
- call,
336549
- wrappedCallback,
336550
- policy,
336551
- brokers,
336552
- metadata,
336553
- normalizedCex,
336554
- cex: cex3,
336555
- symbol: symbol2,
336556
- selectedBrokerAccount,
336557
- broker,
336558
- verity,
336559
- applyVerityToBroker,
336560
- useVerity,
336561
- verityProverUrl,
336562
- otelMetrics
336563
- } = ctx;
336564
- const verityProof = verity.proof;
337189
+ const { cex: cex3, symbol: symbol2, broker } = ctx;
336565
337190
  if (!symbol2) {
336566
337191
  return ctx.wrappedCallback({
336567
- code: grpc7.status.INVALID_ARGUMENT,
337192
+ code: grpc9.status.INVALID_ARGUMENT,
336568
337193
  message: `ValidationError: Symbol required`
336569
337194
  }, null);
336570
337195
  }
@@ -336577,18 +337202,20 @@ async function handleFetchTicker(ctx) {
336577
337202
  } catch (error48) {
336578
337203
  safeLogError(`Error fetching ticker from ${cex3}`, error48);
336579
337204
  ctx.wrappedCallback({
336580
- code: grpc7.status.INTERNAL,
337205
+ code: grpc9.status.INTERNAL,
336581
337206
  message: `Failed to fetch ticker from ${cex3}`
336582
337207
  }, null);
336583
337208
  }
336584
337209
  }
336585
337210
  async function handlePassThrough(ctx) {
336586
337211
  if (ctx.action === Action.FetchCurrency)
336587
- return handleFetchCurrency(ctx);
337212
+ return handleFetchCurrencyEvidence(ctx);
336588
337213
  if (ctx.action === Action.FetchAccountId)
336589
337214
  return handleFetchAccountId(ctx);
336590
337215
  if (ctx.action === Action.FetchFees)
336591
- return handleFetchFees(ctx);
337216
+ return handleFetchFeesEvidence(ctx);
337217
+ if (ctx.action === Action.FetchMarketRules)
337218
+ return handleFetchMarketRulesEvidence(ctx);
336592
337219
  if (ctx.action === Action.FetchDepositAddresses)
336593
337220
  return handleFetchDepositAddresses(ctx);
336594
337221
  if (ctx.action === Action.FetchBalances)
@@ -336598,7 +337225,7 @@ async function handlePassThrough(ctx) {
336598
337225
  }
336599
337226
 
336600
337227
  // src/handlers/execute-action/perp-config.ts
336601
- var grpc8 = __toESM(require_src3(), 1);
337228
+ var grpc10 = __toESM(require_src3(), 1);
336602
337229
  function exchangeSupports(broker, capability) {
336603
337230
  return broker.has?.[capability] === true;
336604
337231
  }
@@ -336617,14 +337244,14 @@ async function handleGetPerpConfigState(ctx) {
336617
337244
  }
336618
337245
  if (!broker) {
336619
337246
  return wrappedCallback({
336620
- code: grpc8.status.INVALID_ARGUMENT,
337247
+ code: grpc10.status.INVALID_ARGUMENT,
336621
337248
  message: `Invalid CEX key: ${cex3}`
336622
337249
  }, null);
336623
337250
  }
336624
337251
  const exchange = broker;
336625
337252
  if (!exchangeSupports(exchange, "fetchPositions")) {
336626
337253
  return wrappedCallback({
336627
- code: grpc8.status.UNIMPLEMENTED,
337254
+ code: grpc10.status.UNIMPLEMENTED,
336628
337255
  message: `${normalizedCex} does not support fetchPositions`
336629
337256
  }, null);
336630
337257
  }
@@ -336641,7 +337268,7 @@ async function handleGetPerpConfigState(ctx) {
336641
337268
  } catch (error48) {
336642
337269
  safeLogError(`GetPerpConfigState failed for ${cex3}`, error48);
336643
337270
  ctx.wrappedCallback({
336644
- code: grpc8.status.INTERNAL,
337271
+ code: grpc10.status.INTERNAL,
336645
337272
  message: `GetPerpConfigState failed: ${sanitizeErrorDetail(error48)}`
336646
337273
  }, null);
336647
337274
  }
@@ -336654,14 +337281,14 @@ async function handleSetPerpConfigState(ctx) {
336654
337281
  }
336655
337282
  if (!broker) {
336656
337283
  return wrappedCallback({
336657
- code: grpc8.status.INVALID_ARGUMENT,
337284
+ code: grpc10.status.INVALID_ARGUMENT,
336658
337285
  message: `Invalid CEX key: ${cex3}`
336659
337286
  }, null);
336660
337287
  }
336661
337288
  const exchange = broker;
336662
337289
  if (!exchangeSupports(exchange, "setLeverage")) {
336663
337290
  return wrappedCallback({
336664
- code: grpc8.status.UNIMPLEMENTED,
337291
+ code: grpc10.status.UNIMPLEMENTED,
336665
337292
  message: `${normalizedCex} does not support setLeverage`
336666
337293
  }, null);
336667
337294
  }
@@ -336682,7 +337309,7 @@ async function handleSetPerpConfigState(ctx) {
336682
337309
  } catch (error48) {
336683
337310
  safeLogError(`SetPerpConfigState failed for ${cex3}`, error48);
336684
337311
  ctx.wrappedCallback({
336685
- code: grpc8.status.INTERNAL,
337312
+ code: grpc10.status.INTERNAL,
336686
337313
  message: `SetPerpConfigState failed: ${sanitizeErrorDetail(error48)}`
336687
337314
  }, null);
336688
337315
  }
@@ -336697,7 +337324,7 @@ async function handlePerpConfig(ctx) {
336697
337324
  }
336698
337325
 
336699
337326
  // src/handlers/execute-action/treasury-call.ts
336700
- var grpc9 = __toESM(require_src3(), 1);
337327
+ var grpc11 = __toESM(require_src3(), 1);
336701
337328
  async function handleTreasuryCall(ctx) {
336702
337329
  const { broker } = ctx;
336703
337330
  const callValue = parsePayloadForAction(ctx, CallPayloadSchema);
@@ -336708,7 +337335,7 @@ async function handleTreasuryCall(ctx) {
336708
337335
  try {
336709
337336
  if (callValue.functionName.startsWith("_") || callValue.functionName.includes("constructor") || callValue.functionName.includes("prototype")) {
336710
337337
  return ctx.wrappedCallback({
336711
- code: grpc9.status.PERMISSION_DENIED,
337338
+ code: grpc11.status.PERMISSION_DENIED,
336712
337339
  message: "Access to the requested function is denied"
336713
337340
  }, null);
336714
337341
  }
@@ -336723,7 +337350,7 @@ async function handleTreasuryCall(ctx) {
336723
337350
  const fn = broker[callValue.functionName];
336724
337351
  if (typeof fn !== "function" || broker.has?.[callValue.functionName] === false) {
336725
337352
  return ctx.wrappedCallback({
336726
- code: grpc9.status.INVALID_ARGUMENT,
337353
+ code: grpc11.status.INVALID_ARGUMENT,
336727
337354
  message: `Function not found on broker: ${callValue.functionName}`
336728
337355
  }, null);
336729
337356
  }
@@ -336802,7 +337429,7 @@ function asFiniteNumber(value) {
336802
337429
  }
336803
337430
 
336804
337431
  // src/handlers/execute-action/withdraw.ts
336805
- var grpc10 = __toESM(require_src3(), 1);
337432
+ var grpc12 = __toESM(require_src3(), 1);
336806
337433
  async function handleWithdraw(ctx) {
336807
337434
  const {
336808
337435
  call,
@@ -336825,7 +337452,7 @@ async function handleWithdraw(ctx) {
336825
337452
  const verityProof = verity.proof;
336826
337453
  if (!symbol2) {
336827
337454
  return ctx.wrappedCallback({
336828
- code: grpc10.status.INVALID_ARGUMENT,
337455
+ code: grpc12.status.INVALID_ARGUMENT,
336829
337456
  message: `ValidationError: Symbol required`
336830
337457
  }, null);
336831
337458
  }
@@ -336838,21 +337465,21 @@ async function handleWithdraw(ctx) {
336838
337465
  } catch (error48) {
336839
337466
  const message = getErrorMessage(error48);
336840
337467
  return ctx.wrappedCallback({
336841
- code: stableGrpcErrorCode(message) ?? grpc10.status.INVALID_ARGUMENT,
337468
+ code: stableGrpcErrorCode(message) ?? grpc12.status.INVALID_ARGUMENT,
336842
337469
  message
336843
337470
  }, null);
336844
337471
  }
336845
337472
  const transferValidation = validateWithdraw(policy, cex3, withdrawNetwork.brokerNetworkId, transferValue.recipientAddress, transferValue.amount, symbol2);
336846
337473
  if (!transferValidation.valid) {
336847
337474
  return ctx.wrappedCallback({
336848
- code: grpc10.status.PERMISSION_DENIED,
337475
+ code: grpc12.status.PERMISSION_DENIED,
336849
337476
  message: `policy_withdrawal_denied: ${transferValidation.error}`
336850
337477
  }, null);
336851
337478
  }
336852
337479
  const travelRule = resolveTravelRuleDecision(policy, cex3, transferValue.recipientAddress);
336853
337480
  if (travelRule.mode === "denied") {
336854
337481
  return ctx.wrappedCallback({
336855
- code: grpc10.status.FAILED_PRECONDITION,
337482
+ code: grpc12.status.FAILED_PRECONDITION,
336856
337483
  message: `travel_rule_denied: ${travelRule.error}`
336857
337484
  }, null);
336858
337485
  }
@@ -336919,7 +337546,7 @@ async function handleWithdraw(ctx) {
336919
337546
  payload: { recipientAddress: transferValue.recipientAddress }
336920
337547
  }
336921
337548
  });
336922
- const code = mapCcxtErrorToGrpcStatus(error48) ?? grpc10.status.INTERNAL;
337549
+ const code = mapCcxtErrorToGrpcStatus(error48) ?? grpc12.status.INTERNAL;
336923
337550
  ctx.wrappedCallback({
336924
337551
  code,
336925
337552
  message: `Withdraw failed: ${sanitizeErrorDetail(error48)}`
@@ -336928,33 +337555,152 @@ async function handleWithdraw(ctx) {
336928
337555
  }
336929
337556
 
336930
337557
  // src/handlers/execute-action/registry.ts
336931
- var ACTION_HANDLERS = {
336932
- [Action.Deposit]: handleDeposit,
336933
- [Action.Withdraw]: handleWithdraw,
336934
- [Action.Call]: handleTreasuryCall,
336935
- [Action.InternalTransfer]: handleInternalTransfer,
336936
- [Action.CreateOrder]: handleOrders,
336937
- [Action.GetOrderDetails]: handleOrders,
336938
- [Action.CancelOrder]: handleOrders,
336939
- [Action.FetchCurrency]: handlePassThrough,
336940
- [Action.FetchAccountId]: handlePassThrough,
336941
- [Action.FetchFees]: handlePassThrough,
336942
- [Action.FetchDepositAddresses]: handlePassThrough,
336943
- [Action.FetchBalances]: handlePassThrough,
336944
- [Action.FetchTicker]: handlePassThrough,
336945
- [Action.GetPerpConfigState]: handlePerpConfig,
336946
- [Action.SetPerpConfigState]: handlePerpConfig
337558
+ function valid() {
337559
+ return { valid: true };
337560
+ }
337561
+ function requireSymbol2(request) {
337562
+ return request.symbol?.trim() ? valid() : { valid: false, message: "symbol is required" };
337563
+ }
337564
+ function requireSpotSymbol(request) {
337565
+ const symbol2 = request.symbol?.trim() ?? "";
337566
+ return /^[^/\s]+\/[^/\s]+$/.test(symbol2) ? valid() : {
337567
+ valid: false,
337568
+ message: "symbol must be a slash-delimited spot pair"
337569
+ };
337570
+ }
337571
+ function validatePayload(schema, request) {
337572
+ const parsed = parsePayload(schema, request.payload);
337573
+ return parsed.success ? valid() : { valid: false, message: parsed.message };
337574
+ }
337575
+ function validateSymbolAndPayload(schema) {
337576
+ return (request) => {
337577
+ const symbolValidation = requireSymbol2(request);
337578
+ return symbolValidation.valid ? validatePayload(schema, request) : symbolValidation;
337579
+ };
337580
+ }
337581
+ function validateSpotSymbolAndPayload(schema) {
337582
+ return (request) => {
337583
+ const symbolValidation = requireSpotSymbol(request);
337584
+ return symbolValidation.valid ? validatePayload(schema, request) : symbolValidation;
337585
+ };
337586
+ }
337587
+ function validateFetchBalances(request) {
337588
+ const balanceType = request.payload?.balanceType;
337589
+ if (balanceType !== undefined && !new Set(["free", "used", "total"]).has(balanceType)) {
337590
+ return {
337591
+ valid: false,
337592
+ message: "balanceType must be free, used, or total"
337593
+ };
337594
+ }
337595
+ return valid();
337596
+ }
337597
+ var ACTION_DESCRIPTORS = {
337598
+ [Action.Deposit]: {
337599
+ handler: handleDeposit,
337600
+ access: "write",
337601
+ batchable: false
337602
+ },
337603
+ [Action.Withdraw]: {
337604
+ handler: handleWithdraw,
337605
+ access: "write",
337606
+ batchable: false
337607
+ },
337608
+ [Action.Call]: {
337609
+ handler: handleTreasuryCall,
337610
+ access: "write",
337611
+ batchable: false
337612
+ },
337613
+ [Action.InternalTransfer]: {
337614
+ handler: handleInternalTransfer,
337615
+ access: "write",
337616
+ batchable: false
337617
+ },
337618
+ [Action.CreateOrder]: {
337619
+ handler: handleOrders,
337620
+ access: "write",
337621
+ batchable: false
337622
+ },
337623
+ [Action.GetOrderDetails]: {
337624
+ handler: handleOrders,
337625
+ access: "read",
337626
+ batchable: false
337627
+ },
337628
+ [Action.CancelOrder]: {
337629
+ handler: handleOrders,
337630
+ access: "write",
337631
+ batchable: false
337632
+ },
337633
+ [Action.FetchCurrency]: {
337634
+ handler: handlePassThrough,
337635
+ access: "read",
337636
+ batchable: true,
337637
+ validateBatchRequest: validateSymbolAndPayload(FetchCurrencyPayloadSchema)
337638
+ },
337639
+ [Action.FetchAccountId]: {
337640
+ handler: handlePassThrough,
337641
+ access: "read",
337642
+ batchable: true,
337643
+ validateBatchRequest: (request) => validatePayload(EmptyActionPayloadSchema, request)
337644
+ },
337645
+ [Action.FetchFees]: {
337646
+ handler: handlePassThrough,
337647
+ access: "read",
337648
+ batchable: true,
337649
+ validateBatchRequest: validateSpotSymbolAndPayload(FetchFeesPayloadSchema)
337650
+ },
337651
+ [Action.FetchDepositAddresses]: {
337652
+ handler: handlePassThrough,
337653
+ access: "read",
337654
+ batchable: false
337655
+ },
337656
+ [Action.FetchBalances]: {
337657
+ handler: handlePassThrough,
337658
+ access: "read",
337659
+ batchable: true,
337660
+ validateBatchRequest: validateFetchBalances
337661
+ },
337662
+ [Action.FetchTicker]: {
337663
+ handler: handlePassThrough,
337664
+ access: "read",
337665
+ batchable: true,
337666
+ validateBatchRequest: validateSymbolAndPayload(EmptyActionPayloadSchema)
337667
+ },
337668
+ [Action.GetPerpConfigState]: {
337669
+ handler: handlePerpConfig,
337670
+ access: "read",
337671
+ batchable: true,
337672
+ validateBatchRequest: (request) => validatePayload(GetPerpConfigStatePayloadSchema, request)
337673
+ },
337674
+ [Action.SetPerpConfigState]: {
337675
+ handler: handlePerpConfig,
337676
+ access: "write",
337677
+ batchable: false
337678
+ },
337679
+ [Action.FetchMarketRules]: {
337680
+ handler: handlePassThrough,
337681
+ access: "read",
337682
+ batchable: true,
337683
+ validateBatchRequest: validateSpotSymbolAndPayload(EmptyActionPayloadSchema)
337684
+ },
337685
+ [Action.Batch]: {
337686
+ handler: (ctx) => handleBatch(ctx, getActionDescriptor),
337687
+ access: "read",
337688
+ batchable: false
337689
+ }
336947
337690
  };
337691
+ function getActionDescriptor(action) {
337692
+ return ACTION_DESCRIPTORS[action];
337693
+ }
336948
337694
  async function dispatchExecuteAction(ctx) {
336949
- const handler = ACTION_HANDLERS[ctx.action];
336950
- if (!handler) {
337695
+ const descriptor = getActionDescriptor(ctx.action);
337696
+ if (!descriptor) {
336951
337697
  ctx.wrappedCallback({
336952
- code: grpc11.status.INVALID_ARGUMENT,
337698
+ code: grpc13.status.INVALID_ARGUMENT,
336953
337699
  message: "Invalid Action"
336954
337700
  }, null);
336955
337701
  return;
336956
337702
  }
336957
- await handler(ctx);
337703
+ await descriptor.handler(ctx);
336958
337704
  }
336959
337705
 
336960
337706
  // src/handlers/execute-action/handler.ts
@@ -336970,7 +337716,7 @@ function grpcStatusName(error48) {
336970
337716
  if (typeof error48.code !== "number") {
336971
337717
  return "UNKNOWN";
336972
337718
  }
336973
- return grpc12.status[error48.code] ?? "UNKNOWN";
337719
+ return grpc14.status[error48.code] ?? "UNKNOWN";
336974
337720
  }
336975
337721
  function createExecuteActionHandler(deps) {
336976
337722
  const {
@@ -337018,7 +337764,7 @@ function createExecuteActionHandler(deps) {
337018
337764
  otelMetrics?.recordCounter("execute_action_errors_total", 1, {
337019
337765
  action: actionName,
337020
337766
  cex: cex3 || "unknown",
337021
- error_type: error48.code ? grpc12.status[error48.code] || "unknown" : "unknown"
337767
+ error_type: error48.code ? grpc14.status[error48.code] || "unknown" : "unknown"
337022
337768
  });
337023
337769
  } else {
337024
337770
  otelMetrics?.recordCounter("execute_action_success_total", 1, {
@@ -337041,13 +337787,13 @@ function createExecuteActionHandler(deps) {
337041
337787
  });
337042
337788
  if (!authenticateRequest(call, whitelistIps)) {
337043
337789
  return wrappedCallback({
337044
- code: grpc12.status.PERMISSION_DENIED,
337790
+ code: grpc14.status.PERMISSION_DENIED,
337045
337791
  message: "Access denied: Unauthorized IP"
337046
337792
  }, null);
337047
337793
  }
337048
337794
  if (!action || !cex3) {
337049
337795
  return wrappedCallback({
337050
- code: grpc12.status.INVALID_ARGUMENT,
337796
+ code: grpc14.status.INVALID_ARGUMENT,
337051
337797
  message: "`action` AND `cex` fields are required"
337052
337798
  }, null);
337053
337799
  }
@@ -337057,17 +337803,20 @@ function createExecuteActionHandler(deps) {
337057
337803
  const broker = selectedBrokerAccount?.exchange ?? createBroker(normalizedCex, call.metadata) ?? (isPublicMarketDataAction(action, call.request.payload) ? createPublicBroker(normalizedCex) : null);
337058
337804
  if (!broker) {
337059
337805
  return wrappedCallback({
337060
- code: grpc12.status.UNAUTHENTICATED,
337806
+ code: grpc14.status.UNAUTHENTICATED,
337061
337807
  message: `This Exchange is not registered and No API metadata was found`
337062
337808
  }, null);
337063
337809
  }
337064
337810
  const verity = { proof: "" };
337065
- const applyVerityToBroker = (targetBroker) => {
337811
+ const applyVerityToBroker = (targetBroker, proofState = verity) => {
337066
337812
  if (!useVerity)
337067
337813
  return;
337068
337814
  const override = buildHttpClientOverrideFromMetadata(metadata, verityProverUrl, (proof, notaryPubKey) => {
337069
- verity.proof = proof;
337070
- log.debug(`Verity proof:`, { proof, notaryPubKey });
337815
+ proofState.proof = proof;
337816
+ log.debug(`Verity proof received`, {
337817
+ has_proof: proof.length > 0,
337818
+ has_notary_public_key: Boolean(notaryPubKey)
337819
+ });
337071
337820
  });
337072
337821
  targetBroker.setHttpClientOverride(override, verityHttpClientOverridePredicate);
337073
337822
  };
@@ -337103,7 +337852,7 @@ function createExecuteActionHandler(deps) {
337103
337852
  } catch (error48) {
337104
337853
  safeLogError("ExecuteAction unhandled error", error48);
337105
337854
  return wrappedCallback({
337106
- code: grpc12.status.INTERNAL,
337855
+ code: grpc14.status.INTERNAL,
337107
337856
  message: "ExecuteAction failed unexpectedly"
337108
337857
  }, null);
337109
337858
  }
@@ -337163,7 +337912,7 @@ class SubscribeBrokerLifecycle {
337163
337912
  }
337164
337913
  }
337165
337914
  // src/handlers/subscribe/handler.ts
337166
- var grpc13 = __toESM(require_src3(), 1);
337915
+ var grpc15 = __toESM(require_src3(), 1);
337167
337916
 
337168
337917
  // src/helpers/binance-user-data-normalization.ts
337169
337918
  function requireQuantity(entry, key2) {
@@ -337274,8 +338023,8 @@ async function writeSubscribeError(call, isClosed, frame) {
337274
338023
  call.end();
337275
338024
  }
337276
338025
  }
337277
- function grpcStatusName2(status14) {
337278
- return typeof status14 === "number" ? grpc13.status[status14] ?? "UNKNOWN" : "UNKNOWN";
338026
+ function grpcStatusName2(status16) {
338027
+ return typeof status16 === "number" ? grpc15.status[status16] ?? "UNKNOWN" : "UNKNOWN";
337279
338028
  }
337280
338029
  function getBinanceEventMarketId(event) {
337281
338030
  const value = event.s;
@@ -337468,9 +338217,9 @@ function createSubscribeHandler(deps) {
337468
338217
  log.withMetadata(fields).info("Subscribe ended");
337469
338218
  }
337470
338219
  };
337471
- const writeTerminalError = async (frame, status14 = grpc13.status.UNKNOWN) => {
338220
+ const writeTerminalError = async (frame, status16 = grpc15.status.UNKNOWN) => {
337472
338221
  terminalOutcome = "error";
337473
- terminalGrpcStatus = grpcStatusName2(status14);
338222
+ terminalGrpcStatus = grpcStatusName2(status16);
337474
338223
  await writeSubscribeError(call, isStreamClosed, frame);
337475
338224
  };
337476
338225
  log.withMetadata(operationalFields).info("Subscribe started");
@@ -337506,7 +338255,7 @@ function createSubscribeHandler(deps) {
337506
338255
  error_type: "permission_denied"
337507
338256
  });
337508
338257
  call.emit("error", {
337509
- code: grpc13.status.PERMISSION_DENIED,
338258
+ code: grpc15.status.PERMISSION_DENIED,
337510
338259
  message: "Access denied: Unauthorized IP"
337511
338260
  }, null);
337512
338261
  call.destroy(new Error("Access denied: Unauthorized IP"));
@@ -337527,7 +338276,7 @@ function createSubscribeHandler(deps) {
337527
338276
  timestamp: Date.now(),
337528
338277
  symbol: symbol2 || "",
337529
338278
  type: subscriptionType2
337530
- }, grpc13.status.INVALID_ARGUMENT);
338279
+ }, grpc15.status.INVALID_ARGUMENT);
337531
338280
  return;
337532
338281
  }
337533
338282
  if (isPublicMarketDataSubscription(subscriptionType2)) {
@@ -337584,7 +338333,7 @@ function createSubscribeHandler(deps) {
337584
338333
  timestamp: Date.now(),
337585
338334
  symbol: symbol2,
337586
338335
  type: subscriptionType2
337587
- }, grpc13.status.NOT_FOUND);
338336
+ }, grpc15.status.NOT_FOUND);
337588
338337
  return;
337589
338338
  }
337590
338339
  if (!selectedBrokerAccount) {
@@ -337619,7 +338368,7 @@ function createSubscribeHandler(deps) {
337619
338368
  timestamp: Date.now(),
337620
338369
  symbol: resolvedSymbol,
337621
338370
  type: subscriptionType2
337622
- }, grpc13.status.FAILED_PRECONDITION);
338371
+ }, grpc15.status.FAILED_PRECONDITION);
337623
338372
  return;
337624
338373
  }
337625
338374
  const marketId = subscriptionType2 === SubscriptionType.ORDERS ? await getBinanceMarketId(accountBroker, resolvedSymbol) : undefined;
@@ -337633,7 +338382,7 @@ function createSubscribeHandler(deps) {
337633
338382
  timestamp: Date.now(),
337634
338383
  symbol: resolvedSymbol,
337635
338384
  type: subscriptionType2
337636
- }, grpc13.status.FAILED_PRECONDITION);
338385
+ }, grpc15.status.FAILED_PRECONDITION);
337637
338386
  return;
337638
338387
  }
337639
338388
  userDataSource = userDataStreamSupervisor.subscribe({
@@ -337691,7 +338440,7 @@ function createSubscribeHandler(deps) {
337691
338440
  timestamp: Date.now(),
337692
338441
  symbol: symbol2,
337693
338442
  type: subscriptionType2
337694
- }, grpc13.status.INVALID_ARGUMENT);
338443
+ }, grpc15.status.INVALID_ARGUMENT);
337695
338444
  }
337696
338445
  } catch (error48) {
337697
338446
  log.error("Error in Subscribe stream:", error48);
@@ -337701,7 +338450,7 @@ function createSubscribeHandler(deps) {
337701
338450
  timestamp: Date.now(),
337702
338451
  symbol: "",
337703
338452
  type: subscriptionType2
337704
- }, grpc13.status.INTERNAL);
338453
+ }, grpc15.status.INTERNAL);
337705
338454
  } finally {
337706
338455
  call.off("cancelled", closeOwnedBrokerOnCallEnd);
337707
338456
  call.off("error", closeOwnedBrokerOnCallEnd);
@@ -337833,7 +338582,9 @@ var descriptor = {
337833
338582
  FetchFees: 12,
337834
338583
  InternalTransfer: 13,
337835
338584
  GetPerpConfigState: 14,
337836
- SetPerpConfigState: 15
338585
+ SetPerpConfigState: 15,
338586
+ FetchMarketRules: 16,
338587
+ Batch: 17
337837
338588
  }
337838
338589
  }
337839
338590
  }
@@ -337855,10 +338606,10 @@ var PROTO_LOADER_OPTIONS = {
337855
338606
  var CEX_BROKER_PACKAGE_DEFINITION = protoLoader.fromJSON(node_descriptor_default, PROTO_LOADER_OPTIONS);
337856
338607
 
337857
338608
  // src/server.ts
337858
- var grpcObj = grpc14.loadPackageDefinition(CEX_BROKER_PACKAGE_DEFINITION);
338609
+ var grpcObj = grpc16.loadPackageDefinition(CEX_BROKER_PACKAGE_DEFINITION);
337859
338610
  var cexNode = grpcObj.cex_broker;
337860
338611
  function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, otelMetrics, brokerArchiver, orderActivityTracker, withdrawalObservationTracker, subscribeBrokerLifecycle, userDataStreamSupervisor, publicMarketDataFeedSupervisor) {
337861
- const server = new grpc14.Server;
338612
+ const server = new grpc16.Server;
337862
338613
  server.addService(cexNode.cex_service.service, {
337863
338614
  ExecuteAction: createExecuteActionHandler({
337864
338615
  policy,
@@ -338247,7 +338998,10 @@ class CEXBroker {
338247
338998
  await this.otelMetrics.initialize();
338248
338999
  }
338249
339000
  if (!this.userDataStreamSupervisor && Object.keys(this.brokers).length > 0) {
338250
- const publisher = new StreamHealthPublisher(streamHealthPublisherConfigFromEnv());
339001
+ const publisher = new StreamHealthPublisher({
339002
+ ...streamHealthPublisherConfigFromEnv(),
339003
+ producerId: USER_DATA_STREAM_HEALTH_PRODUCER_ID
339004
+ });
338251
339005
  this.userDataStreamSupervisor = new UserDataStreamSupervisor({
338252
339006
  brokers: this.brokers,
338253
339007
  publisher
@@ -338260,7 +339014,7 @@ class CEXBroker {
338260
339014
  otelMetrics: this.otelMetrics
338261
339015
  });
338262
339016
  this.server = getServer(this.policy, this.brokers, this.whitelistIps, this.useVerity, this.#verityProverUrl, this.otelMetrics, this.brokerArchiver, this.orderActivityTracker, this.withdrawalObservationTracker, undefined, this.userDataStreamSupervisor, this.publicMarketDataFeedSupervisor);
338263
- this.server.bindAsync(`0.0.0.0:${this.port}`, grpc15.ServerCredentials.createInsecure(), (err2, port) => {
339017
+ this.server.bindAsync(`0.0.0.0:${this.port}`, grpc17.ServerCredentials.createInsecure(), (err2, port) => {
338264
339018
  if (err2) {
338265
339019
  log.error(err2);
338266
339020
  return;
@@ -338285,7 +339039,8 @@ class CEXBroker {
338285
339039
  this.depositArchivePoller = new DepositArchivePoller({
338286
339040
  brokers: this.brokers,
338287
339041
  archiver: this.brokerArchiver,
338288
- metrics: this.otelMetrics
339042
+ metrics: this.otelMetrics,
339043
+ coveragePublisher: Object.keys(this.brokers).length > 0 ? new StreamHealthPublisher(depositPollerStreamHealthPublisherConfigFromEnv()) : undefined
338289
339044
  });
338290
339045
  this.depositArchivePoller.start();
338291
339046
  if (this.userDataStreamSupervisor) {