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