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