@usherlabs/cex-broker 0.2.36 → 0.2.37
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 +11 -0
- package/dist/commands/cli.js +2480 -2009
- package/dist/helpers/broker-execution-archive/index.d.ts +2 -2
- package/dist/helpers/broker-execution-archive/rows.d.ts +4 -1
- package/dist/helpers/broker-execution-archive/types.d.ts +4 -2
- package/dist/helpers/broker-execution-archive/writer.d.ts +6 -1
- package/dist/helpers/market-data-archive/canonical-orderbook.d.ts +20 -0
- package/dist/helpers/market-data-archive/capture-context.d.ts +32 -0
- package/dist/helpers/market-data-archive/capture-contract.d.ts +29 -0
- package/dist/helpers/market-data-archive/capture.d.ts +2 -0
- package/dist/helpers/market-data-archive/index.d.ts +6 -2
- package/dist/helpers/market-data-archive/legacy-migration.d.ts +37 -0
- package/dist/helpers/market-data-archive/rows.d.ts +7 -11
- package/dist/helpers/market-data-archive/types.d.ts +26 -2
- package/dist/index.js +2481 -2010
- package/dist/index.js.map +22 -19
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -291041,7 +291041,7 @@ function compactUndefined2(record) {
|
|
|
291041
291041
|
}
|
|
291042
291042
|
function buildCommonArchiveTags(input) {
|
|
291043
291043
|
return {
|
|
291044
|
-
source: BROKER_WRITE_SOURCE,
|
|
291044
|
+
source: input.source ?? BROKER_WRITE_SOURCE,
|
|
291045
291045
|
deployment_id: input.deploymentId,
|
|
291046
291046
|
account_selector: input.accountSelector ?? "unknown",
|
|
291047
291047
|
exchange: input.exchange.trim().toLowerCase() || "unknown",
|
|
@@ -291104,6 +291104,10 @@ function buildSubscribeStreamArchiveRow(input) {
|
|
|
291104
291104
|
function quantityString(...values2) {
|
|
291105
291105
|
return firstString2(...values2);
|
|
291106
291106
|
}
|
|
291107
|
+
function extractBinanceInternalTransferId(response) {
|
|
291108
|
+
const record = asRecord(response);
|
|
291109
|
+
return firstString2(record?.txnId, record?.tranId);
|
|
291110
|
+
}
|
|
291107
291111
|
function buildTransferEventArchiveRow(input) {
|
|
291108
291112
|
const { tags, transfer } = input;
|
|
291109
291113
|
return {
|
|
@@ -291136,6 +291140,7 @@ function normalizeCcxtTransactionForArchive(transaction) {
|
|
|
291136
291140
|
const fee = asRecord(record?.fee);
|
|
291137
291141
|
return compactUndefined2({
|
|
291138
291142
|
externalId: firstString2(record?.id, info?.id, record?.txid, info?.txId),
|
|
291143
|
+
clientWithdrawalId: firstString2(info?.withdrawOrderId),
|
|
291139
291144
|
txid: firstString2(record?.txid, info?.txId, info?.txid, info?.tx_hash),
|
|
291140
291145
|
address: firstString2(record?.address, record?.addressTo, info?.address),
|
|
291141
291146
|
network: firstString2(record?.network, info?.network),
|
|
@@ -291243,14 +291248,40 @@ var DEFAULT_BATCH_SIZE = 10;
|
|
|
291243
291248
|
var DEFAULT_FLUSH_INTERVAL_MS = 1000;
|
|
291244
291249
|
var DEFAULT_FORWARDER_TIMEOUT_MS = 3000;
|
|
291245
291250
|
var SHED_WARN_INTERVAL_MS = 60000;
|
|
291251
|
+
var MARKET_FEEDS = new Set(["ORDERBOOK", "TICKER", "TRADES", "OHLCV"]);
|
|
291252
|
+
function archiveFeed(row) {
|
|
291253
|
+
const declared = row.row.feed ?? row.row.stream_type;
|
|
291254
|
+
if (typeof declared === "string" && MARKET_FEEDS.has(declared)) {
|
|
291255
|
+
return declared;
|
|
291256
|
+
}
|
|
291257
|
+
if (row.table === "market_data.orderbook_snapshots" || row.table.startsWith("market_data.cex_order_book_")) {
|
|
291258
|
+
return "ORDERBOOK";
|
|
291259
|
+
}
|
|
291260
|
+
if (row.table === "market_data.candles" || row.table === "market_data.cex_ohlcv") {
|
|
291261
|
+
return "OHLCV";
|
|
291262
|
+
}
|
|
291263
|
+
if (row.table === "market_data.cex_ticker_events")
|
|
291264
|
+
return "TICKER";
|
|
291265
|
+
if (row.table === "market_data.cex_trades")
|
|
291266
|
+
return "TRADES";
|
|
291267
|
+
return "NON_MARKET";
|
|
291268
|
+
}
|
|
291246
291269
|
function isArchiveOtelLogsEnabled() {
|
|
291247
291270
|
return process.env.CEX_BROKER_ARCHIVE_OTEL_LOGS_ENABLED === "true";
|
|
291248
291271
|
}
|
|
291249
291272
|
function resolveArchiveForwarderUrlFromEnv() {
|
|
291250
291273
|
return process.env.CEX_BROKER_ARCHIVE_FORWARDER_URL?.trim() || undefined;
|
|
291251
291274
|
}
|
|
291275
|
+
function resolveArchiveSourceFromEnv(value = process.env.CEX_BROKER_ARCHIVE_SOURCE) {
|
|
291276
|
+
const source = value?.trim() || BROKER_WRITE_SOURCE;
|
|
291277
|
+
if (source !== "broker_read" && source !== "broker_write") {
|
|
291278
|
+
throw new Error("CEX_BROKER_ARCHIVE_SOURCE must be broker_read or broker_write");
|
|
291279
|
+
}
|
|
291280
|
+
return source;
|
|
291281
|
+
}
|
|
291252
291282
|
|
|
291253
291283
|
class BrokerExecutionArchiver {
|
|
291284
|
+
source;
|
|
291254
291285
|
deploymentId;
|
|
291255
291286
|
otelLogs;
|
|
291256
291287
|
otelMetrics;
|
|
@@ -291271,10 +291302,12 @@ class BrokerExecutionArchiver {
|
|
|
291271
291302
|
flushTimer = null;
|
|
291272
291303
|
flushInFlight = null;
|
|
291273
291304
|
lastShedWarnAtMs = 0;
|
|
291305
|
+
closing = false;
|
|
291274
291306
|
closed = false;
|
|
291275
291307
|
enabled;
|
|
291276
291308
|
forwarderAuthToken;
|
|
291277
291309
|
constructor(options) {
|
|
291310
|
+
this.source = options.source ?? BROKER_WRITE_SOURCE;
|
|
291278
291311
|
this.deploymentId = options.deploymentId?.trim() || process.env.CEX_BROKER_DEPLOYMENT_ID?.trim() || "unknown";
|
|
291279
291312
|
this.otelLogs = options.otelLogs;
|
|
291280
291313
|
this.otelMetrics = options.otelMetrics;
|
|
@@ -291306,6 +291339,7 @@ class BrokerExecutionArchiver {
|
|
|
291306
291339
|
this.flushTimer.unref?.();
|
|
291307
291340
|
log.info("Broker execution archive enabled", {
|
|
291308
291341
|
enabled: true,
|
|
291342
|
+
source: this.source,
|
|
291309
291343
|
otel_mirror_enabled: Boolean(this.otelLogs?.isOtelEnabled())
|
|
291310
291344
|
});
|
|
291311
291345
|
} catch (error) {
|
|
@@ -291322,8 +291356,11 @@ class BrokerExecutionArchiver {
|
|
|
291322
291356
|
getDeploymentId() {
|
|
291323
291357
|
return this.deploymentId;
|
|
291324
291358
|
}
|
|
291359
|
+
getSource() {
|
|
291360
|
+
return this.source;
|
|
291361
|
+
}
|
|
291325
291362
|
isEnabled() {
|
|
291326
|
-
return this.enabled && !this.closed;
|
|
291363
|
+
return this.enabled && !this.closing && !this.closed;
|
|
291327
291364
|
}
|
|
291328
291365
|
canPersistMarketMetadataSnapshot() {
|
|
291329
291366
|
return this.isEnabled();
|
|
@@ -291335,6 +291372,10 @@ class BrokerExecutionArchiver {
|
|
|
291335
291372
|
if (!this.enabled || this.closed) {
|
|
291336
291373
|
return;
|
|
291337
291374
|
}
|
|
291375
|
+
const archiveRow = {
|
|
291376
|
+
table: row.table,
|
|
291377
|
+
row: { ...row.row, source: this.source }
|
|
291378
|
+
};
|
|
291338
291379
|
if (this.queue.length >= this.maxQueueSize) {
|
|
291339
291380
|
const shedRow = this.queue[0];
|
|
291340
291381
|
if (shedRow) {
|
|
@@ -291343,7 +291384,14 @@ class BrokerExecutionArchiver {
|
|
|
291343
291384
|
}
|
|
291344
291385
|
this.stats.shed += 1;
|
|
291345
291386
|
this.recordArchiveMetric("cex_archive_rows_shed_total", {
|
|
291346
|
-
table: shedRow?.table ?? "unknown"
|
|
291387
|
+
table: shedRow?.table ?? "unknown",
|
|
291388
|
+
source: this.source,
|
|
291389
|
+
feed: shedRow ? archiveFeed(shedRow) : "NON_MARKET"
|
|
291390
|
+
});
|
|
291391
|
+
this.recordArchiveMetric("cex_archive_queue_saturated_rows_total", {
|
|
291392
|
+
table: shedRow?.table ?? "unknown",
|
|
291393
|
+
source: this.source,
|
|
291394
|
+
feed: shedRow ? archiveFeed(shedRow) : "NON_MARKET"
|
|
291347
291395
|
});
|
|
291348
291396
|
const now3 = Date.now();
|
|
291349
291397
|
if (now3 - this.lastShedWarnAtMs >= SHED_WARN_INTERVAL_MS) {
|
|
@@ -291355,10 +291403,12 @@ class BrokerExecutionArchiver {
|
|
|
291355
291403
|
this.lastShedWarnAtMs = now3;
|
|
291356
291404
|
}
|
|
291357
291405
|
}
|
|
291358
|
-
this.queue.push(
|
|
291406
|
+
this.queue.push(archiveRow);
|
|
291359
291407
|
this.stats.enqueued += 1;
|
|
291360
291408
|
this.recordArchiveMetric("cex_archive_rows_enqueued_total", {
|
|
291361
|
-
table:
|
|
291409
|
+
table: archiveRow.table,
|
|
291410
|
+
source: this.source,
|
|
291411
|
+
feed: archiveFeed(archiveRow)
|
|
291362
291412
|
});
|
|
291363
291413
|
if (this.queue.length >= this.batchSize) {
|
|
291364
291414
|
this.flush();
|
|
@@ -291378,11 +291428,15 @@ class BrokerExecutionArchiver {
|
|
|
291378
291428
|
return;
|
|
291379
291429
|
}).finally(() => {
|
|
291380
291430
|
this.flushInFlight = null;
|
|
291431
|
+
if (!this.closed && !this.closing && this.enabled && this.queue.length >= this.batchSize) {
|
|
291432
|
+
queueMicrotask(() => void this.flush());
|
|
291433
|
+
}
|
|
291381
291434
|
});
|
|
291382
291435
|
this.flushInFlight = inFlight;
|
|
291383
291436
|
return inFlight;
|
|
291384
291437
|
}
|
|
291385
291438
|
async close() {
|
|
291439
|
+
this.closing = true;
|
|
291386
291440
|
if (this.flushTimer) {
|
|
291387
291441
|
clearInterval(this.flushTimer);
|
|
291388
291442
|
this.flushTimer = null;
|
|
@@ -291447,7 +291501,14 @@ class BrokerExecutionArchiver {
|
|
|
291447
291501
|
this.queue.shift();
|
|
291448
291502
|
this.stats.shed += 1;
|
|
291449
291503
|
this.recordArchiveMetric("cex_archive_rows_shed_total", {
|
|
291450
|
-
table: dropped?.table ?? "unknown"
|
|
291504
|
+
table: dropped?.table ?? "unknown",
|
|
291505
|
+
source: this.source,
|
|
291506
|
+
feed: archiveFeed(dropped)
|
|
291507
|
+
});
|
|
291508
|
+
this.recordArchiveMetric("cex_archive_queue_saturated_rows_total", {
|
|
291509
|
+
table: dropped.table,
|
|
291510
|
+
source: this.source,
|
|
291511
|
+
feed: archiveFeed(dropped)
|
|
291451
291512
|
});
|
|
291452
291513
|
}
|
|
291453
291514
|
}
|
|
@@ -291461,6 +291522,7 @@ class BrokerExecutionArchiver {
|
|
|
291461
291522
|
const timestamp = new Date().toISOString();
|
|
291462
291523
|
const records = rows.map((payload) => ({
|
|
291463
291524
|
timestamp,
|
|
291525
|
+
source: this.source,
|
|
291464
291526
|
deployment_id: this.deploymentId,
|
|
291465
291527
|
reason,
|
|
291466
291528
|
payload
|
|
@@ -291474,6 +291536,21 @@ class BrokerExecutionArchiver {
|
|
|
291474
291536
|
throw new Error(`wrote ${written} of ${bytes2.length} bytes`);
|
|
291475
291537
|
}
|
|
291476
291538
|
fsyncSync(this.deadLetterFd);
|
|
291539
|
+
const byFeedAndTable = new Map;
|
|
291540
|
+
for (const row of rows) {
|
|
291541
|
+
const key = `${row.table}\x00${archiveFeed(row)}`;
|
|
291542
|
+
const grouped = byFeedAndTable.get(key) ?? { row, count: 0 };
|
|
291543
|
+
grouped.count += 1;
|
|
291544
|
+
byFeedAndTable.set(key, grouped);
|
|
291545
|
+
}
|
|
291546
|
+
for (const { row, count: count2 } of byFeedAndTable.values()) {
|
|
291547
|
+
this.recordArchiveMetric("cex_archive_rows_journaled_total", {
|
|
291548
|
+
table: row.table,
|
|
291549
|
+
source: this.source,
|
|
291550
|
+
feed: archiveFeed(row),
|
|
291551
|
+
reason
|
|
291552
|
+
}, count2);
|
|
291553
|
+
}
|
|
291477
291554
|
} catch (error) {
|
|
291478
291555
|
throw new BrokerExecutionArchiveDurabilityError(`Broker execution archive failed to durably record ${reason}; affected row(s) were retained`, { cause: error });
|
|
291479
291556
|
}
|
|
@@ -291509,10 +291586,17 @@ class BrokerExecutionArchiver {
|
|
|
291509
291586
|
recordFlushHealth(batch) {
|
|
291510
291587
|
const countByTable = new Map;
|
|
291511
291588
|
for (const entry of batch) {
|
|
291512
|
-
|
|
291513
|
-
|
|
291514
|
-
|
|
291515
|
-
|
|
291589
|
+
const key = `${entry.table}\x00${archiveFeed(entry)}`;
|
|
291590
|
+
const grouped = countByTable.get(key) ?? { row: entry, count: 0 };
|
|
291591
|
+
grouped.count += 1;
|
|
291592
|
+
countByTable.set(key, grouped);
|
|
291593
|
+
}
|
|
291594
|
+
for (const { row, count: count2 } of countByTable.values()) {
|
|
291595
|
+
this.recordArchiveMetric("cex_archive_rows_flushed_total", {
|
|
291596
|
+
table: row.table,
|
|
291597
|
+
source: this.source,
|
|
291598
|
+
feed: archiveFeed(row)
|
|
291599
|
+
}, count2);
|
|
291516
291600
|
}
|
|
291517
291601
|
this.recordArchiveGauge("cex_archive_last_flush_success", Math.floor(Date.now() / 1000));
|
|
291518
291602
|
}
|
|
@@ -291546,7 +291630,7 @@ class BrokerExecutionArchiver {
|
|
|
291546
291630
|
return Promise.resolve();
|
|
291547
291631
|
}
|
|
291548
291632
|
const body = JSON.stringify({
|
|
291549
|
-
source:
|
|
291633
|
+
source: this.source,
|
|
291550
291634
|
deployment_id: this.deploymentId,
|
|
291551
291635
|
rows: batch
|
|
291552
291636
|
});
|
|
@@ -291616,6 +291700,7 @@ function createBrokerExecutionArchiverFromEnv(otelLogs, otelMetrics) {
|
|
|
291616
291700
|
const forwarderUrl = resolveArchiveForwarderUrlFromEnv();
|
|
291617
291701
|
const archiveOtelLogs = isArchiveOtelLogsEnabled() ? otelLogs : undefined;
|
|
291618
291702
|
return BrokerExecutionArchiver.create({
|
|
291703
|
+
source: resolveArchiveSourceFromEnv(),
|
|
291619
291704
|
otelLogs: archiveOtelLogs,
|
|
291620
291705
|
otelMetrics,
|
|
291621
291706
|
forwarderUrl: forwarderUrl ?? "",
|
|
@@ -291754,6 +291839,7 @@ function archiveWithdrawalObservationsInBackground(archiver, tracker, input) {
|
|
|
291754
291839
|
address: normalized.address,
|
|
291755
291840
|
network: normalized.network,
|
|
291756
291841
|
externalId: normalized.externalId,
|
|
291842
|
+
clientWithdrawalId: normalized.clientWithdrawalId,
|
|
291757
291843
|
txid: normalized.txid,
|
|
291758
291844
|
resultIndex,
|
|
291759
291845
|
feeAmount: normalized.feeAmount,
|
|
@@ -306850,9 +306936,6 @@ function selectBrokerAccountForCex(normalizedCex, brokers, metadata) {
|
|
|
306850
306936
|
return selectBrokerAccount(brokers[normalizedCex], metadata) ?? undefined;
|
|
306851
306937
|
}
|
|
306852
306938
|
|
|
306853
|
-
// src/handlers/execute-action/order-book-call.ts
|
|
306854
|
-
import * as grpc4 from "@grpc/grpc-js";
|
|
306855
|
-
|
|
306856
306939
|
// src/helpers/order-book.ts
|
|
306857
306940
|
var ORDER_BOOK_CALL_METHODS = {
|
|
306858
306941
|
FETCH_CAPABILITY: "fetch_order_book_capability",
|
|
@@ -307095,726 +307178,1200 @@ function buildHistoricalOrderBookUnsupported(payload) {
|
|
|
307095
307178
|
}
|
|
307096
307179
|
|
|
307097
307180
|
// src/handlers/execute-action/order-book-call.ts
|
|
307098
|
-
|
|
307099
|
-
|
|
307100
|
-
|
|
307101
|
-
|
|
307102
|
-
|
|
307103
|
-
|
|
307104
|
-
|
|
307105
|
-
|
|
307106
|
-
|
|
307107
|
-
|
|
307108
|
-
|
|
307181
|
+
import * as grpc4 from "@grpc/grpc-js";
|
|
307182
|
+
|
|
307183
|
+
// src/helpers/market-data-archive/capture-contract.ts
|
|
307184
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
307185
|
+
var MARKET_CAPTURE_SCHEMA_VERSION = "1.0.0";
|
|
307186
|
+
var CHECKSUM_ALGORITHM = "sha256-canonical-json-v1";
|
|
307187
|
+
var ARCHIVE_SOURCES = ["broker_read", "broker_write"];
|
|
307188
|
+
var CAPTURE_FEEDS = [
|
|
307189
|
+
"ORDERBOOK",
|
|
307190
|
+
"TICKER",
|
|
307191
|
+
"TRADES",
|
|
307192
|
+
"OHLCV"
|
|
307193
|
+
];
|
|
307194
|
+
var SOURCE_MODES = [
|
|
307195
|
+
"broker_live_stream_v1",
|
|
307196
|
+
"broker_live_sampling_v1",
|
|
307197
|
+
"broker_current_snapshot_v1",
|
|
307198
|
+
"broker_bootstrap_fetch_v1",
|
|
307199
|
+
"external_ccxt_fallback_v1",
|
|
307200
|
+
"external_hummingbot_fallback_v1",
|
|
307201
|
+
"legacy_migration_v1"
|
|
307202
|
+
];
|
|
307203
|
+
var RAW_CAPTURE_SCOPES = [
|
|
307204
|
+
"ccxt_normalized_object",
|
|
307205
|
+
"broker_visible_payload",
|
|
307206
|
+
"exchange_wire_frame"
|
|
307207
|
+
];
|
|
307208
|
+
var CHECKSUM_FIELDS = new Set([
|
|
307209
|
+
"normalized_row_checksum",
|
|
307210
|
+
"raw_checksum",
|
|
307211
|
+
"checksum"
|
|
307212
|
+
]);
|
|
307213
|
+
function canonicalDecimal(value) {
|
|
307214
|
+
if (!Number.isFinite(value)) {
|
|
307215
|
+
throw new Error("Canonical numbers must be finite");
|
|
307109
307216
|
}
|
|
307110
|
-
if (
|
|
307111
|
-
return
|
|
307217
|
+
if (Object.is(value, -0)) {
|
|
307218
|
+
return "0";
|
|
307112
307219
|
}
|
|
307113
|
-
const
|
|
307114
|
-
if (!
|
|
307115
|
-
|
|
307116
|
-
code: grpc4.status.INVALID_ARGUMENT,
|
|
307117
|
-
message: `Unsupported exchange for order-book market data: ${ctx.normalizedCex}`
|
|
307118
|
-
}, null);
|
|
307119
|
-
return true;
|
|
307220
|
+
const rendered = String(value).toLowerCase();
|
|
307221
|
+
if (!rendered.includes("e")) {
|
|
307222
|
+
return rendered;
|
|
307120
307223
|
}
|
|
307121
|
-
|
|
307122
|
-
|
|
307123
|
-
|
|
307124
|
-
|
|
307125
|
-
|
|
307126
|
-
|
|
307127
|
-
|
|
307128
|
-
|
|
307129
|
-
|
|
307130
|
-
}
|
|
307131
|
-
|
|
307132
|
-
|
|
307133
|
-
|
|
307134
|
-
|
|
307135
|
-
|
|
307136
|
-
|
|
307137
|
-
|
|
307138
|
-
|
|
307139
|
-
|
|
307140
|
-
|
|
307141
|
-
|
|
307142
|
-
|
|
307143
|
-
|
|
307144
|
-
|
|
307145
|
-
|
|
307224
|
+
const [coefficient = "0", exponentText = "0"] = rendered.split("e");
|
|
307225
|
+
const exponent = Number.parseInt(exponentText, 10);
|
|
307226
|
+
const negative = coefficient.startsWith("-");
|
|
307227
|
+
const unsigned = negative ? coefficient.slice(1) : coefficient;
|
|
307228
|
+
const [integer2 = "0", fraction = ""] = unsigned.split(".");
|
|
307229
|
+
const digits = `${integer2}${fraction}`;
|
|
307230
|
+
const decimalIndex = integer2.length + exponent;
|
|
307231
|
+
let result;
|
|
307232
|
+
if (decimalIndex <= 0) {
|
|
307233
|
+
result = `0.${"0".repeat(-decimalIndex)}${digits}`;
|
|
307234
|
+
} else if (decimalIndex >= digits.length) {
|
|
307235
|
+
result = `${digits}${"0".repeat(decimalIndex - digits.length)}`;
|
|
307236
|
+
} else {
|
|
307237
|
+
result = `${digits.slice(0, decimalIndex)}.${digits.slice(decimalIndex)}`;
|
|
307238
|
+
}
|
|
307239
|
+
return negative ? `-${result}` : result;
|
|
307240
|
+
}
|
|
307241
|
+
function serializeCanonical(value, stack) {
|
|
307242
|
+
if (value === null)
|
|
307243
|
+
return "null";
|
|
307244
|
+
if (typeof value === "string")
|
|
307245
|
+
return JSON.stringify(value);
|
|
307246
|
+
if (typeof value === "boolean")
|
|
307247
|
+
return value ? "true" : "false";
|
|
307248
|
+
if (typeof value === "number")
|
|
307249
|
+
return canonicalDecimal(value);
|
|
307250
|
+
if (typeof value === "bigint")
|
|
307251
|
+
return value.toString(10);
|
|
307252
|
+
if (value instanceof Date) {
|
|
307253
|
+
if (Number.isNaN(value.getTime())) {
|
|
307254
|
+
throw new Error("Canonical timestamps must be valid");
|
|
307146
307255
|
}
|
|
307147
|
-
|
|
307148
|
-
const rawOrderBook = await fetchOrderBook.call(orderBookBroker, orderBookPayload.symbol, orderBookPayload.depthLimit);
|
|
307149
|
-
ctx.wrappedCallback(null, {
|
|
307150
|
-
proof: ctx.verity.proof,
|
|
307151
|
-
result: JSON.stringify(normalizeOrderBookSnapshot(rawOrderBook, {
|
|
307152
|
-
exchange: orderBookPayload.exchange,
|
|
307153
|
-
symbol: orderBookPayload.symbol,
|
|
307154
|
-
depthLimit: orderBookPayload.depthLimit,
|
|
307155
|
-
receivedTimestamp
|
|
307156
|
-
}))
|
|
307157
|
-
});
|
|
307158
|
-
} catch (error48) {
|
|
307159
|
-
safeLogError("Order-book Call failed", error48);
|
|
307160
|
-
ctx.wrappedCallback({
|
|
307161
|
-
code: mapCcxtErrorToGrpcStatus(error48) ?? grpc4.status.INTERNAL,
|
|
307162
|
-
message: `Order-book Call failed: ${sanitizeErrorDetail(error48)}`
|
|
307163
|
-
}, null);
|
|
307256
|
+
return value.getTime().toString(10);
|
|
307164
307257
|
}
|
|
307165
|
-
|
|
307258
|
+
if (Array.isArray(value)) {
|
|
307259
|
+
if (stack.has(value))
|
|
307260
|
+
throw new Error("Canonical values must be acyclic");
|
|
307261
|
+
stack.add(value);
|
|
307262
|
+
const result = `[${value.map((entry) => entry === undefined ? "null" : serializeCanonical(entry, stack)).join(",")}]`;
|
|
307263
|
+
stack.delete(value);
|
|
307264
|
+
return result;
|
|
307265
|
+
}
|
|
307266
|
+
if (typeof value === "object") {
|
|
307267
|
+
if (stack.has(value))
|
|
307268
|
+
throw new Error("Canonical values must be acyclic");
|
|
307269
|
+
stack.add(value);
|
|
307270
|
+
const entries = Object.entries(value).filter(([, entry]) => entry !== undefined).sort(([left], [right]) => left.localeCompare(right));
|
|
307271
|
+
const result = `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${serializeCanonical(entry, stack)}`).join(",")}}`;
|
|
307272
|
+
stack.delete(value);
|
|
307273
|
+
return result;
|
|
307274
|
+
}
|
|
307275
|
+
throw new Error(`Unsupported canonical value type: ${typeof value}`);
|
|
307166
307276
|
}
|
|
307167
|
-
|
|
307168
|
-
|
|
307169
|
-
|
|
307170
|
-
|
|
307171
|
-
|
|
307172
|
-
|
|
307173
|
-
|
|
307174
|
-
|
|
307175
|
-
brokers,
|
|
307176
|
-
metadata,
|
|
307177
|
-
normalizedCex,
|
|
307178
|
-
symbol: symbol2,
|
|
307179
|
-
verity,
|
|
307180
|
-
useVerity,
|
|
307181
|
-
verityProverUrl,
|
|
307182
|
-
brokerArchiver
|
|
307183
|
-
} = ctx;
|
|
307184
|
-
if (!symbol2) {
|
|
307185
|
-
return ctx.wrappedCallback({
|
|
307186
|
-
code: grpc5.status.INVALID_ARGUMENT,
|
|
307187
|
-
message: `ValidationError: Symbol required`
|
|
307188
|
-
}, null);
|
|
307277
|
+
function canonicalSerialize(value) {
|
|
307278
|
+
return serializeCanonical(value, new Set);
|
|
307279
|
+
}
|
|
307280
|
+
function omitChecksumFields(value) {
|
|
307281
|
+
if (Array.isArray(value))
|
|
307282
|
+
return value.map(omitChecksumFields);
|
|
307283
|
+
if (value && typeof value === "object" && !(value instanceof Date)) {
|
|
307284
|
+
return Object.fromEntries(Object.entries(value).filter(([key]) => !CHECKSUM_FIELDS.has(key)).map(([key, entry]) => [key, omitChecksumFields(entry)]));
|
|
307189
307285
|
}
|
|
307190
|
-
|
|
307191
|
-
|
|
307192
|
-
|
|
307193
|
-
|
|
307194
|
-
|
|
307195
|
-
|
|
307196
|
-
|
|
307197
|
-
|
|
307286
|
+
return value;
|
|
307287
|
+
}
|
|
307288
|
+
function sha256Canonical(value) {
|
|
307289
|
+
return createHash3("sha256").update(canonicalSerialize(omitChecksumFields(value))).digest("hex");
|
|
307290
|
+
}
|
|
307291
|
+
function normalizeTimestampMs(value, field) {
|
|
307292
|
+
let timestamp;
|
|
307293
|
+
if (value instanceof Date) {
|
|
307294
|
+
timestamp = value.getTime();
|
|
307295
|
+
} else if (typeof value === "number") {
|
|
307296
|
+
timestamp = value;
|
|
307297
|
+
} else if (typeof value === "string" && /^\d+$/.test(value.trim())) {
|
|
307298
|
+
timestamp = Number(value.trim());
|
|
307299
|
+
} else if (typeof value === "string") {
|
|
307300
|
+
timestamp = Date.parse(value);
|
|
307301
|
+
} else {
|
|
307302
|
+
timestamp = Number.NaN;
|
|
307198
307303
|
}
|
|
307199
|
-
|
|
307200
|
-
|
|
307201
|
-
return ctx.wrappedCallback({
|
|
307202
|
-
code: grpc5.status.FAILED_PRECONDITION,
|
|
307203
|
-
message: `No broker accounts configured for ${normalizedCex}`
|
|
307204
|
-
}, null);
|
|
307304
|
+
if (!Number.isSafeInteger(timestamp) || timestamp < 0) {
|
|
307305
|
+
throw new Error(`${field} must be a non-negative millisecond timestamp`);
|
|
307205
307306
|
}
|
|
307206
|
-
|
|
307207
|
-
|
|
307208
|
-
|
|
307209
|
-
if (!
|
|
307210
|
-
|
|
307211
|
-
code: grpc5.status.INVALID_ARGUMENT,
|
|
307212
|
-
message: `Source account "${fromSelector}" is not configured`
|
|
307213
|
-
}, null);
|
|
307307
|
+
return timestamp;
|
|
307308
|
+
}
|
|
307309
|
+
function assertCaptureContext(context2) {
|
|
307310
|
+
if (!ARCHIVE_SOURCES.includes(context2.source)) {
|
|
307311
|
+
throw new Error(`Unsupported archive source: ${context2.source}`);
|
|
307214
307312
|
}
|
|
307215
|
-
|
|
307216
|
-
|
|
307217
|
-
return ctx.wrappedCallback({
|
|
307218
|
-
code: grpc5.status.INVALID_ARGUMENT,
|
|
307219
|
-
message: `Destination account "${toSelector}" is not configured`
|
|
307220
|
-
}, null);
|
|
307313
|
+
if (!CAPTURE_FEEDS.includes(context2.feed)) {
|
|
307314
|
+
throw new Error(`Unsupported capture feed: ${context2.feed}`);
|
|
307221
307315
|
}
|
|
307222
|
-
|
|
307223
|
-
|
|
307224
|
-
|
|
307225
|
-
|
|
307226
|
-
|
|
307227
|
-
|
|
307316
|
+
if (!SOURCE_MODES.includes(context2.sourceMode)) {
|
|
307317
|
+
throw new Error(`Unsupported source mode: ${context2.sourceMode}`);
|
|
307318
|
+
}
|
|
307319
|
+
for (const [field, value] of [
|
|
307320
|
+
["deployment_id", context2.deploymentId],
|
|
307321
|
+
["capture_bundle_id", context2.captureBundleId],
|
|
307322
|
+
["exchange", context2.exchange],
|
|
307323
|
+
["symbol", context2.symbol],
|
|
307324
|
+
["provider", context2.provider]
|
|
307325
|
+
]) {
|
|
307326
|
+
if (!value.trim())
|
|
307327
|
+
throw new Error(`${field} must not be empty`);
|
|
307328
|
+
}
|
|
307329
|
+
}
|
|
307330
|
+
function createRawCapture(context2, input) {
|
|
307331
|
+
assertCaptureContext(context2);
|
|
307332
|
+
if (!RAW_CAPTURE_SCOPES.includes(input.scope)) {
|
|
307333
|
+
throw new Error(`Unsupported raw capture scope: ${input.scope}`);
|
|
307334
|
+
}
|
|
307335
|
+
const eventTimeMs = normalizeTimestampMs(input.eventTimeMs, "event_time_ms");
|
|
307336
|
+
const receivedTimeMs = normalizeTimestampMs(input.receivedTimeMs, "received_time_ms");
|
|
307337
|
+
const redactedPayload = redactStreamPayload(input.payload);
|
|
307338
|
+
const rawChecksum = sha256Canonical(redactedPayload);
|
|
307339
|
+
const rawCaptureId = sha256Canonical({
|
|
307340
|
+
capture_bundle_id: context2.captureBundleId,
|
|
307341
|
+
exchange: context2.exchange.trim().toLowerCase(),
|
|
307342
|
+
feed: context2.feed,
|
|
307343
|
+
raw_capture_scope: input.scope,
|
|
307344
|
+
raw_payload_sha256: rawChecksum,
|
|
307345
|
+
schema_version: context2.schemaVersion,
|
|
307346
|
+
source_mode: context2.sourceMode,
|
|
307347
|
+
source_symbol: context2.symbol.trim(),
|
|
307348
|
+
source_time_ms: eventTimeMs
|
|
307349
|
+
});
|
|
307350
|
+
return {
|
|
307351
|
+
rawCaptureId,
|
|
307352
|
+
rawCaptureScope: input.scope,
|
|
307353
|
+
rawChecksum,
|
|
307354
|
+
redactedPayload,
|
|
307355
|
+
eventTimeMs,
|
|
307356
|
+
receivedTimeMs,
|
|
307357
|
+
checksumAlgorithm: context2.checksumAlgorithm
|
|
307358
|
+
};
|
|
307359
|
+
}
|
|
307360
|
+
function captureCoreFields(context2, rawCapture) {
|
|
307361
|
+
assertCaptureContext(context2);
|
|
307362
|
+
return {
|
|
307363
|
+
source: context2.source,
|
|
307364
|
+
deployment_id: context2.deploymentId,
|
|
307365
|
+
capture_bundle_id: context2.captureBundleId,
|
|
307366
|
+
exchange: context2.exchange.trim().toLowerCase(),
|
|
307367
|
+
symbol: context2.symbol.trim(),
|
|
307368
|
+
trading_pair: context2.symbol.trim().replace("/", "-"),
|
|
307369
|
+
source_symbol: context2.symbol.trim(),
|
|
307370
|
+
asset_type: context2.assetType,
|
|
307371
|
+
feed: context2.feed,
|
|
307372
|
+
provider: context2.provider,
|
|
307373
|
+
source_mode: context2.sourceMode,
|
|
307374
|
+
source_time_ms: rawCapture.eventTimeMs,
|
|
307375
|
+
received_time_ms: rawCapture.receivedTimeMs,
|
|
307376
|
+
raw_capture_id: rawCapture.rawCaptureId,
|
|
307377
|
+
raw_capture_scope: rawCapture.rawCaptureScope,
|
|
307378
|
+
schema_version: context2.schemaVersion,
|
|
307379
|
+
checksum_algorithm: context2.checksumAlgorithm,
|
|
307380
|
+
raw_checksum: rawCapture.rawChecksum,
|
|
307381
|
+
provenance_complete: context2.provenanceComplete ? 1 : 0
|
|
307382
|
+
};
|
|
307383
|
+
}
|
|
307384
|
+
|
|
307385
|
+
// src/helpers/market-data-archive/canonical-orderbook.ts
|
|
307386
|
+
class OrderBookValidationError extends Error {
|
|
307387
|
+
reason;
|
|
307388
|
+
constructor(reason) {
|
|
307389
|
+
super(`Invalid order-book evidence: ${reason}`);
|
|
307390
|
+
this.name = "OrderBookValidationError";
|
|
307391
|
+
this.reason = reason;
|
|
307392
|
+
}
|
|
307393
|
+
}
|
|
307394
|
+
function parseSequence(value) {
|
|
307395
|
+
if (value === undefined)
|
|
307396
|
+
return;
|
|
307397
|
+
const parsed = typeof value === "number" ? value : typeof value === "string" && /^\d+$/.test(value) ? Number(value) : Number.NaN;
|
|
307398
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0) {
|
|
307399
|
+
throw new OrderBookValidationError("sequence must be a non-negative integer");
|
|
307400
|
+
}
|
|
307401
|
+
return parsed;
|
|
307402
|
+
}
|
|
307403
|
+
function validateSide(side, levels, depthLimit) {
|
|
307404
|
+
if (levels.length === 0) {
|
|
307405
|
+
throw new OrderBookValidationError(`${side} side is missing`);
|
|
307406
|
+
}
|
|
307407
|
+
const retained = levels.slice(0, depthLimit);
|
|
307408
|
+
for (let index2 = 0;index2 < retained.length; index2 += 1) {
|
|
307409
|
+
const entry = retained[index2];
|
|
307410
|
+
const price = entry?.[0];
|
|
307411
|
+
const amount = entry?.[1];
|
|
307412
|
+
if (!Array.isArray(entry) || entry.length < 2 || price === undefined || amount === undefined || !Number.isFinite(price) || !Number.isFinite(amount) || price <= 0 || amount <= 0) {
|
|
307413
|
+
throw new OrderBookValidationError(`${side} level ${index2} has a non-positive or non-finite price/amount`);
|
|
307228
307414
|
}
|
|
307229
|
-
|
|
307230
|
-
|
|
307231
|
-
|
|
307232
|
-
|
|
307233
|
-
accountSelector: fromSelector,
|
|
307234
|
-
assetSymbol: symbol2,
|
|
307235
|
-
transfer: {
|
|
307236
|
-
eventKind: "internal_transfer",
|
|
307237
|
-
lifecycleAction: "submit_internal_transfer",
|
|
307238
|
-
status: normalized.status ?? "ok",
|
|
307239
|
-
amount: normalized.amount ?? String(transferPayload.amount),
|
|
307240
|
-
network: "internal",
|
|
307241
|
-
externalId: normalized.externalId,
|
|
307242
|
-
payload: { from: fromSelector, to: toSelector, result }
|
|
307415
|
+
if (index2 > 0) {
|
|
307416
|
+
const previous = retained[index2 - 1]?.[0];
|
|
307417
|
+
if (previous === undefined || (side === "bid" ? price >= previous : price <= previous)) {
|
|
307418
|
+
throw new OrderBookValidationError(`${side} levels are not strictly ${side === "bid" ? "descending" : "ascending"}`);
|
|
307243
307419
|
}
|
|
307244
|
-
});
|
|
307245
|
-
ctx.wrappedCallback(null, {
|
|
307246
|
-
proof: verity.proof,
|
|
307247
|
-
result: JSON.stringify(result)
|
|
307248
|
-
});
|
|
307249
|
-
} catch (error48) {
|
|
307250
|
-
safeLogError("InternalTransfer failed", error48);
|
|
307251
|
-
if (error48 instanceof BrokerAccountPreconditionError) {
|
|
307252
|
-
return ctx.wrappedCallback({
|
|
307253
|
-
code: grpc5.status.FAILED_PRECONDITION,
|
|
307254
|
-
message: getErrorMessage(error48)
|
|
307255
|
-
}, null);
|
|
307256
307420
|
}
|
|
307257
|
-
|
|
307258
|
-
|
|
307259
|
-
|
|
307260
|
-
|
|
307261
|
-
|
|
307262
|
-
|
|
307263
|
-
|
|
307264
|
-
|
|
307421
|
+
}
|
|
307422
|
+
return retained.map(([price, amount]) => ({
|
|
307423
|
+
price,
|
|
307424
|
+
amount
|
|
307425
|
+
}));
|
|
307426
|
+
}
|
|
307427
|
+
function normalizedBands(input) {
|
|
307428
|
+
const bands = input ?? [10, 25, 50, 100];
|
|
307429
|
+
for (const band of bands) {
|
|
307430
|
+
if (!Number.isFinite(band) || band <= 0 || !Number.isInteger(band)) {
|
|
307431
|
+
throw new OrderBookValidationError("measurement bands must be positive integer basis points");
|
|
307265
307432
|
}
|
|
307266
|
-
ctx.wrappedCallback({
|
|
307267
|
-
code,
|
|
307268
|
-
message: `InternalTransfer failed: ${sanitizeErrorDetail(error48)}`
|
|
307269
|
-
}, null);
|
|
307270
307433
|
}
|
|
307434
|
+
return [...new Set(bands)].sort((left, right) => left - right);
|
|
307271
307435
|
}
|
|
307272
|
-
|
|
307273
|
-
|
|
307274
|
-
import * as grpc6 from "@grpc/grpc-js";
|
|
307275
|
-
|
|
307276
|
-
// src/helpers/passive-order.ts
|
|
307277
|
-
var PASSIVE_ORDER_ERROR_CODES = {
|
|
307278
|
-
unsupported: "passive_order_unsupported",
|
|
307279
|
-
rejected: "passive_order_rejected",
|
|
307280
|
-
wouldCross: "passive_order_would_cross"
|
|
307281
|
-
};
|
|
307282
|
-
function identifiesWouldCross(message) {
|
|
307283
|
-
const normalized = message.toLowerCase();
|
|
307284
|
-
return normalized.includes("would immediately match and take") || /post[\s-]?only\b.*\bwould\b.*\bimmediately\b.*\b(?:execute|fill|match)/.test(normalized) || /post[\s-]?only\b.*\bwould\b.*\b(?:execute|fill|match)\w*\b.*\bimmediately/.test(normalized);
|
|
307436
|
+
function checksumRow(row) {
|
|
307437
|
+
return { ...row, normalized_row_checksum: sha256Canonical(row) };
|
|
307285
307438
|
}
|
|
307286
|
-
function
|
|
307287
|
-
|
|
307288
|
-
|
|
307439
|
+
function commonEvidenceFields(context2, rawCapture, input) {
|
|
307440
|
+
return {
|
|
307441
|
+
...captureCoreFields(context2, rawCapture),
|
|
307442
|
+
source_time_ms: input.eventTimeMs,
|
|
307443
|
+
received_time_ms: input.receivedTimeMs,
|
|
307444
|
+
snapshot_id: input.snapshotId,
|
|
307445
|
+
construction_mode: "sampled_top_n_snapshot",
|
|
307446
|
+
gap_policy: "record_gap",
|
|
307447
|
+
depth_limit: input.depthLimit,
|
|
307448
|
+
sequence: input.sequence,
|
|
307449
|
+
exact_l2_reconstruction_complete: 0
|
|
307450
|
+
};
|
|
307289
307451
|
}
|
|
307290
|
-
function
|
|
307291
|
-
if (
|
|
307292
|
-
|
|
307452
|
+
function buildCanonicalOrderBookRows(input) {
|
|
307453
|
+
if (!Number.isSafeInteger(input.depthLimit) || input.depthLimit <= 0 || input.depthLimit > 500) {
|
|
307454
|
+
throw new OrderBookValidationError("depth limit must be an integer between 1 and 500");
|
|
307293
307455
|
}
|
|
307294
|
-
if (
|
|
307295
|
-
|
|
307456
|
+
if (input.context.feed !== "ORDERBOOK") {
|
|
307457
|
+
throw new OrderBookValidationError("capture context feed is not ORDERBOOK");
|
|
307296
307458
|
}
|
|
307297
|
-
|
|
307298
|
-
|
|
307299
|
-
return PASSIVE_ORDER_ERROR_CODES.wouldCross;
|
|
307459
|
+
if (input.constructionMode === "exact_l2_reconstruction") {
|
|
307460
|
+
throw new OrderBookValidationError("exact L2 requires a complete continuity proof and is unsupported");
|
|
307300
307461
|
}
|
|
307301
|
-
|
|
307302
|
-
|
|
307462
|
+
let eventTimeMs;
|
|
307463
|
+
let receivedTimeMs;
|
|
307464
|
+
try {
|
|
307465
|
+
eventTimeMs = normalizeTimestampMs(input.snapshot.timestamp, "source_time_ms");
|
|
307466
|
+
receivedTimeMs = normalizeTimestampMs(input.snapshot.receivedTimestamp, "received_time_ms");
|
|
307467
|
+
} catch (error48) {
|
|
307468
|
+
throw new OrderBookValidationError(error48 instanceof Error ? error48.message : "invalid timestamp");
|
|
307303
307469
|
}
|
|
307304
|
-
|
|
307470
|
+
if (receivedTimeMs < eventTimeMs) {
|
|
307471
|
+
throw new OrderBookValidationError("received timestamp precedes source timestamp");
|
|
307472
|
+
}
|
|
307473
|
+
const bids = validateSide("bid", input.snapshot.bids, input.depthLimit);
|
|
307474
|
+
const asks = validateSide("ask", input.snapshot.asks, input.depthLimit);
|
|
307475
|
+
const bestBid = bids[0];
|
|
307476
|
+
const bestAsk = asks[0];
|
|
307477
|
+
if (bestBid.price >= bestAsk.price) {
|
|
307478
|
+
throw new OrderBookValidationError("book is crossed or locked");
|
|
307479
|
+
}
|
|
307480
|
+
const sequence = parseSequence(input.snapshot.sequence);
|
|
307481
|
+
const midPrice = (bestBid.price + bestAsk.price) / 2;
|
|
307482
|
+
const spread2 = bestAsk.price - bestBid.price;
|
|
307483
|
+
const spreadBps = spread2 / midPrice * 1e4;
|
|
307484
|
+
const snapshotId = sha256Canonical({
|
|
307485
|
+
exchange: input.context.exchange.trim().toLowerCase(),
|
|
307486
|
+
trading_pair: input.context.symbol.trim().replace("/", "-"),
|
|
307487
|
+
source_time_ms: eventTimeMs,
|
|
307488
|
+
sequence,
|
|
307489
|
+
depth_limit: input.depthLimit,
|
|
307490
|
+
bids,
|
|
307491
|
+
asks,
|
|
307492
|
+
schema_version: input.context.schemaVersion
|
|
307493
|
+
});
|
|
307494
|
+
const common = commonEvidenceFields(input.context, input.rawCapture, {
|
|
307495
|
+
snapshotId,
|
|
307496
|
+
sequence,
|
|
307497
|
+
depthLimit: input.depthLimit,
|
|
307498
|
+
eventTimeMs,
|
|
307499
|
+
receivedTimeMs
|
|
307500
|
+
});
|
|
307501
|
+
const levels = [
|
|
307502
|
+
["bid", bids],
|
|
307503
|
+
["ask", asks]
|
|
307504
|
+
].flatMap(([side, sideLevels]) => sideLevels.map(({ price, amount }, levelIndex) => {
|
|
307505
|
+
const row = checksumRow({
|
|
307506
|
+
...common,
|
|
307507
|
+
side,
|
|
307508
|
+
level_index: levelIndex,
|
|
307509
|
+
price,
|
|
307510
|
+
amount,
|
|
307511
|
+
notional: price * amount,
|
|
307512
|
+
mid_price: midPrice,
|
|
307513
|
+
spread_from_mid_bps: Math.abs((price - midPrice) / midPrice) * 1e4
|
|
307514
|
+
});
|
|
307515
|
+
return { table: "market_data.cex_order_book_levels", row };
|
|
307516
|
+
}));
|
|
307517
|
+
const bands = normalizedBands(input.measurementBandsBps);
|
|
307518
|
+
const bidDepth = bands.map((band) => {
|
|
307519
|
+
const minimumPrice = bestBid.price * (1 - band / 1e4);
|
|
307520
|
+
return bids.filter(({ price }) => price >= minimumPrice).reduce((sum3, { amount }) => sum3 + amount, 0);
|
|
307521
|
+
});
|
|
307522
|
+
const askDepth = bands.map((band) => {
|
|
307523
|
+
const maximumPrice = bestAsk.price * (1 + band / 1e4);
|
|
307524
|
+
return asks.filter(({ price }) => price <= maximumPrice).reduce((sum3, { amount }) => sum3 + amount, 0);
|
|
307525
|
+
});
|
|
307526
|
+
const summaryRow = checksumRow({
|
|
307527
|
+
...common,
|
|
307528
|
+
best_bid: bestBid.price,
|
|
307529
|
+
best_ask: bestAsk.price,
|
|
307530
|
+
best_bid_amount: bestBid.amount,
|
|
307531
|
+
best_ask_amount: bestAsk.amount,
|
|
307532
|
+
mid_price: midPrice,
|
|
307533
|
+
spread: spread2,
|
|
307534
|
+
spread_bps: spreadBps,
|
|
307535
|
+
staleness_ms: receivedTimeMs - eventTimeMs,
|
|
307536
|
+
bid_level_count: bids.length,
|
|
307537
|
+
ask_level_count: asks.length,
|
|
307538
|
+
measurement_bands_bps: bands,
|
|
307539
|
+
bid_depth_by_band: bidDepth,
|
|
307540
|
+
ask_depth_by_band: askDepth
|
|
307541
|
+
});
|
|
307542
|
+
return {
|
|
307543
|
+
snapshotId,
|
|
307544
|
+
levels,
|
|
307545
|
+
summary: {
|
|
307546
|
+
table: "market_data.cex_order_book_depth_summary",
|
|
307547
|
+
row: summaryRow
|
|
307548
|
+
}
|
|
307549
|
+
};
|
|
307305
307550
|
}
|
|
307306
307551
|
|
|
307307
|
-
// src/
|
|
307308
|
-
|
|
307309
|
-
const
|
|
307310
|
-
|
|
307311
|
-
|
|
307312
|
-
|
|
307313
|
-
|
|
307314
|
-
|
|
307315
|
-
|
|
307316
|
-
|
|
307552
|
+
// src/helpers/market-data-archive/capture-context.ts
|
|
307553
|
+
function createMarketCaptureContext(input) {
|
|
307554
|
+
const environment = input.environment ?? "development";
|
|
307555
|
+
const deploymentId = input.deploymentId.trim();
|
|
307556
|
+
if (!deploymentId)
|
|
307557
|
+
throw new Error("deployment_id must not be empty");
|
|
307558
|
+
const configuredBundle = input.captureBundleId?.trim();
|
|
307559
|
+
if (environment === "production" && !configuredBundle) {
|
|
307560
|
+
throw new Error("capture_bundle_id is required for production market capture");
|
|
307561
|
+
}
|
|
307562
|
+
const exchange = input.exchange.trim().toLowerCase();
|
|
307563
|
+
const symbol2 = input.symbol.trim();
|
|
307564
|
+
if (!exchange || !symbol2) {
|
|
307565
|
+
throw new Error("exchange and symbol are required for market capture");
|
|
307566
|
+
}
|
|
307567
|
+
return {
|
|
307568
|
+
source: input.source,
|
|
307569
|
+
deploymentId,
|
|
307570
|
+
captureBundleId: configuredBundle ?? `development:${deploymentId}`,
|
|
307571
|
+
exchange,
|
|
307317
307572
|
symbol: symbol2,
|
|
307318
|
-
|
|
307319
|
-
|
|
307320
|
-
|
|
307321
|
-
|
|
307322
|
-
|
|
307323
|
-
|
|
307324
|
-
|
|
307325
|
-
|
|
307326
|
-
|
|
307327
|
-
}
|
|
307328
|
-
|
|
307329
|
-
|
|
307330
|
-
|
|
307331
|
-
|
|
307332
|
-
|
|
307333
|
-
if (isPassiveOrder && orderValue.orderType !== "limit") {
|
|
307334
|
-
return ctx.wrappedCallback({
|
|
307335
|
-
code: grpc6.status.INVALID_ARGUMENT,
|
|
307336
|
-
message: "ValidationError: passive_only order intent requires a limit order"
|
|
307337
|
-
}, null);
|
|
307573
|
+
assetType: input.assetType,
|
|
307574
|
+
feed: input.feed,
|
|
307575
|
+
provider: input.provider?.trim() || `ccxt:${exchange}`,
|
|
307576
|
+
sourceMode: input.sourceMode,
|
|
307577
|
+
schemaVersion: MARKET_CAPTURE_SCHEMA_VERSION,
|
|
307578
|
+
checksumAlgorithm: CHECKSUM_ALGORITHM,
|
|
307579
|
+
provenanceComplete: true,
|
|
307580
|
+
timeframe: input.timeframe,
|
|
307581
|
+
accountSelector: input.accountSelector
|
|
307582
|
+
};
|
|
307583
|
+
}
|
|
307584
|
+
function captureEnvironmentFromEnv(value = process.env.CEX_BROKER_MARKET_CAPTURE_ENVIRONMENT) {
|
|
307585
|
+
const environment = value?.trim() || "development";
|
|
307586
|
+
if (environment !== "development" && environment !== "production") {
|
|
307587
|
+
throw new Error("CEX_BROKER_MARKET_CAPTURE_ENVIRONMENT must be development or production");
|
|
307338
307588
|
}
|
|
307339
|
-
|
|
307340
|
-
|
|
307341
|
-
|
|
307342
|
-
|
|
307343
|
-
|
|
307344
|
-
|
|
307589
|
+
return environment;
|
|
307590
|
+
}
|
|
307591
|
+
|
|
307592
|
+
// src/helpers/market-data-archive/ohlcv-bar-tracker.ts
|
|
307593
|
+
function isFiniteNumber(value) {
|
|
307594
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
307595
|
+
}
|
|
307596
|
+
function parseOhlcvBar(value) {
|
|
307597
|
+
if (!Array.isArray(value) || value.length < 6) {
|
|
307598
|
+
return null;
|
|
307599
|
+
}
|
|
307600
|
+
const [openTimeMs, open, high, low, close, volume, quoteVolume] = value;
|
|
307601
|
+
if (!isFiniteNumber(openTimeMs) || !isFiniteNumber(open) || !isFiniteNumber(high) || !isFiniteNumber(low) || !isFiniteNumber(close) || !isFiniteNumber(volume)) {
|
|
307602
|
+
return null;
|
|
307603
|
+
}
|
|
307604
|
+
const bar = {
|
|
307605
|
+
openTimeMs,
|
|
307606
|
+
open,
|
|
307607
|
+
high,
|
|
307608
|
+
low,
|
|
307609
|
+
close,
|
|
307610
|
+
volume
|
|
307345
307611
|
};
|
|
307346
|
-
|
|
307347
|
-
|
|
307348
|
-
|
|
307349
|
-
|
|
307350
|
-
|
|
307351
|
-
|
|
307352
|
-
|
|
307353
|
-
|
|
307354
|
-
|
|
307612
|
+
if (isFiniteNumber(quoteVolume)) {
|
|
307613
|
+
bar.quoteVolume = quoteVolume;
|
|
307614
|
+
}
|
|
307615
|
+
return bar;
|
|
307616
|
+
}
|
|
307617
|
+
function extractOhlcvBars(payload) {
|
|
307618
|
+
if (!Array.isArray(payload) || payload.length === 0) {
|
|
307619
|
+
return [];
|
|
307620
|
+
}
|
|
307621
|
+
const rawBars = Array.isArray(payload[0]) ? payload : [payload];
|
|
307622
|
+
const byOpenTime = new Map;
|
|
307623
|
+
for (const entry of rawBars) {
|
|
307624
|
+
const bar = parseOhlcvBar(entry);
|
|
307625
|
+
if (bar) {
|
|
307626
|
+
byOpenTime.set(bar.openTimeMs, bar);
|
|
307355
307627
|
}
|
|
307356
|
-
|
|
307357
|
-
|
|
307358
|
-
|
|
307359
|
-
|
|
307360
|
-
|
|
307361
|
-
|
|
307628
|
+
}
|
|
307629
|
+
return [...byOpenTime.values()].sort((a, b2) => a.openTimeMs - b2.openTimeMs);
|
|
307630
|
+
}
|
|
307631
|
+
class OhlcvBarTracker {
|
|
307632
|
+
lastOpenTimeMs = null;
|
|
307633
|
+
lastBar = null;
|
|
307634
|
+
process(payload, brokerVersion) {
|
|
307635
|
+
const bars = extractOhlcvBars(payload);
|
|
307636
|
+
if (bars.length === 0) {
|
|
307637
|
+
return [];
|
|
307362
307638
|
}
|
|
307363
|
-
|
|
307364
|
-
|
|
307365
|
-
|
|
307366
|
-
requestedQuantity: resolution.amountBase ?? orderValue.amount
|
|
307367
|
-
};
|
|
307368
|
-
if (selectedBrokerAccount?.label) {
|
|
307369
|
-
orderActivityTracker?.record(cex3, selectedBrokerAccount.label, resolution.symbol);
|
|
307639
|
+
if (bars.length === 1) {
|
|
307640
|
+
const [bar] = bars;
|
|
307641
|
+
return bar ? this.processSingleBar(bar, brokerVersion) : [];
|
|
307370
307642
|
}
|
|
307371
|
-
|
|
307372
|
-
|
|
307373
|
-
|
|
307374
|
-
|
|
307375
|
-
|
|
307376
|
-
|
|
307377
|
-
|
|
307378
|
-
|
|
307379
|
-
|
|
307380
|
-
|
|
307381
|
-
|
|
307382
|
-
|
|
307383
|
-
|
|
307384
|
-
|
|
307385
|
-
|
|
307386
|
-
|
|
307387
|
-
|
|
307388
|
-
|
|
307389
|
-
side: resolvedOrderTelemetry.side,
|
|
307390
|
-
orderType: orderValue.orderType,
|
|
307391
|
-
requestedQuantity: resolvedOrderTelemetry.requestedQuantity,
|
|
307392
|
-
requestedNotional: orderValue.amount * orderValue.price,
|
|
307393
|
-
orderAuthor: orderValue.orderAuthor,
|
|
307394
|
-
brokerObservedTimestamp: submissionTimestamp,
|
|
307395
|
-
...telemetryIds
|
|
307396
|
-
};
|
|
307397
|
-
emitOrderExecutionTelemetryInBackground(otelMetrics, createOrderContext, order);
|
|
307398
|
-
archiveOrderExecutionInBackground(brokerArchiver, createOrderContext, order, undefined, { marketMetadataHash });
|
|
307399
|
-
ctx.wrappedCallback(null, {
|
|
307400
|
-
result: JSON.stringify({
|
|
307401
|
-
...order,
|
|
307402
|
-
...isPassiveOrder && {
|
|
307403
|
-
passivePlacementOutcome: "accepted_passive"
|
|
307404
|
-
}
|
|
307405
|
-
})
|
|
307643
|
+
return this.processBatch(bars, brokerVersion);
|
|
307644
|
+
}
|
|
307645
|
+
processSingleBar(currentBar, brokerVersion) {
|
|
307646
|
+
if (this.lastOpenTimeMs !== null && currentBar.openTimeMs < this.lastOpenTimeMs) {
|
|
307647
|
+
return [];
|
|
307648
|
+
}
|
|
307649
|
+
const candidates = [];
|
|
307650
|
+
if (this.lastOpenTimeMs !== null && this.lastBar !== null && currentBar.openTimeMs !== this.lastOpenTimeMs) {
|
|
307651
|
+
candidates.push({
|
|
307652
|
+
bar: this.lastBar,
|
|
307653
|
+
isClosed: true,
|
|
307654
|
+
brokerVersion
|
|
307655
|
+
});
|
|
307656
|
+
}
|
|
307657
|
+
candidates.push({
|
|
307658
|
+
bar: currentBar,
|
|
307659
|
+
isClosed: false,
|
|
307660
|
+
brokerVersion
|
|
307406
307661
|
});
|
|
307407
|
-
|
|
307408
|
-
|
|
307409
|
-
|
|
307410
|
-
|
|
307411
|
-
|
|
307412
|
-
|
|
307413
|
-
|
|
307414
|
-
|
|
307415
|
-
|
|
307416
|
-
|
|
307417
|
-
|
|
307418
|
-
|
|
307419
|
-
|
|
307420
|
-
|
|
307421
|
-
|
|
307422
|
-
|
|
307423
|
-
|
|
307424
|
-
if (
|
|
307425
|
-
|
|
307426
|
-
|
|
307427
|
-
|
|
307428
|
-
|
|
307662
|
+
this.lastOpenTimeMs = currentBar.openTimeMs;
|
|
307663
|
+
this.lastBar = currentBar;
|
|
307664
|
+
return candidates;
|
|
307665
|
+
}
|
|
307666
|
+
processBatch(bars, brokerVersion) {
|
|
307667
|
+
const firstBar = bars[0];
|
|
307668
|
+
const lastBar = bars[bars.length - 1];
|
|
307669
|
+
if (!firstBar || !lastBar) {
|
|
307670
|
+
return [];
|
|
307671
|
+
}
|
|
307672
|
+
const lastOpenTimeMs = this.lastOpenTimeMs;
|
|
307673
|
+
if (lastOpenTimeMs !== null && lastBar.openTimeMs < lastOpenTimeMs) {
|
|
307674
|
+
return [];
|
|
307675
|
+
}
|
|
307676
|
+
const barsToProcess = lastOpenTimeMs === null ? bars : bars.filter((bar) => bar.openTimeMs >= lastOpenTimeMs);
|
|
307677
|
+
const firstBarToProcess = barsToProcess[0];
|
|
307678
|
+
const lastBarToProcess = barsToProcess[barsToProcess.length - 1];
|
|
307679
|
+
if (!firstBarToProcess || !lastBarToProcess) {
|
|
307680
|
+
return [];
|
|
307681
|
+
}
|
|
307682
|
+
const candidates = [];
|
|
307683
|
+
if (this.lastBar !== null && lastOpenTimeMs !== null && lastOpenTimeMs < firstBarToProcess.openTimeMs) {
|
|
307684
|
+
candidates.push({
|
|
307685
|
+
bar: this.lastBar,
|
|
307686
|
+
isClosed: true,
|
|
307687
|
+
brokerVersion
|
|
307429
307688
|
});
|
|
307430
307689
|
}
|
|
307431
|
-
|
|
307432
|
-
|
|
307433
|
-
|
|
307434
|
-
|
|
307690
|
+
for (let index2 = 0;index2 < barsToProcess.length - 1; index2 += 1) {
|
|
307691
|
+
const bar = barsToProcess[index2];
|
|
307692
|
+
if (bar) {
|
|
307693
|
+
candidates.push({
|
|
307694
|
+
bar,
|
|
307695
|
+
isClosed: true,
|
|
307696
|
+
brokerVersion
|
|
307697
|
+
});
|
|
307698
|
+
}
|
|
307699
|
+
}
|
|
307700
|
+
candidates.push({
|
|
307701
|
+
bar: lastBarToProcess,
|
|
307702
|
+
isClosed: false,
|
|
307703
|
+
brokerVersion
|
|
307704
|
+
});
|
|
307705
|
+
this.lastOpenTimeMs = lastBarToProcess.openTimeMs;
|
|
307706
|
+
this.lastBar = lastBarToProcess;
|
|
307707
|
+
return candidates;
|
|
307435
307708
|
}
|
|
307436
307709
|
}
|
|
307437
|
-
|
|
307438
|
-
|
|
307439
|
-
|
|
307440
|
-
|
|
307441
|
-
|
|
307442
|
-
|
|
307443
|
-
|
|
307444
|
-
|
|
307445
|
-
|
|
307446
|
-
|
|
307447
|
-
|
|
307448
|
-
|
|
307449
|
-
verity,
|
|
307450
|
-
applyVerityToBroker,
|
|
307451
|
-
useVerity,
|
|
307452
|
-
verityProverUrl,
|
|
307453
|
-
otelMetrics,
|
|
307454
|
-
brokerArchiver,
|
|
307455
|
-
orderActivityTracker
|
|
307456
|
-
} = ctx;
|
|
307457
|
-
const verityProof = verity.proof;
|
|
307458
|
-
const getOrderValue = parsePayloadForAction(ctx, GetOrderDetailsPayloadSchema);
|
|
307459
|
-
if (getOrderValue === null)
|
|
307460
|
-
return;
|
|
307461
|
-
try {
|
|
307462
|
-
if (!broker) {
|
|
307463
|
-
return ctx.wrappedCallback({
|
|
307464
|
-
code: grpc6.status.INVALID_ARGUMENT,
|
|
307465
|
-
message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
|
|
307466
|
-
}, null);
|
|
307467
|
-
}
|
|
307468
|
-
const orderDetails = await broker.fetchOrder(getOrderValue.orderId, symbol2, { ...getOrderValue.params });
|
|
307469
|
-
if (selectedBrokerAccount?.label && symbol2) {
|
|
307470
|
-
orderActivityTracker?.record(cex3, selectedBrokerAccount.label, symbol2);
|
|
307471
|
-
}
|
|
307472
|
-
const getOrderContext = {
|
|
307473
|
-
action: "GetOrderDetails",
|
|
307474
|
-
cex: cex3,
|
|
307475
|
-
accountLabel: selectedBrokerAccount?.label,
|
|
307476
|
-
symbol: symbol2,
|
|
307477
|
-
...extractOrderTelemetryIds(getOrderValue.params)
|
|
307478
|
-
};
|
|
307479
|
-
emitOrderExecutionTelemetryInBackground(otelMetrics, getOrderContext, orderDetails);
|
|
307480
|
-
archiveOrderExecutionInBackground(brokerArchiver, getOrderContext, orderDetails);
|
|
307481
|
-
ctx.wrappedCallback(null, {
|
|
307482
|
-
result: JSON.stringify({
|
|
307483
|
-
orderId: orderDetails.id,
|
|
307484
|
-
status: orderDetails.status,
|
|
307485
|
-
amount: orderDetails.amount,
|
|
307486
|
-
filled: orderDetails.filled,
|
|
307487
|
-
remaining: orderDetails.remaining,
|
|
307488
|
-
symbol: orderDetails.symbol,
|
|
307489
|
-
side: orderDetails.side,
|
|
307490
|
-
price: orderDetails.price
|
|
307491
|
-
})
|
|
307492
|
-
});
|
|
307493
|
-
} catch (error48) {
|
|
307494
|
-
safeLogError(`Error fetching order details from ${cex3}`, error48);
|
|
307495
|
-
const failedGetOrderContext = {
|
|
307496
|
-
action: "GetOrderDetails",
|
|
307497
|
-
cex: cex3,
|
|
307498
|
-
accountLabel: selectedBrokerAccount?.label,
|
|
307499
|
-
symbol: symbol2,
|
|
307500
|
-
...extractOrderTelemetryIds(getOrderValue.params)
|
|
307501
|
-
};
|
|
307502
|
-
emitOrderExecutionTelemetryInBackground(otelMetrics, failedGetOrderContext, undefined, error48);
|
|
307503
|
-
archiveOrderExecutionInBackground(brokerArchiver, failedGetOrderContext, undefined, error48);
|
|
307504
|
-
ctx.wrappedCallback({
|
|
307505
|
-
code: grpc6.status.INTERNAL,
|
|
307506
|
-
message: `Failed to fetch order details from ${cex3}: ${sanitizeErrorDetail(error48)}`
|
|
307507
|
-
}, null);
|
|
307710
|
+
|
|
307711
|
+
// src/helpers/market-data-archive/orderbook-depth.ts
|
|
307712
|
+
var DEFAULT_ORDERBOOK_ARCHIVE_DEPTH_LIMIT = 25;
|
|
307713
|
+
var MAX_ORDERBOOK_ARCHIVE_DEPTH_LIMIT = 500;
|
|
307714
|
+
function getOrderbookArchiveDepthLimit() {
|
|
307715
|
+
const raw = process.env.CEX_BROKER_ORDERBOOK_ARCHIVE_DEPTH_LIMIT;
|
|
307716
|
+
if (!raw) {
|
|
307717
|
+
return DEFAULT_ORDERBOOK_ARCHIVE_DEPTH_LIMIT;
|
|
307718
|
+
}
|
|
307719
|
+
const parsed = Number.parseInt(raw, 10);
|
|
307720
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
307721
|
+
return DEFAULT_ORDERBOOK_ARCHIVE_DEPTH_LIMIT;
|
|
307508
307722
|
}
|
|
307723
|
+
return Math.min(parsed, MAX_ORDERBOOK_ARCHIVE_DEPTH_LIMIT);
|
|
307509
307724
|
}
|
|
307510
|
-
|
|
307511
|
-
|
|
307512
|
-
|
|
307513
|
-
|
|
307514
|
-
|
|
307515
|
-
|
|
307516
|
-
|
|
307517
|
-
|
|
307518
|
-
|
|
307519
|
-
|
|
307520
|
-
|
|
307521
|
-
|
|
307522
|
-
|
|
307523
|
-
|
|
307524
|
-
|
|
307525
|
-
|
|
307526
|
-
|
|
307527
|
-
|
|
307528
|
-
|
|
307529
|
-
|
|
307530
|
-
|
|
307531
|
-
|
|
307532
|
-
|
|
307533
|
-
|
|
307534
|
-
|
|
307535
|
-
if (!broker) {
|
|
307536
|
-
return ctx.wrappedCallback({
|
|
307537
|
-
code: grpc6.status.INVALID_ARGUMENT,
|
|
307538
|
-
message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
|
|
307539
|
-
}, null);
|
|
307725
|
+
|
|
307726
|
+
// src/helpers/market-data-archive/orderbook-sampler.ts
|
|
307727
|
+
var DEFAULT_ORDERBOOK_INTERVAL_MS = 1000;
|
|
307728
|
+
function getOrderbookIntervalMs() {
|
|
307729
|
+
const raw = process.env.CEX_BROKER_ORDERBOOK_INTERVAL_MS ?? process.env.CEX_BROKER_ORDERBOOK_TOB_INTERVAL_MS;
|
|
307730
|
+
if (!raw) {
|
|
307731
|
+
return DEFAULT_ORDERBOOK_INTERVAL_MS;
|
|
307732
|
+
}
|
|
307733
|
+
const parsed = Number.parseInt(raw, 10);
|
|
307734
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_ORDERBOOK_INTERVAL_MS;
|
|
307735
|
+
}
|
|
307736
|
+
function isMarketArchiveEnabled() {
|
|
307737
|
+
return process.env.CEX_BROKER_MARKET_ARCHIVE_ENABLED !== "false";
|
|
307738
|
+
}
|
|
307739
|
+
|
|
307740
|
+
class OrderbookSampler {
|
|
307741
|
+
intervalMs;
|
|
307742
|
+
lastEmitMs = null;
|
|
307743
|
+
constructor(intervalMs = getOrderbookIntervalMs()) {
|
|
307744
|
+
this.intervalMs = intervalMs;
|
|
307745
|
+
}
|
|
307746
|
+
shouldEmit(nowMs = Date.now()) {
|
|
307747
|
+
if (this.lastEmitMs !== null && nowMs < this.lastEmitMs) {
|
|
307748
|
+
this.lastEmitMs = nowMs;
|
|
307749
|
+
return true;
|
|
307540
307750
|
}
|
|
307541
|
-
|
|
307542
|
-
|
|
307543
|
-
cex: cex3,
|
|
307544
|
-
accountLabel: selectedBrokerAccount?.label,
|
|
307545
|
-
symbol: symbol2,
|
|
307546
|
-
...extractOrderTelemetryIds(cancelOrderValue.params)
|
|
307547
|
-
};
|
|
307548
|
-
const cancelledOrder = await broker.cancelOrder(cancelOrderValue.orderId, symbol2, cancelOrderValue.params ?? {});
|
|
307549
|
-
if (selectedBrokerAccount?.label && symbol2) {
|
|
307550
|
-
orderActivityTracker?.record(cex3, selectedBrokerAccount.label, symbol2);
|
|
307751
|
+
if (this.lastEmitMs !== null && nowMs - this.lastEmitMs < this.intervalMs) {
|
|
307752
|
+
return false;
|
|
307551
307753
|
}
|
|
307552
|
-
|
|
307553
|
-
|
|
307554
|
-
ctx.wrappedCallback(null, {
|
|
307555
|
-
result: JSON.stringify({ ...cancelledOrder })
|
|
307556
|
-
});
|
|
307557
|
-
} catch (error48) {
|
|
307558
|
-
safeLogError(`Error cancelling order from ${cex3}`, error48);
|
|
307559
|
-
const failedCancelContext = {
|
|
307560
|
-
action: "CancelOrder",
|
|
307561
|
-
cex: cex3,
|
|
307562
|
-
accountLabel: selectedBrokerAccount?.label,
|
|
307563
|
-
symbol: symbol2,
|
|
307564
|
-
...extractOrderTelemetryIds(cancelOrderValue.params)
|
|
307565
|
-
};
|
|
307566
|
-
emitOrderExecutionTelemetryInBackground(otelMetrics, failedCancelContext, undefined, error48);
|
|
307567
|
-
archiveOrderExecutionInBackground(brokerArchiver, failedCancelContext, undefined, error48);
|
|
307568
|
-
ctx.wrappedCallback({
|
|
307569
|
-
code: grpc6.status.INTERNAL,
|
|
307570
|
-
message: `Failed to cancel order from ${cex3}: ${sanitizeErrorDetail(error48)}`
|
|
307571
|
-
}, null);
|
|
307754
|
+
this.lastEmitMs = nowMs;
|
|
307755
|
+
return true;
|
|
307572
307756
|
}
|
|
307573
307757
|
}
|
|
307574
|
-
async function handleOrders(ctx) {
|
|
307575
|
-
if (ctx.action === Action.CreateOrder)
|
|
307576
|
-
return handleCreateOrder(ctx);
|
|
307577
|
-
if (ctx.action === Action.GetOrderDetails)
|
|
307578
|
-
return handleGetOrderDetails(ctx);
|
|
307579
|
-
if (ctx.action === Action.CancelOrder)
|
|
307580
|
-
return handleCancelOrder(ctx);
|
|
307581
|
-
}
|
|
307582
307758
|
|
|
307583
|
-
// src/
|
|
307584
|
-
|
|
307585
|
-
|
|
307586
|
-
|
|
307587
|
-
|
|
307588
|
-
|
|
307589
|
-
|
|
307590
|
-
brokers,
|
|
307591
|
-
metadata,
|
|
307592
|
-
normalizedCex,
|
|
307593
|
-
cex: cex3,
|
|
307594
|
-
symbol: symbol2,
|
|
307595
|
-
selectedBrokerAccount,
|
|
307596
|
-
broker,
|
|
307597
|
-
verity,
|
|
307598
|
-
applyVerityToBroker,
|
|
307599
|
-
useVerity,
|
|
307600
|
-
verityProverUrl,
|
|
307601
|
-
otelMetrics
|
|
307602
|
-
} = ctx;
|
|
307603
|
-
const verityProof = verity.proof;
|
|
307604
|
-
if (!symbol2) {
|
|
307605
|
-
return ctx.wrappedCallback({
|
|
307606
|
-
code: grpc7.status.INVALID_ARGUMENT,
|
|
307607
|
-
message: `ValidationError: Symbol required`
|
|
307608
|
-
}, null);
|
|
307759
|
+
// src/helpers/market-data-archive/parse-stream.ts
|
|
307760
|
+
function isFiniteNumber2(value) {
|
|
307761
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
307762
|
+
}
|
|
307763
|
+
function toNumber2(value) {
|
|
307764
|
+
if (isFiniteNumber2(value)) {
|
|
307765
|
+
return value;
|
|
307609
307766
|
}
|
|
307610
|
-
|
|
307611
|
-
const
|
|
307612
|
-
|
|
307613
|
-
|
|
307614
|
-
return ctx.wrappedCallback({
|
|
307615
|
-
code: grpc7.status.NOT_FOUND,
|
|
307616
|
-
message: `venue_discovery_unavailable: currency not found for ${assetCode}`
|
|
307617
|
-
}, null);
|
|
307767
|
+
if (typeof value === "string") {
|
|
307768
|
+
const parsed = Number.parseFloat(value);
|
|
307769
|
+
if (Number.isFinite(parsed)) {
|
|
307770
|
+
return parsed;
|
|
307618
307771
|
}
|
|
307619
|
-
const networkEvidence = buildTransferNetworkEvidence(currencyInfo);
|
|
307620
|
-
ctx.wrappedCallback(null, {
|
|
307621
|
-
proof: ctx.verity.proof,
|
|
307622
|
-
result: JSON.stringify({
|
|
307623
|
-
...currencyInfo,
|
|
307624
|
-
exchange: normalizedCex,
|
|
307625
|
-
asset: assetCode,
|
|
307626
|
-
code: currencyInfo.code ?? assetCode,
|
|
307627
|
-
id: currencyInfo.id ?? null,
|
|
307628
|
-
networks: networkEvidence.networks,
|
|
307629
|
-
networkAliases: networkEvidence.aliases,
|
|
307630
|
-
raw: currencyInfo
|
|
307631
|
-
})
|
|
307632
|
-
});
|
|
307633
|
-
} catch (error48) {
|
|
307634
|
-
safeLogError(`Error fetching currency ${symbol2} from ${cex3}`, error48);
|
|
307635
|
-
const message = getErrorMessage(error48);
|
|
307636
|
-
ctx.wrappedCallback({
|
|
307637
|
-
code: stableGrpcErrorCode(message) ?? mapCcxtErrorToGrpcStatus(error48) ?? grpc7.status.INTERNAL,
|
|
307638
|
-
message: message.startsWith("venue_discovery_unavailable:") ? message : `venue_discovery_unavailable: ${message}`
|
|
307639
|
-
}, null);
|
|
307640
307772
|
}
|
|
307773
|
+
return;
|
|
307641
307774
|
}
|
|
307642
|
-
|
|
307643
|
-
|
|
307644
|
-
|
|
307645
|
-
|
|
307646
|
-
|
|
307647
|
-
|
|
307648
|
-
|
|
307649
|
-
|
|
307650
|
-
|
|
307651
|
-
|
|
307652
|
-
|
|
307653
|
-
|
|
307654
|
-
|
|
307655
|
-
|
|
307656
|
-
|
|
307657
|
-
|
|
307658
|
-
|
|
307659
|
-
|
|
307660
|
-
|
|
307775
|
+
function toStringId(value) {
|
|
307776
|
+
if (typeof value === "string" && value.trim()) {
|
|
307777
|
+
return value.trim();
|
|
307778
|
+
}
|
|
307779
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
307780
|
+
return String(value);
|
|
307781
|
+
}
|
|
307782
|
+
return;
|
|
307783
|
+
}
|
|
307784
|
+
function scalarTimestampMs(value, fallbackMs) {
|
|
307785
|
+
const numeric = toNumber2(value);
|
|
307786
|
+
if (numeric !== undefined) {
|
|
307787
|
+
return numeric < 1000000000000 ? numeric * 1000 : numeric;
|
|
307788
|
+
}
|
|
307789
|
+
if (typeof value === "string") {
|
|
307790
|
+
const parsed = Date.parse(value);
|
|
307791
|
+
if (Number.isFinite(parsed)) {
|
|
307792
|
+
return parsed;
|
|
307793
|
+
}
|
|
307794
|
+
}
|
|
307795
|
+
return fallbackMs;
|
|
307796
|
+
}
|
|
307797
|
+
function parseTrade(value, fallbackMs = Date.now()) {
|
|
307798
|
+
const record2 = asRecord(value);
|
|
307799
|
+
if (!record2) {
|
|
307800
|
+
return null;
|
|
307801
|
+
}
|
|
307802
|
+
const tradeId = toStringId(record2.id);
|
|
307803
|
+
const price = toNumber2(record2.price);
|
|
307804
|
+
const amount = toNumber2(record2.amount);
|
|
307805
|
+
const side = typeof record2.side === "string" ? record2.side.toLowerCase() : undefined;
|
|
307806
|
+
if (!tradeId || price === undefined || amount === undefined || !side) {
|
|
307807
|
+
return null;
|
|
307808
|
+
}
|
|
307809
|
+
const parsed = {
|
|
307810
|
+
tradeId,
|
|
307811
|
+
eventTimeMs: scalarTimestampMs(record2.timestamp, fallbackMs),
|
|
307812
|
+
side,
|
|
307813
|
+
price,
|
|
307814
|
+
amount
|
|
307815
|
+
};
|
|
307816
|
+
const cost = toNumber2(record2.cost);
|
|
307817
|
+
if (cost !== undefined) {
|
|
307818
|
+
parsed.cost = cost;
|
|
307819
|
+
}
|
|
307820
|
+
if (typeof record2.takerOrMaker === "string") {
|
|
307821
|
+
parsed.takerOrMaker = record2.takerOrMaker;
|
|
307822
|
+
}
|
|
307823
|
+
return parsed;
|
|
307824
|
+
}
|
|
307825
|
+
function extractTrades(payload, fallbackMs = Date.now()) {
|
|
307826
|
+
if (Array.isArray(payload)) {
|
|
307827
|
+
return payload.map((entry) => parseTrade(entry, fallbackMs)).filter((entry) => entry !== null);
|
|
307828
|
+
}
|
|
307829
|
+
const single = parseTrade(payload, fallbackMs);
|
|
307830
|
+
return single ? [single] : [];
|
|
307831
|
+
}
|
|
307832
|
+
function parseTicker(value, fallbackMs) {
|
|
307833
|
+
const record2 = asRecord(value);
|
|
307834
|
+
if (!record2) {
|
|
307835
|
+
return null;
|
|
307836
|
+
}
|
|
307837
|
+
const parsed = {
|
|
307838
|
+
eventTimeMs: scalarTimestampMs(record2.timestamp, fallbackMs)
|
|
307839
|
+
};
|
|
307840
|
+
const fields = [
|
|
307841
|
+
["last", record2.last],
|
|
307842
|
+
["bid", record2.bid],
|
|
307843
|
+
["ask", record2.ask],
|
|
307844
|
+
["high", record2.high],
|
|
307845
|
+
["low", record2.low],
|
|
307846
|
+
["open", record2.open],
|
|
307847
|
+
["close", record2.close],
|
|
307848
|
+
["baseVolume", record2.baseVolume],
|
|
307849
|
+
["quoteVolume", record2.quoteVolume],
|
|
307850
|
+
["change", record2.change],
|
|
307851
|
+
["percentage", record2.percentage]
|
|
307852
|
+
];
|
|
307853
|
+
for (const [key, rawValue] of fields) {
|
|
307854
|
+
const numeric = toNumber2(rawValue);
|
|
307855
|
+
if (numeric !== undefined) {
|
|
307856
|
+
parsed[key] = numeric;
|
|
307857
|
+
}
|
|
307858
|
+
}
|
|
307859
|
+
return parsed;
|
|
307860
|
+
}
|
|
307861
|
+
|
|
307862
|
+
// src/helpers/market-data-archive/rows.ts
|
|
307863
|
+
function compactUndefined3(record2) {
|
|
307864
|
+
return Object.fromEntries(Object.entries(record2).filter(([, value]) => value !== undefined));
|
|
307865
|
+
}
|
|
307866
|
+
function withNormalizedChecksum(record2) {
|
|
307867
|
+
const compact = compactUndefined3(record2);
|
|
307868
|
+
return {
|
|
307869
|
+
...compact,
|
|
307870
|
+
normalized_row_checksum: sha256Canonical(compact)
|
|
307871
|
+
};
|
|
307872
|
+
}
|
|
307873
|
+
function buildCanonicalCexStreamEventRow(context2, rawCapture) {
|
|
307874
|
+
const row = withNormalizedChecksum({
|
|
307875
|
+
...captureCoreFields(context2, rawCapture),
|
|
307876
|
+
stream_type: context2.feed,
|
|
307877
|
+
event_time_ms: rawCapture.eventTimeMs,
|
|
307878
|
+
payload_encoding: "canonical_json_v1",
|
|
307879
|
+
payload_json: canonicalSerialize(rawCapture.redactedPayload)
|
|
307880
|
+
});
|
|
307881
|
+
return { table: "market_data.cex_stream_events", row };
|
|
307882
|
+
}
|
|
307883
|
+
function buildCanonicalTickerEventRow(context2, rawCapture, ticker) {
|
|
307884
|
+
if (context2.feed !== "TICKER") {
|
|
307885
|
+
throw new Error("Ticker row requires a TICKER capture context");
|
|
307886
|
+
}
|
|
307887
|
+
const row = withNormalizedChecksum({
|
|
307888
|
+
...captureCoreFields(context2, rawCapture),
|
|
307889
|
+
source_time_ms: ticker.eventTimeMs,
|
|
307890
|
+
event_time_ms: ticker.eventTimeMs,
|
|
307891
|
+
last: ticker.last,
|
|
307892
|
+
bid: ticker.bid,
|
|
307893
|
+
ask: ticker.ask,
|
|
307894
|
+
high: ticker.high,
|
|
307895
|
+
low: ticker.low,
|
|
307896
|
+
open: ticker.open,
|
|
307897
|
+
close: ticker.close,
|
|
307898
|
+
base_volume: ticker.baseVolume,
|
|
307899
|
+
quote_volume: ticker.quoteVolume,
|
|
307900
|
+
change: ticker.change,
|
|
307901
|
+
percentage: ticker.percentage
|
|
307902
|
+
});
|
|
307903
|
+
return { table: "market_data.cex_ticker_events", row };
|
|
307904
|
+
}
|
|
307905
|
+
function buildCanonicalTradeRow(context2, rawCapture, trade) {
|
|
307906
|
+
if (context2.feed !== "TRADES") {
|
|
307907
|
+
throw new Error("Trade row requires a TRADES capture context");
|
|
307908
|
+
}
|
|
307909
|
+
const row = withNormalizedChecksum({
|
|
307910
|
+
...captureCoreFields(context2, rawCapture),
|
|
307911
|
+
source_time_ms: trade.eventTimeMs,
|
|
307912
|
+
trade_id: trade.tradeId,
|
|
307913
|
+
event_time_ms: trade.eventTimeMs,
|
|
307914
|
+
side: trade.side,
|
|
307915
|
+
price: trade.price,
|
|
307916
|
+
amount: trade.amount,
|
|
307917
|
+
cost: trade.cost,
|
|
307918
|
+
taker_or_maker: trade.takerOrMaker
|
|
307919
|
+
});
|
|
307920
|
+
return { table: "market_data.cex_trades", row };
|
|
307921
|
+
}
|
|
307922
|
+
function buildCanonicalOhlcvRow(input) {
|
|
307923
|
+
if (input.context.feed !== "OHLCV") {
|
|
307924
|
+
throw new Error("OHLCV row requires an OHLCV capture context");
|
|
307925
|
+
}
|
|
307926
|
+
const row = withNormalizedChecksum({
|
|
307927
|
+
...captureCoreFields(input.context, input.rawCapture),
|
|
307928
|
+
source_time_ms: input.bar.openTimeMs,
|
|
307929
|
+
timeframe: input.context.timeframe ?? "1m",
|
|
307930
|
+
open_time_ms: input.bar.openTimeMs,
|
|
307931
|
+
open: input.bar.open,
|
|
307932
|
+
high: input.bar.high,
|
|
307933
|
+
low: input.bar.low,
|
|
307934
|
+
close: input.bar.close,
|
|
307935
|
+
volume: input.bar.volume,
|
|
307936
|
+
quote_volume: input.bar.quoteVolume,
|
|
307937
|
+
is_closed: input.isClosed ? 1 : 0,
|
|
307938
|
+
broker_version: input.brokerVersion
|
|
307939
|
+
});
|
|
307940
|
+
return { table: "market_data.cex_ohlcv", row };
|
|
307941
|
+
}
|
|
307942
|
+
function buildCexStreamEventRow(input) {
|
|
307943
|
+
const receivedTimeMs = input.receivedTimestamp;
|
|
307944
|
+
const redactedPayload = redactStreamPayload(input.payload);
|
|
307945
|
+
const tags = buildCommonArchiveTags({
|
|
307946
|
+
source: input.source,
|
|
307947
|
+
deploymentId: input.deploymentId,
|
|
307948
|
+
accountSelector: input.accountSelector,
|
|
307949
|
+
exchange: input.exchange,
|
|
307950
|
+
symbol: input.symbol,
|
|
307951
|
+
brokerObservedTimestamp: new Date(receivedTimeMs).toISOString()
|
|
307952
|
+
});
|
|
307953
|
+
return {
|
|
307954
|
+
table: "market_data.cex_stream_events",
|
|
307955
|
+
row: compactUndefined3({
|
|
307956
|
+
...tags,
|
|
307957
|
+
asset_type: input.assetType,
|
|
307958
|
+
stream_type: input.streamType,
|
|
307959
|
+
event_time_ms: input.eventTimeMs ?? receivedTimeMs,
|
|
307960
|
+
received_time_ms: receivedTimeMs,
|
|
307961
|
+
payload_json: JSON.stringify(redactedPayload)
|
|
307962
|
+
})
|
|
307963
|
+
};
|
|
307964
|
+
}
|
|
307965
|
+
|
|
307966
|
+
// src/helpers/market-data-archive/capture.ts
|
|
307967
|
+
async function recordWatchMetric(otelMetrics, metricName, labels) {
|
|
307661
307968
|
try {
|
|
307662
|
-
|
|
307663
|
-
|
|
307969
|
+
await otelMetrics?.recordCounter(metricName, 1, labels);
|
|
307970
|
+
} catch {}
|
|
307971
|
+
}
|
|
307972
|
+
function watchLabels(stream4, input, archiver, feed) {
|
|
307973
|
+
return {
|
|
307974
|
+
stream: stream4,
|
|
307975
|
+
feed,
|
|
307976
|
+
source: archiver?.getSource() ?? "disabled",
|
|
307977
|
+
exchange: input.exchange,
|
|
307978
|
+
symbol: input.symbol
|
|
307979
|
+
};
|
|
307980
|
+
}
|
|
307981
|
+
function resolveCaptureContext(archiver, input, feed, sourceMode) {
|
|
307982
|
+
return createMarketCaptureContext({
|
|
307983
|
+
source: archiver.getSource(),
|
|
307984
|
+
deploymentId: archiver.getDeploymentId(),
|
|
307985
|
+
captureBundleId: process.env.CEX_BROKER_CAPTURE_BUNDLE_ID,
|
|
307986
|
+
exchange: input.exchange,
|
|
307987
|
+
symbol: input.symbol,
|
|
307988
|
+
assetType: input.assetType,
|
|
307989
|
+
feed,
|
|
307990
|
+
provider: `ccxt:${input.exchange.trim().toLowerCase()}`,
|
|
307991
|
+
sourceMode,
|
|
307992
|
+
timeframe: input.timeframe,
|
|
307993
|
+
accountSelector: input.accountSelector,
|
|
307994
|
+
environment: captureEnvironmentFromEnv()
|
|
307995
|
+
});
|
|
307996
|
+
}
|
|
307997
|
+
function archiveOrderbookInBackground(archiver, otelMetrics, input, options) {
|
|
307998
|
+
const labels = watchLabels("orderbook", input, archiver, "ORDERBOOK");
|
|
307999
|
+
recordWatchMetric(otelMetrics, "cex_watch_frames_received_total", labels);
|
|
308000
|
+
if (!isMarketArchiveEnabled() || !archiver?.isEnabled()) {
|
|
308001
|
+
return;
|
|
308002
|
+
}
|
|
308003
|
+
if (options?.sampledOut) {
|
|
308004
|
+
recordWatchMetric(otelMetrics, "cex_watch_frames_sampled_out_total", labels);
|
|
308005
|
+
return;
|
|
308006
|
+
}
|
|
308007
|
+
queueMicrotask(() => {
|
|
308008
|
+
try {
|
|
308009
|
+
const context2 = resolveCaptureContext(archiver, input, "ORDERBOOK", options?.sourceMode ?? "broker_live_sampling_v1");
|
|
308010
|
+
const rawCapture = createRawCapture(context2, {
|
|
308011
|
+
payload: input.snapshot,
|
|
308012
|
+
eventTimeMs: input.snapshot.timestamp,
|
|
308013
|
+
receivedTimeMs: input.snapshot.receivedTimestamp,
|
|
308014
|
+
scope: "ccxt_normalized_object"
|
|
308015
|
+
});
|
|
308016
|
+
const canonical = buildCanonicalOrderBookRows({
|
|
308017
|
+
context: context2,
|
|
308018
|
+
snapshot: input.snapshot,
|
|
308019
|
+
rawCapture,
|
|
308020
|
+
depthLimit: options?.depthLimit ?? getOrderbookArchiveDepthLimit()
|
|
308021
|
+
});
|
|
308022
|
+
archiver.enqueue(buildCanonicalCexStreamEventRow(context2, rawCapture));
|
|
308023
|
+
for (const row of canonical.levels)
|
|
308024
|
+
archiver.enqueue(row);
|
|
308025
|
+
archiver.enqueue(canonical.summary);
|
|
308026
|
+
recordWatchMetric(otelMetrics, "cex_watch_frames_archived_total", labels);
|
|
308027
|
+
} catch (error48) {
|
|
308028
|
+
rethrowArchiveDurabilityError(error48);
|
|
308029
|
+
if (error48 instanceof OrderBookValidationError) {
|
|
308030
|
+
recordWatchMetric(otelMetrics, "cex_watch_frames_invalid_total", {
|
|
308031
|
+
...labels,
|
|
308032
|
+
reason: error48.reason
|
|
308033
|
+
});
|
|
308034
|
+
}
|
|
308035
|
+
log.warn("Failed to archive orderbook snapshot", { error: error48 });
|
|
308036
|
+
}
|
|
308037
|
+
});
|
|
308038
|
+
}
|
|
308039
|
+
function archiveOhlcvInBackground(archiver, otelMetrics, tracker, input) {
|
|
308040
|
+
const labels = watchLabels("ohlcv", input, archiver, "OHLCV");
|
|
308041
|
+
recordWatchMetric(otelMetrics, "cex_watch_frames_received_total", labels);
|
|
308042
|
+
if (!isMarketArchiveEnabled() || !archiver?.isEnabled()) {
|
|
308043
|
+
return;
|
|
308044
|
+
}
|
|
308045
|
+
queueMicrotask(() => {
|
|
308046
|
+
try {
|
|
308047
|
+
const candidates = tracker.process(input.payload, input.receivedTimestamp);
|
|
308048
|
+
const context2 = resolveCaptureContext(archiver, input, "OHLCV", input.sourceMode ?? "broker_live_stream_v1");
|
|
308049
|
+
const rawCapture = candidates.length > 0 ? createRawCapture(context2, {
|
|
308050
|
+
payload: input.payload,
|
|
308051
|
+
eventTimeMs: candidates[0]?.bar.openTimeMs ?? input.receivedTimestamp,
|
|
308052
|
+
receivedTimeMs: input.receivedTimestamp,
|
|
308053
|
+
scope: "ccxt_normalized_object"
|
|
308054
|
+
}) : undefined;
|
|
308055
|
+
if (rawCapture) {
|
|
308056
|
+
archiver.enqueue(buildCanonicalCexStreamEventRow(context2, rawCapture));
|
|
308057
|
+
}
|
|
308058
|
+
for (const candidate of candidates) {
|
|
308059
|
+
if (rawCapture) {
|
|
308060
|
+
archiver.enqueue(buildCanonicalOhlcvRow({
|
|
308061
|
+
context: context2,
|
|
308062
|
+
rawCapture,
|
|
308063
|
+
bar: candidate.bar,
|
|
308064
|
+
isClosed: candidate.isClosed,
|
|
308065
|
+
brokerVersion: candidate.brokerVersion
|
|
308066
|
+
}));
|
|
308067
|
+
}
|
|
308068
|
+
}
|
|
308069
|
+
if (candidates.length > 0) {
|
|
308070
|
+
recordWatchMetric(otelMetrics, "cex_watch_frames_archived_total", labels);
|
|
308071
|
+
}
|
|
308072
|
+
} catch (error48) {
|
|
308073
|
+
rethrowArchiveDurabilityError(error48);
|
|
308074
|
+
log.warn("Failed to archive OHLCV candle", { error: error48 });
|
|
308075
|
+
}
|
|
308076
|
+
});
|
|
308077
|
+
}
|
|
308078
|
+
function createOrderbookSampler() {
|
|
308079
|
+
return new OrderbookSampler;
|
|
308080
|
+
}
|
|
308081
|
+
function createOhlcvBarTracker() {
|
|
308082
|
+
return new OhlcvBarTracker;
|
|
308083
|
+
}
|
|
308084
|
+
function archiveMarketRowsInBackground(archiver, otelMetrics, stream4, input, feed, enqueueRows) {
|
|
308085
|
+
const labels = watchLabels(stream4, input, archiver, feed);
|
|
308086
|
+
recordWatchMetric(otelMetrics, "cex_watch_frames_received_total", labels);
|
|
308087
|
+
if (!isMarketArchiveEnabled() || !archiver?.isEnabled()) {
|
|
308088
|
+
return;
|
|
308089
|
+
}
|
|
308090
|
+
queueMicrotask(() => {
|
|
308091
|
+
try {
|
|
308092
|
+
const rows = enqueueRows();
|
|
308093
|
+
for (const row of rows) {
|
|
308094
|
+
archiver.enqueue(row);
|
|
308095
|
+
}
|
|
308096
|
+
if (rows.length > 0) {
|
|
308097
|
+
recordWatchMetric(otelMetrics, "cex_watch_frames_archived_total", labels);
|
|
308098
|
+
}
|
|
308099
|
+
} catch (error48) {
|
|
308100
|
+
rethrowArchiveDurabilityError(error48);
|
|
308101
|
+
log.warn(`Failed to archive ${stream4} market data`, { error: error48 });
|
|
308102
|
+
}
|
|
308103
|
+
});
|
|
308104
|
+
}
|
|
308105
|
+
function archiveTradesInBackground(archiver, otelMetrics, input) {
|
|
308106
|
+
archiveMarketRowsInBackground(archiver, otelMetrics, "trades", input, "TRADES", () => {
|
|
308107
|
+
if (!archiver)
|
|
308108
|
+
return [];
|
|
308109
|
+
const trades = extractTrades(input.payload, input.receivedTimestamp);
|
|
308110
|
+
if (trades.length === 0)
|
|
308111
|
+
return [];
|
|
308112
|
+
const context2 = resolveCaptureContext(archiver, input, "TRADES", "broker_live_stream_v1");
|
|
308113
|
+
const raw = createRawCapture(context2, {
|
|
308114
|
+
payload: input.payload,
|
|
308115
|
+
eventTimeMs: trades[0]?.eventTimeMs ?? input.receivedTimestamp,
|
|
308116
|
+
receivedTimeMs: input.receivedTimestamp,
|
|
308117
|
+
scope: "ccxt_normalized_object"
|
|
308118
|
+
});
|
|
308119
|
+
return [
|
|
308120
|
+
buildCanonicalCexStreamEventRow(context2, raw),
|
|
308121
|
+
...trades.map((trade) => buildCanonicalTradeRow(context2, raw, trade))
|
|
308122
|
+
];
|
|
308123
|
+
});
|
|
308124
|
+
}
|
|
308125
|
+
function archiveTickerInBackground(archiver, otelMetrics, input) {
|
|
308126
|
+
archiveMarketRowsInBackground(archiver, otelMetrics, "ticker", input, "TICKER", () => {
|
|
308127
|
+
const ticker = parseTicker(input.payload, input.receivedTimestamp);
|
|
308128
|
+
if (!ticker || !archiver)
|
|
308129
|
+
return [];
|
|
308130
|
+
const context2 = resolveCaptureContext(archiver, input, "TICKER", "broker_live_stream_v1");
|
|
308131
|
+
const raw = createRawCapture(context2, {
|
|
308132
|
+
payload: input.payload,
|
|
308133
|
+
eventTimeMs: ticker.eventTimeMs,
|
|
308134
|
+
receivedTimeMs: input.receivedTimestamp,
|
|
308135
|
+
scope: "ccxt_normalized_object"
|
|
308136
|
+
});
|
|
308137
|
+
return [
|
|
308138
|
+
buildCanonicalCexStreamEventRow(context2, raw),
|
|
308139
|
+
buildCanonicalTickerEventRow(context2, raw, ticker)
|
|
308140
|
+
];
|
|
308141
|
+
});
|
|
308142
|
+
}
|
|
308143
|
+
function archiveCexStreamEventInBackground(archiver, otelMetrics, input) {
|
|
308144
|
+
archiveMarketRowsInBackground(archiver, otelMetrics, "stream", input, input.streamType, () => [
|
|
308145
|
+
buildCexStreamEventRow({
|
|
308146
|
+
...input,
|
|
308147
|
+
source: archiver?.getSource()
|
|
308148
|
+
})
|
|
308149
|
+
]);
|
|
308150
|
+
}
|
|
308151
|
+
|
|
308152
|
+
// src/handlers/execute-action/order-book-call.ts
|
|
308153
|
+
async function handleOrderBookCall(ctx) {
|
|
308154
|
+
const parsedOrderBookCall = parseOrderBookCallPayload(ctx.call.request.payload, {
|
|
308155
|
+
exchange: ctx.normalizedCex,
|
|
308156
|
+
symbol: ctx.symbol
|
|
308157
|
+
});
|
|
308158
|
+
if (parsedOrderBookCall.kind === "error") {
|
|
308159
|
+
ctx.wrappedCallback({
|
|
308160
|
+
code: grpc4.status.INVALID_ARGUMENT,
|
|
308161
|
+
message: parsedOrderBookCall.message
|
|
308162
|
+
}, null);
|
|
308163
|
+
return true;
|
|
308164
|
+
}
|
|
308165
|
+
if (parsedOrderBookCall.kind !== "order_book") {
|
|
308166
|
+
return false;
|
|
308167
|
+
}
|
|
308168
|
+
const orderBookBroker = ctx.broker;
|
|
308169
|
+
if (!orderBookBroker) {
|
|
308170
|
+
ctx.wrappedCallback({
|
|
308171
|
+
code: grpc4.status.INVALID_ARGUMENT,
|
|
308172
|
+
message: `Unsupported exchange for order-book market data: ${ctx.normalizedCex}`
|
|
308173
|
+
}, null);
|
|
308174
|
+
return true;
|
|
308175
|
+
}
|
|
308176
|
+
ctx.applyVerityToBroker(orderBookBroker);
|
|
308177
|
+
try {
|
|
308178
|
+
const orderBookPayload = parsedOrderBookCall.payload;
|
|
308179
|
+
if (orderBookPayload.method === ORDER_BOOK_CALL_METHODS.FETCH_CAPABILITY) {
|
|
308180
|
+
ctx.wrappedCallback(null, {
|
|
308181
|
+
proof: ctx.verity.proof,
|
|
308182
|
+
result: JSON.stringify(buildOrderBookCapability(orderBookBroker, orderBookPayload))
|
|
308183
|
+
});
|
|
308184
|
+
return true;
|
|
308185
|
+
}
|
|
308186
|
+
if (orderBookPayload.method === ORDER_BOOK_CALL_METHODS.FETCH_HISTORICAL_SNAPSHOTS) {
|
|
308187
|
+
ctx.wrappedCallback(null, {
|
|
308188
|
+
proof: ctx.verity.proof,
|
|
308189
|
+
result: JSON.stringify(buildHistoricalOrderBookUnsupported(orderBookPayload))
|
|
308190
|
+
});
|
|
308191
|
+
return true;
|
|
308192
|
+
}
|
|
308193
|
+
const fetchOrderBook = orderBookBroker.fetchOrderBook;
|
|
308194
|
+
const canFetchOrderBook = typeof fetchOrderBook === "function" && orderBookBroker.has?.fetchOrderBook !== false;
|
|
308195
|
+
if (!canFetchOrderBook) {
|
|
308196
|
+
ctx.wrappedCallback({
|
|
308197
|
+
code: grpc4.status.UNIMPLEMENTED,
|
|
308198
|
+
message: `Order-book snapshot unsupported for ${ctx.normalizedCex}`
|
|
308199
|
+
}, null);
|
|
308200
|
+
return true;
|
|
308201
|
+
}
|
|
308202
|
+
const receivedTimestamp = Date.now();
|
|
308203
|
+
const rawOrderBook = await fetchOrderBook.call(orderBookBroker, orderBookPayload.symbol, orderBookPayload.depthLimit);
|
|
308204
|
+
const snapshot = normalizeOrderBookSnapshot(rawOrderBook, {
|
|
308205
|
+
exchange: orderBookPayload.exchange,
|
|
308206
|
+
symbol: orderBookPayload.symbol,
|
|
308207
|
+
depthLimit: orderBookPayload.depthLimit,
|
|
308208
|
+
receivedTimestamp
|
|
308209
|
+
});
|
|
308210
|
+
ctx.wrappedCallback(null, {
|
|
307664
308211
|
proof: ctx.verity.proof,
|
|
307665
|
-
result: JSON.stringify(
|
|
308212
|
+
result: JSON.stringify(snapshot)
|
|
308213
|
+
});
|
|
308214
|
+
archiveOrderbookInBackground(ctx.brokerArchiver, ctx.otelMetrics, {
|
|
308215
|
+
exchange: orderBookPayload.exchange,
|
|
308216
|
+
symbol: orderBookPayload.symbol,
|
|
308217
|
+
assetType: "spot",
|
|
308218
|
+
accountSelector: ctx.selectedBrokerAccount?.label,
|
|
308219
|
+
deploymentId: ctx.brokerArchiver?.getDeploymentId() ?? "unarchived",
|
|
308220
|
+
snapshot
|
|
308221
|
+
}, {
|
|
308222
|
+
sourceMode: "broker_current_snapshot_v1",
|
|
308223
|
+
depthLimit: orderBookPayload.depthLimit
|
|
307666
308224
|
});
|
|
307667
308225
|
} catch (error48) {
|
|
307668
|
-
safeLogError(
|
|
308226
|
+
safeLogError("Order-book Call failed", error48);
|
|
307669
308227
|
ctx.wrappedCallback({
|
|
307670
|
-
code:
|
|
307671
|
-
message: `
|
|
308228
|
+
code: mapCcxtErrorToGrpcStatus(error48) ?? grpc4.status.INTERNAL,
|
|
308229
|
+
message: `Order-book Call failed: ${sanitizeErrorDetail(error48)}`
|
|
307672
308230
|
}, null);
|
|
307673
308231
|
}
|
|
308232
|
+
return true;
|
|
307674
308233
|
}
|
|
307675
|
-
|
|
308234
|
+
|
|
308235
|
+
// src/handlers/execute-action/registry.ts
|
|
308236
|
+
import * as grpc11 from "@grpc/grpc-js";
|
|
308237
|
+
|
|
308238
|
+
// src/handlers/execute-action/internal-transfer.ts
|
|
308239
|
+
import * as grpc5 from "@grpc/grpc-js";
|
|
308240
|
+
async function handleInternalTransfer(ctx) {
|
|
307676
308241
|
const {
|
|
307677
|
-
call,
|
|
307678
|
-
wrappedCallback,
|
|
307679
|
-
policy,
|
|
307680
308242
|
brokers,
|
|
307681
308243
|
metadata,
|
|
307682
308244
|
normalizedCex,
|
|
307683
|
-
cex: cex3,
|
|
307684
308245
|
symbol: symbol2,
|
|
307685
|
-
selectedBrokerAccount,
|
|
307686
|
-
broker,
|
|
307687
308246
|
verity,
|
|
307688
|
-
applyVerityToBroker,
|
|
307689
308247
|
useVerity,
|
|
307690
308248
|
verityProverUrl,
|
|
307691
|
-
|
|
308249
|
+
brokerArchiver
|
|
307692
308250
|
} = ctx;
|
|
307693
|
-
const verityProof = verity.proof;
|
|
307694
308251
|
if (!symbol2) {
|
|
307695
308252
|
return ctx.wrappedCallback({
|
|
307696
|
-
code:
|
|
308253
|
+
code: grpc5.status.INVALID_ARGUMENT,
|
|
307697
308254
|
message: `ValidationError: Symbol required`
|
|
307698
308255
|
}, null);
|
|
307699
308256
|
}
|
|
307700
|
-
const
|
|
307701
|
-
if (
|
|
308257
|
+
const transferPayload = parsePayloadForAction(ctx, InternalTransferPayloadSchema);
|
|
308258
|
+
if (transferPayload === null)
|
|
307702
308259
|
return;
|
|
307703
|
-
|
|
308260
|
+
if (normalizedCex !== "binance") {
|
|
308261
|
+
return ctx.wrappedCallback({
|
|
308262
|
+
code: grpc5.status.UNIMPLEMENTED,
|
|
308263
|
+
message: `InternalTransfer is only supported for Binance`
|
|
308264
|
+
}, null);
|
|
308265
|
+
}
|
|
308266
|
+
const pool = brokers[normalizedCex];
|
|
308267
|
+
if (!pool) {
|
|
308268
|
+
return ctx.wrappedCallback({
|
|
308269
|
+
code: grpc5.status.FAILED_PRECONDITION,
|
|
308270
|
+
message: `No broker accounts configured for ${normalizedCex}`
|
|
308271
|
+
}, null);
|
|
308272
|
+
}
|
|
308273
|
+
const fromSelector = transferPayload.fromAccount ?? getCurrentBrokerSelector(metadata);
|
|
308274
|
+
const toSelector = transferPayload.toAccount ?? "primary";
|
|
308275
|
+
const sourceAccount = resolveBrokerAccount(pool, fromSelector);
|
|
308276
|
+
if (!sourceAccount) {
|
|
308277
|
+
return ctx.wrappedCallback({
|
|
308278
|
+
code: grpc5.status.INVALID_ARGUMENT,
|
|
308279
|
+
message: `Source account "${fromSelector}" is not configured`
|
|
308280
|
+
}, null);
|
|
308281
|
+
}
|
|
308282
|
+
const destAccount = resolveBrokerAccount(pool, toSelector);
|
|
308283
|
+
if (!destAccount) {
|
|
308284
|
+
return ctx.wrappedCallback({
|
|
308285
|
+
code: grpc5.status.INVALID_ARGUMENT,
|
|
308286
|
+
message: `Destination account "${toSelector}" is not configured`
|
|
308287
|
+
}, null);
|
|
308288
|
+
}
|
|
307704
308289
|
try {
|
|
307705
|
-
|
|
307706
|
-
|
|
307707
|
-
|
|
307708
|
-
|
|
307709
|
-
|
|
307710
|
-
try {
|
|
307711
|
-
const feeMap = await broker.fetchDepositWithdrawFees(currencyCodes);
|
|
307712
|
-
for (const code of currencyCodes) {
|
|
307713
|
-
const feeInfo = feeMap[code];
|
|
307714
|
-
if (!feeInfo) {
|
|
307715
|
-
continue;
|
|
307716
|
-
}
|
|
307717
|
-
const fallbackFee = feeInfo.fee !== undefined || feeInfo.percentage !== undefined ? {
|
|
307718
|
-
fee: feeInfo.fee ?? null,
|
|
307719
|
-
percentage: feeInfo.percentage ?? null
|
|
307720
|
-
} : null;
|
|
307721
|
-
fundingFeesByCurrency2[code] = {
|
|
307722
|
-
deposit: feeInfo.deposit ?? fallbackFee,
|
|
307723
|
-
withdraw: feeInfo.withdraw ?? fallbackFee,
|
|
307724
|
-
networks: feeInfo.networks ?? {}
|
|
307725
|
-
};
|
|
307726
|
-
}
|
|
307727
|
-
if (Object.keys(fundingFeesByCurrency2).length > 0) {
|
|
307728
|
-
fundingFeeSource2 = "fetchDepositWithdrawFees";
|
|
307729
|
-
}
|
|
307730
|
-
} catch (error48) {
|
|
307731
|
-
safeLogError(`Error fetching deposit/withdraw fee map for ${symbol2} from ${cex3}`, error48);
|
|
307732
|
-
}
|
|
307733
|
-
}
|
|
307734
|
-
if (fundingFeeSource2 === "unavailable") {
|
|
307735
|
-
try {
|
|
307736
|
-
const currencies = await broker.fetchCurrencies();
|
|
307737
|
-
for (const code of currencyCodes) {
|
|
307738
|
-
const currency = currencies[code];
|
|
307739
|
-
if (!currency) {
|
|
307740
|
-
continue;
|
|
307741
|
-
}
|
|
307742
|
-
fundingFeesByCurrency2[code] = {
|
|
307743
|
-
deposit: {
|
|
307744
|
-
enabled: currency.deposit ?? null
|
|
307745
|
-
},
|
|
307746
|
-
withdraw: {
|
|
307747
|
-
enabled: currency.withdraw ?? null,
|
|
307748
|
-
fee: currency.fee ?? null,
|
|
307749
|
-
limits: currency.limits?.withdraw ?? null
|
|
307750
|
-
},
|
|
307751
|
-
networks: currency.networks ?? {}
|
|
307752
|
-
};
|
|
307753
|
-
}
|
|
307754
|
-
if (Object.keys(fundingFeesByCurrency2).length > 0) {
|
|
307755
|
-
fundingFeeSource2 = "currencies";
|
|
307756
|
-
}
|
|
307757
|
-
} catch (error48) {
|
|
307758
|
-
safeLogError(`Error fetching currency metadata for fees for ${symbol2} from ${cex3}`, error48);
|
|
307759
|
-
}
|
|
307760
|
-
}
|
|
307761
|
-
return { fundingFeeSource: fundingFeeSource2, fundingFeesByCurrency: fundingFeesByCurrency2 };
|
|
307762
|
-
};
|
|
307763
|
-
const isMarketSymbol = symbol2.includes("/");
|
|
307764
|
-
if (isMarketSymbol) {
|
|
307765
|
-
const market = await broker.market(symbol2);
|
|
307766
|
-
const generalFee = broker.fees ?? null;
|
|
307767
|
-
const feeStatus = broker.fees ? "available" : "unknown";
|
|
307768
|
-
if (!broker.fees) {
|
|
307769
|
-
log.warn(`Fee metadata unavailable for ${cex3}`, { symbol: symbol2 });
|
|
307770
|
-
}
|
|
307771
|
-
if (!includeAllFees) {
|
|
307772
|
-
return ctx.wrappedCallback(null, {
|
|
307773
|
-
proof: ctx.verity.proof,
|
|
307774
|
-
result: JSON.stringify({
|
|
307775
|
-
feeScope: "market",
|
|
307776
|
-
generalFee,
|
|
307777
|
-
feeStatus,
|
|
307778
|
-
market
|
|
307779
|
-
})
|
|
307780
|
-
});
|
|
307781
|
-
}
|
|
307782
|
-
const currencyCodes = Array.from(new Set([market.base, market.quote]));
|
|
307783
|
-
const { fundingFeeSource: fundingFeeSource2, fundingFeesByCurrency: fundingFeesByCurrency2 } = await fetchFundingFees(currencyCodes);
|
|
307784
|
-
return ctx.wrappedCallback(null, {
|
|
307785
|
-
proof: ctx.verity.proof,
|
|
307786
|
-
result: JSON.stringify({
|
|
307787
|
-
feeScope: "market+funding",
|
|
307788
|
-
generalFee,
|
|
307789
|
-
feeStatus,
|
|
307790
|
-
market,
|
|
307791
|
-
fundingFeeSource: fundingFeeSource2,
|
|
307792
|
-
fundingFeesByCurrency: fundingFeesByCurrency2
|
|
307793
|
-
})
|
|
307794
|
-
});
|
|
308290
|
+
if (useVerity) {
|
|
308291
|
+
sourceAccount.exchange.setHttpClientOverride(buildHttpClientOverrideFromMetadata(metadata, verityProverUrl, (proof, notaryPubKey) => {
|
|
308292
|
+
verity.proof = proof;
|
|
308293
|
+
log.debug(`Verity proof:`, { proof, notaryPubKey });
|
|
308294
|
+
}), verityHttpClientOverridePredicate);
|
|
307795
308295
|
}
|
|
307796
|
-
const
|
|
307797
|
-
|
|
307798
|
-
|
|
307799
|
-
|
|
307800
|
-
|
|
307801
|
-
|
|
307802
|
-
|
|
307803
|
-
|
|
307804
|
-
|
|
307805
|
-
|
|
307806
|
-
|
|
307807
|
-
|
|
308296
|
+
const result = await transferBinanceInternal(sourceAccount, destAccount, symbol2, transferPayload.amount);
|
|
308297
|
+
archiveTransferEventInBackground(brokerArchiver, {
|
|
308298
|
+
exchange: normalizedCex,
|
|
308299
|
+
accountSelector: fromSelector,
|
|
308300
|
+
assetSymbol: symbol2,
|
|
308301
|
+
transfer: {
|
|
308302
|
+
eventKind: "internal_transfer",
|
|
308303
|
+
lifecycleAction: "submit_internal_transfer",
|
|
308304
|
+
status: "ok",
|
|
308305
|
+
amount: String(transferPayload.amount),
|
|
308306
|
+
network: "internal",
|
|
308307
|
+
externalId: extractBinanceInternalTransferId(result),
|
|
308308
|
+
payload: { from: fromSelector, to: toSelector, result }
|
|
308309
|
+
}
|
|
308310
|
+
});
|
|
308311
|
+
ctx.wrappedCallback(null, {
|
|
308312
|
+
proof: verity.proof,
|
|
308313
|
+
result: JSON.stringify(result)
|
|
307808
308314
|
});
|
|
307809
308315
|
} catch (error48) {
|
|
307810
|
-
safeLogError(
|
|
308316
|
+
safeLogError("InternalTransfer failed", error48);
|
|
308317
|
+
if (error48 instanceof BrokerAccountPreconditionError) {
|
|
308318
|
+
return ctx.wrappedCallback({
|
|
308319
|
+
code: grpc5.status.FAILED_PRECONDITION,
|
|
308320
|
+
message: getErrorMessage(error48)
|
|
308321
|
+
}, null);
|
|
308322
|
+
}
|
|
308323
|
+
const msg = getErrorMessage(error48);
|
|
308324
|
+
let code;
|
|
308325
|
+
if (msg.includes("Unsupported transfer direction")) {
|
|
308326
|
+
code = grpc5.status.INVALID_ARGUMENT;
|
|
308327
|
+
} else if (msg.includes("unavailable in this CCXT build")) {
|
|
308328
|
+
code = grpc5.status.UNIMPLEMENTED;
|
|
308329
|
+
} else {
|
|
308330
|
+
code = mapCcxtErrorToGrpcStatus(error48) ?? grpc5.status.INTERNAL;
|
|
308331
|
+
}
|
|
307811
308332
|
ctx.wrappedCallback({
|
|
307812
|
-
code
|
|
307813
|
-
message: `
|
|
308333
|
+
code,
|
|
308334
|
+
message: `InternalTransfer failed: ${sanitizeErrorDetail(error48)}`
|
|
307814
308335
|
}, null);
|
|
307815
308336
|
}
|
|
307816
308337
|
}
|
|
307817
|
-
|
|
308338
|
+
|
|
308339
|
+
// src/handlers/execute-action/orders.ts
|
|
308340
|
+
import * as grpc6 from "@grpc/grpc-js";
|
|
308341
|
+
|
|
308342
|
+
// src/helpers/passive-order.ts
|
|
308343
|
+
var PASSIVE_ORDER_ERROR_CODES = {
|
|
308344
|
+
unsupported: "passive_order_unsupported",
|
|
308345
|
+
rejected: "passive_order_rejected",
|
|
308346
|
+
wouldCross: "passive_order_would_cross"
|
|
308347
|
+
};
|
|
308348
|
+
function identifiesWouldCross(message) {
|
|
308349
|
+
const normalized = message.toLowerCase();
|
|
308350
|
+
return normalized.includes("would immediately match and take") || /post[\s-]?only\b.*\bwould\b.*\bimmediately\b.*\b(?:execute|fill|match)/.test(normalized) || /post[\s-]?only\b.*\bwould\b.*\b(?:execute|fill|match)\w*\b.*\bimmediately/.test(normalized);
|
|
308351
|
+
}
|
|
308352
|
+
function identifiesUnsupported(message) {
|
|
308353
|
+
const normalized = message.toLowerCase();
|
|
308354
|
+
return /post[\s-]?only\b.*\b(?:not supported|unsupported|does not support)\b/.test(normalized) || /\b(?:not supported|unsupported|does not support)\b.*\bpost[\s-]?only\b/.test(normalized);
|
|
308355
|
+
}
|
|
308356
|
+
function classifyPassiveOrderError(error48) {
|
|
308357
|
+
if (error48 instanceof ccxt_default.InsufficientFunds) {
|
|
308358
|
+
return "InsufficientFunds";
|
|
308359
|
+
}
|
|
308360
|
+
if (error48 instanceof ccxt_default.AuthenticationError) {
|
|
308361
|
+
return "AuthenticationError";
|
|
308362
|
+
}
|
|
308363
|
+
const message = getErrorMessage(error48);
|
|
308364
|
+
if (error48 instanceof ccxt_default.OrderImmediatelyFillable || identifiesWouldCross(message)) {
|
|
308365
|
+
return PASSIVE_ORDER_ERROR_CODES.wouldCross;
|
|
308366
|
+
}
|
|
308367
|
+
if (error48 instanceof ccxt_default.NotSupported || identifiesUnsupported(message)) {
|
|
308368
|
+
return PASSIVE_ORDER_ERROR_CODES.unsupported;
|
|
308369
|
+
}
|
|
308370
|
+
return PASSIVE_ORDER_ERROR_CODES.rejected;
|
|
308371
|
+
}
|
|
308372
|
+
|
|
308373
|
+
// src/handlers/execute-action/orders.ts
|
|
308374
|
+
async function handleCreateOrder(ctx) {
|
|
307818
308375
|
const {
|
|
307819
308376
|
call,
|
|
307820
308377
|
wrappedCallback,
|
|
@@ -307830,70 +308387,193 @@ async function handleFetchDepositAddresses(ctx) {
|
|
|
307830
308387
|
applyVerityToBroker,
|
|
307831
308388
|
useVerity,
|
|
307832
308389
|
verityProverUrl,
|
|
307833
|
-
otelMetrics
|
|
308390
|
+
otelMetrics,
|
|
308391
|
+
brokerArchiver,
|
|
308392
|
+
orderActivityTracker
|
|
307834
308393
|
} = ctx;
|
|
307835
308394
|
const verityProof = verity.proof;
|
|
307836
|
-
|
|
307837
|
-
|
|
307838
|
-
code: grpc7.status.INVALID_ARGUMENT,
|
|
307839
|
-
message: `ValidationError: Symbol required`
|
|
307840
|
-
}, null);
|
|
307841
|
-
}
|
|
307842
|
-
const fetchDepositAddresses = parsePayloadForAction(ctx, FetchDepositAddressesPayloadSchema);
|
|
307843
|
-
if (fetchDepositAddresses === null)
|
|
308395
|
+
const orderValue = parsePayloadForAction(ctx, CreateOrderPayloadSchema);
|
|
308396
|
+
if (orderValue === null)
|
|
307844
308397
|
return;
|
|
307845
|
-
|
|
307846
|
-
|
|
307847
|
-
depositNetwork = await resolveTransferNetwork(broker, symbol2, fetchDepositAddresses.chain);
|
|
307848
|
-
} catch (error48) {
|
|
307849
|
-
const message = getErrorMessage(error48);
|
|
307850
|
-
return ctx.wrappedCallback({
|
|
307851
|
-
code: stableGrpcErrorCode(message) ?? grpc7.status.INVALID_ARGUMENT,
|
|
307852
|
-
message
|
|
307853
|
-
}, null);
|
|
307854
|
-
}
|
|
307855
|
-
const depositValidation = validateDeposit(policy, cex3, depositNetwork.brokerNetworkId, symbol2);
|
|
307856
|
-
if (!depositValidation.valid) {
|
|
308398
|
+
const isPassiveOrder = orderValue.orderIntent === "passive_only";
|
|
308399
|
+
if (isPassiveOrder && orderValue.orderType !== "limit") {
|
|
307857
308400
|
return ctx.wrappedCallback({
|
|
307858
|
-
code:
|
|
307859
|
-
message:
|
|
308401
|
+
code: grpc6.status.INVALID_ARGUMENT,
|
|
308402
|
+
message: "ValidationError: passive_only order intent requires a limit order"
|
|
307860
308403
|
}, null);
|
|
307861
308404
|
}
|
|
308405
|
+
const createOrderParams = {
|
|
308406
|
+
...orderValue.params,
|
|
308407
|
+
...orderValue.clientOrderId !== undefined && {
|
|
308408
|
+
clientOrderId: orderValue.clientOrderId
|
|
308409
|
+
},
|
|
308410
|
+
...isPassiveOrder && { postOnly: true }
|
|
308411
|
+
};
|
|
308412
|
+
let resolvedOrderTelemetry = {};
|
|
308413
|
+
let marketMetadataHash;
|
|
308414
|
+
let submission = "not_attempted";
|
|
307862
308415
|
try {
|
|
307863
|
-
|
|
307864
|
-
|
|
307865
|
-
|
|
307866
|
-
|
|
308416
|
+
if (!broker) {
|
|
308417
|
+
return ctx.wrappedCallback({
|
|
308418
|
+
code: grpc6.status.INVALID_ARGUMENT,
|
|
308419
|
+
message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
|
|
308420
|
+
}, null);
|
|
308421
|
+
}
|
|
308422
|
+
const resolution = await resolveOrderExecution(policy, broker, cex3, orderValue.fromToken, orderValue.toToken, orderValue.amount, orderValue.price, orderValue.marketType);
|
|
308423
|
+
if (!resolution.valid || !resolution.symbol || !resolution.side) {
|
|
308424
|
+
return ctx.wrappedCallback({
|
|
308425
|
+
code: grpc6.status.INVALID_ARGUMENT,
|
|
308426
|
+
message: resolution.error ?? "Order rejected by policy: market or limits not satisfied"
|
|
308427
|
+
}, null);
|
|
308428
|
+
}
|
|
308429
|
+
resolvedOrderTelemetry = {
|
|
308430
|
+
symbol: resolution.symbol,
|
|
308431
|
+
side: resolution.side,
|
|
308432
|
+
requestedQuantity: resolution.amountBase ?? orderValue.amount
|
|
308433
|
+
};
|
|
308434
|
+
if (selectedBrokerAccount?.label) {
|
|
308435
|
+
orderActivityTracker?.record(cex3, selectedBrokerAccount.label, resolution.symbol);
|
|
308436
|
+
}
|
|
308437
|
+
const telemetryIds = extractOrderTelemetryIds(createOrderParams);
|
|
308438
|
+
const submissionTimestamp = new Date().toISOString();
|
|
308439
|
+
marketMetadataHash = await captureMarketMetadataSnapshot(brokerArchiver, broker, {
|
|
308440
|
+
exchange: cex3,
|
|
308441
|
+
accountSelector: selectedBrokerAccount?.label,
|
|
308442
|
+
symbol: resolution.symbol,
|
|
308443
|
+
action: "CreateOrder",
|
|
308444
|
+
brokerObservedTimestamp: submissionTimestamp,
|
|
308445
|
+
...telemetryIds
|
|
308446
|
+
});
|
|
308447
|
+
submission = "in_flight";
|
|
308448
|
+
const order = await broker.createOrder(resolution.symbol, orderValue.orderType, resolution.side, resolution.amountBase ?? orderValue.amount, orderValue.price, createOrderParams);
|
|
308449
|
+
submission = "placed";
|
|
308450
|
+
const createOrderContext = {
|
|
308451
|
+
action: "CreateOrder",
|
|
308452
|
+
cex: cex3,
|
|
308453
|
+
accountLabel: selectedBrokerAccount?.label,
|
|
308454
|
+
symbol: resolvedOrderTelemetry.symbol,
|
|
308455
|
+
side: resolvedOrderTelemetry.side,
|
|
308456
|
+
orderType: orderValue.orderType,
|
|
308457
|
+
requestedQuantity: resolvedOrderTelemetry.requestedQuantity,
|
|
308458
|
+
requestedNotional: orderValue.amount * orderValue.price,
|
|
308459
|
+
orderAuthor: orderValue.orderAuthor,
|
|
308460
|
+
brokerObservedTimestamp: submissionTimestamp,
|
|
308461
|
+
...telemetryIds
|
|
308462
|
+
};
|
|
308463
|
+
emitOrderExecutionTelemetryInBackground(otelMetrics, createOrderContext, order);
|
|
308464
|
+
archiveOrderExecutionInBackground(brokerArchiver, createOrderContext, order, undefined, { marketMetadataHash });
|
|
308465
|
+
ctx.wrappedCallback(null, {
|
|
308466
|
+
result: JSON.stringify({
|
|
308467
|
+
...order,
|
|
308468
|
+
...isPassiveOrder && {
|
|
308469
|
+
passivePlacementOutcome: "accepted_passive"
|
|
308470
|
+
}
|
|
307867
308471
|
})
|
|
307868
|
-
] : await broker.fetchDepositAddressesByNetwork(symbol2, {
|
|
307869
|
-
network: depositNetwork.exchangeNetworkId,
|
|
307870
|
-
...fetchDepositAddresses.params ?? {}
|
|
307871
308472
|
});
|
|
307872
|
-
|
|
307873
|
-
|
|
307874
|
-
|
|
307875
|
-
|
|
307876
|
-
|
|
307877
|
-
|
|
307878
|
-
|
|
307879
|
-
|
|
307880
|
-
|
|
308473
|
+
} catch (error48) {
|
|
308474
|
+
rethrowArchiveDurabilityError(error48);
|
|
308475
|
+
safeLogRedactedError("Order Creation failed", error48);
|
|
308476
|
+
const failedCreateContext = {
|
|
308477
|
+
action: "CreateOrder",
|
|
308478
|
+
cex: cex3,
|
|
308479
|
+
accountLabel: selectedBrokerAccount?.label,
|
|
308480
|
+
symbol: resolvedOrderTelemetry.symbol ?? symbol2,
|
|
308481
|
+
side: resolvedOrderTelemetry.side,
|
|
308482
|
+
orderType: orderValue.orderType,
|
|
308483
|
+
requestedQuantity: resolvedOrderTelemetry.requestedQuantity ?? orderValue.amount,
|
|
308484
|
+
requestedNotional: orderValue.amount * orderValue.price,
|
|
308485
|
+
orderAuthor: orderValue.orderAuthor,
|
|
308486
|
+
...extractOrderTelemetryIds(createOrderParams)
|
|
308487
|
+
};
|
|
308488
|
+
emitOrderExecutionTelemetryInBackground(otelMetrics, failedCreateContext, undefined, error48);
|
|
308489
|
+
archiveOrderExecutionInBackground(brokerArchiver, failedCreateContext, undefined, error48, { marketMetadataHash });
|
|
308490
|
+
if (isPassiveOrder && submission === "in_flight") {
|
|
308491
|
+
const stableErrorCode = classifyPassiveOrderError(error48);
|
|
308492
|
+
return rejectWithGrpcError(ctx, error48, {
|
|
308493
|
+
message: `${stableErrorCode}: ${sanitizeErrorDetail(error48)}`,
|
|
308494
|
+
preferStableMessageOnly: true
|
|
307881
308495
|
});
|
|
307882
308496
|
}
|
|
307883
308497
|
ctx.wrappedCallback({
|
|
307884
|
-
code:
|
|
307885
|
-
message:
|
|
308498
|
+
code: grpc6.status.INTERNAL,
|
|
308499
|
+
message: `Order Creation failed: ${sanitizeErrorDetail(error48)}`
|
|
307886
308500
|
}, null);
|
|
308501
|
+
}
|
|
308502
|
+
}
|
|
308503
|
+
async function handleGetOrderDetails(ctx) {
|
|
308504
|
+
const {
|
|
308505
|
+
call,
|
|
308506
|
+
wrappedCallback,
|
|
308507
|
+
policy,
|
|
308508
|
+
brokers,
|
|
308509
|
+
metadata,
|
|
308510
|
+
normalizedCex,
|
|
308511
|
+
cex: cex3,
|
|
308512
|
+
symbol: symbol2,
|
|
308513
|
+
selectedBrokerAccount,
|
|
308514
|
+
broker,
|
|
308515
|
+
verity,
|
|
308516
|
+
applyVerityToBroker,
|
|
308517
|
+
useVerity,
|
|
308518
|
+
verityProverUrl,
|
|
308519
|
+
otelMetrics,
|
|
308520
|
+
brokerArchiver,
|
|
308521
|
+
orderActivityTracker
|
|
308522
|
+
} = ctx;
|
|
308523
|
+
const verityProof = verity.proof;
|
|
308524
|
+
const getOrderValue = parsePayloadForAction(ctx, GetOrderDetailsPayloadSchema);
|
|
308525
|
+
if (getOrderValue === null)
|
|
308526
|
+
return;
|
|
308527
|
+
try {
|
|
308528
|
+
if (!broker) {
|
|
308529
|
+
return ctx.wrappedCallback({
|
|
308530
|
+
code: grpc6.status.INVALID_ARGUMENT,
|
|
308531
|
+
message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
|
|
308532
|
+
}, null);
|
|
308533
|
+
}
|
|
308534
|
+
const orderDetails = await broker.fetchOrder(getOrderValue.orderId, symbol2, { ...getOrderValue.params });
|
|
308535
|
+
if (selectedBrokerAccount?.label && symbol2) {
|
|
308536
|
+
orderActivityTracker?.record(cex3, selectedBrokerAccount.label, symbol2);
|
|
308537
|
+
}
|
|
308538
|
+
const getOrderContext = {
|
|
308539
|
+
action: "GetOrderDetails",
|
|
308540
|
+
cex: cex3,
|
|
308541
|
+
accountLabel: selectedBrokerAccount?.label,
|
|
308542
|
+
symbol: symbol2,
|
|
308543
|
+
...extractOrderTelemetryIds(getOrderValue.params)
|
|
308544
|
+
};
|
|
308545
|
+
emitOrderExecutionTelemetryInBackground(otelMetrics, getOrderContext, orderDetails);
|
|
308546
|
+
archiveOrderExecutionInBackground(brokerArchiver, getOrderContext, orderDetails);
|
|
308547
|
+
ctx.wrappedCallback(null, {
|
|
308548
|
+
result: JSON.stringify({
|
|
308549
|
+
orderId: orderDetails.id,
|
|
308550
|
+
status: orderDetails.status,
|
|
308551
|
+
amount: orderDetails.amount,
|
|
308552
|
+
filled: orderDetails.filled,
|
|
308553
|
+
remaining: orderDetails.remaining,
|
|
308554
|
+
symbol: orderDetails.symbol,
|
|
308555
|
+
side: orderDetails.side,
|
|
308556
|
+
price: orderDetails.price
|
|
308557
|
+
})
|
|
308558
|
+
});
|
|
307887
308559
|
} catch (error48) {
|
|
307888
|
-
safeLogError(
|
|
307889
|
-
const
|
|
308560
|
+
safeLogError(`Error fetching order details from ${cex3}`, error48);
|
|
308561
|
+
const failedGetOrderContext = {
|
|
308562
|
+
action: "GetOrderDetails",
|
|
308563
|
+
cex: cex3,
|
|
308564
|
+
accountLabel: selectedBrokerAccount?.label,
|
|
308565
|
+
symbol: symbol2,
|
|
308566
|
+
...extractOrderTelemetryIds(getOrderValue.params)
|
|
308567
|
+
};
|
|
308568
|
+
emitOrderExecutionTelemetryInBackground(otelMetrics, failedGetOrderContext, undefined, error48);
|
|
308569
|
+
archiveOrderExecutionInBackground(brokerArchiver, failedGetOrderContext, undefined, error48);
|
|
307890
308570
|
ctx.wrappedCallback({
|
|
307891
|
-
code:
|
|
307892
|
-
message:
|
|
308571
|
+
code: grpc6.status.INTERNAL,
|
|
308572
|
+
message: `Failed to fetch order details from ${cex3}: ${sanitizeErrorDetail(error48)}`
|
|
307893
308573
|
}, null);
|
|
307894
308574
|
}
|
|
307895
308575
|
}
|
|
307896
|
-
async function
|
|
308576
|
+
async function handleCancelOrder(ctx) {
|
|
307897
308577
|
const {
|
|
307898
308578
|
call,
|
|
307899
308579
|
wrappedCallback,
|
|
@@ -307909,63 +308589,66 @@ async function handleFetchBalances(ctx) {
|
|
|
307909
308589
|
applyVerityToBroker,
|
|
307910
308590
|
useVerity,
|
|
307911
308591
|
verityProverUrl,
|
|
307912
|
-
otelMetrics
|
|
308592
|
+
otelMetrics,
|
|
308593
|
+
brokerArchiver,
|
|
308594
|
+
orderActivityTracker
|
|
307913
308595
|
} = ctx;
|
|
307914
308596
|
const verityProof = verity.proof;
|
|
308597
|
+
const cancelOrderValue = parsePayloadForAction(ctx, CancelOrderPayloadSchema);
|
|
308598
|
+
if (cancelOrderValue === null)
|
|
308599
|
+
return;
|
|
307915
308600
|
try {
|
|
307916
|
-
|
|
307917
|
-
const providedBalanceType = payload.balanceType;
|
|
307918
|
-
const balanceType = (providedBalanceType ?? "total").toString();
|
|
307919
|
-
const validBalanceTypes = new Set(["free", "used", "total"]);
|
|
307920
|
-
if (!validBalanceTypes.has(balanceType)) {
|
|
308601
|
+
if (!broker) {
|
|
307921
308602
|
return ctx.wrappedCallback({
|
|
307922
|
-
code:
|
|
307923
|
-
message: `
|
|
308603
|
+
code: grpc6.status.INVALID_ARGUMENT,
|
|
308604
|
+
message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
|
|
307924
308605
|
}, null);
|
|
307925
308606
|
}
|
|
307926
|
-
const
|
|
307927
|
-
|
|
307928
|
-
|
|
307929
|
-
|
|
307930
|
-
|
|
307931
|
-
params
|
|
307932
|
-
}
|
|
307933
|
-
|
|
307934
|
-
if (
|
|
307935
|
-
|
|
307936
|
-
responseBalances = partial2 ?? {};
|
|
307937
|
-
} else if (balanceType === "used") {
|
|
307938
|
-
const partial2 = await broker.fetchUsedBalance(params);
|
|
307939
|
-
responseBalances = partial2 ?? {};
|
|
307940
|
-
} else if (balanceType === "total") {
|
|
307941
|
-
const partial2 = await broker.fetchTotalBalance(params);
|
|
307942
|
-
responseBalances = partial2 ?? {};
|
|
307943
|
-
}
|
|
307944
|
-
if (symbol2) {
|
|
307945
|
-
if (typeof responseBalances[symbol2] === "number") {
|
|
307946
|
-
responseBalances = {
|
|
307947
|
-
[symbol2]: responseBalances[symbol2] ?? 0
|
|
307948
|
-
};
|
|
307949
|
-
} else {
|
|
307950
|
-
responseBalances = {};
|
|
307951
|
-
}
|
|
308607
|
+
const cancelOrderContext = {
|
|
308608
|
+
action: "CancelOrder",
|
|
308609
|
+
cex: cex3,
|
|
308610
|
+
accountLabel: selectedBrokerAccount?.label,
|
|
308611
|
+
symbol: symbol2,
|
|
308612
|
+
...extractOrderTelemetryIds(cancelOrderValue.params)
|
|
308613
|
+
};
|
|
308614
|
+
const cancelledOrder = await broker.cancelOrder(cancelOrderValue.orderId, symbol2, cancelOrderValue.params ?? {});
|
|
308615
|
+
if (selectedBrokerAccount?.label && symbol2) {
|
|
308616
|
+
orderActivityTracker?.record(cex3, selectedBrokerAccount.label, symbol2);
|
|
307952
308617
|
}
|
|
308618
|
+
emitOrderExecutionTelemetryInBackground(otelMetrics, cancelOrderContext, cancelledOrder);
|
|
308619
|
+
archiveOrderExecutionInBackground(brokerArchiver, cancelOrderContext, cancelledOrder);
|
|
307953
308620
|
ctx.wrappedCallback(null, {
|
|
307954
|
-
|
|
307955
|
-
result: JSON.stringify({
|
|
307956
|
-
balances: responseBalances,
|
|
307957
|
-
balanceType
|
|
307958
|
-
})
|
|
308621
|
+
result: JSON.stringify({ ...cancelledOrder })
|
|
307959
308622
|
});
|
|
307960
308623
|
} catch (error48) {
|
|
307961
|
-
safeLogError(`Error
|
|
308624
|
+
safeLogError(`Error cancelling order from ${cex3}`, error48);
|
|
308625
|
+
const failedCancelContext = {
|
|
308626
|
+
action: "CancelOrder",
|
|
308627
|
+
cex: cex3,
|
|
308628
|
+
accountLabel: selectedBrokerAccount?.label,
|
|
308629
|
+
symbol: symbol2,
|
|
308630
|
+
...extractOrderTelemetryIds(cancelOrderValue.params)
|
|
308631
|
+
};
|
|
308632
|
+
emitOrderExecutionTelemetryInBackground(otelMetrics, failedCancelContext, undefined, error48);
|
|
308633
|
+
archiveOrderExecutionInBackground(brokerArchiver, failedCancelContext, undefined, error48);
|
|
307962
308634
|
ctx.wrappedCallback({
|
|
307963
|
-
code:
|
|
307964
|
-
message: `Failed to
|
|
308635
|
+
code: grpc6.status.INTERNAL,
|
|
308636
|
+
message: `Failed to cancel order from ${cex3}: ${sanitizeErrorDetail(error48)}`
|
|
307965
308637
|
}, null);
|
|
307966
308638
|
}
|
|
307967
308639
|
}
|
|
307968
|
-
async function
|
|
308640
|
+
async function handleOrders(ctx) {
|
|
308641
|
+
if (ctx.action === Action.CreateOrder)
|
|
308642
|
+
return handleCreateOrder(ctx);
|
|
308643
|
+
if (ctx.action === Action.GetOrderDetails)
|
|
308644
|
+
return handleGetOrderDetails(ctx);
|
|
308645
|
+
if (ctx.action === Action.CancelOrder)
|
|
308646
|
+
return handleCancelOrder(ctx);
|
|
308647
|
+
}
|
|
308648
|
+
|
|
308649
|
+
// src/handlers/execute-action/pass-through.ts
|
|
308650
|
+
import * as grpc7 from "@grpc/grpc-js";
|
|
308651
|
+
async function handleFetchCurrency(ctx) {
|
|
307969
308652
|
const {
|
|
307970
308653
|
call,
|
|
307971
308654
|
wrappedCallback,
|
|
@@ -307991,241 +308674,213 @@ async function handleFetchTicker(ctx) {
|
|
|
307991
308674
|
}, null);
|
|
307992
308675
|
}
|
|
307993
308676
|
try {
|
|
307994
|
-
const
|
|
308677
|
+
const assetCode = symbol2.trim().toUpperCase();
|
|
308678
|
+
const currencyInfo = await fetchCurrencyMetadata(broker, assetCode);
|
|
308679
|
+
if (!currencyInfo) {
|
|
308680
|
+
return ctx.wrappedCallback({
|
|
308681
|
+
code: grpc7.status.NOT_FOUND,
|
|
308682
|
+
message: `venue_discovery_unavailable: currency not found for ${assetCode}`
|
|
308683
|
+
}, null);
|
|
308684
|
+
}
|
|
308685
|
+
const networkEvidence = buildTransferNetworkEvidence(currencyInfo);
|
|
307995
308686
|
ctx.wrappedCallback(null, {
|
|
307996
308687
|
proof: ctx.verity.proof,
|
|
307997
|
-
result: JSON.stringify(ticker)
|
|
307998
|
-
});
|
|
307999
|
-
} catch (error48) {
|
|
308000
|
-
safeLogError(`Error fetching ticker from ${cex3}`, error48);
|
|
308001
|
-
ctx.wrappedCallback({
|
|
308002
|
-
code: grpc7.status.INTERNAL,
|
|
308003
|
-
message: `Failed to fetch ticker from ${cex3}`
|
|
308004
|
-
}, null);
|
|
308005
|
-
}
|
|
308006
|
-
}
|
|
308007
|
-
async function handlePassThrough(ctx) {
|
|
308008
|
-
if (ctx.action === Action.FetchCurrency)
|
|
308009
|
-
return handleFetchCurrency(ctx);
|
|
308010
|
-
if (ctx.action === Action.FetchAccountId)
|
|
308011
|
-
return handleFetchAccountId(ctx);
|
|
308012
|
-
if (ctx.action === Action.FetchFees)
|
|
308013
|
-
return handleFetchFees(ctx);
|
|
308014
|
-
if (ctx.action === Action.FetchDepositAddresses)
|
|
308015
|
-
return handleFetchDepositAddresses(ctx);
|
|
308016
|
-
if (ctx.action === Action.FetchBalances)
|
|
308017
|
-
return handleFetchBalances(ctx);
|
|
308018
|
-
if (ctx.action === Action.FetchTicker)
|
|
308019
|
-
return handleFetchTicker(ctx);
|
|
308020
|
-
}
|
|
308021
|
-
|
|
308022
|
-
// src/handlers/execute-action/perp-config.ts
|
|
308023
|
-
import * as grpc8 from "@grpc/grpc-js";
|
|
308024
|
-
function exchangeSupports(broker, capability) {
|
|
308025
|
-
return broker.has?.[capability] === true;
|
|
308026
|
-
}
|
|
308027
|
-
function extractPerpConfigs(positions) {
|
|
308028
|
-
return positions.map((position) => ({
|
|
308029
|
-
symbol: typeof position.symbol === "string" ? position.symbol : undefined,
|
|
308030
|
-
leverage: typeof position.leverage === "number" ? position.leverage : undefined,
|
|
308031
|
-
marginMode: typeof position.marginMode === "string" ? position.marginMode : undefined
|
|
308032
|
-
}));
|
|
308033
|
-
}
|
|
308034
|
-
async function handleGetPerpConfigState(ctx) {
|
|
308035
|
-
const { wrappedCallback, cex: cex3, normalizedCex, broker } = ctx;
|
|
308036
|
-
const payload = parsePayloadForAction(ctx, GetPerpConfigStatePayloadSchema);
|
|
308037
|
-
if (payload === null) {
|
|
308038
|
-
return;
|
|
308039
|
-
}
|
|
308040
|
-
if (!broker) {
|
|
308041
|
-
return wrappedCallback({
|
|
308042
|
-
code: grpc8.status.INVALID_ARGUMENT,
|
|
308043
|
-
message: `Invalid CEX key: ${cex3}`
|
|
308044
|
-
}, null);
|
|
308045
|
-
}
|
|
308046
|
-
const exchange = broker;
|
|
308047
|
-
if (!exchangeSupports(exchange, "fetchPositions")) {
|
|
308048
|
-
return wrappedCallback({
|
|
308049
|
-
code: grpc8.status.UNIMPLEMENTED,
|
|
308050
|
-
message: `${normalizedCex} does not support fetchPositions`
|
|
308051
|
-
}, null);
|
|
308052
|
-
}
|
|
308053
|
-
try {
|
|
308054
|
-
const symbols = payload.symbol ? [payload.symbol] : undefined;
|
|
308055
|
-
const positions = await exchange.fetchPositions?.(symbols, payload.params);
|
|
308056
|
-
ctx.wrappedCallback(null, {
|
|
308057
308688
|
result: JSON.stringify({
|
|
308689
|
+
...currencyInfo,
|
|
308058
308690
|
exchange: normalizedCex,
|
|
308059
|
-
|
|
308060
|
-
|
|
308691
|
+
asset: assetCode,
|
|
308692
|
+
code: currencyInfo.code ?? assetCode,
|
|
308693
|
+
id: currencyInfo.id ?? null,
|
|
308694
|
+
networks: networkEvidence.networks,
|
|
308695
|
+
networkAliases: networkEvidence.aliases,
|
|
308696
|
+
raw: currencyInfo
|
|
308061
308697
|
})
|
|
308062
308698
|
});
|
|
308063
308699
|
} catch (error48) {
|
|
308064
|
-
safeLogError(`
|
|
308700
|
+
safeLogError(`Error fetching currency ${symbol2} from ${cex3}`, error48);
|
|
308701
|
+
const message = getErrorMessage(error48);
|
|
308065
308702
|
ctx.wrappedCallback({
|
|
308066
|
-
code:
|
|
308067
|
-
message:
|
|
308703
|
+
code: stableGrpcErrorCode(message) ?? mapCcxtErrorToGrpcStatus(error48) ?? grpc7.status.INTERNAL,
|
|
308704
|
+
message: message.startsWith("venue_discovery_unavailable:") ? message : `venue_discovery_unavailable: ${message}`
|
|
308068
308705
|
}, null);
|
|
308069
308706
|
}
|
|
308070
308707
|
}
|
|
308071
|
-
async function
|
|
308072
|
-
const {
|
|
308073
|
-
|
|
308074
|
-
|
|
308075
|
-
|
|
308076
|
-
|
|
308077
|
-
|
|
308078
|
-
|
|
308079
|
-
|
|
308080
|
-
|
|
308081
|
-
|
|
308082
|
-
|
|
308083
|
-
|
|
308084
|
-
|
|
308085
|
-
|
|
308086
|
-
|
|
308087
|
-
|
|
308088
|
-
|
|
308089
|
-
|
|
308708
|
+
async function handleFetchAccountId(ctx) {
|
|
308709
|
+
const {
|
|
308710
|
+
call,
|
|
308711
|
+
wrappedCallback,
|
|
308712
|
+
policy,
|
|
308713
|
+
brokers,
|
|
308714
|
+
metadata,
|
|
308715
|
+
normalizedCex,
|
|
308716
|
+
cex: cex3,
|
|
308717
|
+
symbol: symbol2,
|
|
308718
|
+
selectedBrokerAccount,
|
|
308719
|
+
broker,
|
|
308720
|
+
verity,
|
|
308721
|
+
applyVerityToBroker,
|
|
308722
|
+
useVerity,
|
|
308723
|
+
verityProverUrl,
|
|
308724
|
+
otelMetrics
|
|
308725
|
+
} = ctx;
|
|
308726
|
+
const verityProof = verity.proof;
|
|
308090
308727
|
try {
|
|
308091
|
-
const
|
|
308092
|
-
|
|
308093
|
-
|
|
308094
|
-
|
|
308095
|
-
ctx.wrappedCallback(null, {
|
|
308096
|
-
result: JSON.stringify({
|
|
308097
|
-
exchange: normalizedCex,
|
|
308098
|
-
symbol: payload.symbol,
|
|
308099
|
-
leverage: payload.leverage,
|
|
308100
|
-
marginMode: payload.marginMode ?? "cross",
|
|
308101
|
-
response
|
|
308102
|
-
})
|
|
308728
|
+
const accountId = await broker.fetchAccountId();
|
|
308729
|
+
return ctx.wrappedCallback(null, {
|
|
308730
|
+
proof: ctx.verity.proof,
|
|
308731
|
+
result: JSON.stringify({ accountId })
|
|
308103
308732
|
});
|
|
308104
308733
|
} catch (error48) {
|
|
308105
|
-
safeLogError(`
|
|
308734
|
+
safeLogError(`Error fetching account ID ${cex3}`, error48);
|
|
308106
308735
|
ctx.wrappedCallback({
|
|
308107
|
-
code:
|
|
308108
|
-
message: `
|
|
308736
|
+
code: grpc7.status.INTERNAL,
|
|
308737
|
+
message: `Error fetching account ID from ${cex3}`
|
|
308109
308738
|
}, null);
|
|
308110
308739
|
}
|
|
308111
308740
|
}
|
|
308112
|
-
async function
|
|
308113
|
-
|
|
308114
|
-
|
|
308115
|
-
|
|
308116
|
-
|
|
308117
|
-
|
|
308741
|
+
async function handleFetchFees(ctx) {
|
|
308742
|
+
const {
|
|
308743
|
+
call,
|
|
308744
|
+
wrappedCallback,
|
|
308745
|
+
policy,
|
|
308746
|
+
brokers,
|
|
308747
|
+
metadata,
|
|
308748
|
+
normalizedCex,
|
|
308749
|
+
cex: cex3,
|
|
308750
|
+
symbol: symbol2,
|
|
308751
|
+
selectedBrokerAccount,
|
|
308752
|
+
broker,
|
|
308753
|
+
verity,
|
|
308754
|
+
applyVerityToBroker,
|
|
308755
|
+
useVerity,
|
|
308756
|
+
verityProverUrl,
|
|
308757
|
+
otelMetrics
|
|
308758
|
+
} = ctx;
|
|
308759
|
+
const verityProof = verity.proof;
|
|
308760
|
+
if (!symbol2) {
|
|
308761
|
+
return ctx.wrappedCallback({
|
|
308762
|
+
code: grpc7.status.INVALID_ARGUMENT,
|
|
308763
|
+
message: `ValidationError: Symbol required`
|
|
308764
|
+
}, null);
|
|
308118
308765
|
}
|
|
308119
|
-
|
|
308120
|
-
|
|
308121
|
-
// src/handlers/execute-action/treasury-call.ts
|
|
308122
|
-
import * as grpc9 from "@grpc/grpc-js";
|
|
308123
|
-
async function handleTreasuryCall(ctx) {
|
|
308124
|
-
const { broker } = ctx;
|
|
308125
|
-
const callValue = parsePayloadForAction(ctx, CallPayloadSchema);
|
|
308126
|
-
if (callValue === null)
|
|
308766
|
+
const feesPayload = parsePayloadForAction(ctx, FetchFeesPayloadSchema);
|
|
308767
|
+
if (feesPayload === null)
|
|
308127
308768
|
return;
|
|
308128
|
-
|
|
308129
|
-
let marketMetadataHash;
|
|
308769
|
+
const includeAllFees = feesPayload.includeAllFees || feesPayload.includeFundingFees === true;
|
|
308130
308770
|
try {
|
|
308131
|
-
|
|
308132
|
-
|
|
308133
|
-
|
|
308134
|
-
|
|
308135
|
-
|
|
308136
|
-
|
|
308137
|
-
|
|
308138
|
-
|
|
308139
|
-
|
|
308140
|
-
|
|
308141
|
-
|
|
308142
|
-
|
|
308143
|
-
|
|
308144
|
-
|
|
308145
|
-
|
|
308146
|
-
|
|
308147
|
-
|
|
308148
|
-
|
|
308149
|
-
|
|
308150
|
-
|
|
308151
|
-
|
|
308152
|
-
|
|
308153
|
-
|
|
308154
|
-
|
|
308155
|
-
|
|
308156
|
-
|
|
308157
|
-
|
|
308158
|
-
|
|
308159
|
-
|
|
308160
|
-
|
|
308161
|
-
|
|
308162
|
-
|
|
308163
|
-
|
|
308164
|
-
|
|
308165
|
-
|
|
308166
|
-
|
|
308167
|
-
|
|
308168
|
-
|
|
308169
|
-
|
|
308170
|
-
|
|
308171
|
-
|
|
308172
|
-
|
|
308173
|
-
|
|
308174
|
-
|
|
308175
|
-
|
|
308176
|
-
|
|
308177
|
-
|
|
308178
|
-
|
|
308179
|
-
|
|
308771
|
+
await broker.loadMarkets();
|
|
308772
|
+
const fetchFundingFees = async (currencyCodes) => {
|
|
308773
|
+
let fundingFeeSource2 = "unavailable";
|
|
308774
|
+
const fundingFeesByCurrency2 = {};
|
|
308775
|
+
if (broker.has.fetchDepositWithdrawFees) {
|
|
308776
|
+
try {
|
|
308777
|
+
const feeMap = await broker.fetchDepositWithdrawFees(currencyCodes);
|
|
308778
|
+
for (const code of currencyCodes) {
|
|
308779
|
+
const feeInfo = feeMap[code];
|
|
308780
|
+
if (!feeInfo) {
|
|
308781
|
+
continue;
|
|
308782
|
+
}
|
|
308783
|
+
const fallbackFee = feeInfo.fee !== undefined || feeInfo.percentage !== undefined ? {
|
|
308784
|
+
fee: feeInfo.fee ?? null,
|
|
308785
|
+
percentage: feeInfo.percentage ?? null
|
|
308786
|
+
} : null;
|
|
308787
|
+
fundingFeesByCurrency2[code] = {
|
|
308788
|
+
deposit: feeInfo.deposit ?? fallbackFee,
|
|
308789
|
+
withdraw: feeInfo.withdraw ?? fallbackFee,
|
|
308790
|
+
networks: feeInfo.networks ?? {}
|
|
308791
|
+
};
|
|
308792
|
+
}
|
|
308793
|
+
if (Object.keys(fundingFeesByCurrency2).length > 0) {
|
|
308794
|
+
fundingFeeSource2 = "fetchDepositWithdrawFees";
|
|
308795
|
+
}
|
|
308796
|
+
} catch (error48) {
|
|
308797
|
+
safeLogError(`Error fetching deposit/withdraw fee map for ${symbol2} from ${cex3}`, error48);
|
|
308798
|
+
}
|
|
308799
|
+
}
|
|
308800
|
+
if (fundingFeeSource2 === "unavailable") {
|
|
308801
|
+
try {
|
|
308802
|
+
const currencies = await broker.fetchCurrencies();
|
|
308803
|
+
for (const code of currencyCodes) {
|
|
308804
|
+
const currency = currencies[code];
|
|
308805
|
+
if (!currency) {
|
|
308806
|
+
continue;
|
|
308807
|
+
}
|
|
308808
|
+
fundingFeesByCurrency2[code] = {
|
|
308809
|
+
deposit: {
|
|
308810
|
+
enabled: currency.deposit ?? null
|
|
308811
|
+
},
|
|
308812
|
+
withdraw: {
|
|
308813
|
+
enabled: currency.withdraw ?? null,
|
|
308814
|
+
fee: currency.fee ?? null,
|
|
308815
|
+
limits: currency.limits?.withdraw ?? null
|
|
308816
|
+
},
|
|
308817
|
+
networks: currency.networks ?? {}
|
|
308818
|
+
};
|
|
308819
|
+
}
|
|
308820
|
+
if (Object.keys(fundingFeesByCurrency2).length > 0) {
|
|
308821
|
+
fundingFeeSource2 = "currencies";
|
|
308822
|
+
}
|
|
308823
|
+
} catch (error48) {
|
|
308824
|
+
safeLogError(`Error fetching currency metadata for fees for ${symbol2} from ${cex3}`, error48);
|
|
308825
|
+
}
|
|
308826
|
+
}
|
|
308827
|
+
return { fundingFeeSource: fundingFeeSource2, fundingFeesByCurrency: fundingFeesByCurrency2 };
|
|
308828
|
+
};
|
|
308829
|
+
const isMarketSymbol = symbol2.includes("/");
|
|
308830
|
+
if (isMarketSymbol) {
|
|
308831
|
+
const market = await broker.market(symbol2);
|
|
308832
|
+
const generalFee = broker.fees ?? null;
|
|
308833
|
+
const feeStatus = broker.fees ? "available" : "unknown";
|
|
308834
|
+
if (!broker.fees) {
|
|
308835
|
+
log.warn(`Fee metadata unavailable for ${cex3}`, { symbol: symbol2 });
|
|
308836
|
+
}
|
|
308837
|
+
if (!includeAllFees) {
|
|
308838
|
+
return ctx.wrappedCallback(null, {
|
|
308839
|
+
proof: ctx.verity.proof,
|
|
308840
|
+
result: JSON.stringify({
|
|
308841
|
+
feeScope: "market",
|
|
308842
|
+
generalFee,
|
|
308843
|
+
feeStatus,
|
|
308844
|
+
market
|
|
308845
|
+
})
|
|
308180
308846
|
});
|
|
308181
308847
|
}
|
|
308182
|
-
|
|
308183
|
-
|
|
308184
|
-
|
|
308185
|
-
|
|
308186
|
-
|
|
308187
|
-
|
|
308188
|
-
|
|
308189
|
-
|
|
308190
|
-
|
|
308191
|
-
|
|
308848
|
+
const currencyCodes = Array.from(new Set([market.base, market.quote]));
|
|
308849
|
+
const { fundingFeeSource: fundingFeeSource2, fundingFeesByCurrency: fundingFeesByCurrency2 } = await fetchFundingFees(currencyCodes);
|
|
308850
|
+
return ctx.wrappedCallback(null, {
|
|
308851
|
+
proof: ctx.verity.proof,
|
|
308852
|
+
result: JSON.stringify({
|
|
308853
|
+
feeScope: "market+funding",
|
|
308854
|
+
generalFee,
|
|
308855
|
+
feeStatus,
|
|
308856
|
+
market,
|
|
308857
|
+
fundingFeeSource: fundingFeeSource2,
|
|
308858
|
+
fundingFeesByCurrency: fundingFeesByCurrency2
|
|
308859
|
+
})
|
|
308192
308860
|
});
|
|
308193
308861
|
}
|
|
308194
|
-
|
|
308862
|
+
const tokenCode = symbol2.toUpperCase();
|
|
308863
|
+
const { fundingFeeSource, fundingFeesByCurrency } = await fetchFundingFees([
|
|
308864
|
+
tokenCode
|
|
308865
|
+
]);
|
|
308866
|
+
return ctx.wrappedCallback(null, {
|
|
308195
308867
|
proof: ctx.verity.proof,
|
|
308196
|
-
result: JSON.stringify(
|
|
308868
|
+
result: JSON.stringify({
|
|
308869
|
+
feeScope: "token",
|
|
308870
|
+
symbol: tokenCode,
|
|
308871
|
+
fundingFeeSource,
|
|
308872
|
+
fundingFeesByCurrency
|
|
308873
|
+
})
|
|
308197
308874
|
});
|
|
308198
308875
|
} catch (error48) {
|
|
308199
|
-
|
|
308200
|
-
|
|
308201
|
-
|
|
308202
|
-
|
|
308203
|
-
}
|
|
308204
|
-
safeLogError("Call failed", error48);
|
|
308205
|
-
rejectWithGrpcError(ctx, error48, {
|
|
308206
|
-
message: getErrorMessage(error48),
|
|
308207
|
-
preferStableMessageOnly: true,
|
|
308208
|
-
appendClassName: true
|
|
308209
|
-
});
|
|
308210
|
-
}
|
|
308211
|
-
}
|
|
308212
|
-
function asNonEmptyString(value) {
|
|
308213
|
-
return typeof value === "string" && value.trim() ? value : undefined;
|
|
308214
|
-
}
|
|
308215
|
-
function asFiniteNumber(value) {
|
|
308216
|
-
if (typeof value === "number") {
|
|
308217
|
-
return Number.isFinite(value) ? value : undefined;
|
|
308218
|
-
}
|
|
308219
|
-
if (typeof value !== "string" || !value.trim()) {
|
|
308220
|
-
return;
|
|
308876
|
+
safeLogError(`Error fetching fees for ${symbol2} from ${cex3}`, error48);
|
|
308877
|
+
ctx.wrappedCallback({
|
|
308878
|
+
code: grpc7.status.INTERNAL,
|
|
308879
|
+
message: `Error fetching fees from ${cex3}`
|
|
308880
|
+
}, null);
|
|
308221
308881
|
}
|
|
308222
|
-
const parsed = Number(value);
|
|
308223
|
-
return Number.isFinite(parsed) ? parsed : undefined;
|
|
308224
308882
|
}
|
|
308225
|
-
|
|
308226
|
-
// src/handlers/execute-action/withdraw.ts
|
|
308227
|
-
import * as grpc10 from "@grpc/grpc-js";
|
|
308228
|
-
async function handleWithdraw(ctx) {
|
|
308883
|
+
async function handleFetchDepositAddresses(ctx) {
|
|
308229
308884
|
const {
|
|
308230
308885
|
call,
|
|
308231
308886
|
wrappedCallback,
|
|
@@ -308241,1183 +308896,998 @@ async function handleWithdraw(ctx) {
|
|
|
308241
308896
|
applyVerityToBroker,
|
|
308242
308897
|
useVerity,
|
|
308243
308898
|
verityProverUrl,
|
|
308244
|
-
otelMetrics
|
|
308245
|
-
brokerArchiver
|
|
308899
|
+
otelMetrics
|
|
308246
308900
|
} = ctx;
|
|
308247
308901
|
const verityProof = verity.proof;
|
|
308248
308902
|
if (!symbol2) {
|
|
308249
308903
|
return ctx.wrappedCallback({
|
|
308250
|
-
code:
|
|
308904
|
+
code: grpc7.status.INVALID_ARGUMENT,
|
|
308251
308905
|
message: `ValidationError: Symbol required`
|
|
308252
308906
|
}, null);
|
|
308253
308907
|
}
|
|
308254
|
-
const
|
|
308255
|
-
if (
|
|
308908
|
+
const fetchDepositAddresses = parsePayloadForAction(ctx, FetchDepositAddressesPayloadSchema);
|
|
308909
|
+
if (fetchDepositAddresses === null)
|
|
308256
308910
|
return;
|
|
308257
|
-
let
|
|
308911
|
+
let depositNetwork;
|
|
308258
308912
|
try {
|
|
308259
|
-
|
|
308913
|
+
depositNetwork = await resolveTransferNetwork(broker, symbol2, fetchDepositAddresses.chain);
|
|
308260
308914
|
} catch (error48) {
|
|
308261
308915
|
const message = getErrorMessage(error48);
|
|
308262
308916
|
return ctx.wrappedCallback({
|
|
308263
|
-
code: stableGrpcErrorCode(message) ??
|
|
308917
|
+
code: stableGrpcErrorCode(message) ?? grpc7.status.INVALID_ARGUMENT,
|
|
308264
308918
|
message
|
|
308265
308919
|
}, null);
|
|
308266
308920
|
}
|
|
308267
|
-
const
|
|
308268
|
-
if (!
|
|
308269
|
-
return ctx.wrappedCallback({
|
|
308270
|
-
code: grpc10.status.PERMISSION_DENIED,
|
|
308271
|
-
message: `policy_withdrawal_denied: ${transferValidation.error}`
|
|
308272
|
-
}, null);
|
|
308273
|
-
}
|
|
308274
|
-
const travelRule = resolveTravelRuleDecision(policy, cex3, transferValue.recipientAddress);
|
|
308275
|
-
if (travelRule.mode === "denied") {
|
|
308921
|
+
const depositValidation = validateDeposit(policy, cex3, depositNetwork.brokerNetworkId, symbol2);
|
|
308922
|
+
if (!depositValidation.valid) {
|
|
308276
308923
|
return ctx.wrappedCallback({
|
|
308277
|
-
code:
|
|
308278
|
-
message: `
|
|
308924
|
+
code: grpc7.status.PERMISSION_DENIED,
|
|
308925
|
+
message: `policy_deposit_denied: ${depositValidation.error}`
|
|
308279
308926
|
}, null);
|
|
308280
308927
|
}
|
|
308281
|
-
const withdrawOrderId = transferValue.params.withdrawOrderId;
|
|
308282
|
-
const clientWithdrawalId = typeof withdrawOrderId === "string" && withdrawOrderId.length > 0 ? withdrawOrderId : undefined;
|
|
308283
308928
|
try {
|
|
308284
|
-
const
|
|
308285
|
-
|
|
308286
|
-
|
|
308287
|
-
|
|
308288
|
-
network: withdrawNetwork.exchangeNetworkId,
|
|
308289
|
-
questionnaire: travelRule.questionnaire,
|
|
308290
|
-
params: transferValue.params
|
|
308291
|
-
}) : await broker.withdraw(symbol2, transferValue.amount, transferValue.recipientAddress, undefined, {
|
|
308292
|
-
...transferValue.params ?? {},
|
|
308293
|
-
network: withdrawNetwork.exchangeNetworkId
|
|
308294
|
-
});
|
|
308295
|
-
log.info(`Withdraw Result: ${JSON.stringify(transaction)}`);
|
|
308296
|
-
const normalized = normalizeCcxtTransactionForArchive(transaction);
|
|
308297
|
-
archiveTransferEventInBackground(brokerArchiver, {
|
|
308298
|
-
exchange: cex3,
|
|
308299
|
-
accountSelector: selectedBrokerAccount?.label,
|
|
308300
|
-
assetSymbol: normalized.assetSymbol ?? symbol2,
|
|
308301
|
-
transfer: {
|
|
308302
|
-
eventKind: "withdrawal",
|
|
308303
|
-
lifecycleAction: "submit_withdrawal",
|
|
308304
|
-
status: normalized.status,
|
|
308305
|
-
amount: normalized.amount ?? String(transferValue.amount),
|
|
308306
|
-
address: normalized.address ?? transferValue.recipientAddress,
|
|
308307
|
-
network: normalized.network ?? withdrawNetwork.exchangeNetworkId,
|
|
308308
|
-
externalId: normalized.externalId,
|
|
308309
|
-
clientWithdrawalId,
|
|
308310
|
-
txid: normalized.txid,
|
|
308311
|
-
feeAmount: normalized.feeAmount,
|
|
308312
|
-
feeCurrency: normalized.feeCurrency,
|
|
308313
|
-
exchangeTimestamp: normalized.exchangeTimestamp,
|
|
308314
|
-
payload: transaction
|
|
308315
|
-
}
|
|
308316
|
-
});
|
|
308317
|
-
ctx.wrappedCallback(null, {
|
|
308318
|
-
proof: ctx.verity.proof,
|
|
308319
|
-
result: JSON.stringify({
|
|
308320
|
-
...transaction,
|
|
308321
|
-
operatorAlias: withdrawNetwork.operatorAlias,
|
|
308322
|
-
brokerNetworkId: withdrawNetwork.brokerNetworkId,
|
|
308323
|
-
exchangeNetworkId: withdrawNetwork.exchangeNetworkId
|
|
308929
|
+
const depositAddresses = broker.has.fetchDepositAddress === true ? [
|
|
308930
|
+
await broker.fetchDepositAddress(symbol2, {
|
|
308931
|
+
network: depositNetwork.exchangeNetworkId,
|
|
308932
|
+
...fetchDepositAddresses.params ?? {}
|
|
308324
308933
|
})
|
|
308934
|
+
] : await broker.fetchDepositAddressesByNetwork(symbol2, {
|
|
308935
|
+
network: depositNetwork.exchangeNetworkId,
|
|
308936
|
+
...fetchDepositAddresses.params ?? {}
|
|
308325
308937
|
});
|
|
308326
|
-
|
|
308327
|
-
|
|
308328
|
-
|
|
308329
|
-
|
|
308330
|
-
|
|
308331
|
-
|
|
308332
|
-
|
|
308333
|
-
|
|
308334
|
-
|
|
308335
|
-
|
|
308336
|
-
|
|
308337
|
-
address: transferValue.recipientAddress,
|
|
308338
|
-
network: withdrawNetwork.exchangeNetworkId,
|
|
308339
|
-
clientWithdrawalId,
|
|
308340
|
-
errorSummary: getErrorMessage(error48),
|
|
308341
|
-
payload: { recipientAddress: transferValue.recipientAddress }
|
|
308342
|
-
}
|
|
308343
|
-
});
|
|
308344
|
-
const code = mapCcxtErrorToGrpcStatus(error48) ?? grpc10.status.INTERNAL;
|
|
308938
|
+
if (depositAddresses.length > 0) {
|
|
308939
|
+
return ctx.wrappedCallback(null, {
|
|
308940
|
+
proof: ctx.verity.proof,
|
|
308941
|
+
result: JSON.stringify(depositAddresses.map((depositAddress) => ({
|
|
308942
|
+
...depositAddress,
|
|
308943
|
+
operatorAlias: depositNetwork.operatorAlias,
|
|
308944
|
+
brokerNetworkId: depositNetwork.brokerNetworkId,
|
|
308945
|
+
exchangeNetworkId: depositNetwork.exchangeNetworkId
|
|
308946
|
+
})))
|
|
308947
|
+
});
|
|
308948
|
+
}
|
|
308345
308949
|
ctx.wrappedCallback({
|
|
308346
|
-
code,
|
|
308347
|
-
message:
|
|
308950
|
+
code: grpc7.status.INTERNAL,
|
|
308951
|
+
message: "Deposit confirmation failed"
|
|
308348
308952
|
}, null);
|
|
308349
|
-
}
|
|
308350
|
-
|
|
308351
|
-
|
|
308352
|
-
// src/handlers/execute-action/registry.ts
|
|
308353
|
-
var ACTION_HANDLERS = {
|
|
308354
|
-
[Action.Deposit]: handleDeposit,
|
|
308355
|
-
[Action.Withdraw]: handleWithdraw,
|
|
308356
|
-
[Action.Call]: handleTreasuryCall,
|
|
308357
|
-
[Action.InternalTransfer]: handleInternalTransfer,
|
|
308358
|
-
[Action.CreateOrder]: handleOrders,
|
|
308359
|
-
[Action.GetOrderDetails]: handleOrders,
|
|
308360
|
-
[Action.CancelOrder]: handleOrders,
|
|
308361
|
-
[Action.FetchCurrency]: handlePassThrough,
|
|
308362
|
-
[Action.FetchAccountId]: handlePassThrough,
|
|
308363
|
-
[Action.FetchFees]: handlePassThrough,
|
|
308364
|
-
[Action.FetchDepositAddresses]: handlePassThrough,
|
|
308365
|
-
[Action.FetchBalances]: handlePassThrough,
|
|
308366
|
-
[Action.FetchTicker]: handlePassThrough,
|
|
308367
|
-
[Action.GetPerpConfigState]: handlePerpConfig,
|
|
308368
|
-
[Action.SetPerpConfigState]: handlePerpConfig
|
|
308369
|
-
};
|
|
308370
|
-
async function dispatchExecuteAction(ctx) {
|
|
308371
|
-
const handler = ACTION_HANDLERS[ctx.action];
|
|
308372
|
-
if (!handler) {
|
|
308953
|
+
} catch (error48) {
|
|
308954
|
+
safeLogError("Fetch Deposit Addresses confirmation failed", error48);
|
|
308955
|
+
const message = getErrorMessage(error48);
|
|
308373
308956
|
ctx.wrappedCallback({
|
|
308374
|
-
code:
|
|
308375
|
-
message: "
|
|
308957
|
+
code: grpc7.status.INTERNAL,
|
|
308958
|
+
message: "Fetch Deposit Addresses confirmation failed: " + message
|
|
308376
308959
|
}, null);
|
|
308377
|
-
return;
|
|
308378
308960
|
}
|
|
308379
|
-
await handler(ctx);
|
|
308380
308961
|
}
|
|
308381
|
-
|
|
308382
|
-
// src/handlers/execute-action/handler.ts
|
|
308383
|
-
function createExecuteActionHandler(deps) {
|
|
308962
|
+
async function handleFetchBalances(ctx) {
|
|
308384
308963
|
const {
|
|
308964
|
+
call,
|
|
308965
|
+
wrappedCallback,
|
|
308385
308966
|
policy,
|
|
308386
308967
|
brokers,
|
|
308387
|
-
|
|
308388
|
-
|
|
308389
|
-
|
|
308390
|
-
|
|
308391
|
-
|
|
308392
|
-
|
|
308393
|
-
|
|
308394
|
-
|
|
308395
|
-
|
|
308396
|
-
|
|
308397
|
-
|
|
308398
|
-
|
|
308399
|
-
|
|
308400
|
-
|
|
308401
|
-
|
|
308402
|
-
|
|
308403
|
-
|
|
308404
|
-
|
|
308405
|
-
|
|
308406
|
-
|
|
308407
|
-
|
|
308408
|
-
}
|
|
308409
|
-
if (error48) {
|
|
308410
|
-
otelMetrics?.recordCounter("execute_action_errors_total", 1, {
|
|
308411
|
-
action: actionName,
|
|
308412
|
-
cex: cex3 || "unknown",
|
|
308413
|
-
error_type: error48.code ? grpc12.status[error48.code] || "unknown" : "unknown"
|
|
308414
|
-
});
|
|
308415
|
-
} else {
|
|
308416
|
-
otelMetrics?.recordCounter("execute_action_success_total", 1, {
|
|
308417
|
-
action: actionName,
|
|
308418
|
-
cex: cex3 || "unknown"
|
|
308419
|
-
});
|
|
308420
|
-
}
|
|
308421
|
-
}
|
|
308422
|
-
callback(error48, value);
|
|
308423
|
-
};
|
|
308424
|
-
try {
|
|
308425
|
-
log.info(`Request - ExecuteAction:`, { action, cex: cex3, symbol: symbol2 });
|
|
308426
|
-
otelMetrics?.recordCounter("execute_action_requests_total", 1, {
|
|
308427
|
-
action: getActionName(action),
|
|
308428
|
-
cex: cex3 || "unknown"
|
|
308429
|
-
});
|
|
308430
|
-
if (!authenticateRequest(call, whitelistIps)) {
|
|
308431
|
-
return wrappedCallback({
|
|
308432
|
-
code: grpc12.status.PERMISSION_DENIED,
|
|
308433
|
-
message: "Access denied: Unauthorized IP"
|
|
308434
|
-
}, null);
|
|
308435
|
-
}
|
|
308436
|
-
if (!action || !cex3) {
|
|
308437
|
-
return wrappedCallback({
|
|
308438
|
-
code: grpc12.status.INVALID_ARGUMENT,
|
|
308439
|
-
message: "`action` AND `cex` fields are required"
|
|
308440
|
-
}, null);
|
|
308441
|
-
}
|
|
308442
|
-
const normalizedCex = cex3.trim().toLowerCase();
|
|
308443
|
-
const metadata = call.metadata;
|
|
308444
|
-
const selectedBrokerAccount = selectBrokerAccountForCex(normalizedCex, brokers, metadata);
|
|
308445
|
-
const verity = { proof: "" };
|
|
308446
|
-
const applyVerityToBroker = (targetBroker) => {
|
|
308447
|
-
if (!useVerity)
|
|
308448
|
-
return;
|
|
308449
|
-
const override = buildHttpClientOverrideFromMetadata(metadata, verityProverUrl, (proof, notaryPubKey) => {
|
|
308450
|
-
verity.proof = proof;
|
|
308451
|
-
log.debug(`Verity proof:`, { proof, notaryPubKey });
|
|
308452
|
-
});
|
|
308453
|
-
targetBroker.setHttpClientOverride(override, verityHttpClientOverridePredicate);
|
|
308454
|
-
};
|
|
308455
|
-
const preludeCtx = {
|
|
308456
|
-
call,
|
|
308457
|
-
wrappedCallback,
|
|
308458
|
-
action,
|
|
308459
|
-
policy,
|
|
308460
|
-
brokers,
|
|
308461
|
-
metadata,
|
|
308462
|
-
normalizedCex,
|
|
308463
|
-
cex: cex3,
|
|
308464
|
-
symbol: symbol2,
|
|
308465
|
-
selectedBrokerAccount,
|
|
308466
|
-
broker: selectedBrokerAccount?.exchange ?? createBroker(normalizedCex, metadata),
|
|
308467
|
-
verity,
|
|
308468
|
-
applyVerityToBroker,
|
|
308469
|
-
useVerity,
|
|
308470
|
-
verityProverUrl,
|
|
308471
|
-
otelMetrics,
|
|
308472
|
-
brokerArchiver,
|
|
308473
|
-
orderActivityTracker,
|
|
308474
|
-
withdrawalObservationTracker
|
|
308475
|
-
};
|
|
308476
|
-
if (action === Action.Call) {
|
|
308477
|
-
const handled = await handleOrderBookCall(preludeCtx);
|
|
308478
|
-
if (handled)
|
|
308479
|
-
return;
|
|
308480
|
-
}
|
|
308481
|
-
const broker = selectedBrokerAccount?.exchange ?? createBroker(normalizedCex, metadata);
|
|
308482
|
-
if (!broker) {
|
|
308483
|
-
return wrappedCallback({
|
|
308484
|
-
code: grpc12.status.UNAUTHENTICATED,
|
|
308485
|
-
message: `This Exchange is not registered and No API metadata was found`
|
|
308486
|
-
}, null);
|
|
308487
|
-
}
|
|
308488
|
-
applyVerityToBroker(broker);
|
|
308489
|
-
const ctx = { ...preludeCtx, broker };
|
|
308490
|
-
await dispatchExecuteAction(ctx);
|
|
308491
|
-
} catch (error48) {
|
|
308492
|
-
safeLogError("ExecuteAction unhandled error", error48);
|
|
308493
|
-
return wrappedCallback({
|
|
308494
|
-
code: grpc12.status.INTERNAL,
|
|
308495
|
-
message: "ExecuteAction failed unexpectedly"
|
|
308968
|
+
metadata,
|
|
308969
|
+
normalizedCex,
|
|
308970
|
+
cex: cex3,
|
|
308971
|
+
symbol: symbol2,
|
|
308972
|
+
selectedBrokerAccount,
|
|
308973
|
+
broker,
|
|
308974
|
+
verity,
|
|
308975
|
+
applyVerityToBroker,
|
|
308976
|
+
useVerity,
|
|
308977
|
+
verityProverUrl,
|
|
308978
|
+
otelMetrics
|
|
308979
|
+
} = ctx;
|
|
308980
|
+
const verityProof = verity.proof;
|
|
308981
|
+
try {
|
|
308982
|
+
const payload = call.request.payload || {};
|
|
308983
|
+
const providedBalanceType = payload.balanceType;
|
|
308984
|
+
const balanceType = (providedBalanceType ?? "total").toString();
|
|
308985
|
+
const validBalanceTypes = new Set(["free", "used", "total"]);
|
|
308986
|
+
if (!validBalanceTypes.has(balanceType)) {
|
|
308987
|
+
return ctx.wrappedCallback({
|
|
308988
|
+
code: grpc7.status.INVALID_ARGUMENT,
|
|
308989
|
+
message: `ValidationError: invalid balanceType '${providedBalanceType}'. Expected one of: free | used | total`
|
|
308496
308990
|
}, null);
|
|
308497
308991
|
}
|
|
308498
|
-
|
|
308499
|
-
|
|
308500
|
-
|
|
308501
|
-
|
|
308502
|
-
|
|
308503
|
-
|
|
308504
|
-
#shuttingDown = false;
|
|
308505
|
-
register(broker, context2) {
|
|
308506
|
-
this.#brokers.set(broker, context2);
|
|
308507
|
-
if (this.#shuttingDown) {
|
|
308508
|
-
this.close(broker);
|
|
308992
|
+
const params = { ...payload };
|
|
308993
|
+
delete params.balanceType;
|
|
308994
|
+
const marketType = parseMarketType(params.marketType);
|
|
308995
|
+
delete params.marketType;
|
|
308996
|
+
if (params.type === undefined) {
|
|
308997
|
+
params.type = marketTypeToCcxtType(marketType);
|
|
308509
308998
|
}
|
|
308510
|
-
|
|
308511
|
-
|
|
308512
|
-
|
|
308513
|
-
|
|
308514
|
-
|
|
308999
|
+
let responseBalances = {};
|
|
309000
|
+
if (balanceType === "free") {
|
|
309001
|
+
const partial2 = await broker.fetchFreeBalance(params);
|
|
309002
|
+
responseBalances = partial2 ?? {};
|
|
309003
|
+
} else if (balanceType === "used") {
|
|
309004
|
+
const partial2 = await broker.fetchUsedBalance(params);
|
|
309005
|
+
responseBalances = partial2 ?? {};
|
|
309006
|
+
} else if (balanceType === "total") {
|
|
309007
|
+
const partial2 = await broker.fetchTotalBalance(params);
|
|
309008
|
+
responseBalances = partial2 ?? {};
|
|
308515
309009
|
}
|
|
308516
|
-
|
|
308517
|
-
|
|
308518
|
-
|
|
308519
|
-
|
|
308520
|
-
|
|
308521
|
-
|
|
308522
|
-
|
|
308523
|
-
await broker.close();
|
|
308524
|
-
log.debug("Request-scoped Subscribe broker closed", context2);
|
|
308525
|
-
return "closed";
|
|
308526
|
-
} catch (error48) {
|
|
308527
|
-
log.warn("Failed to close request-scoped Subscribe broker", {
|
|
308528
|
-
...context2,
|
|
308529
|
-
error: error48
|
|
308530
|
-
});
|
|
308531
|
-
return "failed";
|
|
308532
|
-
} finally {
|
|
308533
|
-
this.#closing.delete(broker);
|
|
309010
|
+
if (symbol2) {
|
|
309011
|
+
if (typeof responseBalances[symbol2] === "number") {
|
|
309012
|
+
responseBalances = {
|
|
309013
|
+
[symbol2]: responseBalances[symbol2] ?? 0
|
|
309014
|
+
};
|
|
309015
|
+
} else {
|
|
309016
|
+
responseBalances = {};
|
|
308534
309017
|
}
|
|
308535
|
-
})();
|
|
308536
|
-
this.#closing.set(broker, closing);
|
|
308537
|
-
return closing;
|
|
308538
|
-
}
|
|
308539
|
-
async closeAll() {
|
|
308540
|
-
this.#shuttingDown = true;
|
|
308541
|
-
let failed = 0;
|
|
308542
|
-
while (this.#brokers.size > 0 || this.#closing.size > 0) {
|
|
308543
|
-
const inFlight = [...this.#closing.values()];
|
|
308544
|
-
const fresh = [...this.#brokers.keys()].map((broker) => this.close(broker));
|
|
308545
|
-
const outcomes = await Promise.all([...fresh, ...inFlight]);
|
|
308546
|
-
failed += outcomes.filter((outcome) => outcome === "failed").length;
|
|
308547
|
-
}
|
|
308548
|
-
if (failed > 0) {
|
|
308549
|
-
throw new Error(`${failed} request-scoped Subscribe broker(s) failed to close`);
|
|
308550
309018
|
}
|
|
309019
|
+
ctx.wrappedCallback(null, {
|
|
309020
|
+
proof: ctx.verity.proof,
|
|
309021
|
+
result: JSON.stringify({
|
|
309022
|
+
balances: responseBalances,
|
|
309023
|
+
balanceType
|
|
309024
|
+
})
|
|
309025
|
+
});
|
|
309026
|
+
} catch (error48) {
|
|
309027
|
+
safeLogError(`Error fetching balance from ${cex3}`, error48);
|
|
309028
|
+
ctx.wrappedCallback({
|
|
309029
|
+
code: grpc7.status.INTERNAL,
|
|
309030
|
+
message: `Failed to fetch balance from ${cex3}`
|
|
309031
|
+
}, null);
|
|
308551
309032
|
}
|
|
308552
309033
|
}
|
|
308553
|
-
|
|
308554
|
-
|
|
308555
|
-
|
|
308556
|
-
|
|
308557
|
-
|
|
308558
|
-
|
|
308559
|
-
|
|
308560
|
-
|
|
308561
|
-
|
|
308562
|
-
|
|
308563
|
-
|
|
308564
|
-
|
|
308565
|
-
|
|
308566
|
-
|
|
308567
|
-
|
|
308568
|
-
|
|
308569
|
-
|
|
308570
|
-
|
|
308571
|
-
|
|
308572
|
-
|
|
308573
|
-
|
|
308574
|
-
|
|
308575
|
-
|
|
308576
|
-
|
|
308577
|
-
}
|
|
308578
|
-
const secret = getExchangeString(exchange, "secret");
|
|
308579
|
-
return {
|
|
308580
|
-
...params,
|
|
308581
|
-
signature: createHmac("sha256", secret).update(sortedQuery(params)).digest("hex")
|
|
308582
|
-
};
|
|
308583
|
-
}
|
|
308584
|
-
function getBinanceSpotWsApiUrl(exchange) {
|
|
308585
|
-
const urls = exchange.urls;
|
|
308586
|
-
return urls?.api?.ws?.["ws-api"]?.spot ?? BINANCE_SPOT_WS_API_URL;
|
|
308587
|
-
}
|
|
308588
|
-
function getRecord(value) {
|
|
308589
|
-
return typeof value === "object" && value !== null ? value : null;
|
|
308590
|
-
}
|
|
308591
|
-
function getMessage(value) {
|
|
308592
|
-
if (value instanceof Error) {
|
|
308593
|
-
return value.message;
|
|
308594
|
-
}
|
|
308595
|
-
if (typeof value === "string" && value.length > 0) {
|
|
308596
|
-
return value;
|
|
309034
|
+
async function handleFetchTicker(ctx) {
|
|
309035
|
+
const {
|
|
309036
|
+
call,
|
|
309037
|
+
wrappedCallback,
|
|
309038
|
+
policy,
|
|
309039
|
+
brokers,
|
|
309040
|
+
metadata,
|
|
309041
|
+
normalizedCex,
|
|
309042
|
+
cex: cex3,
|
|
309043
|
+
symbol: symbol2,
|
|
309044
|
+
selectedBrokerAccount,
|
|
309045
|
+
broker,
|
|
309046
|
+
verity,
|
|
309047
|
+
applyVerityToBroker,
|
|
309048
|
+
useVerity,
|
|
309049
|
+
verityProverUrl,
|
|
309050
|
+
otelMetrics
|
|
309051
|
+
} = ctx;
|
|
309052
|
+
const verityProof = verity.proof;
|
|
309053
|
+
if (!symbol2) {
|
|
309054
|
+
return ctx.wrappedCallback({
|
|
309055
|
+
code: grpc7.status.INVALID_ARGUMENT,
|
|
309056
|
+
message: `ValidationError: Symbol required`
|
|
309057
|
+
}, null);
|
|
308597
309058
|
}
|
|
308598
|
-
|
|
308599
|
-
|
|
308600
|
-
|
|
308601
|
-
|
|
308602
|
-
|
|
308603
|
-
|
|
308604
|
-
|
|
308605
|
-
}
|
|
308606
|
-
|
|
308607
|
-
|
|
308608
|
-
|
|
308609
|
-
|
|
308610
|
-
redacted = redacted.split(value).join("[redacted]");
|
|
308611
|
-
}
|
|
309059
|
+
try {
|
|
309060
|
+
const ticker = await broker.fetchTicker(symbol2);
|
|
309061
|
+
ctx.wrappedCallback(null, {
|
|
309062
|
+
proof: ctx.verity.proof,
|
|
309063
|
+
result: JSON.stringify(ticker)
|
|
309064
|
+
});
|
|
309065
|
+
} catch (error48) {
|
|
309066
|
+
safeLogError(`Error fetching ticker from ${cex3}`, error48);
|
|
309067
|
+
ctx.wrappedCallback({
|
|
309068
|
+
code: grpc7.status.INTERNAL,
|
|
309069
|
+
message: `Failed to fetch ticker from ${cex3}`
|
|
309070
|
+
}, null);
|
|
308612
309071
|
}
|
|
308613
|
-
return redacted.replace(/(\b(?:apiKey|secret|signature)\b\s*=\s*)[^\s&,;)]+/gi, "$1[redacted]").replace(/("(?:apiKey|secret|signature)"\s*:\s*")[^"]*(")/gi, "$1[redacted]$2");
|
|
308614
309072
|
}
|
|
308615
|
-
function
|
|
308616
|
-
|
|
308617
|
-
|
|
308618
|
-
|
|
308619
|
-
|
|
309073
|
+
async function handlePassThrough(ctx) {
|
|
309074
|
+
if (ctx.action === Action.FetchCurrency)
|
|
309075
|
+
return handleFetchCurrency(ctx);
|
|
309076
|
+
if (ctx.action === Action.FetchAccountId)
|
|
309077
|
+
return handleFetchAccountId(ctx);
|
|
309078
|
+
if (ctx.action === Action.FetchFees)
|
|
309079
|
+
return handleFetchFees(ctx);
|
|
309080
|
+
if (ctx.action === Action.FetchDepositAddresses)
|
|
309081
|
+
return handleFetchDepositAddresses(ctx);
|
|
309082
|
+
if (ctx.action === Action.FetchBalances)
|
|
309083
|
+
return handleFetchBalances(ctx);
|
|
309084
|
+
if (ctx.action === Action.FetchTicker)
|
|
309085
|
+
return handleFetchTicker(ctx);
|
|
308620
309086
|
}
|
|
308621
|
-
|
|
308622
|
-
|
|
308623
|
-
|
|
308624
|
-
|
|
308625
|
-
|
|
308626
|
-
const reason = value.toString("utf8");
|
|
308627
|
-
return reason.length > 0 ? reason : null;
|
|
308628
|
-
}
|
|
308629
|
-
if (value instanceof Uint8Array) {
|
|
308630
|
-
const reason = Buffer2.from(value).toString("utf8");
|
|
308631
|
-
return reason.length > 0 ? reason : null;
|
|
308632
|
-
}
|
|
308633
|
-
return null;
|
|
309087
|
+
|
|
309088
|
+
// src/handlers/execute-action/perp-config.ts
|
|
309089
|
+
import * as grpc8 from "@grpc/grpc-js";
|
|
309090
|
+
function exchangeSupports(broker, capability) {
|
|
309091
|
+
return broker.has?.[capability] === true;
|
|
308634
309092
|
}
|
|
308635
|
-
function
|
|
308636
|
-
|
|
308637
|
-
|
|
308638
|
-
|
|
308639
|
-
|
|
308640
|
-
|
|
308641
|
-
typeof code === "number" || typeof code === "string" ? `code=${code}` : null,
|
|
308642
|
-
safeReason ? `reason=${safeReason}` : null
|
|
308643
|
-
].filter((detail) => detail !== null);
|
|
308644
|
-
return new Error(details.length > 0 ? `Binance user-data WebSocket closed unexpectedly (${details.join(", ")})` : "Binance user-data WebSocket closed unexpectedly");
|
|
309093
|
+
function extractPerpConfigs(positions) {
|
|
309094
|
+
return positions.map((position) => ({
|
|
309095
|
+
symbol: typeof position.symbol === "string" ? position.symbol : undefined,
|
|
309096
|
+
leverage: typeof position.leverage === "number" ? position.leverage : undefined,
|
|
309097
|
+
marginMode: typeof position.marginMode === "string" ? position.marginMode : undefined
|
|
309098
|
+
}));
|
|
308645
309099
|
}
|
|
308646
|
-
function
|
|
308647
|
-
|
|
308648
|
-
|
|
308649
|
-
|
|
308650
|
-
|
|
308651
|
-
return data.toString("utf8");
|
|
309100
|
+
async function handleGetPerpConfigState(ctx) {
|
|
309101
|
+
const { wrappedCallback, cex: cex3, normalizedCex, broker } = ctx;
|
|
309102
|
+
const payload = parsePayloadForAction(ctx, GetPerpConfigStatePayloadSchema);
|
|
309103
|
+
if (payload === null) {
|
|
309104
|
+
return;
|
|
308652
309105
|
}
|
|
308653
|
-
if (
|
|
308654
|
-
return
|
|
309106
|
+
if (!broker) {
|
|
309107
|
+
return wrappedCallback({
|
|
309108
|
+
code: grpc8.status.INVALID_ARGUMENT,
|
|
309109
|
+
message: `Invalid CEX key: ${cex3}`
|
|
309110
|
+
}, null);
|
|
308655
309111
|
}
|
|
308656
|
-
|
|
308657
|
-
|
|
309112
|
+
const exchange = broker;
|
|
309113
|
+
if (!exchangeSupports(exchange, "fetchPositions")) {
|
|
309114
|
+
return wrappedCallback({
|
|
309115
|
+
code: grpc8.status.UNIMPLEMENTED,
|
|
309116
|
+
message: `${normalizedCex} does not support fetchPositions`
|
|
309117
|
+
}, null);
|
|
308658
309118
|
}
|
|
308659
|
-
|
|
308660
|
-
|
|
309119
|
+
try {
|
|
309120
|
+
const symbols = payload.symbol ? [payload.symbol] : undefined;
|
|
309121
|
+
const positions = await exchange.fetchPositions?.(symbols, payload.params);
|
|
309122
|
+
ctx.wrappedCallback(null, {
|
|
309123
|
+
result: JSON.stringify({
|
|
309124
|
+
exchange: normalizedCex,
|
|
309125
|
+
configs: extractPerpConfigs(positions ?? []),
|
|
309126
|
+
positions: positions ?? []
|
|
309127
|
+
})
|
|
309128
|
+
});
|
|
309129
|
+
} catch (error48) {
|
|
309130
|
+
safeLogError(`GetPerpConfigState failed for ${cex3}`, error48);
|
|
309131
|
+
ctx.wrappedCallback({
|
|
309132
|
+
code: grpc8.status.INTERNAL,
|
|
309133
|
+
message: `GetPerpConfigState failed: ${sanitizeErrorDetail(error48)}`
|
|
309134
|
+
}, null);
|
|
308661
309135
|
}
|
|
308662
|
-
return data;
|
|
308663
309136
|
}
|
|
308664
|
-
|
|
308665
|
-
|
|
308666
|
-
|
|
308667
|
-
|
|
308668
|
-
|
|
308669
|
-
requestId = `user-data-${Date.now()}-${userDataRequestCounter++}`;
|
|
308670
|
-
maxBufferedEvents;
|
|
308671
|
-
queue = [];
|
|
308672
|
-
waiters = [];
|
|
308673
|
-
closed = false;
|
|
308674
|
-
closeError = null;
|
|
308675
|
-
subscriptionId = null;
|
|
308676
|
-
constructor(exchange, options = {}) {
|
|
308677
|
-
this.exchange = exchange;
|
|
308678
|
-
this.maxBufferedEvents = options.maxBufferedEvents ?? DEFAULT_BINANCE_USER_DATA_MAX_BUFFERED_EVENTS;
|
|
308679
|
-
this.secretValues = [
|
|
308680
|
-
getOptionalExchangeString(exchange, "apiKey"),
|
|
308681
|
-
getOptionalExchangeString(exchange, "secret")
|
|
308682
|
-
].filter((value) => value !== null);
|
|
308683
|
-
this.ws = createWebSocket(getBinanceSpotWsApiUrl(exchange));
|
|
308684
|
-
this.ws.on("open", () => this.subscribe());
|
|
308685
|
-
this.ws.on("message", (data) => this.handleMessage(data));
|
|
308686
|
-
this.ws.on("error", (error48) => this.fail(formatBinanceUserDataWebSocketError(error48, this.secretValues)));
|
|
308687
|
-
this.ws.on("close", (code, reason) => this.handleClose(code, reason));
|
|
308688
|
-
}
|
|
308689
|
-
async* [Symbol.asyncIterator]() {
|
|
308690
|
-
while (true) {
|
|
308691
|
-
const event = await this.nextEvent();
|
|
308692
|
-
if (!event) {
|
|
308693
|
-
break;
|
|
308694
|
-
}
|
|
308695
|
-
yield event;
|
|
308696
|
-
}
|
|
309137
|
+
async function handleSetPerpConfigState(ctx) {
|
|
309138
|
+
const { wrappedCallback, cex: cex3, normalizedCex, broker } = ctx;
|
|
309139
|
+
const payload = parsePayloadForAction(ctx, SetPerpConfigStatePayloadSchema);
|
|
309140
|
+
if (payload === null) {
|
|
309141
|
+
return;
|
|
308697
309142
|
}
|
|
308698
|
-
|
|
308699
|
-
|
|
308700
|
-
|
|
308701
|
-
|
|
308702
|
-
|
|
308703
|
-
this.queue.length = 0;
|
|
308704
|
-
try {
|
|
308705
|
-
this.ws.close();
|
|
308706
|
-
} catch {}
|
|
308707
|
-
this.flushWaiters();
|
|
309143
|
+
if (!broker) {
|
|
309144
|
+
return wrappedCallback({
|
|
309145
|
+
code: grpc8.status.INVALID_ARGUMENT,
|
|
309146
|
+
message: `Invalid CEX key: ${cex3}`
|
|
309147
|
+
}, null);
|
|
308708
309148
|
}
|
|
308709
|
-
|
|
308710
|
-
|
|
308711
|
-
|
|
308712
|
-
|
|
308713
|
-
|
|
309149
|
+
const exchange = broker;
|
|
309150
|
+
if (!exchangeSupports(exchange, "setLeverage")) {
|
|
309151
|
+
return wrappedCallback({
|
|
309152
|
+
code: grpc8.status.UNIMPLEMENTED,
|
|
309153
|
+
message: `${normalizedCex} does not support setLeverage`
|
|
309154
|
+
}, null);
|
|
308714
309155
|
}
|
|
308715
|
-
|
|
308716
|
-
const
|
|
308717
|
-
|
|
308718
|
-
|
|
308719
|
-
timestamp: Date.now()
|
|
309156
|
+
try {
|
|
309157
|
+
const response = await exchange.setLeverage?.(payload.leverage, payload.symbol, {
|
|
309158
|
+
marginMode: payload.marginMode ?? "cross",
|
|
309159
|
+
...payload.params
|
|
308720
309160
|
});
|
|
308721
|
-
|
|
308722
|
-
|
|
308723
|
-
|
|
308724
|
-
|
|
308725
|
-
|
|
309161
|
+
ctx.wrappedCallback(null, {
|
|
309162
|
+
result: JSON.stringify({
|
|
309163
|
+
exchange: normalizedCex,
|
|
309164
|
+
symbol: payload.symbol,
|
|
309165
|
+
leverage: payload.leverage,
|
|
309166
|
+
marginMode: payload.marginMode ?? "cross",
|
|
309167
|
+
response
|
|
309168
|
+
})
|
|
309169
|
+
});
|
|
309170
|
+
} catch (error48) {
|
|
309171
|
+
safeLogError(`SetPerpConfigState failed for ${cex3}`, error48);
|
|
309172
|
+
ctx.wrappedCallback({
|
|
309173
|
+
code: grpc8.status.INTERNAL,
|
|
309174
|
+
message: `SetPerpConfigState failed: ${sanitizeErrorDetail(error48)}`
|
|
309175
|
+
}, null);
|
|
308726
309176
|
}
|
|
308727
|
-
|
|
308728
|
-
|
|
308729
|
-
|
|
308730
|
-
|
|
308731
|
-
let message;
|
|
308732
|
-
try {
|
|
308733
|
-
const decodedData = decodeMessageData(data);
|
|
308734
|
-
message = typeof decodedData === "string" ? JSON.parse(decodedData) : decodedData;
|
|
308735
|
-
} catch (error48) {
|
|
308736
|
-
this.fail(error48 instanceof Error ? error48 : new Error("Invalid Binance user-data message"));
|
|
308737
|
-
return;
|
|
308738
|
-
}
|
|
308739
|
-
if ("id" in message && message.id === this.requestId) {
|
|
308740
|
-
if (message.status !== 200) {
|
|
308741
|
-
this.fail(new Error(message.error?.msg ?? message.error?.message ?? `Binance user-data subscription failed with status ${message.status}`));
|
|
308742
|
-
return;
|
|
308743
|
-
}
|
|
308744
|
-
this.subscriptionId = message.result?.subscriptionId ?? null;
|
|
308745
|
-
return;
|
|
308746
|
-
}
|
|
308747
|
-
if ("status" in message && typeof message.status === "number" && message.status !== 200) {
|
|
308748
|
-
const errorMessage = message.error?.msg ?? message.error?.message ?? `Binance user-data request failed with status ${message.status}`;
|
|
308749
|
-
const errorCode2 = message.error?.code;
|
|
308750
|
-
this.fail(new Error(typeof errorCode2 === "number" ? `${errorMessage} (code ${errorCode2})` : errorMessage));
|
|
308751
|
-
return;
|
|
308752
|
-
}
|
|
308753
|
-
if (!("event" in message) || !message.event) {
|
|
308754
|
-
return;
|
|
308755
|
-
}
|
|
308756
|
-
const subscriptionId = message.subscriptionId ?? this.subscriptionId;
|
|
308757
|
-
if (subscriptionId === null || subscriptionId === undefined) {
|
|
308758
|
-
return;
|
|
308759
|
-
}
|
|
308760
|
-
this.push({ subscriptionId, event: message.event });
|
|
309177
|
+
}
|
|
309178
|
+
async function handlePerpConfig(ctx) {
|
|
309179
|
+
if (ctx.action === Action.GetPerpConfigState) {
|
|
309180
|
+
return handleGetPerpConfigState(ctx);
|
|
308761
309181
|
}
|
|
308762
|
-
|
|
308763
|
-
|
|
308764
|
-
|
|
308765
|
-
|
|
308766
|
-
|
|
308767
|
-
|
|
308768
|
-
|
|
308769
|
-
|
|
309182
|
+
if (ctx.action === Action.SetPerpConfigState) {
|
|
309183
|
+
return handleSetPerpConfigState(ctx);
|
|
309184
|
+
}
|
|
309185
|
+
}
|
|
309186
|
+
|
|
309187
|
+
// src/handlers/execute-action/treasury-call.ts
|
|
309188
|
+
import * as grpc9 from "@grpc/grpc-js";
|
|
309189
|
+
async function handleTreasuryCall(ctx) {
|
|
309190
|
+
const { broker } = ctx;
|
|
309191
|
+
const callValue = parsePayloadForAction(ctx, CallPayloadSchema);
|
|
309192
|
+
if (callValue === null)
|
|
309193
|
+
return;
|
|
309194
|
+
let createOrderContext;
|
|
309195
|
+
let marketMetadataHash;
|
|
309196
|
+
try {
|
|
309197
|
+
if (callValue.functionName.startsWith("_") || callValue.functionName.includes("constructor") || callValue.functionName.includes("prototype")) {
|
|
309198
|
+
return ctx.wrappedCallback({
|
|
309199
|
+
code: grpc9.status.PERMISSION_DENIED,
|
|
309200
|
+
message: "Access to the requested function is denied"
|
|
309201
|
+
}, null);
|
|
308770
309202
|
}
|
|
308771
|
-
|
|
308772
|
-
|
|
308773
|
-
|
|
309203
|
+
const argsArray = callArgs(callValue.args, callValue.params ?? {});
|
|
309204
|
+
const treasuryDiscovery = await handleTreasuryDiscoveryCall(broker, callValue.functionName, callValue.args, callValue.params ?? {});
|
|
309205
|
+
if (treasuryDiscovery.handled) {
|
|
309206
|
+
return ctx.wrappedCallback(null, {
|
|
309207
|
+
proof: ctx.verity.proof,
|
|
309208
|
+
result: JSON.stringify(treasuryDiscovery.result)
|
|
309209
|
+
});
|
|
308774
309210
|
}
|
|
308775
|
-
|
|
308776
|
-
|
|
308777
|
-
|
|
308778
|
-
|
|
308779
|
-
|
|
308780
|
-
|
|
309211
|
+
const fn = broker[callValue.functionName];
|
|
309212
|
+
if (typeof fn !== "function" || broker.has?.[callValue.functionName] === false) {
|
|
309213
|
+
return ctx.wrappedCallback({
|
|
309214
|
+
code: grpc9.status.INVALID_ARGUMENT,
|
|
309215
|
+
message: `Function not found on broker: ${callValue.functionName}`
|
|
309216
|
+
}, null);
|
|
308781
309217
|
}
|
|
308782
|
-
if (
|
|
308783
|
-
|
|
309218
|
+
if (callValue.functionName === "createOrder") {
|
|
309219
|
+
const [symbol2, orderType, side, quantity, price] = callValue.args;
|
|
309220
|
+
const requestedQuantity = asFiniteNumber(quantity);
|
|
309221
|
+
const requestedPrice = asFiniteNumber(price);
|
|
309222
|
+
const requestedNotional = requestedQuantity !== undefined && requestedPrice !== undefined ? asFiniteNumber(requestedQuantity * requestedPrice) : undefined;
|
|
309223
|
+
const telemetryIds = extractOrderTelemetryIds(callValue.params);
|
|
309224
|
+
const submissionTimestamp = new Date().toISOString();
|
|
309225
|
+
createOrderContext = {
|
|
309226
|
+
action: "CreateOrder",
|
|
309227
|
+
cex: ctx.cex,
|
|
309228
|
+
accountLabel: ctx.selectedBrokerAccount?.label,
|
|
309229
|
+
symbol: asNonEmptyString(symbol2),
|
|
309230
|
+
orderType: asNonEmptyString(orderType),
|
|
309231
|
+
side: asNonEmptyString(side),
|
|
309232
|
+
requestedQuantity,
|
|
309233
|
+
requestedNotional,
|
|
309234
|
+
orderAuthor: callValue.orderAuthor,
|
|
309235
|
+
brokerObservedTimestamp: submissionTimestamp,
|
|
309236
|
+
...telemetryIds
|
|
309237
|
+
};
|
|
309238
|
+
if (createOrderContext.symbol !== undefined) {
|
|
309239
|
+
marketMetadataHash = await captureMarketMetadataSnapshot(ctx.brokerArchiver, broker, {
|
|
309240
|
+
exchange: ctx.cex,
|
|
309241
|
+
accountSelector: ctx.selectedBrokerAccount?.label,
|
|
309242
|
+
symbol: createOrderContext.symbol,
|
|
309243
|
+
action: "CreateOrder",
|
|
309244
|
+
brokerObservedTimestamp: submissionTimestamp,
|
|
309245
|
+
...telemetryIds
|
|
309246
|
+
});
|
|
309247
|
+
}
|
|
308784
309248
|
}
|
|
308785
|
-
|
|
308786
|
-
|
|
309249
|
+
const result = await fn.apply(broker, argsArray);
|
|
309250
|
+
if (createOrderContext !== undefined) {
|
|
309251
|
+
emitOrderExecutionTelemetryInBackground(ctx.otelMetrics, createOrderContext, result);
|
|
309252
|
+
archiveOrderExecutionInBackground(ctx.brokerArchiver, createOrderContext, result, undefined, { marketMetadataHash });
|
|
309253
|
+
} else if (callValue.functionName === "fetchWithdrawals") {
|
|
309254
|
+
archiveWithdrawalObservationsInBackground(ctx.brokerArchiver, ctx.withdrawalObservationTracker, {
|
|
309255
|
+
exchange: ctx.normalizedCex,
|
|
309256
|
+
accountSelector: ctx.selectedBrokerAccount?.label,
|
|
309257
|
+
transactions: result
|
|
309258
|
+
});
|
|
308787
309259
|
}
|
|
308788
|
-
|
|
308789
|
-
|
|
309260
|
+
ctx.wrappedCallback(null, {
|
|
309261
|
+
proof: ctx.verity.proof,
|
|
309262
|
+
result: JSON.stringify(result)
|
|
308790
309263
|
});
|
|
308791
|
-
}
|
|
308792
|
-
|
|
308793
|
-
|
|
308794
|
-
|
|
308795
|
-
|
|
308796
|
-
this.closeError = error48;
|
|
308797
|
-
this.closed = true;
|
|
308798
|
-
this.queue.length = 0;
|
|
308799
|
-
this.flushWaiters();
|
|
308800
|
-
try {
|
|
308801
|
-
this.ws.close();
|
|
308802
|
-
} catch {}
|
|
308803
|
-
}
|
|
308804
|
-
flushWaiters() {
|
|
308805
|
-
const error48 = this.closeError;
|
|
308806
|
-
for (const waiter of this.waiters.splice(0)) {
|
|
308807
|
-
if (error48) {
|
|
308808
|
-
waiter.reject(error48);
|
|
308809
|
-
} else {
|
|
308810
|
-
waiter.resolve(null);
|
|
308811
|
-
}
|
|
309264
|
+
} catch (error48) {
|
|
309265
|
+
if (createOrderContext !== undefined) {
|
|
309266
|
+
rethrowArchiveDurabilityError(error48);
|
|
309267
|
+
emitOrderExecutionTelemetryInBackground(ctx.otelMetrics, createOrderContext, undefined, error48);
|
|
309268
|
+
archiveOrderExecutionInBackground(ctx.brokerArchiver, createOrderContext, undefined, error48, { marketMetadataHash });
|
|
308812
309269
|
}
|
|
309270
|
+
safeLogError("Call failed", error48);
|
|
309271
|
+
rejectWithGrpcError(ctx, error48, {
|
|
309272
|
+
message: getErrorMessage(error48),
|
|
309273
|
+
preferStableMessageOnly: true,
|
|
309274
|
+
appendClassName: true
|
|
309275
|
+
});
|
|
308813
309276
|
}
|
|
308814
309277
|
}
|
|
308815
|
-
function
|
|
308816
|
-
return
|
|
308817
|
-
}
|
|
308818
|
-
function isBinanceOrderUserDataEvent(event) {
|
|
308819
|
-
return event.e === "executionReport" || event.e === "listStatus";
|
|
308820
|
-
}
|
|
308821
|
-
|
|
308822
|
-
// src/helpers/market-data-archive/ohlcv-bar-tracker.ts
|
|
308823
|
-
function isFiniteNumber(value) {
|
|
308824
|
-
return typeof value === "number" && Number.isFinite(value);
|
|
309278
|
+
function asNonEmptyString(value) {
|
|
309279
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
308825
309280
|
}
|
|
308826
|
-
function
|
|
308827
|
-
if (
|
|
308828
|
-
return
|
|
308829
|
-
}
|
|
308830
|
-
const [openTimeMs, open, high, low, close, volume, quoteVolume] = value;
|
|
308831
|
-
if (!isFiniteNumber(openTimeMs) || !isFiniteNumber(open) || !isFiniteNumber(high) || !isFiniteNumber(low) || !isFiniteNumber(close) || !isFiniteNumber(volume)) {
|
|
308832
|
-
return null;
|
|
309281
|
+
function asFiniteNumber(value) {
|
|
309282
|
+
if (typeof value === "number") {
|
|
309283
|
+
return Number.isFinite(value) ? value : undefined;
|
|
308833
309284
|
}
|
|
308834
|
-
|
|
308835
|
-
|
|
308836
|
-
open,
|
|
308837
|
-
high,
|
|
308838
|
-
low,
|
|
308839
|
-
close,
|
|
308840
|
-
volume
|
|
308841
|
-
};
|
|
308842
|
-
if (isFiniteNumber(quoteVolume)) {
|
|
308843
|
-
bar.quoteVolume = quoteVolume;
|
|
309285
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
309286
|
+
return;
|
|
308844
309287
|
}
|
|
308845
|
-
|
|
309288
|
+
const parsed = Number(value);
|
|
309289
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
308846
309290
|
}
|
|
308847
|
-
|
|
308848
|
-
|
|
308849
|
-
|
|
309291
|
+
|
|
309292
|
+
// src/handlers/execute-action/withdraw.ts
|
|
309293
|
+
import * as grpc10 from "@grpc/grpc-js";
|
|
309294
|
+
async function handleWithdraw(ctx) {
|
|
309295
|
+
const {
|
|
309296
|
+
call,
|
|
309297
|
+
wrappedCallback,
|
|
309298
|
+
policy,
|
|
309299
|
+
brokers,
|
|
309300
|
+
metadata,
|
|
309301
|
+
normalizedCex,
|
|
309302
|
+
cex: cex3,
|
|
309303
|
+
symbol: symbol2,
|
|
309304
|
+
selectedBrokerAccount,
|
|
309305
|
+
broker,
|
|
309306
|
+
verity,
|
|
309307
|
+
applyVerityToBroker,
|
|
309308
|
+
useVerity,
|
|
309309
|
+
verityProverUrl,
|
|
309310
|
+
otelMetrics,
|
|
309311
|
+
brokerArchiver
|
|
309312
|
+
} = ctx;
|
|
309313
|
+
const verityProof = verity.proof;
|
|
309314
|
+
if (!symbol2) {
|
|
309315
|
+
return ctx.wrappedCallback({
|
|
309316
|
+
code: grpc10.status.INVALID_ARGUMENT,
|
|
309317
|
+
message: `ValidationError: Symbol required`
|
|
309318
|
+
}, null);
|
|
308850
309319
|
}
|
|
308851
|
-
const
|
|
308852
|
-
|
|
308853
|
-
|
|
308854
|
-
|
|
308855
|
-
|
|
308856
|
-
|
|
308857
|
-
|
|
309320
|
+
const transferValue = parsePayloadForAction(ctx, WithdrawPayloadSchema);
|
|
309321
|
+
if (transferValue === null)
|
|
309322
|
+
return;
|
|
309323
|
+
let withdrawNetwork;
|
|
309324
|
+
try {
|
|
309325
|
+
withdrawNetwork = await resolveTransferNetwork(broker, symbol2, transferValue.chain);
|
|
309326
|
+
} catch (error48) {
|
|
309327
|
+
const message = getErrorMessage(error48);
|
|
309328
|
+
return ctx.wrappedCallback({
|
|
309329
|
+
code: stableGrpcErrorCode(message) ?? grpc10.status.INVALID_ARGUMENT,
|
|
309330
|
+
message
|
|
309331
|
+
}, null);
|
|
308858
309332
|
}
|
|
308859
|
-
|
|
308860
|
-
|
|
308861
|
-
|
|
308862
|
-
|
|
308863
|
-
|
|
308864
|
-
|
|
308865
|
-
const bars = extractOhlcvBars(payload);
|
|
308866
|
-
if (bars.length === 0) {
|
|
308867
|
-
return [];
|
|
308868
|
-
}
|
|
308869
|
-
if (bars.length === 1) {
|
|
308870
|
-
const [bar] = bars;
|
|
308871
|
-
return bar ? this.processSingleBar(bar, brokerVersion) : [];
|
|
308872
|
-
}
|
|
308873
|
-
return this.processBatch(bars, brokerVersion);
|
|
309333
|
+
const transferValidation = validateWithdraw(policy, cex3, withdrawNetwork.brokerNetworkId, transferValue.recipientAddress, transferValue.amount, symbol2);
|
|
309334
|
+
if (!transferValidation.valid) {
|
|
309335
|
+
return ctx.wrappedCallback({
|
|
309336
|
+
code: grpc10.status.PERMISSION_DENIED,
|
|
309337
|
+
message: `policy_withdrawal_denied: ${transferValidation.error}`
|
|
309338
|
+
}, null);
|
|
308874
309339
|
}
|
|
308875
|
-
|
|
308876
|
-
|
|
308877
|
-
|
|
308878
|
-
|
|
308879
|
-
|
|
308880
|
-
|
|
308881
|
-
candidates.push({
|
|
308882
|
-
bar: this.lastBar,
|
|
308883
|
-
isClosed: true,
|
|
308884
|
-
brokerVersion
|
|
308885
|
-
});
|
|
308886
|
-
}
|
|
308887
|
-
candidates.push({
|
|
308888
|
-
bar: currentBar,
|
|
308889
|
-
isClosed: false,
|
|
308890
|
-
brokerVersion
|
|
308891
|
-
});
|
|
308892
|
-
this.lastOpenTimeMs = currentBar.openTimeMs;
|
|
308893
|
-
this.lastBar = currentBar;
|
|
308894
|
-
return candidates;
|
|
309340
|
+
const travelRule = resolveTravelRuleDecision(policy, cex3, transferValue.recipientAddress);
|
|
309341
|
+
if (travelRule.mode === "denied") {
|
|
309342
|
+
return ctx.wrappedCallback({
|
|
309343
|
+
code: grpc10.status.FAILED_PRECONDITION,
|
|
309344
|
+
message: `travel_rule_denied: ${travelRule.error}`
|
|
309345
|
+
}, null);
|
|
308895
309346
|
}
|
|
308896
|
-
|
|
308897
|
-
|
|
308898
|
-
|
|
308899
|
-
|
|
308900
|
-
|
|
308901
|
-
|
|
308902
|
-
|
|
308903
|
-
|
|
308904
|
-
|
|
308905
|
-
|
|
308906
|
-
|
|
308907
|
-
|
|
308908
|
-
|
|
308909
|
-
|
|
308910
|
-
|
|
308911
|
-
|
|
308912
|
-
|
|
308913
|
-
|
|
308914
|
-
|
|
308915
|
-
|
|
308916
|
-
|
|
308917
|
-
|
|
308918
|
-
|
|
308919
|
-
|
|
308920
|
-
|
|
308921
|
-
|
|
308922
|
-
|
|
308923
|
-
|
|
308924
|
-
|
|
308925
|
-
|
|
308926
|
-
|
|
308927
|
-
|
|
309347
|
+
const withdrawOrderId = transferValue.params.withdrawOrderId;
|
|
309348
|
+
const clientWithdrawalId = typeof withdrawOrderId === "string" && withdrawOrderId.length > 0 ? withdrawOrderId : undefined;
|
|
309349
|
+
try {
|
|
309350
|
+
const transaction = travelRule.mode === "localentity" ? await withdrawViaLocalEntity(broker, {
|
|
309351
|
+
code: symbol2,
|
|
309352
|
+
amount: transferValue.amount,
|
|
309353
|
+
address: transferValue.recipientAddress,
|
|
309354
|
+
network: withdrawNetwork.exchangeNetworkId,
|
|
309355
|
+
questionnaire: travelRule.questionnaire,
|
|
309356
|
+
params: transferValue.params
|
|
309357
|
+
}) : await broker.withdraw(symbol2, transferValue.amount, transferValue.recipientAddress, undefined, {
|
|
309358
|
+
...transferValue.params ?? {},
|
|
309359
|
+
network: withdrawNetwork.exchangeNetworkId
|
|
309360
|
+
});
|
|
309361
|
+
log.info(`Withdraw Result: ${JSON.stringify(transaction)}`);
|
|
309362
|
+
const normalized = normalizeCcxtTransactionForArchive(transaction);
|
|
309363
|
+
archiveTransferEventInBackground(brokerArchiver, {
|
|
309364
|
+
exchange: cex3,
|
|
309365
|
+
accountSelector: selectedBrokerAccount?.label,
|
|
309366
|
+
assetSymbol: normalized.assetSymbol ?? symbol2,
|
|
309367
|
+
transfer: {
|
|
309368
|
+
eventKind: "withdrawal",
|
|
309369
|
+
lifecycleAction: "submit_withdrawal",
|
|
309370
|
+
status: normalized.status,
|
|
309371
|
+
amount: normalized.amount ?? String(transferValue.amount),
|
|
309372
|
+
address: normalized.address ?? transferValue.recipientAddress,
|
|
309373
|
+
network: normalized.network ?? withdrawNetwork.exchangeNetworkId,
|
|
309374
|
+
externalId: normalized.externalId,
|
|
309375
|
+
clientWithdrawalId,
|
|
309376
|
+
txid: normalized.txid,
|
|
309377
|
+
feeAmount: normalized.feeAmount,
|
|
309378
|
+
feeCurrency: normalized.feeCurrency,
|
|
309379
|
+
exchangeTimestamp: normalized.exchangeTimestamp,
|
|
309380
|
+
payload: transaction
|
|
309381
|
+
}
|
|
309382
|
+
});
|
|
309383
|
+
ctx.wrappedCallback(null, {
|
|
309384
|
+
proof: ctx.verity.proof,
|
|
309385
|
+
result: JSON.stringify({
|
|
309386
|
+
...transaction,
|
|
309387
|
+
operatorAlias: withdrawNetwork.operatorAlias,
|
|
309388
|
+
brokerNetworkId: withdrawNetwork.brokerNetworkId,
|
|
309389
|
+
exchangeNetworkId: withdrawNetwork.exchangeNetworkId
|
|
309390
|
+
})
|
|
309391
|
+
});
|
|
309392
|
+
} catch (error48) {
|
|
309393
|
+
safeLogError("Withdraw failed", error48);
|
|
309394
|
+
archiveTransferEventInBackground(brokerArchiver, {
|
|
309395
|
+
exchange: cex3,
|
|
309396
|
+
accountSelector: selectedBrokerAccount?.label,
|
|
309397
|
+
assetSymbol: symbol2,
|
|
309398
|
+
transfer: {
|
|
309399
|
+
eventKind: "withdrawal",
|
|
309400
|
+
lifecycleAction: "submit_withdrawal",
|
|
309401
|
+
status: "failed",
|
|
309402
|
+
amount: String(transferValue.amount),
|
|
309403
|
+
address: transferValue.recipientAddress,
|
|
309404
|
+
network: withdrawNetwork.exchangeNetworkId,
|
|
309405
|
+
clientWithdrawalId,
|
|
309406
|
+
errorSummary: getErrorMessage(error48),
|
|
309407
|
+
payload: { recipientAddress: transferValue.recipientAddress }
|
|
308928
309408
|
}
|
|
308929
|
-
}
|
|
308930
|
-
candidates.push({
|
|
308931
|
-
bar: lastBarToProcess,
|
|
308932
|
-
isClosed: false,
|
|
308933
|
-
brokerVersion
|
|
308934
309409
|
});
|
|
308935
|
-
|
|
308936
|
-
|
|
308937
|
-
|
|
308938
|
-
|
|
308939
|
-
}
|
|
308940
|
-
|
|
308941
|
-
// src/helpers/market-data-archive/orderbook-sampler.ts
|
|
308942
|
-
var DEFAULT_ORDERBOOK_INTERVAL_MS = 1000;
|
|
308943
|
-
function getOrderbookIntervalMs() {
|
|
308944
|
-
const raw = process.env.CEX_BROKER_ORDERBOOK_INTERVAL_MS ?? process.env.CEX_BROKER_ORDERBOOK_TOB_INTERVAL_MS;
|
|
308945
|
-
if (!raw) {
|
|
308946
|
-
return DEFAULT_ORDERBOOK_INTERVAL_MS;
|
|
309410
|
+
const code = mapCcxtErrorToGrpcStatus(error48) ?? grpc10.status.INTERNAL;
|
|
309411
|
+
ctx.wrappedCallback({
|
|
309412
|
+
code,
|
|
309413
|
+
message: `Withdraw failed: ${sanitizeErrorDetail(error48)}`
|
|
309414
|
+
}, null);
|
|
308947
309415
|
}
|
|
308948
|
-
const parsed = Number.parseInt(raw, 10);
|
|
308949
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_ORDERBOOK_INTERVAL_MS;
|
|
308950
|
-
}
|
|
308951
|
-
function isMarketArchiveEnabled() {
|
|
308952
|
-
return process.env.CEX_BROKER_MARKET_ARCHIVE_ENABLED !== "false";
|
|
308953
309416
|
}
|
|
308954
309417
|
|
|
308955
|
-
|
|
308956
|
-
|
|
308957
|
-
|
|
308958
|
-
|
|
308959
|
-
|
|
308960
|
-
|
|
308961
|
-
|
|
308962
|
-
|
|
308963
|
-
|
|
308964
|
-
|
|
308965
|
-
|
|
308966
|
-
|
|
308967
|
-
|
|
308968
|
-
|
|
308969
|
-
|
|
308970
|
-
|
|
309418
|
+
// src/handlers/execute-action/registry.ts
|
|
309419
|
+
var ACTION_HANDLERS = {
|
|
309420
|
+
[Action.Deposit]: handleDeposit,
|
|
309421
|
+
[Action.Withdraw]: handleWithdraw,
|
|
309422
|
+
[Action.Call]: handleTreasuryCall,
|
|
309423
|
+
[Action.InternalTransfer]: handleInternalTransfer,
|
|
309424
|
+
[Action.CreateOrder]: handleOrders,
|
|
309425
|
+
[Action.GetOrderDetails]: handleOrders,
|
|
309426
|
+
[Action.CancelOrder]: handleOrders,
|
|
309427
|
+
[Action.FetchCurrency]: handlePassThrough,
|
|
309428
|
+
[Action.FetchAccountId]: handlePassThrough,
|
|
309429
|
+
[Action.FetchFees]: handlePassThrough,
|
|
309430
|
+
[Action.FetchDepositAddresses]: handlePassThrough,
|
|
309431
|
+
[Action.FetchBalances]: handlePassThrough,
|
|
309432
|
+
[Action.FetchTicker]: handlePassThrough,
|
|
309433
|
+
[Action.GetPerpConfigState]: handlePerpConfig,
|
|
309434
|
+
[Action.SetPerpConfigState]: handlePerpConfig
|
|
309435
|
+
};
|
|
309436
|
+
async function dispatchExecuteAction(ctx) {
|
|
309437
|
+
const handler = ACTION_HANDLERS[ctx.action];
|
|
309438
|
+
if (!handler) {
|
|
309439
|
+
ctx.wrappedCallback({
|
|
309440
|
+
code: grpc11.status.INVALID_ARGUMENT,
|
|
309441
|
+
message: "Invalid Action"
|
|
309442
|
+
}, null);
|
|
309443
|
+
return;
|
|
308971
309444
|
}
|
|
309445
|
+
await handler(ctx);
|
|
308972
309446
|
}
|
|
308973
309447
|
|
|
308974
|
-
// src/
|
|
308975
|
-
function
|
|
308976
|
-
|
|
308977
|
-
|
|
308978
|
-
|
|
308979
|
-
if (isFiniteNumber2(value)) {
|
|
308980
|
-
return value;
|
|
308981
|
-
}
|
|
308982
|
-
if (typeof value === "string") {
|
|
308983
|
-
const parsed = Number.parseFloat(value);
|
|
308984
|
-
if (Number.isFinite(parsed)) {
|
|
308985
|
-
return parsed;
|
|
308986
|
-
}
|
|
308987
|
-
}
|
|
308988
|
-
return;
|
|
308989
|
-
}
|
|
308990
|
-
function toStringId(value) {
|
|
308991
|
-
if (typeof value === "string" && value.trim()) {
|
|
308992
|
-
return value.trim();
|
|
308993
|
-
}
|
|
308994
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
308995
|
-
return String(value);
|
|
308996
|
-
}
|
|
308997
|
-
return;
|
|
308998
|
-
}
|
|
308999
|
-
function scalarTimestampMs(value, fallbackMs) {
|
|
309000
|
-
const numeric = toNumber2(value);
|
|
309001
|
-
if (numeric !== undefined) {
|
|
309002
|
-
return numeric < 1000000000000 ? numeric * 1000 : numeric;
|
|
309003
|
-
}
|
|
309004
|
-
if (typeof value === "string") {
|
|
309005
|
-
const parsed = Date.parse(value);
|
|
309006
|
-
if (Number.isFinite(parsed)) {
|
|
309007
|
-
return parsed;
|
|
309008
|
-
}
|
|
309009
|
-
}
|
|
309010
|
-
return fallbackMs;
|
|
309011
|
-
}
|
|
309012
|
-
function parseTrade(value, fallbackMs = Date.now()) {
|
|
309013
|
-
const record2 = asRecord(value);
|
|
309014
|
-
if (!record2) {
|
|
309015
|
-
return null;
|
|
309016
|
-
}
|
|
309017
|
-
const tradeId = toStringId(record2.id);
|
|
309018
|
-
const price = toNumber2(record2.price);
|
|
309019
|
-
const amount = toNumber2(record2.amount);
|
|
309020
|
-
const side = typeof record2.side === "string" ? record2.side.toLowerCase() : undefined;
|
|
309021
|
-
if (!tradeId || price === undefined || amount === undefined || !side) {
|
|
309022
|
-
return null;
|
|
309023
|
-
}
|
|
309024
|
-
const parsed = {
|
|
309025
|
-
tradeId,
|
|
309026
|
-
eventTimeMs: scalarTimestampMs(record2.timestamp, fallbackMs),
|
|
309027
|
-
side,
|
|
309028
|
-
price,
|
|
309029
|
-
amount
|
|
309030
|
-
};
|
|
309031
|
-
const cost = toNumber2(record2.cost);
|
|
309032
|
-
if (cost !== undefined) {
|
|
309033
|
-
parsed.cost = cost;
|
|
309034
|
-
}
|
|
309035
|
-
if (typeof record2.takerOrMaker === "string") {
|
|
309036
|
-
parsed.takerOrMaker = record2.takerOrMaker;
|
|
309037
|
-
}
|
|
309038
|
-
return parsed;
|
|
309039
|
-
}
|
|
309040
|
-
function extractTrades(payload, fallbackMs = Date.now()) {
|
|
309041
|
-
if (Array.isArray(payload)) {
|
|
309042
|
-
return payload.map((entry) => parseTrade(entry, fallbackMs)).filter((entry) => entry !== null);
|
|
309043
|
-
}
|
|
309044
|
-
const single = parseTrade(payload, fallbackMs);
|
|
309045
|
-
return single ? [single] : [];
|
|
309448
|
+
// src/handlers/execute-action/handler.ts
|
|
309449
|
+
function isPublicMarketDataAction(action, payload) {
|
|
309450
|
+
if (action !== Action.Call)
|
|
309451
|
+
return false;
|
|
309452
|
+
return isOrderBookCallMethod(payload?.method ?? payload?.functionName);
|
|
309046
309453
|
}
|
|
309047
|
-
function
|
|
309048
|
-
const
|
|
309049
|
-
|
|
309050
|
-
|
|
309051
|
-
|
|
309052
|
-
|
|
309053
|
-
|
|
309054
|
-
|
|
309055
|
-
|
|
309056
|
-
|
|
309057
|
-
|
|
309058
|
-
|
|
309059
|
-
|
|
309060
|
-
|
|
309061
|
-
|
|
309062
|
-
|
|
309063
|
-
|
|
309064
|
-
|
|
309065
|
-
|
|
309066
|
-
|
|
309067
|
-
|
|
309068
|
-
|
|
309069
|
-
|
|
309070
|
-
|
|
309071
|
-
|
|
309454
|
+
function createExecuteActionHandler(deps) {
|
|
309455
|
+
const {
|
|
309456
|
+
policy,
|
|
309457
|
+
brokers,
|
|
309458
|
+
whitelistIps,
|
|
309459
|
+
useVerity,
|
|
309460
|
+
verityProverUrl,
|
|
309461
|
+
otelMetrics,
|
|
309462
|
+
brokerArchiver,
|
|
309463
|
+
orderActivityTracker
|
|
309464
|
+
} = deps;
|
|
309465
|
+
const withdrawalObservationTracker = deps.withdrawalObservationTracker ?? new WithdrawalObservationTracker;
|
|
309466
|
+
return async (call, callback) => {
|
|
309467
|
+
const startTime = Date.now();
|
|
309468
|
+
const { action: rawAction, cex: cex3, symbol: symbol2 } = call.request;
|
|
309469
|
+
const action = resolveAction(rawAction);
|
|
309470
|
+
let actionCompleted = false;
|
|
309471
|
+
const wrappedCallback = (error48, value) => {
|
|
309472
|
+
if (!actionCompleted) {
|
|
309473
|
+
actionCompleted = true;
|
|
309474
|
+
const latency = Date.now() - startTime;
|
|
309475
|
+
const actionName = getActionName(action);
|
|
309476
|
+
otelMetrics?.recordHistogram("execute_action_duration_ms", latency, {
|
|
309477
|
+
action: actionName,
|
|
309478
|
+
cex: cex3 || "unknown"
|
|
309479
|
+
});
|
|
309480
|
+
if (error48) {
|
|
309481
|
+
otelMetrics?.recordCounter("execute_action_errors_total", 1, {
|
|
309482
|
+
action: actionName,
|
|
309483
|
+
cex: cex3 || "unknown",
|
|
309484
|
+
error_type: error48.code ? grpc12.status[error48.code] || "unknown" : "unknown"
|
|
309485
|
+
});
|
|
309486
|
+
} else {
|
|
309487
|
+
otelMetrics?.recordCounter("execute_action_success_total", 1, {
|
|
309488
|
+
action: actionName,
|
|
309489
|
+
cex: cex3 || "unknown"
|
|
309490
|
+
});
|
|
309491
|
+
}
|
|
309492
|
+
}
|
|
309493
|
+
callback(error48, value);
|
|
309494
|
+
};
|
|
309495
|
+
try {
|
|
309496
|
+
log.info(`Request - ExecuteAction:`, { action, cex: cex3, symbol: symbol2 });
|
|
309497
|
+
otelMetrics?.recordCounter("execute_action_requests_total", 1, {
|
|
309498
|
+
action: getActionName(action),
|
|
309499
|
+
cex: cex3 || "unknown"
|
|
309500
|
+
});
|
|
309501
|
+
if (!authenticateRequest(call, whitelistIps)) {
|
|
309502
|
+
return wrappedCallback({
|
|
309503
|
+
code: grpc12.status.PERMISSION_DENIED,
|
|
309504
|
+
message: "Access denied: Unauthorized IP"
|
|
309505
|
+
}, null);
|
|
309506
|
+
}
|
|
309507
|
+
if (!action || !cex3) {
|
|
309508
|
+
return wrappedCallback({
|
|
309509
|
+
code: grpc12.status.INVALID_ARGUMENT,
|
|
309510
|
+
message: "`action` AND `cex` fields are required"
|
|
309511
|
+
}, null);
|
|
309512
|
+
}
|
|
309513
|
+
const normalizedCex = cex3.trim().toLowerCase();
|
|
309514
|
+
const metadata = call.metadata;
|
|
309515
|
+
const selectedBrokerAccount = selectBrokerAccountForCex(normalizedCex, brokers, metadata);
|
|
309516
|
+
const broker = selectedBrokerAccount?.exchange ?? createBroker(normalizedCex, call.metadata) ?? (isPublicMarketDataAction(action, call.request.payload) ? createPublicBroker(normalizedCex) : null);
|
|
309517
|
+
if (!broker) {
|
|
309518
|
+
return wrappedCallback({
|
|
309519
|
+
code: grpc12.status.UNAUTHENTICATED,
|
|
309520
|
+
message: `This Exchange is not registered and No API metadata was found`
|
|
309521
|
+
}, null);
|
|
309522
|
+
}
|
|
309523
|
+
const verity = { proof: "" };
|
|
309524
|
+
const applyVerityToBroker = (targetBroker) => {
|
|
309525
|
+
if (!useVerity)
|
|
309526
|
+
return;
|
|
309527
|
+
const override = buildHttpClientOverrideFromMetadata(metadata, verityProverUrl, (proof, notaryPubKey) => {
|
|
309528
|
+
verity.proof = proof;
|
|
309529
|
+
log.debug(`Verity proof:`, { proof, notaryPubKey });
|
|
309530
|
+
});
|
|
309531
|
+
targetBroker.setHttpClientOverride(override, verityHttpClientOverridePredicate);
|
|
309532
|
+
};
|
|
309533
|
+
const preludeCtx = {
|
|
309534
|
+
call,
|
|
309535
|
+
wrappedCallback,
|
|
309536
|
+
action,
|
|
309537
|
+
policy,
|
|
309538
|
+
brokers,
|
|
309539
|
+
metadata,
|
|
309540
|
+
normalizedCex,
|
|
309541
|
+
cex: cex3,
|
|
309542
|
+
symbol: symbol2,
|
|
309543
|
+
selectedBrokerAccount,
|
|
309544
|
+
broker,
|
|
309545
|
+
verity,
|
|
309546
|
+
applyVerityToBroker,
|
|
309547
|
+
useVerity,
|
|
309548
|
+
verityProverUrl,
|
|
309549
|
+
otelMetrics,
|
|
309550
|
+
brokerArchiver,
|
|
309551
|
+
orderActivityTracker,
|
|
309552
|
+
withdrawalObservationTracker
|
|
309553
|
+
};
|
|
309554
|
+
if (action === Action.Call) {
|
|
309555
|
+
const handled = await handleOrderBookCall(preludeCtx);
|
|
309556
|
+
if (handled)
|
|
309557
|
+
return;
|
|
309558
|
+
}
|
|
309559
|
+
applyVerityToBroker(broker);
|
|
309560
|
+
const ctx = { ...preludeCtx, broker };
|
|
309561
|
+
await dispatchExecuteAction(ctx);
|
|
309562
|
+
} catch (error48) {
|
|
309563
|
+
safeLogError("ExecuteAction unhandled error", error48);
|
|
309564
|
+
return wrappedCallback({
|
|
309565
|
+
code: grpc12.status.INTERNAL,
|
|
309566
|
+
message: "ExecuteAction failed unexpectedly"
|
|
309567
|
+
}, null);
|
|
309072
309568
|
}
|
|
309073
|
-
}
|
|
309074
|
-
return parsed;
|
|
309569
|
+
};
|
|
309075
309570
|
}
|
|
309076
|
-
|
|
309077
|
-
|
|
309078
|
-
|
|
309079
|
-
|
|
309080
|
-
|
|
309081
|
-
|
|
309082
|
-
|
|
309083
|
-
|
|
309571
|
+
// src/handlers/subscribe/broker-lifecycle.ts
|
|
309572
|
+
class SubscribeBrokerLifecycle {
|
|
309573
|
+
#brokers = new Map;
|
|
309574
|
+
#closing = new Map;
|
|
309575
|
+
#shuttingDown = false;
|
|
309576
|
+
register(broker, context2) {
|
|
309577
|
+
this.#brokers.set(broker, context2);
|
|
309578
|
+
if (this.#shuttingDown) {
|
|
309579
|
+
this.close(broker);
|
|
309580
|
+
}
|
|
309084
309581
|
}
|
|
309085
|
-
|
|
309086
|
-
|
|
309087
|
-
|
|
309582
|
+
close(broker) {
|
|
309583
|
+
const existing = this.#closing.get(broker);
|
|
309584
|
+
if (existing) {
|
|
309585
|
+
return existing;
|
|
309586
|
+
}
|
|
309587
|
+
const context2 = this.#brokers.get(broker) ?? {
|
|
309588
|
+
cex: "unknown",
|
|
309589
|
+
symbol: "unknown"
|
|
309590
|
+
};
|
|
309591
|
+
this.#brokers.delete(broker);
|
|
309592
|
+
const closing = (async () => {
|
|
309593
|
+
try {
|
|
309594
|
+
await broker.close();
|
|
309595
|
+
log.debug("Request-scoped Subscribe broker closed", context2);
|
|
309596
|
+
return "closed";
|
|
309597
|
+
} catch (error48) {
|
|
309598
|
+
log.warn("Failed to close request-scoped Subscribe broker", {
|
|
309599
|
+
...context2,
|
|
309600
|
+
error: error48
|
|
309601
|
+
});
|
|
309602
|
+
return "failed";
|
|
309603
|
+
} finally {
|
|
309604
|
+
this.#closing.delete(broker);
|
|
309605
|
+
}
|
|
309606
|
+
})();
|
|
309607
|
+
this.#closing.set(broker, closing);
|
|
309608
|
+
return closing;
|
|
309088
309609
|
}
|
|
309089
|
-
|
|
309090
|
-
|
|
309091
|
-
|
|
309092
|
-
|
|
309093
|
-
|
|
309094
|
-
|
|
309095
|
-
|
|
309096
|
-
|
|
309610
|
+
async closeAll() {
|
|
309611
|
+
this.#shuttingDown = true;
|
|
309612
|
+
let failed = 0;
|
|
309613
|
+
while (this.#brokers.size > 0 || this.#closing.size > 0) {
|
|
309614
|
+
const inFlight = [...this.#closing.values()];
|
|
309615
|
+
const fresh = [...this.#brokers.keys()].map((broker) => this.close(broker));
|
|
309616
|
+
const outcomes = await Promise.all([...fresh, ...inFlight]);
|
|
309617
|
+
failed += outcomes.filter((outcome) => outcome === "failed").length;
|
|
309097
309618
|
}
|
|
309098
|
-
|
|
309099
|
-
|
|
309100
|
-
if (price === undefined || size === undefined || !Number.isFinite(price) || !Number.isFinite(size)) {
|
|
309101
|
-
continue;
|
|
309619
|
+
if (failed > 0) {
|
|
309620
|
+
throw new Error(`${failed} request-scoped Subscribe broker(s) failed to close`);
|
|
309102
309621
|
}
|
|
309103
|
-
prices.push(price);
|
|
309104
|
-
sizes.push(size);
|
|
309105
309622
|
}
|
|
309106
|
-
return { prices, sizes };
|
|
309107
309623
|
}
|
|
309624
|
+
// src/handlers/subscribe/handler.ts
|
|
309625
|
+
import * as grpc13 from "@grpc/grpc-js";
|
|
309108
309626
|
|
|
309109
|
-
// src/helpers/
|
|
309110
|
-
|
|
309111
|
-
|
|
309112
|
-
|
|
309113
|
-
|
|
309114
|
-
|
|
309115
|
-
|
|
309116
|
-
|
|
309117
|
-
|
|
309118
|
-
|
|
309119
|
-
|
|
309120
|
-
if (Number.isFinite(numeric)) {
|
|
309121
|
-
return numeric;
|
|
309122
|
-
}
|
|
309123
|
-
}
|
|
309124
|
-
const parsed = Date.parse(value);
|
|
309125
|
-
if (Number.isFinite(parsed)) {
|
|
309126
|
-
return parsed;
|
|
309127
|
-
}
|
|
309627
|
+
// src/helpers/binance-user-data-stream.ts
|
|
309628
|
+
import { Buffer as Buffer2 } from "node:buffer";
|
|
309629
|
+
import { createHmac } from "node:crypto";
|
|
309630
|
+
var BINANCE_SPOT_WS_API_URL = "wss://ws-api.binance.com:443/ws-api/v3";
|
|
309631
|
+
var DEFAULT_BINANCE_USER_DATA_MAX_BUFFERED_EVENTS = 16;
|
|
309632
|
+
var createWebSocket = (url3) => new wrapper_default(url3);
|
|
309633
|
+
var userDataRequestCounter = 0;
|
|
309634
|
+
function getExchangeString(exchange, key) {
|
|
309635
|
+
const value = exchange[key];
|
|
309636
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
309637
|
+
throw new Error(`Binance user-data stream requires exchange.${key}`);
|
|
309128
309638
|
}
|
|
309129
|
-
return
|
|
309639
|
+
return value;
|
|
309130
309640
|
}
|
|
309131
|
-
function
|
|
309132
|
-
|
|
309133
|
-
return value;
|
|
309134
|
-
}
|
|
309135
|
-
if (typeof value === "string" && /^\d+$/.test(value)) {
|
|
309136
|
-
return Number.parseInt(value, 10);
|
|
309137
|
-
}
|
|
309138
|
-
return;
|
|
309641
|
+
function sortedQuery(params) {
|
|
309642
|
+
return Object.entries(params).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`).join("&");
|
|
309139
309643
|
}
|
|
309140
|
-
function
|
|
309141
|
-
const
|
|
309142
|
-
if (
|
|
309143
|
-
return
|
|
309144
|
-
}
|
|
309145
|
-
const price = level[0];
|
|
309146
|
-
const size = level[1];
|
|
309147
|
-
if (price === undefined || size === undefined || !Number.isFinite(price) || !Number.isFinite(size)) {
|
|
309148
|
-
return null;
|
|
309644
|
+
function signUserDataStreamParams(exchange, params) {
|
|
309645
|
+
const signParams = exchange.signParams;
|
|
309646
|
+
if (typeof signParams === "function") {
|
|
309647
|
+
return signParams.call(exchange, params);
|
|
309149
309648
|
}
|
|
309150
|
-
|
|
309649
|
+
const secret = getExchangeString(exchange, "secret");
|
|
309650
|
+
return {
|
|
309651
|
+
...params,
|
|
309652
|
+
signature: createHmac("sha256", secret).update(sortedQuery(params)).digest("hex")
|
|
309653
|
+
};
|
|
309151
309654
|
}
|
|
309152
|
-
function
|
|
309153
|
-
|
|
309154
|
-
|
|
309155
|
-
}
|
|
309156
|
-
const mid = (bestBid + bestAsk) / 2;
|
|
309157
|
-
if (mid <= 0) {
|
|
309158
|
-
return 0;
|
|
309159
|
-
}
|
|
309160
|
-
return (bestAsk - bestBid) / mid * 1e4;
|
|
309655
|
+
function getBinanceSpotWsApiUrl(exchange) {
|
|
309656
|
+
const urls = exchange.urls;
|
|
309657
|
+
return urls?.api?.ws?.["ws-api"]?.spot ?? BINANCE_SPOT_WS_API_URL;
|
|
309161
309658
|
}
|
|
309162
|
-
function
|
|
309163
|
-
return
|
|
309164
|
-
deploymentId: input.deploymentId,
|
|
309165
|
-
accountSelector: input.accountSelector,
|
|
309166
|
-
exchange: input.exchange,
|
|
309167
|
-
symbol: input.symbol,
|
|
309168
|
-
brokerObservedTimestamp: new Date(receivedTimeMs).toISOString()
|
|
309169
|
-
});
|
|
309659
|
+
function getRecord(value) {
|
|
309660
|
+
return typeof value === "object" && value !== null ? value : null;
|
|
309170
309661
|
}
|
|
309171
|
-
function
|
|
309172
|
-
|
|
309173
|
-
|
|
309174
|
-
if (!bid || !ask) {
|
|
309175
|
-
return null;
|
|
309662
|
+
function getMessage(value) {
|
|
309663
|
+
if (value instanceof Error) {
|
|
309664
|
+
return value.message;
|
|
309176
309665
|
}
|
|
309177
|
-
|
|
309178
|
-
|
|
309179
|
-
const asks = splitOrderBookSide(input.snapshot.asks, archiveDepthLimit);
|
|
309180
|
-
if (bids.prices.length === 0 || asks.prices.length === 0) {
|
|
309181
|
-
return null;
|
|
309666
|
+
if (typeof value === "string" && value.length > 0) {
|
|
309667
|
+
return value;
|
|
309182
309668
|
}
|
|
309183
|
-
const
|
|
309184
|
-
const
|
|
309185
|
-
|
|
309186
|
-
const sequence = parseSequence(input.snapshot.sequence);
|
|
309187
|
-
return {
|
|
309188
|
-
table: "market_data.orderbook_snapshots",
|
|
309189
|
-
row: compactUndefined3({
|
|
309190
|
-
...buildOrderbookArchiveTags(input, receivedTimeMs),
|
|
309191
|
-
asset_type: input.assetType,
|
|
309192
|
-
event_time_ms: eventTimeMs,
|
|
309193
|
-
received_time_ms: receivedTimeMs,
|
|
309194
|
-
best_bid: bid.price,
|
|
309195
|
-
best_ask: ask.price,
|
|
309196
|
-
bid_size: bid.size,
|
|
309197
|
-
ask_size: ask.size,
|
|
309198
|
-
mid,
|
|
309199
|
-
spread_bps: computeSpreadBps(bid.price, ask.price),
|
|
309200
|
-
depth_limit: archiveDepthLimit,
|
|
309201
|
-
bid_levels: bids.prices.length,
|
|
309202
|
-
ask_levels: asks.prices.length,
|
|
309203
|
-
bids_price: bids.prices,
|
|
309204
|
-
bids_size: bids.sizes,
|
|
309205
|
-
asks_price: asks.prices,
|
|
309206
|
-
asks_size: asks.sizes,
|
|
309207
|
-
sequence
|
|
309208
|
-
})
|
|
309209
|
-
};
|
|
309210
|
-
}
|
|
309211
|
-
function buildCandleRow(input) {
|
|
309212
|
-
const { context: context2, bar, isClosed, brokerVersion, receivedTimestamp } = input;
|
|
309213
|
-
const tags = buildCommonArchiveTags({
|
|
309214
|
-
deploymentId: context2.deploymentId,
|
|
309215
|
-
accountSelector: context2.accountSelector,
|
|
309216
|
-
exchange: context2.exchange,
|
|
309217
|
-
symbol: context2.symbol,
|
|
309218
|
-
brokerObservedTimestamp: new Date(receivedTimestamp).toISOString()
|
|
309219
|
-
});
|
|
309220
|
-
return {
|
|
309221
|
-
table: "market_data.candles",
|
|
309222
|
-
row: compactUndefined3({
|
|
309223
|
-
...tags,
|
|
309224
|
-
asset_type: context2.assetType,
|
|
309225
|
-
timeframe: context2.timeframe ?? "1m",
|
|
309226
|
-
open_time_ms: bar.openTimeMs,
|
|
309227
|
-
open: bar.open,
|
|
309228
|
-
high: bar.high,
|
|
309229
|
-
low: bar.low,
|
|
309230
|
-
close: bar.close,
|
|
309231
|
-
volume: bar.volume,
|
|
309232
|
-
quote_volume: bar.quoteVolume,
|
|
309233
|
-
is_closed: isClosed ? 1 : 0,
|
|
309234
|
-
broker_version: brokerVersion
|
|
309235
|
-
})
|
|
309236
|
-
};
|
|
309669
|
+
const record2 = getRecord(value);
|
|
309670
|
+
const message = record2?.message;
|
|
309671
|
+
return typeof message === "string" && message.length > 0 ? message : null;
|
|
309237
309672
|
}
|
|
309238
|
-
function
|
|
309239
|
-
const
|
|
309240
|
-
|
|
309241
|
-
const tags = buildCommonArchiveTags({
|
|
309242
|
-
deploymentId: input.deploymentId,
|
|
309243
|
-
accountSelector: input.accountSelector,
|
|
309244
|
-
exchange: input.exchange,
|
|
309245
|
-
symbol: input.symbol,
|
|
309246
|
-
brokerObservedTimestamp: new Date(receivedTimeMs).toISOString()
|
|
309247
|
-
});
|
|
309248
|
-
return {
|
|
309249
|
-
table: "market_data.cex_stream_events",
|
|
309250
|
-
row: compactUndefined3({
|
|
309251
|
-
...tags,
|
|
309252
|
-
asset_type: input.assetType,
|
|
309253
|
-
stream_type: input.streamType,
|
|
309254
|
-
event_time_ms: input.eventTimeMs ?? receivedTimeMs,
|
|
309255
|
-
received_time_ms: receivedTimeMs,
|
|
309256
|
-
payload_json: JSON.stringify(redactedPayload)
|
|
309257
|
-
})
|
|
309258
|
-
};
|
|
309673
|
+
function getOptionalExchangeString(exchange, key) {
|
|
309674
|
+
const value = exchange[key];
|
|
309675
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
309259
309676
|
}
|
|
309260
|
-
function
|
|
309261
|
-
|
|
309262
|
-
const
|
|
309263
|
-
|
|
309264
|
-
|
|
309265
|
-
|
|
309266
|
-
|
|
309267
|
-
|
|
309268
|
-
});
|
|
309269
|
-
return {
|
|
309270
|
-
table: "market_data.cex_ticker_events",
|
|
309271
|
-
row: compactUndefined3({
|
|
309272
|
-
...tags,
|
|
309273
|
-
asset_type: input.assetType,
|
|
309274
|
-
event_time_ms: ticker.eventTimeMs,
|
|
309275
|
-
received_time_ms: receivedTimeMs,
|
|
309276
|
-
last: ticker.last,
|
|
309277
|
-
bid: ticker.bid,
|
|
309278
|
-
ask: ticker.ask,
|
|
309279
|
-
high: ticker.high,
|
|
309280
|
-
low: ticker.low,
|
|
309281
|
-
open: ticker.open,
|
|
309282
|
-
close: ticker.close,
|
|
309283
|
-
base_volume: ticker.baseVolume,
|
|
309284
|
-
quote_volume: ticker.quoteVolume,
|
|
309285
|
-
change: ticker.change,
|
|
309286
|
-
percentage: ticker.percentage,
|
|
309287
|
-
payload_json: JSON.stringify(redactStreamPayload(input.payload))
|
|
309288
|
-
})
|
|
309289
|
-
};
|
|
309677
|
+
function redactDiagnosticMessage(message, secretValues) {
|
|
309678
|
+
let redacted = message;
|
|
309679
|
+
for (const value of secretValues) {
|
|
309680
|
+
if (value.length > 0) {
|
|
309681
|
+
redacted = redacted.split(value).join("[redacted]");
|
|
309682
|
+
}
|
|
309683
|
+
}
|
|
309684
|
+
return redacted.replace(/(\b(?:apiKey|secret|signature)\b\s*=\s*)[^\s&,;)]+/gi, "$1[redacted]").replace(/("(?:apiKey|secret|signature)"\s*:\s*")[^"]*(")/gi, "$1[redacted]$2");
|
|
309290
309685
|
}
|
|
309291
|
-
function
|
|
309292
|
-
const
|
|
309293
|
-
const
|
|
309294
|
-
|
|
309295
|
-
|
|
309296
|
-
exchange: input.exchange,
|
|
309297
|
-
symbol: input.symbol,
|
|
309298
|
-
brokerObservedTimestamp: new Date(receivedTimeMs).toISOString()
|
|
309299
|
-
});
|
|
309300
|
-
return {
|
|
309301
|
-
table: "market_data.cex_trades",
|
|
309302
|
-
row: compactUndefined3({
|
|
309303
|
-
...tags,
|
|
309304
|
-
asset_type: input.assetType,
|
|
309305
|
-
trade_id: trade.tradeId,
|
|
309306
|
-
event_time_ms: trade.eventTimeMs,
|
|
309307
|
-
received_time_ms: receivedTimeMs,
|
|
309308
|
-
side: trade.side,
|
|
309309
|
-
price: trade.price,
|
|
309310
|
-
amount: trade.amount,
|
|
309311
|
-
cost: trade.cost,
|
|
309312
|
-
taker_or_maker: trade.takerOrMaker
|
|
309313
|
-
})
|
|
309314
|
-
};
|
|
309686
|
+
function formatBinanceUserDataWebSocketError(event, secretValues) {
|
|
309687
|
+
const record2 = getRecord(event);
|
|
309688
|
+
const message = getMessage(record2?.error) ?? getMessage(record2?.message) ?? getMessage(event);
|
|
309689
|
+
const safeMessage = message === null ? null : redactDiagnosticMessage(message, secretValues);
|
|
309690
|
+
return new Error(safeMessage ? `Binance user-data WebSocket error: ${safeMessage}` : "Binance user-data WebSocket error");
|
|
309315
309691
|
}
|
|
309316
|
-
|
|
309317
|
-
|
|
309318
|
-
|
|
309319
|
-
|
|
309320
|
-
|
|
309321
|
-
|
|
309692
|
+
function getCloseReason(value) {
|
|
309693
|
+
if (typeof value === "string") {
|
|
309694
|
+
return value.length > 0 ? value : null;
|
|
309695
|
+
}
|
|
309696
|
+
if (Buffer2.isBuffer(value)) {
|
|
309697
|
+
const reason = value.toString("utf8");
|
|
309698
|
+
return reason.length > 0 ? reason : null;
|
|
309699
|
+
}
|
|
309700
|
+
if (value instanceof Uint8Array) {
|
|
309701
|
+
const reason = Buffer2.from(value).toString("utf8");
|
|
309702
|
+
return reason.length > 0 ? reason : null;
|
|
309703
|
+
}
|
|
309704
|
+
return null;
|
|
309322
309705
|
}
|
|
309323
|
-
function
|
|
309324
|
-
|
|
309325
|
-
|
|
309326
|
-
|
|
309327
|
-
|
|
309328
|
-
|
|
309706
|
+
function formatBinanceUserDataWebSocketClose(codeOrEvent, reasonOrUndefined, secretValues) {
|
|
309707
|
+
const record2 = getRecord(codeOrEvent);
|
|
309708
|
+
const code = record2 ? record2.code : codeOrEvent;
|
|
309709
|
+
const reason = getCloseReason(record2 ? record2.reason : reasonOrUndefined);
|
|
309710
|
+
const safeReason = reason === null ? null : redactDiagnosticMessage(reason, secretValues);
|
|
309711
|
+
const details = [
|
|
309712
|
+
typeof code === "number" || typeof code === "string" ? `code=${code}` : null,
|
|
309713
|
+
safeReason ? `reason=${safeReason}` : null
|
|
309714
|
+
].filter((detail) => detail !== null);
|
|
309715
|
+
return new Error(details.length > 0 ? `Binance user-data WebSocket closed unexpectedly (${details.join(", ")})` : "Binance user-data WebSocket closed unexpectedly");
|
|
309329
309716
|
}
|
|
309330
|
-
function
|
|
309331
|
-
|
|
309332
|
-
|
|
309333
|
-
if (!isMarketArchiveEnabled() || !archiver?.isEnabled()) {
|
|
309334
|
-
return;
|
|
309717
|
+
function decodeMessageData(data) {
|
|
309718
|
+
if (typeof data === "string") {
|
|
309719
|
+
return data;
|
|
309335
309720
|
}
|
|
309336
|
-
if (
|
|
309337
|
-
|
|
309338
|
-
return;
|
|
309721
|
+
if (Buffer2.isBuffer(data)) {
|
|
309722
|
+
return data.toString("utf8");
|
|
309339
309723
|
}
|
|
309340
|
-
|
|
309341
|
-
|
|
309342
|
-
|
|
309343
|
-
|
|
309344
|
-
|
|
309345
|
-
|
|
309724
|
+
if (data instanceof ArrayBuffer) {
|
|
309725
|
+
return Buffer2.from(data).toString("utf8");
|
|
309726
|
+
}
|
|
309727
|
+
if (ArrayBuffer.isView(data)) {
|
|
309728
|
+
return Buffer2.from(data.buffer, data.byteOffset, data.byteLength).toString("utf8");
|
|
309729
|
+
}
|
|
309730
|
+
if (Array.isArray(data) && data.every((item) => Buffer2.isBuffer(item))) {
|
|
309731
|
+
return Buffer2.concat(data).toString("utf8");
|
|
309732
|
+
}
|
|
309733
|
+
return data;
|
|
309734
|
+
}
|
|
309735
|
+
|
|
309736
|
+
class BinanceSpotUserDataStream {
|
|
309737
|
+
exchange;
|
|
309738
|
+
ws;
|
|
309739
|
+
secretValues;
|
|
309740
|
+
requestId = `user-data-${Date.now()}-${userDataRequestCounter++}`;
|
|
309741
|
+
maxBufferedEvents;
|
|
309742
|
+
queue = [];
|
|
309743
|
+
waiters = [];
|
|
309744
|
+
closed = false;
|
|
309745
|
+
closeError = null;
|
|
309746
|
+
subscriptionId = null;
|
|
309747
|
+
constructor(exchange, options = {}) {
|
|
309748
|
+
this.exchange = exchange;
|
|
309749
|
+
this.maxBufferedEvents = options.maxBufferedEvents ?? DEFAULT_BINANCE_USER_DATA_MAX_BUFFERED_EVENTS;
|
|
309750
|
+
this.secretValues = [
|
|
309751
|
+
getOptionalExchangeString(exchange, "apiKey"),
|
|
309752
|
+
getOptionalExchangeString(exchange, "secret")
|
|
309753
|
+
].filter((value) => value !== null);
|
|
309754
|
+
this.ws = createWebSocket(getBinanceSpotWsApiUrl(exchange));
|
|
309755
|
+
this.ws.on("open", () => this.subscribe());
|
|
309756
|
+
this.ws.on("message", (data) => this.handleMessage(data));
|
|
309757
|
+
this.ws.on("error", (error48) => this.fail(formatBinanceUserDataWebSocketError(error48, this.secretValues)));
|
|
309758
|
+
this.ws.on("close", (code, reason) => this.handleClose(code, reason));
|
|
309759
|
+
}
|
|
309760
|
+
async* [Symbol.asyncIterator]() {
|
|
309761
|
+
while (true) {
|
|
309762
|
+
const event = await this.nextEvent();
|
|
309763
|
+
if (!event) {
|
|
309764
|
+
break;
|
|
309346
309765
|
}
|
|
309347
|
-
|
|
309348
|
-
rethrowArchiveDurabilityError(error48);
|
|
309349
|
-
log.warn("Failed to archive orderbook snapshot", { error: error48 });
|
|
309766
|
+
yield event;
|
|
309350
309767
|
}
|
|
309351
|
-
});
|
|
309352
|
-
}
|
|
309353
|
-
function archiveOhlcvInBackground(archiver, otelMetrics, tracker, input) {
|
|
309354
|
-
const labels = watchLabels("ohlcv", input);
|
|
309355
|
-
recordWatchMetric(otelMetrics, "cex_watch_frames_received_total", labels);
|
|
309356
|
-
if (!isMarketArchiveEnabled() || !archiver?.isEnabled()) {
|
|
309357
|
-
return;
|
|
309358
309768
|
}
|
|
309359
|
-
|
|
309769
|
+
close() {
|
|
309770
|
+
if (this.closed) {
|
|
309771
|
+
return;
|
|
309772
|
+
}
|
|
309773
|
+
this.closed = true;
|
|
309774
|
+
this.queue.length = 0;
|
|
309360
309775
|
try {
|
|
309361
|
-
|
|
309362
|
-
|
|
309363
|
-
|
|
309364
|
-
|
|
309365
|
-
|
|
309366
|
-
|
|
309367
|
-
|
|
309368
|
-
receivedTimestamp: input.receivedTimestamp
|
|
309369
|
-
});
|
|
309370
|
-
archiver.enqueue(row);
|
|
309371
|
-
}
|
|
309372
|
-
if (candidates.length > 0) {
|
|
309373
|
-
recordWatchMetric(otelMetrics, "cex_watch_frames_archived_total", labels);
|
|
309374
|
-
}
|
|
309375
|
-
} catch (error48) {
|
|
309376
|
-
rethrowArchiveDurabilityError(error48);
|
|
309377
|
-
log.warn("Failed to archive OHLCV candle", { error: error48 });
|
|
309776
|
+
this.ws.close();
|
|
309777
|
+
} catch {}
|
|
309778
|
+
this.flushWaiters();
|
|
309779
|
+
}
|
|
309780
|
+
handleClose(code, reason) {
|
|
309781
|
+
if (this.closed) {
|
|
309782
|
+
return;
|
|
309378
309783
|
}
|
|
309379
|
-
|
|
309380
|
-
}
|
|
309381
|
-
function createOrderbookSampler() {
|
|
309382
|
-
return new OrderbookSampler;
|
|
309383
|
-
}
|
|
309384
|
-
function createOhlcvBarTracker() {
|
|
309385
|
-
return new OhlcvBarTracker;
|
|
309386
|
-
}
|
|
309387
|
-
function archiveMarketRowsInBackground(archiver, otelMetrics, stream4, input, enqueueRows) {
|
|
309388
|
-
const labels = watchLabels(stream4, input);
|
|
309389
|
-
recordWatchMetric(otelMetrics, "cex_watch_frames_received_total", labels);
|
|
309390
|
-
if (!isMarketArchiveEnabled() || !archiver?.isEnabled()) {
|
|
309391
|
-
return;
|
|
309784
|
+
this.fail(formatBinanceUserDataWebSocketClose(code, reason, this.secretValues));
|
|
309392
309785
|
}
|
|
309393
|
-
|
|
309786
|
+
subscribe() {
|
|
309787
|
+
const apiKey = getExchangeString(this.exchange, "apiKey");
|
|
309788
|
+
const signedParams = signUserDataStreamParams(this.exchange, {
|
|
309789
|
+
apiKey,
|
|
309790
|
+
timestamp: Date.now()
|
|
309791
|
+
});
|
|
309792
|
+
this.ws.send(JSON.stringify({
|
|
309793
|
+
id: this.requestId,
|
|
309794
|
+
method: "userDataStream.subscribe.signature",
|
|
309795
|
+
params: signedParams
|
|
309796
|
+
}));
|
|
309797
|
+
}
|
|
309798
|
+
handleMessage(data) {
|
|
309799
|
+
if (this.closed) {
|
|
309800
|
+
return;
|
|
309801
|
+
}
|
|
309802
|
+
let message;
|
|
309394
309803
|
try {
|
|
309395
|
-
const
|
|
309396
|
-
|
|
309397
|
-
|
|
309804
|
+
const decodedData = decodeMessageData(data);
|
|
309805
|
+
message = typeof decodedData === "string" ? JSON.parse(decodedData) : decodedData;
|
|
309806
|
+
} catch (error48) {
|
|
309807
|
+
this.fail(error48 instanceof Error ? error48 : new Error("Invalid Binance user-data message"));
|
|
309808
|
+
return;
|
|
309809
|
+
}
|
|
309810
|
+
if ("id" in message && message.id === this.requestId) {
|
|
309811
|
+
if (message.status !== 200) {
|
|
309812
|
+
this.fail(new Error(message.error?.msg ?? message.error?.message ?? `Binance user-data subscription failed with status ${message.status}`));
|
|
309813
|
+
return;
|
|
309398
309814
|
}
|
|
309399
|
-
|
|
309400
|
-
|
|
309815
|
+
this.subscriptionId = message.result?.subscriptionId ?? null;
|
|
309816
|
+
return;
|
|
309817
|
+
}
|
|
309818
|
+
if ("status" in message && typeof message.status === "number" && message.status !== 200) {
|
|
309819
|
+
const errorMessage = message.error?.msg ?? message.error?.message ?? `Binance user-data request failed with status ${message.status}`;
|
|
309820
|
+
const errorCode2 = message.error?.code;
|
|
309821
|
+
this.fail(new Error(typeof errorCode2 === "number" ? `${errorMessage} (code ${errorCode2})` : errorMessage));
|
|
309822
|
+
return;
|
|
309823
|
+
}
|
|
309824
|
+
if (!("event" in message) || !message.event) {
|
|
309825
|
+
return;
|
|
309826
|
+
}
|
|
309827
|
+
const subscriptionId = message.subscriptionId ?? this.subscriptionId;
|
|
309828
|
+
if (subscriptionId === null || subscriptionId === undefined) {
|
|
309829
|
+
return;
|
|
309830
|
+
}
|
|
309831
|
+
this.push({ subscriptionId, event: message.event });
|
|
309832
|
+
}
|
|
309833
|
+
push(event) {
|
|
309834
|
+
if (this.closed) {
|
|
309835
|
+
return;
|
|
309836
|
+
}
|
|
309837
|
+
const waiter = this.waiters.shift();
|
|
309838
|
+
if (waiter) {
|
|
309839
|
+
waiter.resolve(event);
|
|
309840
|
+
return;
|
|
309841
|
+
}
|
|
309842
|
+
if (this.queue.length >= this.maxBufferedEvents) {
|
|
309843
|
+
this.fail(new Error(`Binance user-data stream buffered event limit exceeded (${this.maxBufferedEvents}); downstream consumer is not keeping up`));
|
|
309844
|
+
return;
|
|
309845
|
+
}
|
|
309846
|
+
this.queue.push(event);
|
|
309847
|
+
}
|
|
309848
|
+
nextEvent() {
|
|
309849
|
+
const event = this.queue.shift();
|
|
309850
|
+
if (event) {
|
|
309851
|
+
return Promise.resolve(event);
|
|
309852
|
+
}
|
|
309853
|
+
if (this.closeError) {
|
|
309854
|
+
return Promise.reject(this.closeError);
|
|
309855
|
+
}
|
|
309856
|
+
if (this.closed) {
|
|
309857
|
+
return Promise.resolve(null);
|
|
309858
|
+
}
|
|
309859
|
+
return new Promise((resolve, reject) => {
|
|
309860
|
+
this.waiters.push({ resolve, reject });
|
|
309861
|
+
});
|
|
309862
|
+
}
|
|
309863
|
+
fail(error48) {
|
|
309864
|
+
if (this.closeError) {
|
|
309865
|
+
return;
|
|
309866
|
+
}
|
|
309867
|
+
this.closeError = error48;
|
|
309868
|
+
this.closed = true;
|
|
309869
|
+
this.queue.length = 0;
|
|
309870
|
+
this.flushWaiters();
|
|
309871
|
+
try {
|
|
309872
|
+
this.ws.close();
|
|
309873
|
+
} catch {}
|
|
309874
|
+
}
|
|
309875
|
+
flushWaiters() {
|
|
309876
|
+
const error48 = this.closeError;
|
|
309877
|
+
for (const waiter of this.waiters.splice(0)) {
|
|
309878
|
+
if (error48) {
|
|
309879
|
+
waiter.reject(error48);
|
|
309880
|
+
} else {
|
|
309881
|
+
waiter.resolve(null);
|
|
309401
309882
|
}
|
|
309402
|
-
} catch (error48) {
|
|
309403
|
-
rethrowArchiveDurabilityError(error48);
|
|
309404
|
-
log.warn(`Failed to archive ${stream4} market data`, { error: error48 });
|
|
309405
309883
|
}
|
|
309406
|
-
}
|
|
309407
|
-
}
|
|
309408
|
-
function archiveTradesInBackground(archiver, otelMetrics, input) {
|
|
309409
|
-
archiveMarketRowsInBackground(archiver, otelMetrics, "trades", input, () => extractTrades(input.payload, input.receivedTimestamp).map((trade) => buildCexTradeRow(input, trade)));
|
|
309884
|
+
}
|
|
309410
309885
|
}
|
|
309411
|
-
function
|
|
309412
|
-
|
|
309413
|
-
const ticker = parseTicker(input.payload, input.receivedTimestamp);
|
|
309414
|
-
return ticker ? [buildCexTickerEventRow(input, ticker)] : [];
|
|
309415
|
-
});
|
|
309886
|
+
function isBinanceBalanceUserDataEvent(event) {
|
|
309887
|
+
return event.e === "outboundAccountPosition" || event.e === "balanceUpdate" || event.e === "externalLockUpdate";
|
|
309416
309888
|
}
|
|
309417
|
-
function
|
|
309418
|
-
|
|
309419
|
-
buildCexStreamEventRow(input)
|
|
309420
|
-
]);
|
|
309889
|
+
function isBinanceOrderUserDataEvent(event) {
|
|
309890
|
+
return event.e === "executionReport" || event.e === "listStatus";
|
|
309421
309891
|
}
|
|
309422
309892
|
// src/helpers/market-data-archive/ohlcv-bootstrap.ts
|
|
309423
309893
|
var DEFAULT_OHLCV_BOOTSTRAP_LIMIT = 100;
|
|
@@ -309456,6 +309926,7 @@ async function bootstrapOhlcvHistory(broker, archiver, otelMetrics, tracker, inp
|
|
|
309456
309926
|
const receivedTimestamp = Date.now();
|
|
309457
309927
|
archiveOhlcvInBackground(archiver, otelMetrics, tracker, {
|
|
309458
309928
|
...input,
|
|
309929
|
+
sourceMode: "broker_bootstrap_fetch_v1",
|
|
309459
309930
|
payload,
|
|
309460
309931
|
receivedTimestamp
|
|
309461
309932
|
});
|
|
@@ -310515,7 +310986,7 @@ class CEXBroker {
|
|
|
310515
310986
|
if (this.otelMetrics?.isOtelEnabled()) {
|
|
310516
310987
|
await this.otelMetrics.initialize();
|
|
310517
310988
|
}
|
|
310518
|
-
this.server = getServer(this.policy, this.brokers, this.whitelistIps, this.useVerity, this.#verityProverUrl, this.otelMetrics, this.brokerArchiver, this.orderActivityTracker, this.withdrawalObservationTracker);
|
|
310989
|
+
this.server = getServer(this.policy, this.brokers, this.whitelistIps, this.useVerity, this.#verityProverUrl, this.otelMetrics, this.brokerArchiver, this.orderActivityTracker, this.withdrawalObservationTracker, undefined);
|
|
310519
310990
|
this.server.bindAsync(`0.0.0.0:${this.port}`, grpc15.ServerCredentials.createInsecure(), (err2, port) => {
|
|
310520
310991
|
if (err2) {
|
|
310521
310992
|
log.error(err2);
|
|
@@ -310560,4 +311031,4 @@ export {
|
|
|
310560
311031
|
CEXBroker as default
|
|
310561
311032
|
};
|
|
310562
311033
|
|
|
310563
|
-
//# debugId=
|
|
311034
|
+
//# debugId=89161A06846CFA7064756E2164756E21
|