@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/commands/cli.js
CHANGED
|
@@ -315527,7 +315527,7 @@ function compactUndefined2(record) {
|
|
|
315527
315527
|
}
|
|
315528
315528
|
function buildCommonArchiveTags(input) {
|
|
315529
315529
|
return {
|
|
315530
|
-
source: BROKER_WRITE_SOURCE,
|
|
315530
|
+
source: input.source ?? BROKER_WRITE_SOURCE,
|
|
315531
315531
|
deployment_id: input.deploymentId,
|
|
315532
315532
|
account_selector: input.accountSelector ?? "unknown",
|
|
315533
315533
|
exchange: input.exchange.trim().toLowerCase() || "unknown",
|
|
@@ -315590,6 +315590,10 @@ function buildSubscribeStreamArchiveRow(input) {
|
|
|
315590
315590
|
function quantityString(...values2) {
|
|
315591
315591
|
return firstString2(...values2);
|
|
315592
315592
|
}
|
|
315593
|
+
function extractBinanceInternalTransferId(response) {
|
|
315594
|
+
const record = asRecord(response);
|
|
315595
|
+
return firstString2(record?.txnId, record?.tranId);
|
|
315596
|
+
}
|
|
315593
315597
|
function buildTransferEventArchiveRow(input) {
|
|
315594
315598
|
const { tags, transfer } = input;
|
|
315595
315599
|
return {
|
|
@@ -315622,6 +315626,7 @@ function normalizeCcxtTransactionForArchive(transaction) {
|
|
|
315622
315626
|
const fee = asRecord(record?.fee);
|
|
315623
315627
|
return compactUndefined2({
|
|
315624
315628
|
externalId: firstString2(record?.id, info?.id, record?.txid, info?.txId),
|
|
315629
|
+
clientWithdrawalId: firstString2(info?.withdrawOrderId),
|
|
315625
315630
|
txid: firstString2(record?.txid, info?.txId, info?.txid, info?.tx_hash),
|
|
315626
315631
|
address: firstString2(record?.address, record?.addressTo, info?.address),
|
|
315627
315632
|
network: firstString2(record?.network, info?.network),
|
|
@@ -315729,14 +315734,40 @@ var DEFAULT_BATCH_SIZE = 10;
|
|
|
315729
315734
|
var DEFAULT_FLUSH_INTERVAL_MS = 1000;
|
|
315730
315735
|
var DEFAULT_FORWARDER_TIMEOUT_MS = 3000;
|
|
315731
315736
|
var SHED_WARN_INTERVAL_MS = 60000;
|
|
315737
|
+
var MARKET_FEEDS = new Set(["ORDERBOOK", "TICKER", "TRADES", "OHLCV"]);
|
|
315738
|
+
function archiveFeed(row) {
|
|
315739
|
+
const declared = row.row.feed ?? row.row.stream_type;
|
|
315740
|
+
if (typeof declared === "string" && MARKET_FEEDS.has(declared)) {
|
|
315741
|
+
return declared;
|
|
315742
|
+
}
|
|
315743
|
+
if (row.table === "market_data.orderbook_snapshots" || row.table.startsWith("market_data.cex_order_book_")) {
|
|
315744
|
+
return "ORDERBOOK";
|
|
315745
|
+
}
|
|
315746
|
+
if (row.table === "market_data.candles" || row.table === "market_data.cex_ohlcv") {
|
|
315747
|
+
return "OHLCV";
|
|
315748
|
+
}
|
|
315749
|
+
if (row.table === "market_data.cex_ticker_events")
|
|
315750
|
+
return "TICKER";
|
|
315751
|
+
if (row.table === "market_data.cex_trades")
|
|
315752
|
+
return "TRADES";
|
|
315753
|
+
return "NON_MARKET";
|
|
315754
|
+
}
|
|
315732
315755
|
function isArchiveOtelLogsEnabled() {
|
|
315733
315756
|
return process.env.CEX_BROKER_ARCHIVE_OTEL_LOGS_ENABLED === "true";
|
|
315734
315757
|
}
|
|
315735
315758
|
function resolveArchiveForwarderUrlFromEnv() {
|
|
315736
315759
|
return process.env.CEX_BROKER_ARCHIVE_FORWARDER_URL?.trim() || undefined;
|
|
315737
315760
|
}
|
|
315761
|
+
function resolveArchiveSourceFromEnv(value = process.env.CEX_BROKER_ARCHIVE_SOURCE) {
|
|
315762
|
+
const source = value?.trim() || BROKER_WRITE_SOURCE;
|
|
315763
|
+
if (source !== "broker_read" && source !== "broker_write") {
|
|
315764
|
+
throw new Error("CEX_BROKER_ARCHIVE_SOURCE must be broker_read or broker_write");
|
|
315765
|
+
}
|
|
315766
|
+
return source;
|
|
315767
|
+
}
|
|
315738
315768
|
|
|
315739
315769
|
class BrokerExecutionArchiver {
|
|
315770
|
+
source;
|
|
315740
315771
|
deploymentId;
|
|
315741
315772
|
otelLogs;
|
|
315742
315773
|
otelMetrics;
|
|
@@ -315757,10 +315788,12 @@ class BrokerExecutionArchiver {
|
|
|
315757
315788
|
flushTimer = null;
|
|
315758
315789
|
flushInFlight = null;
|
|
315759
315790
|
lastShedWarnAtMs = 0;
|
|
315791
|
+
closing = false;
|
|
315760
315792
|
closed = false;
|
|
315761
315793
|
enabled;
|
|
315762
315794
|
forwarderAuthToken;
|
|
315763
315795
|
constructor(options) {
|
|
315796
|
+
this.source = options.source ?? BROKER_WRITE_SOURCE;
|
|
315764
315797
|
this.deploymentId = options.deploymentId?.trim() || process.env.CEX_BROKER_DEPLOYMENT_ID?.trim() || "unknown";
|
|
315765
315798
|
this.otelLogs = options.otelLogs;
|
|
315766
315799
|
this.otelMetrics = options.otelMetrics;
|
|
@@ -315792,6 +315825,7 @@ class BrokerExecutionArchiver {
|
|
|
315792
315825
|
this.flushTimer.unref?.();
|
|
315793
315826
|
log.info("Broker execution archive enabled", {
|
|
315794
315827
|
enabled: true,
|
|
315828
|
+
source: this.source,
|
|
315795
315829
|
otel_mirror_enabled: Boolean(this.otelLogs?.isOtelEnabled())
|
|
315796
315830
|
});
|
|
315797
315831
|
} catch (error) {
|
|
@@ -315808,8 +315842,11 @@ class BrokerExecutionArchiver {
|
|
|
315808
315842
|
getDeploymentId() {
|
|
315809
315843
|
return this.deploymentId;
|
|
315810
315844
|
}
|
|
315845
|
+
getSource() {
|
|
315846
|
+
return this.source;
|
|
315847
|
+
}
|
|
315811
315848
|
isEnabled() {
|
|
315812
|
-
return this.enabled && !this.closed;
|
|
315849
|
+
return this.enabled && !this.closing && !this.closed;
|
|
315813
315850
|
}
|
|
315814
315851
|
canPersistMarketMetadataSnapshot() {
|
|
315815
315852
|
return this.isEnabled();
|
|
@@ -315821,6 +315858,10 @@ class BrokerExecutionArchiver {
|
|
|
315821
315858
|
if (!this.enabled || this.closed) {
|
|
315822
315859
|
return;
|
|
315823
315860
|
}
|
|
315861
|
+
const archiveRow = {
|
|
315862
|
+
table: row.table,
|
|
315863
|
+
row: { ...row.row, source: this.source }
|
|
315864
|
+
};
|
|
315824
315865
|
if (this.queue.length >= this.maxQueueSize) {
|
|
315825
315866
|
const shedRow = this.queue[0];
|
|
315826
315867
|
if (shedRow) {
|
|
@@ -315829,7 +315870,14 @@ class BrokerExecutionArchiver {
|
|
|
315829
315870
|
}
|
|
315830
315871
|
this.stats.shed += 1;
|
|
315831
315872
|
this.recordArchiveMetric("cex_archive_rows_shed_total", {
|
|
315832
|
-
table: shedRow?.table ?? "unknown"
|
|
315873
|
+
table: shedRow?.table ?? "unknown",
|
|
315874
|
+
source: this.source,
|
|
315875
|
+
feed: shedRow ? archiveFeed(shedRow) : "NON_MARKET"
|
|
315876
|
+
});
|
|
315877
|
+
this.recordArchiveMetric("cex_archive_queue_saturated_rows_total", {
|
|
315878
|
+
table: shedRow?.table ?? "unknown",
|
|
315879
|
+
source: this.source,
|
|
315880
|
+
feed: shedRow ? archiveFeed(shedRow) : "NON_MARKET"
|
|
315833
315881
|
});
|
|
315834
315882
|
const now3 = Date.now();
|
|
315835
315883
|
if (now3 - this.lastShedWarnAtMs >= SHED_WARN_INTERVAL_MS) {
|
|
@@ -315841,10 +315889,12 @@ class BrokerExecutionArchiver {
|
|
|
315841
315889
|
this.lastShedWarnAtMs = now3;
|
|
315842
315890
|
}
|
|
315843
315891
|
}
|
|
315844
|
-
this.queue.push(
|
|
315892
|
+
this.queue.push(archiveRow);
|
|
315845
315893
|
this.stats.enqueued += 1;
|
|
315846
315894
|
this.recordArchiveMetric("cex_archive_rows_enqueued_total", {
|
|
315847
|
-
table:
|
|
315895
|
+
table: archiveRow.table,
|
|
315896
|
+
source: this.source,
|
|
315897
|
+
feed: archiveFeed(archiveRow)
|
|
315848
315898
|
});
|
|
315849
315899
|
if (this.queue.length >= this.batchSize) {
|
|
315850
315900
|
this.flush();
|
|
@@ -315864,11 +315914,15 @@ class BrokerExecutionArchiver {
|
|
|
315864
315914
|
return;
|
|
315865
315915
|
}).finally(() => {
|
|
315866
315916
|
this.flushInFlight = null;
|
|
315917
|
+
if (!this.closed && !this.closing && this.enabled && this.queue.length >= this.batchSize) {
|
|
315918
|
+
queueMicrotask(() => void this.flush());
|
|
315919
|
+
}
|
|
315867
315920
|
});
|
|
315868
315921
|
this.flushInFlight = inFlight;
|
|
315869
315922
|
return inFlight;
|
|
315870
315923
|
}
|
|
315871
315924
|
async close() {
|
|
315925
|
+
this.closing = true;
|
|
315872
315926
|
if (this.flushTimer) {
|
|
315873
315927
|
clearInterval(this.flushTimer);
|
|
315874
315928
|
this.flushTimer = null;
|
|
@@ -315933,7 +315987,14 @@ class BrokerExecutionArchiver {
|
|
|
315933
315987
|
this.queue.shift();
|
|
315934
315988
|
this.stats.shed += 1;
|
|
315935
315989
|
this.recordArchiveMetric("cex_archive_rows_shed_total", {
|
|
315936
|
-
table: dropped?.table ?? "unknown"
|
|
315990
|
+
table: dropped?.table ?? "unknown",
|
|
315991
|
+
source: this.source,
|
|
315992
|
+
feed: archiveFeed(dropped)
|
|
315993
|
+
});
|
|
315994
|
+
this.recordArchiveMetric("cex_archive_queue_saturated_rows_total", {
|
|
315995
|
+
table: dropped.table,
|
|
315996
|
+
source: this.source,
|
|
315997
|
+
feed: archiveFeed(dropped)
|
|
315937
315998
|
});
|
|
315938
315999
|
}
|
|
315939
316000
|
}
|
|
@@ -315947,6 +316008,7 @@ class BrokerExecutionArchiver {
|
|
|
315947
316008
|
const timestamp = new Date().toISOString();
|
|
315948
316009
|
const records = rows.map((payload) => ({
|
|
315949
316010
|
timestamp,
|
|
316011
|
+
source: this.source,
|
|
315950
316012
|
deployment_id: this.deploymentId,
|
|
315951
316013
|
reason,
|
|
315952
316014
|
payload
|
|
@@ -315960,6 +316022,21 @@ class BrokerExecutionArchiver {
|
|
|
315960
316022
|
throw new Error(`wrote ${written} of ${bytes2.length} bytes`);
|
|
315961
316023
|
}
|
|
315962
316024
|
fsyncSync(this.deadLetterFd);
|
|
316025
|
+
const byFeedAndTable = new Map;
|
|
316026
|
+
for (const row of rows) {
|
|
316027
|
+
const key = `${row.table}\x00${archiveFeed(row)}`;
|
|
316028
|
+
const grouped = byFeedAndTable.get(key) ?? { row, count: 0 };
|
|
316029
|
+
grouped.count += 1;
|
|
316030
|
+
byFeedAndTable.set(key, grouped);
|
|
316031
|
+
}
|
|
316032
|
+
for (const { row, count: count2 } of byFeedAndTable.values()) {
|
|
316033
|
+
this.recordArchiveMetric("cex_archive_rows_journaled_total", {
|
|
316034
|
+
table: row.table,
|
|
316035
|
+
source: this.source,
|
|
316036
|
+
feed: archiveFeed(row),
|
|
316037
|
+
reason
|
|
316038
|
+
}, count2);
|
|
316039
|
+
}
|
|
315963
316040
|
} catch (error) {
|
|
315964
316041
|
throw new BrokerExecutionArchiveDurabilityError(`Broker execution archive failed to durably record ${reason}; affected row(s) were retained`, { cause: error });
|
|
315965
316042
|
}
|
|
@@ -315995,10 +316072,17 @@ class BrokerExecutionArchiver {
|
|
|
315995
316072
|
recordFlushHealth(batch) {
|
|
315996
316073
|
const countByTable = new Map;
|
|
315997
316074
|
for (const entry of batch) {
|
|
315998
|
-
|
|
315999
|
-
|
|
316000
|
-
|
|
316001
|
-
|
|
316075
|
+
const key = `${entry.table}\x00${archiveFeed(entry)}`;
|
|
316076
|
+
const grouped = countByTable.get(key) ?? { row: entry, count: 0 };
|
|
316077
|
+
grouped.count += 1;
|
|
316078
|
+
countByTable.set(key, grouped);
|
|
316079
|
+
}
|
|
316080
|
+
for (const { row, count: count2 } of countByTable.values()) {
|
|
316081
|
+
this.recordArchiveMetric("cex_archive_rows_flushed_total", {
|
|
316082
|
+
table: row.table,
|
|
316083
|
+
source: this.source,
|
|
316084
|
+
feed: archiveFeed(row)
|
|
316085
|
+
}, count2);
|
|
316002
316086
|
}
|
|
316003
316087
|
this.recordArchiveGauge("cex_archive_last_flush_success", Math.floor(Date.now() / 1000));
|
|
316004
316088
|
}
|
|
@@ -316032,7 +316116,7 @@ class BrokerExecutionArchiver {
|
|
|
316032
316116
|
return Promise.resolve();
|
|
316033
316117
|
}
|
|
316034
316118
|
const body = JSON.stringify({
|
|
316035
|
-
source:
|
|
316119
|
+
source: this.source,
|
|
316036
316120
|
deployment_id: this.deploymentId,
|
|
316037
316121
|
rows: batch
|
|
316038
316122
|
});
|
|
@@ -316102,6 +316186,7 @@ function createBrokerExecutionArchiverFromEnv(otelLogs, otelMetrics) {
|
|
|
316102
316186
|
const forwarderUrl = resolveArchiveForwarderUrlFromEnv();
|
|
316103
316187
|
const archiveOtelLogs = isArchiveOtelLogsEnabled() ? otelLogs : undefined;
|
|
316104
316188
|
return BrokerExecutionArchiver.create({
|
|
316189
|
+
source: resolveArchiveSourceFromEnv(),
|
|
316105
316190
|
otelLogs: archiveOtelLogs,
|
|
316106
316191
|
otelMetrics,
|
|
316107
316192
|
forwarderUrl: forwarderUrl ?? "",
|
|
@@ -316240,6 +316325,7 @@ function archiveWithdrawalObservationsInBackground(archiver, tracker, input) {
|
|
|
316240
316325
|
address: normalized.address,
|
|
316241
316326
|
network: normalized.network,
|
|
316242
316327
|
externalId: normalized.externalId,
|
|
316328
|
+
clientWithdrawalId: normalized.clientWithdrawalId,
|
|
316243
316329
|
txid: normalized.txid,
|
|
316244
316330
|
resultIndex,
|
|
316245
316331
|
feeAmount: normalized.feeAmount,
|
|
@@ -331336,9 +331422,6 @@ function selectBrokerAccountForCex(normalizedCex, brokers, metadata) {
|
|
|
331336
331422
|
return selectBrokerAccount(brokers[normalizedCex], metadata) ?? undefined;
|
|
331337
331423
|
}
|
|
331338
331424
|
|
|
331339
|
-
// src/handlers/execute-action/order-book-call.ts
|
|
331340
|
-
var grpc4 = __toESM(require_src3(), 1);
|
|
331341
|
-
|
|
331342
331425
|
// src/helpers/order-book.ts
|
|
331343
331426
|
var ORDER_BOOK_CALL_METHODS = {
|
|
331344
331427
|
FETCH_CAPABILITY: "fetch_order_book_capability",
|
|
@@ -331581,726 +331664,1200 @@ function buildHistoricalOrderBookUnsupported(payload) {
|
|
|
331581
331664
|
}
|
|
331582
331665
|
|
|
331583
331666
|
// src/handlers/execute-action/order-book-call.ts
|
|
331584
|
-
|
|
331585
|
-
|
|
331586
|
-
|
|
331587
|
-
|
|
331588
|
-
|
|
331589
|
-
|
|
331590
|
-
|
|
331591
|
-
|
|
331592
|
-
|
|
331593
|
-
|
|
331594
|
-
|
|
331667
|
+
var grpc4 = __toESM(require_src3(), 1);
|
|
331668
|
+
|
|
331669
|
+
// src/helpers/market-data-archive/capture-contract.ts
|
|
331670
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
331671
|
+
var MARKET_CAPTURE_SCHEMA_VERSION = "1.0.0";
|
|
331672
|
+
var CHECKSUM_ALGORITHM = "sha256-canonical-json-v1";
|
|
331673
|
+
var ARCHIVE_SOURCES = ["broker_read", "broker_write"];
|
|
331674
|
+
var CAPTURE_FEEDS = [
|
|
331675
|
+
"ORDERBOOK",
|
|
331676
|
+
"TICKER",
|
|
331677
|
+
"TRADES",
|
|
331678
|
+
"OHLCV"
|
|
331679
|
+
];
|
|
331680
|
+
var SOURCE_MODES = [
|
|
331681
|
+
"broker_live_stream_v1",
|
|
331682
|
+
"broker_live_sampling_v1",
|
|
331683
|
+
"broker_current_snapshot_v1",
|
|
331684
|
+
"broker_bootstrap_fetch_v1",
|
|
331685
|
+
"external_ccxt_fallback_v1",
|
|
331686
|
+
"external_hummingbot_fallback_v1",
|
|
331687
|
+
"legacy_migration_v1"
|
|
331688
|
+
];
|
|
331689
|
+
var RAW_CAPTURE_SCOPES = [
|
|
331690
|
+
"ccxt_normalized_object",
|
|
331691
|
+
"broker_visible_payload",
|
|
331692
|
+
"exchange_wire_frame"
|
|
331693
|
+
];
|
|
331694
|
+
var CHECKSUM_FIELDS = new Set([
|
|
331695
|
+
"normalized_row_checksum",
|
|
331696
|
+
"raw_checksum",
|
|
331697
|
+
"checksum"
|
|
331698
|
+
]);
|
|
331699
|
+
function canonicalDecimal(value) {
|
|
331700
|
+
if (!Number.isFinite(value)) {
|
|
331701
|
+
throw new Error("Canonical numbers must be finite");
|
|
331595
331702
|
}
|
|
331596
|
-
if (
|
|
331597
|
-
return
|
|
331703
|
+
if (Object.is(value, -0)) {
|
|
331704
|
+
return "0";
|
|
331598
331705
|
}
|
|
331599
|
-
const
|
|
331600
|
-
if (!
|
|
331601
|
-
|
|
331602
|
-
code: grpc4.status.INVALID_ARGUMENT,
|
|
331603
|
-
message: `Unsupported exchange for order-book market data: ${ctx.normalizedCex}`
|
|
331604
|
-
}, null);
|
|
331605
|
-
return true;
|
|
331706
|
+
const rendered = String(value).toLowerCase();
|
|
331707
|
+
if (!rendered.includes("e")) {
|
|
331708
|
+
return rendered;
|
|
331606
331709
|
}
|
|
331607
|
-
|
|
331608
|
-
|
|
331609
|
-
|
|
331610
|
-
|
|
331611
|
-
|
|
331612
|
-
|
|
331613
|
-
|
|
331614
|
-
|
|
331615
|
-
|
|
331616
|
-
}
|
|
331617
|
-
|
|
331618
|
-
|
|
331619
|
-
|
|
331620
|
-
|
|
331621
|
-
|
|
331622
|
-
|
|
331623
|
-
|
|
331624
|
-
|
|
331625
|
-
|
|
331626
|
-
|
|
331627
|
-
|
|
331628
|
-
|
|
331629
|
-
|
|
331630
|
-
|
|
331631
|
-
|
|
331710
|
+
const [coefficient = "0", exponentText = "0"] = rendered.split("e");
|
|
331711
|
+
const exponent = Number.parseInt(exponentText, 10);
|
|
331712
|
+
const negative = coefficient.startsWith("-");
|
|
331713
|
+
const unsigned = negative ? coefficient.slice(1) : coefficient;
|
|
331714
|
+
const [integer2 = "0", fraction = ""] = unsigned.split(".");
|
|
331715
|
+
const digits = `${integer2}${fraction}`;
|
|
331716
|
+
const decimalIndex = integer2.length + exponent;
|
|
331717
|
+
let result;
|
|
331718
|
+
if (decimalIndex <= 0) {
|
|
331719
|
+
result = `0.${"0".repeat(-decimalIndex)}${digits}`;
|
|
331720
|
+
} else if (decimalIndex >= digits.length) {
|
|
331721
|
+
result = `${digits}${"0".repeat(decimalIndex - digits.length)}`;
|
|
331722
|
+
} else {
|
|
331723
|
+
result = `${digits.slice(0, decimalIndex)}.${digits.slice(decimalIndex)}`;
|
|
331724
|
+
}
|
|
331725
|
+
return negative ? `-${result}` : result;
|
|
331726
|
+
}
|
|
331727
|
+
function serializeCanonical(value, stack) {
|
|
331728
|
+
if (value === null)
|
|
331729
|
+
return "null";
|
|
331730
|
+
if (typeof value === "string")
|
|
331731
|
+
return JSON.stringify(value);
|
|
331732
|
+
if (typeof value === "boolean")
|
|
331733
|
+
return value ? "true" : "false";
|
|
331734
|
+
if (typeof value === "number")
|
|
331735
|
+
return canonicalDecimal(value);
|
|
331736
|
+
if (typeof value === "bigint")
|
|
331737
|
+
return value.toString(10);
|
|
331738
|
+
if (value instanceof Date) {
|
|
331739
|
+
if (Number.isNaN(value.getTime())) {
|
|
331740
|
+
throw new Error("Canonical timestamps must be valid");
|
|
331632
331741
|
}
|
|
331633
|
-
|
|
331634
|
-
const rawOrderBook = await fetchOrderBook.call(orderBookBroker, orderBookPayload.symbol, orderBookPayload.depthLimit);
|
|
331635
|
-
ctx.wrappedCallback(null, {
|
|
331636
|
-
proof: ctx.verity.proof,
|
|
331637
|
-
result: JSON.stringify(normalizeOrderBookSnapshot(rawOrderBook, {
|
|
331638
|
-
exchange: orderBookPayload.exchange,
|
|
331639
|
-
symbol: orderBookPayload.symbol,
|
|
331640
|
-
depthLimit: orderBookPayload.depthLimit,
|
|
331641
|
-
receivedTimestamp
|
|
331642
|
-
}))
|
|
331643
|
-
});
|
|
331644
|
-
} catch (error48) {
|
|
331645
|
-
safeLogError("Order-book Call failed", error48);
|
|
331646
|
-
ctx.wrappedCallback({
|
|
331647
|
-
code: mapCcxtErrorToGrpcStatus(error48) ?? grpc4.status.INTERNAL,
|
|
331648
|
-
message: `Order-book Call failed: ${sanitizeErrorDetail(error48)}`
|
|
331649
|
-
}, null);
|
|
331742
|
+
return value.getTime().toString(10);
|
|
331650
331743
|
}
|
|
331651
|
-
|
|
331744
|
+
if (Array.isArray(value)) {
|
|
331745
|
+
if (stack.has(value))
|
|
331746
|
+
throw new Error("Canonical values must be acyclic");
|
|
331747
|
+
stack.add(value);
|
|
331748
|
+
const result = `[${value.map((entry) => entry === undefined ? "null" : serializeCanonical(entry, stack)).join(",")}]`;
|
|
331749
|
+
stack.delete(value);
|
|
331750
|
+
return result;
|
|
331751
|
+
}
|
|
331752
|
+
if (typeof value === "object") {
|
|
331753
|
+
if (stack.has(value))
|
|
331754
|
+
throw new Error("Canonical values must be acyclic");
|
|
331755
|
+
stack.add(value);
|
|
331756
|
+
const entries = Object.entries(value).filter(([, entry]) => entry !== undefined).sort(([left], [right]) => left.localeCompare(right));
|
|
331757
|
+
const result = `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${serializeCanonical(entry, stack)}`).join(",")}}`;
|
|
331758
|
+
stack.delete(value);
|
|
331759
|
+
return result;
|
|
331760
|
+
}
|
|
331761
|
+
throw new Error(`Unsupported canonical value type: ${typeof value}`);
|
|
331652
331762
|
}
|
|
331653
|
-
|
|
331654
|
-
|
|
331655
|
-
|
|
331656
|
-
|
|
331657
|
-
|
|
331658
|
-
|
|
331659
|
-
|
|
331660
|
-
|
|
331661
|
-
brokers,
|
|
331662
|
-
metadata,
|
|
331663
|
-
normalizedCex,
|
|
331664
|
-
symbol: symbol2,
|
|
331665
|
-
verity,
|
|
331666
|
-
useVerity,
|
|
331667
|
-
verityProverUrl,
|
|
331668
|
-
brokerArchiver
|
|
331669
|
-
} = ctx;
|
|
331670
|
-
if (!symbol2) {
|
|
331671
|
-
return ctx.wrappedCallback({
|
|
331672
|
-
code: grpc5.status.INVALID_ARGUMENT,
|
|
331673
|
-
message: `ValidationError: Symbol required`
|
|
331674
|
-
}, null);
|
|
331763
|
+
function canonicalSerialize(value) {
|
|
331764
|
+
return serializeCanonical(value, new Set);
|
|
331765
|
+
}
|
|
331766
|
+
function omitChecksumFields(value) {
|
|
331767
|
+
if (Array.isArray(value))
|
|
331768
|
+
return value.map(omitChecksumFields);
|
|
331769
|
+
if (value && typeof value === "object" && !(value instanceof Date)) {
|
|
331770
|
+
return Object.fromEntries(Object.entries(value).filter(([key]) => !CHECKSUM_FIELDS.has(key)).map(([key, entry]) => [key, omitChecksumFields(entry)]));
|
|
331675
331771
|
}
|
|
331676
|
-
|
|
331677
|
-
|
|
331678
|
-
|
|
331679
|
-
|
|
331680
|
-
|
|
331681
|
-
|
|
331682
|
-
|
|
331683
|
-
|
|
331772
|
+
return value;
|
|
331773
|
+
}
|
|
331774
|
+
function sha256Canonical(value) {
|
|
331775
|
+
return createHash3("sha256").update(canonicalSerialize(omitChecksumFields(value))).digest("hex");
|
|
331776
|
+
}
|
|
331777
|
+
function normalizeTimestampMs(value, field) {
|
|
331778
|
+
let timestamp;
|
|
331779
|
+
if (value instanceof Date) {
|
|
331780
|
+
timestamp = value.getTime();
|
|
331781
|
+
} else if (typeof value === "number") {
|
|
331782
|
+
timestamp = value;
|
|
331783
|
+
} else if (typeof value === "string" && /^\d+$/.test(value.trim())) {
|
|
331784
|
+
timestamp = Number(value.trim());
|
|
331785
|
+
} else if (typeof value === "string") {
|
|
331786
|
+
timestamp = Date.parse(value);
|
|
331787
|
+
} else {
|
|
331788
|
+
timestamp = Number.NaN;
|
|
331684
331789
|
}
|
|
331685
|
-
|
|
331686
|
-
|
|
331687
|
-
return ctx.wrappedCallback({
|
|
331688
|
-
code: grpc5.status.FAILED_PRECONDITION,
|
|
331689
|
-
message: `No broker accounts configured for ${normalizedCex}`
|
|
331690
|
-
}, null);
|
|
331790
|
+
if (!Number.isSafeInteger(timestamp) || timestamp < 0) {
|
|
331791
|
+
throw new Error(`${field} must be a non-negative millisecond timestamp`);
|
|
331691
331792
|
}
|
|
331692
|
-
|
|
331693
|
-
|
|
331694
|
-
|
|
331695
|
-
if (!
|
|
331696
|
-
|
|
331697
|
-
code: grpc5.status.INVALID_ARGUMENT,
|
|
331698
|
-
message: `Source account "${fromSelector}" is not configured`
|
|
331699
|
-
}, null);
|
|
331793
|
+
return timestamp;
|
|
331794
|
+
}
|
|
331795
|
+
function assertCaptureContext(context2) {
|
|
331796
|
+
if (!ARCHIVE_SOURCES.includes(context2.source)) {
|
|
331797
|
+
throw new Error(`Unsupported archive source: ${context2.source}`);
|
|
331700
331798
|
}
|
|
331701
|
-
|
|
331702
|
-
|
|
331703
|
-
return ctx.wrappedCallback({
|
|
331704
|
-
code: grpc5.status.INVALID_ARGUMENT,
|
|
331705
|
-
message: `Destination account "${toSelector}" is not configured`
|
|
331706
|
-
}, null);
|
|
331799
|
+
if (!CAPTURE_FEEDS.includes(context2.feed)) {
|
|
331800
|
+
throw new Error(`Unsupported capture feed: ${context2.feed}`);
|
|
331707
331801
|
}
|
|
331708
|
-
|
|
331709
|
-
|
|
331710
|
-
|
|
331711
|
-
|
|
331712
|
-
|
|
331713
|
-
|
|
331802
|
+
if (!SOURCE_MODES.includes(context2.sourceMode)) {
|
|
331803
|
+
throw new Error(`Unsupported source mode: ${context2.sourceMode}`);
|
|
331804
|
+
}
|
|
331805
|
+
for (const [field, value] of [
|
|
331806
|
+
["deployment_id", context2.deploymentId],
|
|
331807
|
+
["capture_bundle_id", context2.captureBundleId],
|
|
331808
|
+
["exchange", context2.exchange],
|
|
331809
|
+
["symbol", context2.symbol],
|
|
331810
|
+
["provider", context2.provider]
|
|
331811
|
+
]) {
|
|
331812
|
+
if (!value.trim())
|
|
331813
|
+
throw new Error(`${field} must not be empty`);
|
|
331814
|
+
}
|
|
331815
|
+
}
|
|
331816
|
+
function createRawCapture(context2, input) {
|
|
331817
|
+
assertCaptureContext(context2);
|
|
331818
|
+
if (!RAW_CAPTURE_SCOPES.includes(input.scope)) {
|
|
331819
|
+
throw new Error(`Unsupported raw capture scope: ${input.scope}`);
|
|
331820
|
+
}
|
|
331821
|
+
const eventTimeMs = normalizeTimestampMs(input.eventTimeMs, "event_time_ms");
|
|
331822
|
+
const receivedTimeMs = normalizeTimestampMs(input.receivedTimeMs, "received_time_ms");
|
|
331823
|
+
const redactedPayload = redactStreamPayload(input.payload);
|
|
331824
|
+
const rawChecksum = sha256Canonical(redactedPayload);
|
|
331825
|
+
const rawCaptureId = sha256Canonical({
|
|
331826
|
+
capture_bundle_id: context2.captureBundleId,
|
|
331827
|
+
exchange: context2.exchange.trim().toLowerCase(),
|
|
331828
|
+
feed: context2.feed,
|
|
331829
|
+
raw_capture_scope: input.scope,
|
|
331830
|
+
raw_payload_sha256: rawChecksum,
|
|
331831
|
+
schema_version: context2.schemaVersion,
|
|
331832
|
+
source_mode: context2.sourceMode,
|
|
331833
|
+
source_symbol: context2.symbol.trim(),
|
|
331834
|
+
source_time_ms: eventTimeMs
|
|
331835
|
+
});
|
|
331836
|
+
return {
|
|
331837
|
+
rawCaptureId,
|
|
331838
|
+
rawCaptureScope: input.scope,
|
|
331839
|
+
rawChecksum,
|
|
331840
|
+
redactedPayload,
|
|
331841
|
+
eventTimeMs,
|
|
331842
|
+
receivedTimeMs,
|
|
331843
|
+
checksumAlgorithm: context2.checksumAlgorithm
|
|
331844
|
+
};
|
|
331845
|
+
}
|
|
331846
|
+
function captureCoreFields(context2, rawCapture) {
|
|
331847
|
+
assertCaptureContext(context2);
|
|
331848
|
+
return {
|
|
331849
|
+
source: context2.source,
|
|
331850
|
+
deployment_id: context2.deploymentId,
|
|
331851
|
+
capture_bundle_id: context2.captureBundleId,
|
|
331852
|
+
exchange: context2.exchange.trim().toLowerCase(),
|
|
331853
|
+
symbol: context2.symbol.trim(),
|
|
331854
|
+
trading_pair: context2.symbol.trim().replace("/", "-"),
|
|
331855
|
+
source_symbol: context2.symbol.trim(),
|
|
331856
|
+
asset_type: context2.assetType,
|
|
331857
|
+
feed: context2.feed,
|
|
331858
|
+
provider: context2.provider,
|
|
331859
|
+
source_mode: context2.sourceMode,
|
|
331860
|
+
source_time_ms: rawCapture.eventTimeMs,
|
|
331861
|
+
received_time_ms: rawCapture.receivedTimeMs,
|
|
331862
|
+
raw_capture_id: rawCapture.rawCaptureId,
|
|
331863
|
+
raw_capture_scope: rawCapture.rawCaptureScope,
|
|
331864
|
+
schema_version: context2.schemaVersion,
|
|
331865
|
+
checksum_algorithm: context2.checksumAlgorithm,
|
|
331866
|
+
raw_checksum: rawCapture.rawChecksum,
|
|
331867
|
+
provenance_complete: context2.provenanceComplete ? 1 : 0
|
|
331868
|
+
};
|
|
331869
|
+
}
|
|
331870
|
+
|
|
331871
|
+
// src/helpers/market-data-archive/canonical-orderbook.ts
|
|
331872
|
+
class OrderBookValidationError extends Error {
|
|
331873
|
+
reason;
|
|
331874
|
+
constructor(reason) {
|
|
331875
|
+
super(`Invalid order-book evidence: ${reason}`);
|
|
331876
|
+
this.name = "OrderBookValidationError";
|
|
331877
|
+
this.reason = reason;
|
|
331878
|
+
}
|
|
331879
|
+
}
|
|
331880
|
+
function parseSequence(value) {
|
|
331881
|
+
if (value === undefined)
|
|
331882
|
+
return;
|
|
331883
|
+
const parsed = typeof value === "number" ? value : typeof value === "string" && /^\d+$/.test(value) ? Number(value) : Number.NaN;
|
|
331884
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0) {
|
|
331885
|
+
throw new OrderBookValidationError("sequence must be a non-negative integer");
|
|
331886
|
+
}
|
|
331887
|
+
return parsed;
|
|
331888
|
+
}
|
|
331889
|
+
function validateSide(side, levels, depthLimit) {
|
|
331890
|
+
if (levels.length === 0) {
|
|
331891
|
+
throw new OrderBookValidationError(`${side} side is missing`);
|
|
331892
|
+
}
|
|
331893
|
+
const retained = levels.slice(0, depthLimit);
|
|
331894
|
+
for (let index2 = 0;index2 < retained.length; index2 += 1) {
|
|
331895
|
+
const entry = retained[index2];
|
|
331896
|
+
const price = entry?.[0];
|
|
331897
|
+
const amount = entry?.[1];
|
|
331898
|
+
if (!Array.isArray(entry) || entry.length < 2 || price === undefined || amount === undefined || !Number.isFinite(price) || !Number.isFinite(amount) || price <= 0 || amount <= 0) {
|
|
331899
|
+
throw new OrderBookValidationError(`${side} level ${index2} has a non-positive or non-finite price/amount`);
|
|
331714
331900
|
}
|
|
331715
|
-
|
|
331716
|
-
|
|
331717
|
-
|
|
331718
|
-
|
|
331719
|
-
accountSelector: fromSelector,
|
|
331720
|
-
assetSymbol: symbol2,
|
|
331721
|
-
transfer: {
|
|
331722
|
-
eventKind: "internal_transfer",
|
|
331723
|
-
lifecycleAction: "submit_internal_transfer",
|
|
331724
|
-
status: normalized.status ?? "ok",
|
|
331725
|
-
amount: normalized.amount ?? String(transferPayload.amount),
|
|
331726
|
-
network: "internal",
|
|
331727
|
-
externalId: normalized.externalId,
|
|
331728
|
-
payload: { from: fromSelector, to: toSelector, result }
|
|
331901
|
+
if (index2 > 0) {
|
|
331902
|
+
const previous = retained[index2 - 1]?.[0];
|
|
331903
|
+
if (previous === undefined || (side === "bid" ? price >= previous : price <= previous)) {
|
|
331904
|
+
throw new OrderBookValidationError(`${side} levels are not strictly ${side === "bid" ? "descending" : "ascending"}`);
|
|
331729
331905
|
}
|
|
331730
|
-
});
|
|
331731
|
-
ctx.wrappedCallback(null, {
|
|
331732
|
-
proof: verity.proof,
|
|
331733
|
-
result: JSON.stringify(result)
|
|
331734
|
-
});
|
|
331735
|
-
} catch (error48) {
|
|
331736
|
-
safeLogError("InternalTransfer failed", error48);
|
|
331737
|
-
if (error48 instanceof BrokerAccountPreconditionError) {
|
|
331738
|
-
return ctx.wrappedCallback({
|
|
331739
|
-
code: grpc5.status.FAILED_PRECONDITION,
|
|
331740
|
-
message: getErrorMessage(error48)
|
|
331741
|
-
}, null);
|
|
331742
331906
|
}
|
|
331743
|
-
|
|
331744
|
-
|
|
331745
|
-
|
|
331746
|
-
|
|
331747
|
-
|
|
331748
|
-
|
|
331749
|
-
|
|
331750
|
-
|
|
331907
|
+
}
|
|
331908
|
+
return retained.map(([price, amount]) => ({
|
|
331909
|
+
price,
|
|
331910
|
+
amount
|
|
331911
|
+
}));
|
|
331912
|
+
}
|
|
331913
|
+
function normalizedBands(input) {
|
|
331914
|
+
const bands = input ?? [10, 25, 50, 100];
|
|
331915
|
+
for (const band of bands) {
|
|
331916
|
+
if (!Number.isFinite(band) || band <= 0 || !Number.isInteger(band)) {
|
|
331917
|
+
throw new OrderBookValidationError("measurement bands must be positive integer basis points");
|
|
331751
331918
|
}
|
|
331752
|
-
ctx.wrappedCallback({
|
|
331753
|
-
code,
|
|
331754
|
-
message: `InternalTransfer failed: ${sanitizeErrorDetail(error48)}`
|
|
331755
|
-
}, null);
|
|
331756
331919
|
}
|
|
331920
|
+
return [...new Set(bands)].sort((left, right) => left - right);
|
|
331757
331921
|
}
|
|
331758
|
-
|
|
331759
|
-
|
|
331760
|
-
var grpc6 = __toESM(require_src3(), 1);
|
|
331761
|
-
|
|
331762
|
-
// src/helpers/passive-order.ts
|
|
331763
|
-
var PASSIVE_ORDER_ERROR_CODES = {
|
|
331764
|
-
unsupported: "passive_order_unsupported",
|
|
331765
|
-
rejected: "passive_order_rejected",
|
|
331766
|
-
wouldCross: "passive_order_would_cross"
|
|
331767
|
-
};
|
|
331768
|
-
function identifiesWouldCross(message) {
|
|
331769
|
-
const normalized = message.toLowerCase();
|
|
331770
|
-
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);
|
|
331922
|
+
function checksumRow(row) {
|
|
331923
|
+
return { ...row, normalized_row_checksum: sha256Canonical(row) };
|
|
331771
331924
|
}
|
|
331772
|
-
function
|
|
331773
|
-
|
|
331774
|
-
|
|
331925
|
+
function commonEvidenceFields(context2, rawCapture, input) {
|
|
331926
|
+
return {
|
|
331927
|
+
...captureCoreFields(context2, rawCapture),
|
|
331928
|
+
source_time_ms: input.eventTimeMs,
|
|
331929
|
+
received_time_ms: input.receivedTimeMs,
|
|
331930
|
+
snapshot_id: input.snapshotId,
|
|
331931
|
+
construction_mode: "sampled_top_n_snapshot",
|
|
331932
|
+
gap_policy: "record_gap",
|
|
331933
|
+
depth_limit: input.depthLimit,
|
|
331934
|
+
sequence: input.sequence,
|
|
331935
|
+
exact_l2_reconstruction_complete: 0
|
|
331936
|
+
};
|
|
331775
331937
|
}
|
|
331776
|
-
function
|
|
331777
|
-
if (
|
|
331778
|
-
|
|
331938
|
+
function buildCanonicalOrderBookRows(input) {
|
|
331939
|
+
if (!Number.isSafeInteger(input.depthLimit) || input.depthLimit <= 0 || input.depthLimit > 500) {
|
|
331940
|
+
throw new OrderBookValidationError("depth limit must be an integer between 1 and 500");
|
|
331779
331941
|
}
|
|
331780
|
-
if (
|
|
331781
|
-
|
|
331942
|
+
if (input.context.feed !== "ORDERBOOK") {
|
|
331943
|
+
throw new OrderBookValidationError("capture context feed is not ORDERBOOK");
|
|
331782
331944
|
}
|
|
331783
|
-
|
|
331784
|
-
|
|
331785
|
-
return PASSIVE_ORDER_ERROR_CODES.wouldCross;
|
|
331945
|
+
if (input.constructionMode === "exact_l2_reconstruction") {
|
|
331946
|
+
throw new OrderBookValidationError("exact L2 requires a complete continuity proof and is unsupported");
|
|
331786
331947
|
}
|
|
331787
|
-
|
|
331788
|
-
|
|
331948
|
+
let eventTimeMs;
|
|
331949
|
+
let receivedTimeMs;
|
|
331950
|
+
try {
|
|
331951
|
+
eventTimeMs = normalizeTimestampMs(input.snapshot.timestamp, "source_time_ms");
|
|
331952
|
+
receivedTimeMs = normalizeTimestampMs(input.snapshot.receivedTimestamp, "received_time_ms");
|
|
331953
|
+
} catch (error48) {
|
|
331954
|
+
throw new OrderBookValidationError(error48 instanceof Error ? error48.message : "invalid timestamp");
|
|
331789
331955
|
}
|
|
331790
|
-
|
|
331956
|
+
if (receivedTimeMs < eventTimeMs) {
|
|
331957
|
+
throw new OrderBookValidationError("received timestamp precedes source timestamp");
|
|
331958
|
+
}
|
|
331959
|
+
const bids = validateSide("bid", input.snapshot.bids, input.depthLimit);
|
|
331960
|
+
const asks = validateSide("ask", input.snapshot.asks, input.depthLimit);
|
|
331961
|
+
const bestBid = bids[0];
|
|
331962
|
+
const bestAsk = asks[0];
|
|
331963
|
+
if (bestBid.price >= bestAsk.price) {
|
|
331964
|
+
throw new OrderBookValidationError("book is crossed or locked");
|
|
331965
|
+
}
|
|
331966
|
+
const sequence = parseSequence(input.snapshot.sequence);
|
|
331967
|
+
const midPrice = (bestBid.price + bestAsk.price) / 2;
|
|
331968
|
+
const spread2 = bestAsk.price - bestBid.price;
|
|
331969
|
+
const spreadBps = spread2 / midPrice * 1e4;
|
|
331970
|
+
const snapshotId = sha256Canonical({
|
|
331971
|
+
exchange: input.context.exchange.trim().toLowerCase(),
|
|
331972
|
+
trading_pair: input.context.symbol.trim().replace("/", "-"),
|
|
331973
|
+
source_time_ms: eventTimeMs,
|
|
331974
|
+
sequence,
|
|
331975
|
+
depth_limit: input.depthLimit,
|
|
331976
|
+
bids,
|
|
331977
|
+
asks,
|
|
331978
|
+
schema_version: input.context.schemaVersion
|
|
331979
|
+
});
|
|
331980
|
+
const common = commonEvidenceFields(input.context, input.rawCapture, {
|
|
331981
|
+
snapshotId,
|
|
331982
|
+
sequence,
|
|
331983
|
+
depthLimit: input.depthLimit,
|
|
331984
|
+
eventTimeMs,
|
|
331985
|
+
receivedTimeMs
|
|
331986
|
+
});
|
|
331987
|
+
const levels = [
|
|
331988
|
+
["bid", bids],
|
|
331989
|
+
["ask", asks]
|
|
331990
|
+
].flatMap(([side, sideLevels]) => sideLevels.map(({ price, amount }, levelIndex) => {
|
|
331991
|
+
const row = checksumRow({
|
|
331992
|
+
...common,
|
|
331993
|
+
side,
|
|
331994
|
+
level_index: levelIndex,
|
|
331995
|
+
price,
|
|
331996
|
+
amount,
|
|
331997
|
+
notional: price * amount,
|
|
331998
|
+
mid_price: midPrice,
|
|
331999
|
+
spread_from_mid_bps: Math.abs((price - midPrice) / midPrice) * 1e4
|
|
332000
|
+
});
|
|
332001
|
+
return { table: "market_data.cex_order_book_levels", row };
|
|
332002
|
+
}));
|
|
332003
|
+
const bands = normalizedBands(input.measurementBandsBps);
|
|
332004
|
+
const bidDepth = bands.map((band) => {
|
|
332005
|
+
const minimumPrice = bestBid.price * (1 - band / 1e4);
|
|
332006
|
+
return bids.filter(({ price }) => price >= minimumPrice).reduce((sum3, { amount }) => sum3 + amount, 0);
|
|
332007
|
+
});
|
|
332008
|
+
const askDepth = bands.map((band) => {
|
|
332009
|
+
const maximumPrice = bestAsk.price * (1 + band / 1e4);
|
|
332010
|
+
return asks.filter(({ price }) => price <= maximumPrice).reduce((sum3, { amount }) => sum3 + amount, 0);
|
|
332011
|
+
});
|
|
332012
|
+
const summaryRow = checksumRow({
|
|
332013
|
+
...common,
|
|
332014
|
+
best_bid: bestBid.price,
|
|
332015
|
+
best_ask: bestAsk.price,
|
|
332016
|
+
best_bid_amount: bestBid.amount,
|
|
332017
|
+
best_ask_amount: bestAsk.amount,
|
|
332018
|
+
mid_price: midPrice,
|
|
332019
|
+
spread: spread2,
|
|
332020
|
+
spread_bps: spreadBps,
|
|
332021
|
+
staleness_ms: receivedTimeMs - eventTimeMs,
|
|
332022
|
+
bid_level_count: bids.length,
|
|
332023
|
+
ask_level_count: asks.length,
|
|
332024
|
+
measurement_bands_bps: bands,
|
|
332025
|
+
bid_depth_by_band: bidDepth,
|
|
332026
|
+
ask_depth_by_band: askDepth
|
|
332027
|
+
});
|
|
332028
|
+
return {
|
|
332029
|
+
snapshotId,
|
|
332030
|
+
levels,
|
|
332031
|
+
summary: {
|
|
332032
|
+
table: "market_data.cex_order_book_depth_summary",
|
|
332033
|
+
row: summaryRow
|
|
332034
|
+
}
|
|
332035
|
+
};
|
|
331791
332036
|
}
|
|
331792
332037
|
|
|
331793
|
-
// src/
|
|
331794
|
-
|
|
331795
|
-
const
|
|
331796
|
-
|
|
331797
|
-
|
|
331798
|
-
|
|
331799
|
-
|
|
331800
|
-
|
|
331801
|
-
|
|
331802
|
-
|
|
332038
|
+
// src/helpers/market-data-archive/capture-context.ts
|
|
332039
|
+
function createMarketCaptureContext(input) {
|
|
332040
|
+
const environment = input.environment ?? "development";
|
|
332041
|
+
const deploymentId = input.deploymentId.trim();
|
|
332042
|
+
if (!deploymentId)
|
|
332043
|
+
throw new Error("deployment_id must not be empty");
|
|
332044
|
+
const configuredBundle = input.captureBundleId?.trim();
|
|
332045
|
+
if (environment === "production" && !configuredBundle) {
|
|
332046
|
+
throw new Error("capture_bundle_id is required for production market capture");
|
|
332047
|
+
}
|
|
332048
|
+
const exchange = input.exchange.trim().toLowerCase();
|
|
332049
|
+
const symbol2 = input.symbol.trim();
|
|
332050
|
+
if (!exchange || !symbol2) {
|
|
332051
|
+
throw new Error("exchange and symbol are required for market capture");
|
|
332052
|
+
}
|
|
332053
|
+
return {
|
|
332054
|
+
source: input.source,
|
|
332055
|
+
deploymentId,
|
|
332056
|
+
captureBundleId: configuredBundle ?? `development:${deploymentId}`,
|
|
332057
|
+
exchange,
|
|
331803
332058
|
symbol: symbol2,
|
|
331804
|
-
|
|
331805
|
-
|
|
331806
|
-
|
|
331807
|
-
|
|
331808
|
-
|
|
331809
|
-
|
|
331810
|
-
|
|
331811
|
-
|
|
331812
|
-
|
|
331813
|
-
}
|
|
331814
|
-
|
|
331815
|
-
|
|
331816
|
-
|
|
331817
|
-
|
|
331818
|
-
|
|
331819
|
-
if (isPassiveOrder && orderValue.orderType !== "limit") {
|
|
331820
|
-
return ctx.wrappedCallback({
|
|
331821
|
-
code: grpc6.status.INVALID_ARGUMENT,
|
|
331822
|
-
message: "ValidationError: passive_only order intent requires a limit order"
|
|
331823
|
-
}, null);
|
|
332059
|
+
assetType: input.assetType,
|
|
332060
|
+
feed: input.feed,
|
|
332061
|
+
provider: input.provider?.trim() || `ccxt:${exchange}`,
|
|
332062
|
+
sourceMode: input.sourceMode,
|
|
332063
|
+
schemaVersion: MARKET_CAPTURE_SCHEMA_VERSION,
|
|
332064
|
+
checksumAlgorithm: CHECKSUM_ALGORITHM,
|
|
332065
|
+
provenanceComplete: true,
|
|
332066
|
+
timeframe: input.timeframe,
|
|
332067
|
+
accountSelector: input.accountSelector
|
|
332068
|
+
};
|
|
332069
|
+
}
|
|
332070
|
+
function captureEnvironmentFromEnv(value = process.env.CEX_BROKER_MARKET_CAPTURE_ENVIRONMENT) {
|
|
332071
|
+
const environment = value?.trim() || "development";
|
|
332072
|
+
if (environment !== "development" && environment !== "production") {
|
|
332073
|
+
throw new Error("CEX_BROKER_MARKET_CAPTURE_ENVIRONMENT must be development or production");
|
|
331824
332074
|
}
|
|
331825
|
-
|
|
331826
|
-
|
|
331827
|
-
|
|
331828
|
-
|
|
331829
|
-
|
|
331830
|
-
|
|
332075
|
+
return environment;
|
|
332076
|
+
}
|
|
332077
|
+
|
|
332078
|
+
// src/helpers/market-data-archive/ohlcv-bar-tracker.ts
|
|
332079
|
+
function isFiniteNumber(value) {
|
|
332080
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
332081
|
+
}
|
|
332082
|
+
function parseOhlcvBar(value) {
|
|
332083
|
+
if (!Array.isArray(value) || value.length < 6) {
|
|
332084
|
+
return null;
|
|
332085
|
+
}
|
|
332086
|
+
const [openTimeMs, open, high, low, close, volume, quoteVolume] = value;
|
|
332087
|
+
if (!isFiniteNumber(openTimeMs) || !isFiniteNumber(open) || !isFiniteNumber(high) || !isFiniteNumber(low) || !isFiniteNumber(close) || !isFiniteNumber(volume)) {
|
|
332088
|
+
return null;
|
|
332089
|
+
}
|
|
332090
|
+
const bar = {
|
|
332091
|
+
openTimeMs,
|
|
332092
|
+
open,
|
|
332093
|
+
high,
|
|
332094
|
+
low,
|
|
332095
|
+
close,
|
|
332096
|
+
volume
|
|
331831
332097
|
};
|
|
331832
|
-
|
|
331833
|
-
|
|
331834
|
-
|
|
331835
|
-
|
|
331836
|
-
|
|
331837
|
-
|
|
331838
|
-
|
|
331839
|
-
|
|
331840
|
-
|
|
332098
|
+
if (isFiniteNumber(quoteVolume)) {
|
|
332099
|
+
bar.quoteVolume = quoteVolume;
|
|
332100
|
+
}
|
|
332101
|
+
return bar;
|
|
332102
|
+
}
|
|
332103
|
+
function extractOhlcvBars(payload) {
|
|
332104
|
+
if (!Array.isArray(payload) || payload.length === 0) {
|
|
332105
|
+
return [];
|
|
332106
|
+
}
|
|
332107
|
+
const rawBars = Array.isArray(payload[0]) ? payload : [payload];
|
|
332108
|
+
const byOpenTime = new Map;
|
|
332109
|
+
for (const entry of rawBars) {
|
|
332110
|
+
const bar = parseOhlcvBar(entry);
|
|
332111
|
+
if (bar) {
|
|
332112
|
+
byOpenTime.set(bar.openTimeMs, bar);
|
|
331841
332113
|
}
|
|
331842
|
-
|
|
331843
|
-
|
|
331844
|
-
|
|
331845
|
-
|
|
331846
|
-
|
|
331847
|
-
|
|
332114
|
+
}
|
|
332115
|
+
return [...byOpenTime.values()].sort((a, b2) => a.openTimeMs - b2.openTimeMs);
|
|
332116
|
+
}
|
|
332117
|
+
class OhlcvBarTracker {
|
|
332118
|
+
lastOpenTimeMs = null;
|
|
332119
|
+
lastBar = null;
|
|
332120
|
+
process(payload, brokerVersion) {
|
|
332121
|
+
const bars = extractOhlcvBars(payload);
|
|
332122
|
+
if (bars.length === 0) {
|
|
332123
|
+
return [];
|
|
331848
332124
|
}
|
|
331849
|
-
|
|
331850
|
-
|
|
331851
|
-
|
|
331852
|
-
requestedQuantity: resolution.amountBase ?? orderValue.amount
|
|
331853
|
-
};
|
|
331854
|
-
if (selectedBrokerAccount?.label) {
|
|
331855
|
-
orderActivityTracker?.record(cex3, selectedBrokerAccount.label, resolution.symbol);
|
|
332125
|
+
if (bars.length === 1) {
|
|
332126
|
+
const [bar] = bars;
|
|
332127
|
+
return bar ? this.processSingleBar(bar, brokerVersion) : [];
|
|
331856
332128
|
}
|
|
331857
|
-
|
|
331858
|
-
|
|
331859
|
-
|
|
331860
|
-
|
|
331861
|
-
|
|
331862
|
-
|
|
331863
|
-
|
|
331864
|
-
|
|
331865
|
-
|
|
331866
|
-
|
|
331867
|
-
|
|
331868
|
-
|
|
331869
|
-
|
|
331870
|
-
|
|
331871
|
-
|
|
331872
|
-
|
|
331873
|
-
|
|
331874
|
-
|
|
331875
|
-
side: resolvedOrderTelemetry.side,
|
|
331876
|
-
orderType: orderValue.orderType,
|
|
331877
|
-
requestedQuantity: resolvedOrderTelemetry.requestedQuantity,
|
|
331878
|
-
requestedNotional: orderValue.amount * orderValue.price,
|
|
331879
|
-
orderAuthor: orderValue.orderAuthor,
|
|
331880
|
-
brokerObservedTimestamp: submissionTimestamp,
|
|
331881
|
-
...telemetryIds
|
|
331882
|
-
};
|
|
331883
|
-
emitOrderExecutionTelemetryInBackground(otelMetrics, createOrderContext, order);
|
|
331884
|
-
archiveOrderExecutionInBackground(brokerArchiver, createOrderContext, order, undefined, { marketMetadataHash });
|
|
331885
|
-
ctx.wrappedCallback(null, {
|
|
331886
|
-
result: JSON.stringify({
|
|
331887
|
-
...order,
|
|
331888
|
-
...isPassiveOrder && {
|
|
331889
|
-
passivePlacementOutcome: "accepted_passive"
|
|
331890
|
-
}
|
|
331891
|
-
})
|
|
332129
|
+
return this.processBatch(bars, brokerVersion);
|
|
332130
|
+
}
|
|
332131
|
+
processSingleBar(currentBar, brokerVersion) {
|
|
332132
|
+
if (this.lastOpenTimeMs !== null && currentBar.openTimeMs < this.lastOpenTimeMs) {
|
|
332133
|
+
return [];
|
|
332134
|
+
}
|
|
332135
|
+
const candidates = [];
|
|
332136
|
+
if (this.lastOpenTimeMs !== null && this.lastBar !== null && currentBar.openTimeMs !== this.lastOpenTimeMs) {
|
|
332137
|
+
candidates.push({
|
|
332138
|
+
bar: this.lastBar,
|
|
332139
|
+
isClosed: true,
|
|
332140
|
+
brokerVersion
|
|
332141
|
+
});
|
|
332142
|
+
}
|
|
332143
|
+
candidates.push({
|
|
332144
|
+
bar: currentBar,
|
|
332145
|
+
isClosed: false,
|
|
332146
|
+
brokerVersion
|
|
331892
332147
|
});
|
|
331893
|
-
|
|
331894
|
-
|
|
331895
|
-
|
|
331896
|
-
|
|
331897
|
-
|
|
331898
|
-
|
|
331899
|
-
|
|
331900
|
-
|
|
331901
|
-
|
|
331902
|
-
|
|
331903
|
-
|
|
331904
|
-
|
|
331905
|
-
|
|
331906
|
-
|
|
331907
|
-
|
|
331908
|
-
|
|
331909
|
-
|
|
331910
|
-
if (
|
|
331911
|
-
|
|
331912
|
-
|
|
331913
|
-
|
|
331914
|
-
|
|
332148
|
+
this.lastOpenTimeMs = currentBar.openTimeMs;
|
|
332149
|
+
this.lastBar = currentBar;
|
|
332150
|
+
return candidates;
|
|
332151
|
+
}
|
|
332152
|
+
processBatch(bars, brokerVersion) {
|
|
332153
|
+
const firstBar = bars[0];
|
|
332154
|
+
const lastBar = bars[bars.length - 1];
|
|
332155
|
+
if (!firstBar || !lastBar) {
|
|
332156
|
+
return [];
|
|
332157
|
+
}
|
|
332158
|
+
const lastOpenTimeMs = this.lastOpenTimeMs;
|
|
332159
|
+
if (lastOpenTimeMs !== null && lastBar.openTimeMs < lastOpenTimeMs) {
|
|
332160
|
+
return [];
|
|
332161
|
+
}
|
|
332162
|
+
const barsToProcess = lastOpenTimeMs === null ? bars : bars.filter((bar) => bar.openTimeMs >= lastOpenTimeMs);
|
|
332163
|
+
const firstBarToProcess = barsToProcess[0];
|
|
332164
|
+
const lastBarToProcess = barsToProcess[barsToProcess.length - 1];
|
|
332165
|
+
if (!firstBarToProcess || !lastBarToProcess) {
|
|
332166
|
+
return [];
|
|
332167
|
+
}
|
|
332168
|
+
const candidates = [];
|
|
332169
|
+
if (this.lastBar !== null && lastOpenTimeMs !== null && lastOpenTimeMs < firstBarToProcess.openTimeMs) {
|
|
332170
|
+
candidates.push({
|
|
332171
|
+
bar: this.lastBar,
|
|
332172
|
+
isClosed: true,
|
|
332173
|
+
brokerVersion
|
|
331915
332174
|
});
|
|
331916
332175
|
}
|
|
331917
|
-
|
|
331918
|
-
|
|
331919
|
-
|
|
331920
|
-
|
|
332176
|
+
for (let index2 = 0;index2 < barsToProcess.length - 1; index2 += 1) {
|
|
332177
|
+
const bar = barsToProcess[index2];
|
|
332178
|
+
if (bar) {
|
|
332179
|
+
candidates.push({
|
|
332180
|
+
bar,
|
|
332181
|
+
isClosed: true,
|
|
332182
|
+
brokerVersion
|
|
332183
|
+
});
|
|
332184
|
+
}
|
|
332185
|
+
}
|
|
332186
|
+
candidates.push({
|
|
332187
|
+
bar: lastBarToProcess,
|
|
332188
|
+
isClosed: false,
|
|
332189
|
+
brokerVersion
|
|
332190
|
+
});
|
|
332191
|
+
this.lastOpenTimeMs = lastBarToProcess.openTimeMs;
|
|
332192
|
+
this.lastBar = lastBarToProcess;
|
|
332193
|
+
return candidates;
|
|
331921
332194
|
}
|
|
331922
332195
|
}
|
|
331923
|
-
|
|
331924
|
-
|
|
331925
|
-
|
|
331926
|
-
|
|
331927
|
-
|
|
331928
|
-
|
|
331929
|
-
|
|
331930
|
-
|
|
331931
|
-
|
|
331932
|
-
|
|
331933
|
-
|
|
331934
|
-
|
|
331935
|
-
verity,
|
|
331936
|
-
applyVerityToBroker,
|
|
331937
|
-
useVerity,
|
|
331938
|
-
verityProverUrl,
|
|
331939
|
-
otelMetrics,
|
|
331940
|
-
brokerArchiver,
|
|
331941
|
-
orderActivityTracker
|
|
331942
|
-
} = ctx;
|
|
331943
|
-
const verityProof = verity.proof;
|
|
331944
|
-
const getOrderValue = parsePayloadForAction(ctx, GetOrderDetailsPayloadSchema);
|
|
331945
|
-
if (getOrderValue === null)
|
|
331946
|
-
return;
|
|
331947
|
-
try {
|
|
331948
|
-
if (!broker) {
|
|
331949
|
-
return ctx.wrappedCallback({
|
|
331950
|
-
code: grpc6.status.INVALID_ARGUMENT,
|
|
331951
|
-
message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
|
|
331952
|
-
}, null);
|
|
331953
|
-
}
|
|
331954
|
-
const orderDetails = await broker.fetchOrder(getOrderValue.orderId, symbol2, { ...getOrderValue.params });
|
|
331955
|
-
if (selectedBrokerAccount?.label && symbol2) {
|
|
331956
|
-
orderActivityTracker?.record(cex3, selectedBrokerAccount.label, symbol2);
|
|
331957
|
-
}
|
|
331958
|
-
const getOrderContext = {
|
|
331959
|
-
action: "GetOrderDetails",
|
|
331960
|
-
cex: cex3,
|
|
331961
|
-
accountLabel: selectedBrokerAccount?.label,
|
|
331962
|
-
symbol: symbol2,
|
|
331963
|
-
...extractOrderTelemetryIds(getOrderValue.params)
|
|
331964
|
-
};
|
|
331965
|
-
emitOrderExecutionTelemetryInBackground(otelMetrics, getOrderContext, orderDetails);
|
|
331966
|
-
archiveOrderExecutionInBackground(brokerArchiver, getOrderContext, orderDetails);
|
|
331967
|
-
ctx.wrappedCallback(null, {
|
|
331968
|
-
result: JSON.stringify({
|
|
331969
|
-
orderId: orderDetails.id,
|
|
331970
|
-
status: orderDetails.status,
|
|
331971
|
-
amount: orderDetails.amount,
|
|
331972
|
-
filled: orderDetails.filled,
|
|
331973
|
-
remaining: orderDetails.remaining,
|
|
331974
|
-
symbol: orderDetails.symbol,
|
|
331975
|
-
side: orderDetails.side,
|
|
331976
|
-
price: orderDetails.price
|
|
331977
|
-
})
|
|
331978
|
-
});
|
|
331979
|
-
} catch (error48) {
|
|
331980
|
-
safeLogError(`Error fetching order details from ${cex3}`, error48);
|
|
331981
|
-
const failedGetOrderContext = {
|
|
331982
|
-
action: "GetOrderDetails",
|
|
331983
|
-
cex: cex3,
|
|
331984
|
-
accountLabel: selectedBrokerAccount?.label,
|
|
331985
|
-
symbol: symbol2,
|
|
331986
|
-
...extractOrderTelemetryIds(getOrderValue.params)
|
|
331987
|
-
};
|
|
331988
|
-
emitOrderExecutionTelemetryInBackground(otelMetrics, failedGetOrderContext, undefined, error48);
|
|
331989
|
-
archiveOrderExecutionInBackground(brokerArchiver, failedGetOrderContext, undefined, error48);
|
|
331990
|
-
ctx.wrappedCallback({
|
|
331991
|
-
code: grpc6.status.INTERNAL,
|
|
331992
|
-
message: `Failed to fetch order details from ${cex3}: ${sanitizeErrorDetail(error48)}`
|
|
331993
|
-
}, null);
|
|
332196
|
+
|
|
332197
|
+
// src/helpers/market-data-archive/orderbook-depth.ts
|
|
332198
|
+
var DEFAULT_ORDERBOOK_ARCHIVE_DEPTH_LIMIT = 25;
|
|
332199
|
+
var MAX_ORDERBOOK_ARCHIVE_DEPTH_LIMIT = 500;
|
|
332200
|
+
function getOrderbookArchiveDepthLimit() {
|
|
332201
|
+
const raw = process.env.CEX_BROKER_ORDERBOOK_ARCHIVE_DEPTH_LIMIT;
|
|
332202
|
+
if (!raw) {
|
|
332203
|
+
return DEFAULT_ORDERBOOK_ARCHIVE_DEPTH_LIMIT;
|
|
332204
|
+
}
|
|
332205
|
+
const parsed = Number.parseInt(raw, 10);
|
|
332206
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
332207
|
+
return DEFAULT_ORDERBOOK_ARCHIVE_DEPTH_LIMIT;
|
|
331994
332208
|
}
|
|
332209
|
+
return Math.min(parsed, MAX_ORDERBOOK_ARCHIVE_DEPTH_LIMIT);
|
|
331995
332210
|
}
|
|
331996
|
-
|
|
331997
|
-
|
|
331998
|
-
|
|
331999
|
-
|
|
332000
|
-
|
|
332001
|
-
|
|
332002
|
-
|
|
332003
|
-
|
|
332004
|
-
|
|
332005
|
-
|
|
332006
|
-
|
|
332007
|
-
|
|
332008
|
-
|
|
332009
|
-
|
|
332010
|
-
|
|
332011
|
-
|
|
332012
|
-
|
|
332013
|
-
|
|
332014
|
-
|
|
332015
|
-
|
|
332016
|
-
|
|
332017
|
-
|
|
332018
|
-
|
|
332019
|
-
|
|
332020
|
-
|
|
332021
|
-
if (!broker) {
|
|
332022
|
-
return ctx.wrappedCallback({
|
|
332023
|
-
code: grpc6.status.INVALID_ARGUMENT,
|
|
332024
|
-
message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
|
|
332025
|
-
}, null);
|
|
332211
|
+
|
|
332212
|
+
// src/helpers/market-data-archive/orderbook-sampler.ts
|
|
332213
|
+
var DEFAULT_ORDERBOOK_INTERVAL_MS = 1000;
|
|
332214
|
+
function getOrderbookIntervalMs() {
|
|
332215
|
+
const raw = process.env.CEX_BROKER_ORDERBOOK_INTERVAL_MS ?? process.env.CEX_BROKER_ORDERBOOK_TOB_INTERVAL_MS;
|
|
332216
|
+
if (!raw) {
|
|
332217
|
+
return DEFAULT_ORDERBOOK_INTERVAL_MS;
|
|
332218
|
+
}
|
|
332219
|
+
const parsed = Number.parseInt(raw, 10);
|
|
332220
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_ORDERBOOK_INTERVAL_MS;
|
|
332221
|
+
}
|
|
332222
|
+
function isMarketArchiveEnabled() {
|
|
332223
|
+
return process.env.CEX_BROKER_MARKET_ARCHIVE_ENABLED !== "false";
|
|
332224
|
+
}
|
|
332225
|
+
|
|
332226
|
+
class OrderbookSampler {
|
|
332227
|
+
intervalMs;
|
|
332228
|
+
lastEmitMs = null;
|
|
332229
|
+
constructor(intervalMs = getOrderbookIntervalMs()) {
|
|
332230
|
+
this.intervalMs = intervalMs;
|
|
332231
|
+
}
|
|
332232
|
+
shouldEmit(nowMs = Date.now()) {
|
|
332233
|
+
if (this.lastEmitMs !== null && nowMs < this.lastEmitMs) {
|
|
332234
|
+
this.lastEmitMs = nowMs;
|
|
332235
|
+
return true;
|
|
332026
332236
|
}
|
|
332027
|
-
|
|
332028
|
-
|
|
332029
|
-
cex: cex3,
|
|
332030
|
-
accountLabel: selectedBrokerAccount?.label,
|
|
332031
|
-
symbol: symbol2,
|
|
332032
|
-
...extractOrderTelemetryIds(cancelOrderValue.params)
|
|
332033
|
-
};
|
|
332034
|
-
const cancelledOrder = await broker.cancelOrder(cancelOrderValue.orderId, symbol2, cancelOrderValue.params ?? {});
|
|
332035
|
-
if (selectedBrokerAccount?.label && symbol2) {
|
|
332036
|
-
orderActivityTracker?.record(cex3, selectedBrokerAccount.label, symbol2);
|
|
332237
|
+
if (this.lastEmitMs !== null && nowMs - this.lastEmitMs < this.intervalMs) {
|
|
332238
|
+
return false;
|
|
332037
332239
|
}
|
|
332038
|
-
|
|
332039
|
-
|
|
332040
|
-
ctx.wrappedCallback(null, {
|
|
332041
|
-
result: JSON.stringify({ ...cancelledOrder })
|
|
332042
|
-
});
|
|
332043
|
-
} catch (error48) {
|
|
332044
|
-
safeLogError(`Error cancelling order from ${cex3}`, error48);
|
|
332045
|
-
const failedCancelContext = {
|
|
332046
|
-
action: "CancelOrder",
|
|
332047
|
-
cex: cex3,
|
|
332048
|
-
accountLabel: selectedBrokerAccount?.label,
|
|
332049
|
-
symbol: symbol2,
|
|
332050
|
-
...extractOrderTelemetryIds(cancelOrderValue.params)
|
|
332051
|
-
};
|
|
332052
|
-
emitOrderExecutionTelemetryInBackground(otelMetrics, failedCancelContext, undefined, error48);
|
|
332053
|
-
archiveOrderExecutionInBackground(brokerArchiver, failedCancelContext, undefined, error48);
|
|
332054
|
-
ctx.wrappedCallback({
|
|
332055
|
-
code: grpc6.status.INTERNAL,
|
|
332056
|
-
message: `Failed to cancel order from ${cex3}: ${sanitizeErrorDetail(error48)}`
|
|
332057
|
-
}, null);
|
|
332240
|
+
this.lastEmitMs = nowMs;
|
|
332241
|
+
return true;
|
|
332058
332242
|
}
|
|
332059
332243
|
}
|
|
332060
|
-
async function handleOrders(ctx) {
|
|
332061
|
-
if (ctx.action === Action.CreateOrder)
|
|
332062
|
-
return handleCreateOrder(ctx);
|
|
332063
|
-
if (ctx.action === Action.GetOrderDetails)
|
|
332064
|
-
return handleGetOrderDetails(ctx);
|
|
332065
|
-
if (ctx.action === Action.CancelOrder)
|
|
332066
|
-
return handleCancelOrder(ctx);
|
|
332067
|
-
}
|
|
332068
332244
|
|
|
332069
|
-
// src/
|
|
332070
|
-
|
|
332071
|
-
|
|
332072
|
-
|
|
332073
|
-
|
|
332074
|
-
|
|
332075
|
-
|
|
332076
|
-
brokers,
|
|
332077
|
-
metadata,
|
|
332078
|
-
normalizedCex,
|
|
332079
|
-
cex: cex3,
|
|
332080
|
-
symbol: symbol2,
|
|
332081
|
-
selectedBrokerAccount,
|
|
332082
|
-
broker,
|
|
332083
|
-
verity,
|
|
332084
|
-
applyVerityToBroker,
|
|
332085
|
-
useVerity,
|
|
332086
|
-
verityProverUrl,
|
|
332087
|
-
otelMetrics
|
|
332088
|
-
} = ctx;
|
|
332089
|
-
const verityProof = verity.proof;
|
|
332090
|
-
if (!symbol2) {
|
|
332091
|
-
return ctx.wrappedCallback({
|
|
332092
|
-
code: grpc7.status.INVALID_ARGUMENT,
|
|
332093
|
-
message: `ValidationError: Symbol required`
|
|
332094
|
-
}, null);
|
|
332245
|
+
// src/helpers/market-data-archive/parse-stream.ts
|
|
332246
|
+
function isFiniteNumber2(value) {
|
|
332247
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
332248
|
+
}
|
|
332249
|
+
function toNumber2(value) {
|
|
332250
|
+
if (isFiniteNumber2(value)) {
|
|
332251
|
+
return value;
|
|
332095
332252
|
}
|
|
332096
|
-
|
|
332097
|
-
const
|
|
332098
|
-
|
|
332099
|
-
|
|
332100
|
-
return ctx.wrappedCallback({
|
|
332101
|
-
code: grpc7.status.NOT_FOUND,
|
|
332102
|
-
message: `venue_discovery_unavailable: currency not found for ${assetCode}`
|
|
332103
|
-
}, null);
|
|
332253
|
+
if (typeof value === "string") {
|
|
332254
|
+
const parsed = Number.parseFloat(value);
|
|
332255
|
+
if (Number.isFinite(parsed)) {
|
|
332256
|
+
return parsed;
|
|
332104
332257
|
}
|
|
332105
|
-
const networkEvidence = buildTransferNetworkEvidence(currencyInfo);
|
|
332106
|
-
ctx.wrappedCallback(null, {
|
|
332107
|
-
proof: ctx.verity.proof,
|
|
332108
|
-
result: JSON.stringify({
|
|
332109
|
-
...currencyInfo,
|
|
332110
|
-
exchange: normalizedCex,
|
|
332111
|
-
asset: assetCode,
|
|
332112
|
-
code: currencyInfo.code ?? assetCode,
|
|
332113
|
-
id: currencyInfo.id ?? null,
|
|
332114
|
-
networks: networkEvidence.networks,
|
|
332115
|
-
networkAliases: networkEvidence.aliases,
|
|
332116
|
-
raw: currencyInfo
|
|
332117
|
-
})
|
|
332118
|
-
});
|
|
332119
|
-
} catch (error48) {
|
|
332120
|
-
safeLogError(`Error fetching currency ${symbol2} from ${cex3}`, error48);
|
|
332121
|
-
const message = getErrorMessage(error48);
|
|
332122
|
-
ctx.wrappedCallback({
|
|
332123
|
-
code: stableGrpcErrorCode(message) ?? mapCcxtErrorToGrpcStatus(error48) ?? grpc7.status.INTERNAL,
|
|
332124
|
-
message: message.startsWith("venue_discovery_unavailable:") ? message : `venue_discovery_unavailable: ${message}`
|
|
332125
|
-
}, null);
|
|
332126
332258
|
}
|
|
332259
|
+
return;
|
|
332127
332260
|
}
|
|
332128
|
-
|
|
332129
|
-
|
|
332130
|
-
|
|
332131
|
-
|
|
332132
|
-
|
|
332133
|
-
|
|
332134
|
-
|
|
332135
|
-
|
|
332136
|
-
|
|
332137
|
-
|
|
332138
|
-
|
|
332139
|
-
|
|
332140
|
-
|
|
332141
|
-
|
|
332142
|
-
|
|
332143
|
-
|
|
332144
|
-
|
|
332145
|
-
|
|
332146
|
-
|
|
332261
|
+
function toStringId(value) {
|
|
332262
|
+
if (typeof value === "string" && value.trim()) {
|
|
332263
|
+
return value.trim();
|
|
332264
|
+
}
|
|
332265
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
332266
|
+
return String(value);
|
|
332267
|
+
}
|
|
332268
|
+
return;
|
|
332269
|
+
}
|
|
332270
|
+
function scalarTimestampMs(value, fallbackMs) {
|
|
332271
|
+
const numeric = toNumber2(value);
|
|
332272
|
+
if (numeric !== undefined) {
|
|
332273
|
+
return numeric < 1000000000000 ? numeric * 1000 : numeric;
|
|
332274
|
+
}
|
|
332275
|
+
if (typeof value === "string") {
|
|
332276
|
+
const parsed = Date.parse(value);
|
|
332277
|
+
if (Number.isFinite(parsed)) {
|
|
332278
|
+
return parsed;
|
|
332279
|
+
}
|
|
332280
|
+
}
|
|
332281
|
+
return fallbackMs;
|
|
332282
|
+
}
|
|
332283
|
+
function parseTrade(value, fallbackMs = Date.now()) {
|
|
332284
|
+
const record2 = asRecord(value);
|
|
332285
|
+
if (!record2) {
|
|
332286
|
+
return null;
|
|
332287
|
+
}
|
|
332288
|
+
const tradeId = toStringId(record2.id);
|
|
332289
|
+
const price = toNumber2(record2.price);
|
|
332290
|
+
const amount = toNumber2(record2.amount);
|
|
332291
|
+
const side = typeof record2.side === "string" ? record2.side.toLowerCase() : undefined;
|
|
332292
|
+
if (!tradeId || price === undefined || amount === undefined || !side) {
|
|
332293
|
+
return null;
|
|
332294
|
+
}
|
|
332295
|
+
const parsed = {
|
|
332296
|
+
tradeId,
|
|
332297
|
+
eventTimeMs: scalarTimestampMs(record2.timestamp, fallbackMs),
|
|
332298
|
+
side,
|
|
332299
|
+
price,
|
|
332300
|
+
amount
|
|
332301
|
+
};
|
|
332302
|
+
const cost = toNumber2(record2.cost);
|
|
332303
|
+
if (cost !== undefined) {
|
|
332304
|
+
parsed.cost = cost;
|
|
332305
|
+
}
|
|
332306
|
+
if (typeof record2.takerOrMaker === "string") {
|
|
332307
|
+
parsed.takerOrMaker = record2.takerOrMaker;
|
|
332308
|
+
}
|
|
332309
|
+
return parsed;
|
|
332310
|
+
}
|
|
332311
|
+
function extractTrades(payload, fallbackMs = Date.now()) {
|
|
332312
|
+
if (Array.isArray(payload)) {
|
|
332313
|
+
return payload.map((entry) => parseTrade(entry, fallbackMs)).filter((entry) => entry !== null);
|
|
332314
|
+
}
|
|
332315
|
+
const single = parseTrade(payload, fallbackMs);
|
|
332316
|
+
return single ? [single] : [];
|
|
332317
|
+
}
|
|
332318
|
+
function parseTicker(value, fallbackMs) {
|
|
332319
|
+
const record2 = asRecord(value);
|
|
332320
|
+
if (!record2) {
|
|
332321
|
+
return null;
|
|
332322
|
+
}
|
|
332323
|
+
const parsed = {
|
|
332324
|
+
eventTimeMs: scalarTimestampMs(record2.timestamp, fallbackMs)
|
|
332325
|
+
};
|
|
332326
|
+
const fields = [
|
|
332327
|
+
["last", record2.last],
|
|
332328
|
+
["bid", record2.bid],
|
|
332329
|
+
["ask", record2.ask],
|
|
332330
|
+
["high", record2.high],
|
|
332331
|
+
["low", record2.low],
|
|
332332
|
+
["open", record2.open],
|
|
332333
|
+
["close", record2.close],
|
|
332334
|
+
["baseVolume", record2.baseVolume],
|
|
332335
|
+
["quoteVolume", record2.quoteVolume],
|
|
332336
|
+
["change", record2.change],
|
|
332337
|
+
["percentage", record2.percentage]
|
|
332338
|
+
];
|
|
332339
|
+
for (const [key, rawValue] of fields) {
|
|
332340
|
+
const numeric = toNumber2(rawValue);
|
|
332341
|
+
if (numeric !== undefined) {
|
|
332342
|
+
parsed[key] = numeric;
|
|
332343
|
+
}
|
|
332344
|
+
}
|
|
332345
|
+
return parsed;
|
|
332346
|
+
}
|
|
332347
|
+
|
|
332348
|
+
// src/helpers/market-data-archive/rows.ts
|
|
332349
|
+
function compactUndefined3(record2) {
|
|
332350
|
+
return Object.fromEntries(Object.entries(record2).filter(([, value]) => value !== undefined));
|
|
332351
|
+
}
|
|
332352
|
+
function withNormalizedChecksum(record2) {
|
|
332353
|
+
const compact = compactUndefined3(record2);
|
|
332354
|
+
return {
|
|
332355
|
+
...compact,
|
|
332356
|
+
normalized_row_checksum: sha256Canonical(compact)
|
|
332357
|
+
};
|
|
332358
|
+
}
|
|
332359
|
+
function buildCanonicalCexStreamEventRow(context2, rawCapture) {
|
|
332360
|
+
const row = withNormalizedChecksum({
|
|
332361
|
+
...captureCoreFields(context2, rawCapture),
|
|
332362
|
+
stream_type: context2.feed,
|
|
332363
|
+
event_time_ms: rawCapture.eventTimeMs,
|
|
332364
|
+
payload_encoding: "canonical_json_v1",
|
|
332365
|
+
payload_json: canonicalSerialize(rawCapture.redactedPayload)
|
|
332366
|
+
});
|
|
332367
|
+
return { table: "market_data.cex_stream_events", row };
|
|
332368
|
+
}
|
|
332369
|
+
function buildCanonicalTickerEventRow(context2, rawCapture, ticker) {
|
|
332370
|
+
if (context2.feed !== "TICKER") {
|
|
332371
|
+
throw new Error("Ticker row requires a TICKER capture context");
|
|
332372
|
+
}
|
|
332373
|
+
const row = withNormalizedChecksum({
|
|
332374
|
+
...captureCoreFields(context2, rawCapture),
|
|
332375
|
+
source_time_ms: ticker.eventTimeMs,
|
|
332376
|
+
event_time_ms: ticker.eventTimeMs,
|
|
332377
|
+
last: ticker.last,
|
|
332378
|
+
bid: ticker.bid,
|
|
332379
|
+
ask: ticker.ask,
|
|
332380
|
+
high: ticker.high,
|
|
332381
|
+
low: ticker.low,
|
|
332382
|
+
open: ticker.open,
|
|
332383
|
+
close: ticker.close,
|
|
332384
|
+
base_volume: ticker.baseVolume,
|
|
332385
|
+
quote_volume: ticker.quoteVolume,
|
|
332386
|
+
change: ticker.change,
|
|
332387
|
+
percentage: ticker.percentage
|
|
332388
|
+
});
|
|
332389
|
+
return { table: "market_data.cex_ticker_events", row };
|
|
332390
|
+
}
|
|
332391
|
+
function buildCanonicalTradeRow(context2, rawCapture, trade) {
|
|
332392
|
+
if (context2.feed !== "TRADES") {
|
|
332393
|
+
throw new Error("Trade row requires a TRADES capture context");
|
|
332394
|
+
}
|
|
332395
|
+
const row = withNormalizedChecksum({
|
|
332396
|
+
...captureCoreFields(context2, rawCapture),
|
|
332397
|
+
source_time_ms: trade.eventTimeMs,
|
|
332398
|
+
trade_id: trade.tradeId,
|
|
332399
|
+
event_time_ms: trade.eventTimeMs,
|
|
332400
|
+
side: trade.side,
|
|
332401
|
+
price: trade.price,
|
|
332402
|
+
amount: trade.amount,
|
|
332403
|
+
cost: trade.cost,
|
|
332404
|
+
taker_or_maker: trade.takerOrMaker
|
|
332405
|
+
});
|
|
332406
|
+
return { table: "market_data.cex_trades", row };
|
|
332407
|
+
}
|
|
332408
|
+
function buildCanonicalOhlcvRow(input) {
|
|
332409
|
+
if (input.context.feed !== "OHLCV") {
|
|
332410
|
+
throw new Error("OHLCV row requires an OHLCV capture context");
|
|
332411
|
+
}
|
|
332412
|
+
const row = withNormalizedChecksum({
|
|
332413
|
+
...captureCoreFields(input.context, input.rawCapture),
|
|
332414
|
+
source_time_ms: input.bar.openTimeMs,
|
|
332415
|
+
timeframe: input.context.timeframe ?? "1m",
|
|
332416
|
+
open_time_ms: input.bar.openTimeMs,
|
|
332417
|
+
open: input.bar.open,
|
|
332418
|
+
high: input.bar.high,
|
|
332419
|
+
low: input.bar.low,
|
|
332420
|
+
close: input.bar.close,
|
|
332421
|
+
volume: input.bar.volume,
|
|
332422
|
+
quote_volume: input.bar.quoteVolume,
|
|
332423
|
+
is_closed: input.isClosed ? 1 : 0,
|
|
332424
|
+
broker_version: input.brokerVersion
|
|
332425
|
+
});
|
|
332426
|
+
return { table: "market_data.cex_ohlcv", row };
|
|
332427
|
+
}
|
|
332428
|
+
function buildCexStreamEventRow(input) {
|
|
332429
|
+
const receivedTimeMs = input.receivedTimestamp;
|
|
332430
|
+
const redactedPayload = redactStreamPayload(input.payload);
|
|
332431
|
+
const tags = buildCommonArchiveTags({
|
|
332432
|
+
source: input.source,
|
|
332433
|
+
deploymentId: input.deploymentId,
|
|
332434
|
+
accountSelector: input.accountSelector,
|
|
332435
|
+
exchange: input.exchange,
|
|
332436
|
+
symbol: input.symbol,
|
|
332437
|
+
brokerObservedTimestamp: new Date(receivedTimeMs).toISOString()
|
|
332438
|
+
});
|
|
332439
|
+
return {
|
|
332440
|
+
table: "market_data.cex_stream_events",
|
|
332441
|
+
row: compactUndefined3({
|
|
332442
|
+
...tags,
|
|
332443
|
+
asset_type: input.assetType,
|
|
332444
|
+
stream_type: input.streamType,
|
|
332445
|
+
event_time_ms: input.eventTimeMs ?? receivedTimeMs,
|
|
332446
|
+
received_time_ms: receivedTimeMs,
|
|
332447
|
+
payload_json: JSON.stringify(redactedPayload)
|
|
332448
|
+
})
|
|
332449
|
+
};
|
|
332450
|
+
}
|
|
332451
|
+
|
|
332452
|
+
// src/helpers/market-data-archive/capture.ts
|
|
332453
|
+
async function recordWatchMetric(otelMetrics, metricName, labels) {
|
|
332147
332454
|
try {
|
|
332148
|
-
|
|
332149
|
-
|
|
332455
|
+
await otelMetrics?.recordCounter(metricName, 1, labels);
|
|
332456
|
+
} catch {}
|
|
332457
|
+
}
|
|
332458
|
+
function watchLabels(stream4, input, archiver, feed) {
|
|
332459
|
+
return {
|
|
332460
|
+
stream: stream4,
|
|
332461
|
+
feed,
|
|
332462
|
+
source: archiver?.getSource() ?? "disabled",
|
|
332463
|
+
exchange: input.exchange,
|
|
332464
|
+
symbol: input.symbol
|
|
332465
|
+
};
|
|
332466
|
+
}
|
|
332467
|
+
function resolveCaptureContext(archiver, input, feed, sourceMode) {
|
|
332468
|
+
return createMarketCaptureContext({
|
|
332469
|
+
source: archiver.getSource(),
|
|
332470
|
+
deploymentId: archiver.getDeploymentId(),
|
|
332471
|
+
captureBundleId: process.env.CEX_BROKER_CAPTURE_BUNDLE_ID,
|
|
332472
|
+
exchange: input.exchange,
|
|
332473
|
+
symbol: input.symbol,
|
|
332474
|
+
assetType: input.assetType,
|
|
332475
|
+
feed,
|
|
332476
|
+
provider: `ccxt:${input.exchange.trim().toLowerCase()}`,
|
|
332477
|
+
sourceMode,
|
|
332478
|
+
timeframe: input.timeframe,
|
|
332479
|
+
accountSelector: input.accountSelector,
|
|
332480
|
+
environment: captureEnvironmentFromEnv()
|
|
332481
|
+
});
|
|
332482
|
+
}
|
|
332483
|
+
function archiveOrderbookInBackground(archiver, otelMetrics, input, options) {
|
|
332484
|
+
const labels = watchLabels("orderbook", input, archiver, "ORDERBOOK");
|
|
332485
|
+
recordWatchMetric(otelMetrics, "cex_watch_frames_received_total", labels);
|
|
332486
|
+
if (!isMarketArchiveEnabled() || !archiver?.isEnabled()) {
|
|
332487
|
+
return;
|
|
332488
|
+
}
|
|
332489
|
+
if (options?.sampledOut) {
|
|
332490
|
+
recordWatchMetric(otelMetrics, "cex_watch_frames_sampled_out_total", labels);
|
|
332491
|
+
return;
|
|
332492
|
+
}
|
|
332493
|
+
queueMicrotask(() => {
|
|
332494
|
+
try {
|
|
332495
|
+
const context2 = resolveCaptureContext(archiver, input, "ORDERBOOK", options?.sourceMode ?? "broker_live_sampling_v1");
|
|
332496
|
+
const rawCapture = createRawCapture(context2, {
|
|
332497
|
+
payload: input.snapshot,
|
|
332498
|
+
eventTimeMs: input.snapshot.timestamp,
|
|
332499
|
+
receivedTimeMs: input.snapshot.receivedTimestamp,
|
|
332500
|
+
scope: "ccxt_normalized_object"
|
|
332501
|
+
});
|
|
332502
|
+
const canonical = buildCanonicalOrderBookRows({
|
|
332503
|
+
context: context2,
|
|
332504
|
+
snapshot: input.snapshot,
|
|
332505
|
+
rawCapture,
|
|
332506
|
+
depthLimit: options?.depthLimit ?? getOrderbookArchiveDepthLimit()
|
|
332507
|
+
});
|
|
332508
|
+
archiver.enqueue(buildCanonicalCexStreamEventRow(context2, rawCapture));
|
|
332509
|
+
for (const row of canonical.levels)
|
|
332510
|
+
archiver.enqueue(row);
|
|
332511
|
+
archiver.enqueue(canonical.summary);
|
|
332512
|
+
recordWatchMetric(otelMetrics, "cex_watch_frames_archived_total", labels);
|
|
332513
|
+
} catch (error48) {
|
|
332514
|
+
rethrowArchiveDurabilityError(error48);
|
|
332515
|
+
if (error48 instanceof OrderBookValidationError) {
|
|
332516
|
+
recordWatchMetric(otelMetrics, "cex_watch_frames_invalid_total", {
|
|
332517
|
+
...labels,
|
|
332518
|
+
reason: error48.reason
|
|
332519
|
+
});
|
|
332520
|
+
}
|
|
332521
|
+
log.warn("Failed to archive orderbook snapshot", { error: error48 });
|
|
332522
|
+
}
|
|
332523
|
+
});
|
|
332524
|
+
}
|
|
332525
|
+
function archiveOhlcvInBackground(archiver, otelMetrics, tracker, input) {
|
|
332526
|
+
const labels = watchLabels("ohlcv", input, archiver, "OHLCV");
|
|
332527
|
+
recordWatchMetric(otelMetrics, "cex_watch_frames_received_total", labels);
|
|
332528
|
+
if (!isMarketArchiveEnabled() || !archiver?.isEnabled()) {
|
|
332529
|
+
return;
|
|
332530
|
+
}
|
|
332531
|
+
queueMicrotask(() => {
|
|
332532
|
+
try {
|
|
332533
|
+
const candidates = tracker.process(input.payload, input.receivedTimestamp);
|
|
332534
|
+
const context2 = resolveCaptureContext(archiver, input, "OHLCV", input.sourceMode ?? "broker_live_stream_v1");
|
|
332535
|
+
const rawCapture = candidates.length > 0 ? createRawCapture(context2, {
|
|
332536
|
+
payload: input.payload,
|
|
332537
|
+
eventTimeMs: candidates[0]?.bar.openTimeMs ?? input.receivedTimestamp,
|
|
332538
|
+
receivedTimeMs: input.receivedTimestamp,
|
|
332539
|
+
scope: "ccxt_normalized_object"
|
|
332540
|
+
}) : undefined;
|
|
332541
|
+
if (rawCapture) {
|
|
332542
|
+
archiver.enqueue(buildCanonicalCexStreamEventRow(context2, rawCapture));
|
|
332543
|
+
}
|
|
332544
|
+
for (const candidate of candidates) {
|
|
332545
|
+
if (rawCapture) {
|
|
332546
|
+
archiver.enqueue(buildCanonicalOhlcvRow({
|
|
332547
|
+
context: context2,
|
|
332548
|
+
rawCapture,
|
|
332549
|
+
bar: candidate.bar,
|
|
332550
|
+
isClosed: candidate.isClosed,
|
|
332551
|
+
brokerVersion: candidate.brokerVersion
|
|
332552
|
+
}));
|
|
332553
|
+
}
|
|
332554
|
+
}
|
|
332555
|
+
if (candidates.length > 0) {
|
|
332556
|
+
recordWatchMetric(otelMetrics, "cex_watch_frames_archived_total", labels);
|
|
332557
|
+
}
|
|
332558
|
+
} catch (error48) {
|
|
332559
|
+
rethrowArchiveDurabilityError(error48);
|
|
332560
|
+
log.warn("Failed to archive OHLCV candle", { error: error48 });
|
|
332561
|
+
}
|
|
332562
|
+
});
|
|
332563
|
+
}
|
|
332564
|
+
function createOrderbookSampler() {
|
|
332565
|
+
return new OrderbookSampler;
|
|
332566
|
+
}
|
|
332567
|
+
function createOhlcvBarTracker() {
|
|
332568
|
+
return new OhlcvBarTracker;
|
|
332569
|
+
}
|
|
332570
|
+
function archiveMarketRowsInBackground(archiver, otelMetrics, stream4, input, feed, enqueueRows) {
|
|
332571
|
+
const labels = watchLabels(stream4, input, archiver, feed);
|
|
332572
|
+
recordWatchMetric(otelMetrics, "cex_watch_frames_received_total", labels);
|
|
332573
|
+
if (!isMarketArchiveEnabled() || !archiver?.isEnabled()) {
|
|
332574
|
+
return;
|
|
332575
|
+
}
|
|
332576
|
+
queueMicrotask(() => {
|
|
332577
|
+
try {
|
|
332578
|
+
const rows = enqueueRows();
|
|
332579
|
+
for (const row of rows) {
|
|
332580
|
+
archiver.enqueue(row);
|
|
332581
|
+
}
|
|
332582
|
+
if (rows.length > 0) {
|
|
332583
|
+
recordWatchMetric(otelMetrics, "cex_watch_frames_archived_total", labels);
|
|
332584
|
+
}
|
|
332585
|
+
} catch (error48) {
|
|
332586
|
+
rethrowArchiveDurabilityError(error48);
|
|
332587
|
+
log.warn(`Failed to archive ${stream4} market data`, { error: error48 });
|
|
332588
|
+
}
|
|
332589
|
+
});
|
|
332590
|
+
}
|
|
332591
|
+
function archiveTradesInBackground(archiver, otelMetrics, input) {
|
|
332592
|
+
archiveMarketRowsInBackground(archiver, otelMetrics, "trades", input, "TRADES", () => {
|
|
332593
|
+
if (!archiver)
|
|
332594
|
+
return [];
|
|
332595
|
+
const trades = extractTrades(input.payload, input.receivedTimestamp);
|
|
332596
|
+
if (trades.length === 0)
|
|
332597
|
+
return [];
|
|
332598
|
+
const context2 = resolveCaptureContext(archiver, input, "TRADES", "broker_live_stream_v1");
|
|
332599
|
+
const raw = createRawCapture(context2, {
|
|
332600
|
+
payload: input.payload,
|
|
332601
|
+
eventTimeMs: trades[0]?.eventTimeMs ?? input.receivedTimestamp,
|
|
332602
|
+
receivedTimeMs: input.receivedTimestamp,
|
|
332603
|
+
scope: "ccxt_normalized_object"
|
|
332604
|
+
});
|
|
332605
|
+
return [
|
|
332606
|
+
buildCanonicalCexStreamEventRow(context2, raw),
|
|
332607
|
+
...trades.map((trade) => buildCanonicalTradeRow(context2, raw, trade))
|
|
332608
|
+
];
|
|
332609
|
+
});
|
|
332610
|
+
}
|
|
332611
|
+
function archiveTickerInBackground(archiver, otelMetrics, input) {
|
|
332612
|
+
archiveMarketRowsInBackground(archiver, otelMetrics, "ticker", input, "TICKER", () => {
|
|
332613
|
+
const ticker = parseTicker(input.payload, input.receivedTimestamp);
|
|
332614
|
+
if (!ticker || !archiver)
|
|
332615
|
+
return [];
|
|
332616
|
+
const context2 = resolveCaptureContext(archiver, input, "TICKER", "broker_live_stream_v1");
|
|
332617
|
+
const raw = createRawCapture(context2, {
|
|
332618
|
+
payload: input.payload,
|
|
332619
|
+
eventTimeMs: ticker.eventTimeMs,
|
|
332620
|
+
receivedTimeMs: input.receivedTimestamp,
|
|
332621
|
+
scope: "ccxt_normalized_object"
|
|
332622
|
+
});
|
|
332623
|
+
return [
|
|
332624
|
+
buildCanonicalCexStreamEventRow(context2, raw),
|
|
332625
|
+
buildCanonicalTickerEventRow(context2, raw, ticker)
|
|
332626
|
+
];
|
|
332627
|
+
});
|
|
332628
|
+
}
|
|
332629
|
+
function archiveCexStreamEventInBackground(archiver, otelMetrics, input) {
|
|
332630
|
+
archiveMarketRowsInBackground(archiver, otelMetrics, "stream", input, input.streamType, () => [
|
|
332631
|
+
buildCexStreamEventRow({
|
|
332632
|
+
...input,
|
|
332633
|
+
source: archiver?.getSource()
|
|
332634
|
+
})
|
|
332635
|
+
]);
|
|
332636
|
+
}
|
|
332637
|
+
|
|
332638
|
+
// src/handlers/execute-action/order-book-call.ts
|
|
332639
|
+
async function handleOrderBookCall(ctx) {
|
|
332640
|
+
const parsedOrderBookCall = parseOrderBookCallPayload(ctx.call.request.payload, {
|
|
332641
|
+
exchange: ctx.normalizedCex,
|
|
332642
|
+
symbol: ctx.symbol
|
|
332643
|
+
});
|
|
332644
|
+
if (parsedOrderBookCall.kind === "error") {
|
|
332645
|
+
ctx.wrappedCallback({
|
|
332646
|
+
code: grpc4.status.INVALID_ARGUMENT,
|
|
332647
|
+
message: parsedOrderBookCall.message
|
|
332648
|
+
}, null);
|
|
332649
|
+
return true;
|
|
332650
|
+
}
|
|
332651
|
+
if (parsedOrderBookCall.kind !== "order_book") {
|
|
332652
|
+
return false;
|
|
332653
|
+
}
|
|
332654
|
+
const orderBookBroker = ctx.broker;
|
|
332655
|
+
if (!orderBookBroker) {
|
|
332656
|
+
ctx.wrappedCallback({
|
|
332657
|
+
code: grpc4.status.INVALID_ARGUMENT,
|
|
332658
|
+
message: `Unsupported exchange for order-book market data: ${ctx.normalizedCex}`
|
|
332659
|
+
}, null);
|
|
332660
|
+
return true;
|
|
332661
|
+
}
|
|
332662
|
+
ctx.applyVerityToBroker(orderBookBroker);
|
|
332663
|
+
try {
|
|
332664
|
+
const orderBookPayload = parsedOrderBookCall.payload;
|
|
332665
|
+
if (orderBookPayload.method === ORDER_BOOK_CALL_METHODS.FETCH_CAPABILITY) {
|
|
332666
|
+
ctx.wrappedCallback(null, {
|
|
332667
|
+
proof: ctx.verity.proof,
|
|
332668
|
+
result: JSON.stringify(buildOrderBookCapability(orderBookBroker, orderBookPayload))
|
|
332669
|
+
});
|
|
332670
|
+
return true;
|
|
332671
|
+
}
|
|
332672
|
+
if (orderBookPayload.method === ORDER_BOOK_CALL_METHODS.FETCH_HISTORICAL_SNAPSHOTS) {
|
|
332673
|
+
ctx.wrappedCallback(null, {
|
|
332674
|
+
proof: ctx.verity.proof,
|
|
332675
|
+
result: JSON.stringify(buildHistoricalOrderBookUnsupported(orderBookPayload))
|
|
332676
|
+
});
|
|
332677
|
+
return true;
|
|
332678
|
+
}
|
|
332679
|
+
const fetchOrderBook = orderBookBroker.fetchOrderBook;
|
|
332680
|
+
const canFetchOrderBook = typeof fetchOrderBook === "function" && orderBookBroker.has?.fetchOrderBook !== false;
|
|
332681
|
+
if (!canFetchOrderBook) {
|
|
332682
|
+
ctx.wrappedCallback({
|
|
332683
|
+
code: grpc4.status.UNIMPLEMENTED,
|
|
332684
|
+
message: `Order-book snapshot unsupported for ${ctx.normalizedCex}`
|
|
332685
|
+
}, null);
|
|
332686
|
+
return true;
|
|
332687
|
+
}
|
|
332688
|
+
const receivedTimestamp = Date.now();
|
|
332689
|
+
const rawOrderBook = await fetchOrderBook.call(orderBookBroker, orderBookPayload.symbol, orderBookPayload.depthLimit);
|
|
332690
|
+
const snapshot = normalizeOrderBookSnapshot(rawOrderBook, {
|
|
332691
|
+
exchange: orderBookPayload.exchange,
|
|
332692
|
+
symbol: orderBookPayload.symbol,
|
|
332693
|
+
depthLimit: orderBookPayload.depthLimit,
|
|
332694
|
+
receivedTimestamp
|
|
332695
|
+
});
|
|
332696
|
+
ctx.wrappedCallback(null, {
|
|
332150
332697
|
proof: ctx.verity.proof,
|
|
332151
|
-
result: JSON.stringify(
|
|
332698
|
+
result: JSON.stringify(snapshot)
|
|
332699
|
+
});
|
|
332700
|
+
archiveOrderbookInBackground(ctx.brokerArchiver, ctx.otelMetrics, {
|
|
332701
|
+
exchange: orderBookPayload.exchange,
|
|
332702
|
+
symbol: orderBookPayload.symbol,
|
|
332703
|
+
assetType: "spot",
|
|
332704
|
+
accountSelector: ctx.selectedBrokerAccount?.label,
|
|
332705
|
+
deploymentId: ctx.brokerArchiver?.getDeploymentId() ?? "unarchived",
|
|
332706
|
+
snapshot
|
|
332707
|
+
}, {
|
|
332708
|
+
sourceMode: "broker_current_snapshot_v1",
|
|
332709
|
+
depthLimit: orderBookPayload.depthLimit
|
|
332152
332710
|
});
|
|
332153
332711
|
} catch (error48) {
|
|
332154
|
-
safeLogError(
|
|
332712
|
+
safeLogError("Order-book Call failed", error48);
|
|
332155
332713
|
ctx.wrappedCallback({
|
|
332156
|
-
code:
|
|
332157
|
-
message: `
|
|
332714
|
+
code: mapCcxtErrorToGrpcStatus(error48) ?? grpc4.status.INTERNAL,
|
|
332715
|
+
message: `Order-book Call failed: ${sanitizeErrorDetail(error48)}`
|
|
332158
332716
|
}, null);
|
|
332159
332717
|
}
|
|
332718
|
+
return true;
|
|
332160
332719
|
}
|
|
332161
|
-
|
|
332720
|
+
|
|
332721
|
+
// src/handlers/execute-action/registry.ts
|
|
332722
|
+
var grpc11 = __toESM(require_src3(), 1);
|
|
332723
|
+
|
|
332724
|
+
// src/handlers/execute-action/internal-transfer.ts
|
|
332725
|
+
var grpc5 = __toESM(require_src3(), 1);
|
|
332726
|
+
async function handleInternalTransfer(ctx) {
|
|
332162
332727
|
const {
|
|
332163
|
-
call,
|
|
332164
|
-
wrappedCallback,
|
|
332165
|
-
policy,
|
|
332166
332728
|
brokers,
|
|
332167
332729
|
metadata,
|
|
332168
332730
|
normalizedCex,
|
|
332169
|
-
cex: cex3,
|
|
332170
332731
|
symbol: symbol2,
|
|
332171
|
-
selectedBrokerAccount,
|
|
332172
|
-
broker,
|
|
332173
332732
|
verity,
|
|
332174
|
-
applyVerityToBroker,
|
|
332175
332733
|
useVerity,
|
|
332176
332734
|
verityProverUrl,
|
|
332177
|
-
|
|
332735
|
+
brokerArchiver
|
|
332178
332736
|
} = ctx;
|
|
332179
|
-
const verityProof = verity.proof;
|
|
332180
332737
|
if (!symbol2) {
|
|
332181
332738
|
return ctx.wrappedCallback({
|
|
332182
|
-
code:
|
|
332739
|
+
code: grpc5.status.INVALID_ARGUMENT,
|
|
332183
332740
|
message: `ValidationError: Symbol required`
|
|
332184
332741
|
}, null);
|
|
332185
332742
|
}
|
|
332186
|
-
const
|
|
332187
|
-
if (
|
|
332743
|
+
const transferPayload = parsePayloadForAction(ctx, InternalTransferPayloadSchema);
|
|
332744
|
+
if (transferPayload === null)
|
|
332188
332745
|
return;
|
|
332189
|
-
|
|
332746
|
+
if (normalizedCex !== "binance") {
|
|
332747
|
+
return ctx.wrappedCallback({
|
|
332748
|
+
code: grpc5.status.UNIMPLEMENTED,
|
|
332749
|
+
message: `InternalTransfer is only supported for Binance`
|
|
332750
|
+
}, null);
|
|
332751
|
+
}
|
|
332752
|
+
const pool = brokers[normalizedCex];
|
|
332753
|
+
if (!pool) {
|
|
332754
|
+
return ctx.wrappedCallback({
|
|
332755
|
+
code: grpc5.status.FAILED_PRECONDITION,
|
|
332756
|
+
message: `No broker accounts configured for ${normalizedCex}`
|
|
332757
|
+
}, null);
|
|
332758
|
+
}
|
|
332759
|
+
const fromSelector = transferPayload.fromAccount ?? getCurrentBrokerSelector(metadata);
|
|
332760
|
+
const toSelector = transferPayload.toAccount ?? "primary";
|
|
332761
|
+
const sourceAccount = resolveBrokerAccount(pool, fromSelector);
|
|
332762
|
+
if (!sourceAccount) {
|
|
332763
|
+
return ctx.wrappedCallback({
|
|
332764
|
+
code: grpc5.status.INVALID_ARGUMENT,
|
|
332765
|
+
message: `Source account "${fromSelector}" is not configured`
|
|
332766
|
+
}, null);
|
|
332767
|
+
}
|
|
332768
|
+
const destAccount = resolveBrokerAccount(pool, toSelector);
|
|
332769
|
+
if (!destAccount) {
|
|
332770
|
+
return ctx.wrappedCallback({
|
|
332771
|
+
code: grpc5.status.INVALID_ARGUMENT,
|
|
332772
|
+
message: `Destination account "${toSelector}" is not configured`
|
|
332773
|
+
}, null);
|
|
332774
|
+
}
|
|
332190
332775
|
try {
|
|
332191
|
-
|
|
332192
|
-
|
|
332193
|
-
|
|
332194
|
-
|
|
332195
|
-
|
|
332196
|
-
try {
|
|
332197
|
-
const feeMap = await broker.fetchDepositWithdrawFees(currencyCodes);
|
|
332198
|
-
for (const code of currencyCodes) {
|
|
332199
|
-
const feeInfo = feeMap[code];
|
|
332200
|
-
if (!feeInfo) {
|
|
332201
|
-
continue;
|
|
332202
|
-
}
|
|
332203
|
-
const fallbackFee = feeInfo.fee !== undefined || feeInfo.percentage !== undefined ? {
|
|
332204
|
-
fee: feeInfo.fee ?? null,
|
|
332205
|
-
percentage: feeInfo.percentage ?? null
|
|
332206
|
-
} : null;
|
|
332207
|
-
fundingFeesByCurrency2[code] = {
|
|
332208
|
-
deposit: feeInfo.deposit ?? fallbackFee,
|
|
332209
|
-
withdraw: feeInfo.withdraw ?? fallbackFee,
|
|
332210
|
-
networks: feeInfo.networks ?? {}
|
|
332211
|
-
};
|
|
332212
|
-
}
|
|
332213
|
-
if (Object.keys(fundingFeesByCurrency2).length > 0) {
|
|
332214
|
-
fundingFeeSource2 = "fetchDepositWithdrawFees";
|
|
332215
|
-
}
|
|
332216
|
-
} catch (error48) {
|
|
332217
|
-
safeLogError(`Error fetching deposit/withdraw fee map for ${symbol2} from ${cex3}`, error48);
|
|
332218
|
-
}
|
|
332219
|
-
}
|
|
332220
|
-
if (fundingFeeSource2 === "unavailable") {
|
|
332221
|
-
try {
|
|
332222
|
-
const currencies = await broker.fetchCurrencies();
|
|
332223
|
-
for (const code of currencyCodes) {
|
|
332224
|
-
const currency = currencies[code];
|
|
332225
|
-
if (!currency) {
|
|
332226
|
-
continue;
|
|
332227
|
-
}
|
|
332228
|
-
fundingFeesByCurrency2[code] = {
|
|
332229
|
-
deposit: {
|
|
332230
|
-
enabled: currency.deposit ?? null
|
|
332231
|
-
},
|
|
332232
|
-
withdraw: {
|
|
332233
|
-
enabled: currency.withdraw ?? null,
|
|
332234
|
-
fee: currency.fee ?? null,
|
|
332235
|
-
limits: currency.limits?.withdraw ?? null
|
|
332236
|
-
},
|
|
332237
|
-
networks: currency.networks ?? {}
|
|
332238
|
-
};
|
|
332239
|
-
}
|
|
332240
|
-
if (Object.keys(fundingFeesByCurrency2).length > 0) {
|
|
332241
|
-
fundingFeeSource2 = "currencies";
|
|
332242
|
-
}
|
|
332243
|
-
} catch (error48) {
|
|
332244
|
-
safeLogError(`Error fetching currency metadata for fees for ${symbol2} from ${cex3}`, error48);
|
|
332245
|
-
}
|
|
332246
|
-
}
|
|
332247
|
-
return { fundingFeeSource: fundingFeeSource2, fundingFeesByCurrency: fundingFeesByCurrency2 };
|
|
332248
|
-
};
|
|
332249
|
-
const isMarketSymbol = symbol2.includes("/");
|
|
332250
|
-
if (isMarketSymbol) {
|
|
332251
|
-
const market = await broker.market(symbol2);
|
|
332252
|
-
const generalFee = broker.fees ?? null;
|
|
332253
|
-
const feeStatus = broker.fees ? "available" : "unknown";
|
|
332254
|
-
if (!broker.fees) {
|
|
332255
|
-
log.warn(`Fee metadata unavailable for ${cex3}`, { symbol: symbol2 });
|
|
332256
|
-
}
|
|
332257
|
-
if (!includeAllFees) {
|
|
332258
|
-
return ctx.wrappedCallback(null, {
|
|
332259
|
-
proof: ctx.verity.proof,
|
|
332260
|
-
result: JSON.stringify({
|
|
332261
|
-
feeScope: "market",
|
|
332262
|
-
generalFee,
|
|
332263
|
-
feeStatus,
|
|
332264
|
-
market
|
|
332265
|
-
})
|
|
332266
|
-
});
|
|
332267
|
-
}
|
|
332268
|
-
const currencyCodes = Array.from(new Set([market.base, market.quote]));
|
|
332269
|
-
const { fundingFeeSource: fundingFeeSource2, fundingFeesByCurrency: fundingFeesByCurrency2 } = await fetchFundingFees(currencyCodes);
|
|
332270
|
-
return ctx.wrappedCallback(null, {
|
|
332271
|
-
proof: ctx.verity.proof,
|
|
332272
|
-
result: JSON.stringify({
|
|
332273
|
-
feeScope: "market+funding",
|
|
332274
|
-
generalFee,
|
|
332275
|
-
feeStatus,
|
|
332276
|
-
market,
|
|
332277
|
-
fundingFeeSource: fundingFeeSource2,
|
|
332278
|
-
fundingFeesByCurrency: fundingFeesByCurrency2
|
|
332279
|
-
})
|
|
332280
|
-
});
|
|
332776
|
+
if (useVerity) {
|
|
332777
|
+
sourceAccount.exchange.setHttpClientOverride(buildHttpClientOverrideFromMetadata(metadata, verityProverUrl, (proof, notaryPubKey) => {
|
|
332778
|
+
verity.proof = proof;
|
|
332779
|
+
log.debug(`Verity proof:`, { proof, notaryPubKey });
|
|
332780
|
+
}), verityHttpClientOverridePredicate);
|
|
332281
332781
|
}
|
|
332282
|
-
const
|
|
332283
|
-
|
|
332284
|
-
|
|
332285
|
-
|
|
332286
|
-
|
|
332287
|
-
|
|
332288
|
-
|
|
332289
|
-
|
|
332290
|
-
|
|
332291
|
-
|
|
332292
|
-
|
|
332293
|
-
|
|
332782
|
+
const result = await transferBinanceInternal(sourceAccount, destAccount, symbol2, transferPayload.amount);
|
|
332783
|
+
archiveTransferEventInBackground(brokerArchiver, {
|
|
332784
|
+
exchange: normalizedCex,
|
|
332785
|
+
accountSelector: fromSelector,
|
|
332786
|
+
assetSymbol: symbol2,
|
|
332787
|
+
transfer: {
|
|
332788
|
+
eventKind: "internal_transfer",
|
|
332789
|
+
lifecycleAction: "submit_internal_transfer",
|
|
332790
|
+
status: "ok",
|
|
332791
|
+
amount: String(transferPayload.amount),
|
|
332792
|
+
network: "internal",
|
|
332793
|
+
externalId: extractBinanceInternalTransferId(result),
|
|
332794
|
+
payload: { from: fromSelector, to: toSelector, result }
|
|
332795
|
+
}
|
|
332796
|
+
});
|
|
332797
|
+
ctx.wrappedCallback(null, {
|
|
332798
|
+
proof: verity.proof,
|
|
332799
|
+
result: JSON.stringify(result)
|
|
332294
332800
|
});
|
|
332295
332801
|
} catch (error48) {
|
|
332296
|
-
safeLogError(
|
|
332802
|
+
safeLogError("InternalTransfer failed", error48);
|
|
332803
|
+
if (error48 instanceof BrokerAccountPreconditionError) {
|
|
332804
|
+
return ctx.wrappedCallback({
|
|
332805
|
+
code: grpc5.status.FAILED_PRECONDITION,
|
|
332806
|
+
message: getErrorMessage(error48)
|
|
332807
|
+
}, null);
|
|
332808
|
+
}
|
|
332809
|
+
const msg = getErrorMessage(error48);
|
|
332810
|
+
let code;
|
|
332811
|
+
if (msg.includes("Unsupported transfer direction")) {
|
|
332812
|
+
code = grpc5.status.INVALID_ARGUMENT;
|
|
332813
|
+
} else if (msg.includes("unavailable in this CCXT build")) {
|
|
332814
|
+
code = grpc5.status.UNIMPLEMENTED;
|
|
332815
|
+
} else {
|
|
332816
|
+
code = mapCcxtErrorToGrpcStatus(error48) ?? grpc5.status.INTERNAL;
|
|
332817
|
+
}
|
|
332297
332818
|
ctx.wrappedCallback({
|
|
332298
|
-
code
|
|
332299
|
-
message: `
|
|
332819
|
+
code,
|
|
332820
|
+
message: `InternalTransfer failed: ${sanitizeErrorDetail(error48)}`
|
|
332300
332821
|
}, null);
|
|
332301
332822
|
}
|
|
332302
332823
|
}
|
|
332303
|
-
|
|
332824
|
+
|
|
332825
|
+
// src/handlers/execute-action/orders.ts
|
|
332826
|
+
var grpc6 = __toESM(require_src3(), 1);
|
|
332827
|
+
|
|
332828
|
+
// src/helpers/passive-order.ts
|
|
332829
|
+
var PASSIVE_ORDER_ERROR_CODES = {
|
|
332830
|
+
unsupported: "passive_order_unsupported",
|
|
332831
|
+
rejected: "passive_order_rejected",
|
|
332832
|
+
wouldCross: "passive_order_would_cross"
|
|
332833
|
+
};
|
|
332834
|
+
function identifiesWouldCross(message) {
|
|
332835
|
+
const normalized = message.toLowerCase();
|
|
332836
|
+
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);
|
|
332837
|
+
}
|
|
332838
|
+
function identifiesUnsupported(message) {
|
|
332839
|
+
const normalized = message.toLowerCase();
|
|
332840
|
+
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);
|
|
332841
|
+
}
|
|
332842
|
+
function classifyPassiveOrderError(error48) {
|
|
332843
|
+
if (error48 instanceof ccxt_default.InsufficientFunds) {
|
|
332844
|
+
return "InsufficientFunds";
|
|
332845
|
+
}
|
|
332846
|
+
if (error48 instanceof ccxt_default.AuthenticationError) {
|
|
332847
|
+
return "AuthenticationError";
|
|
332848
|
+
}
|
|
332849
|
+
const message = getErrorMessage(error48);
|
|
332850
|
+
if (error48 instanceof ccxt_default.OrderImmediatelyFillable || identifiesWouldCross(message)) {
|
|
332851
|
+
return PASSIVE_ORDER_ERROR_CODES.wouldCross;
|
|
332852
|
+
}
|
|
332853
|
+
if (error48 instanceof ccxt_default.NotSupported || identifiesUnsupported(message)) {
|
|
332854
|
+
return PASSIVE_ORDER_ERROR_CODES.unsupported;
|
|
332855
|
+
}
|
|
332856
|
+
return PASSIVE_ORDER_ERROR_CODES.rejected;
|
|
332857
|
+
}
|
|
332858
|
+
|
|
332859
|
+
// src/handlers/execute-action/orders.ts
|
|
332860
|
+
async function handleCreateOrder(ctx) {
|
|
332304
332861
|
const {
|
|
332305
332862
|
call,
|
|
332306
332863
|
wrappedCallback,
|
|
@@ -332316,70 +332873,193 @@ async function handleFetchDepositAddresses(ctx) {
|
|
|
332316
332873
|
applyVerityToBroker,
|
|
332317
332874
|
useVerity,
|
|
332318
332875
|
verityProverUrl,
|
|
332319
|
-
otelMetrics
|
|
332876
|
+
otelMetrics,
|
|
332877
|
+
brokerArchiver,
|
|
332878
|
+
orderActivityTracker
|
|
332320
332879
|
} = ctx;
|
|
332321
332880
|
const verityProof = verity.proof;
|
|
332322
|
-
|
|
332323
|
-
|
|
332324
|
-
code: grpc7.status.INVALID_ARGUMENT,
|
|
332325
|
-
message: `ValidationError: Symbol required`
|
|
332326
|
-
}, null);
|
|
332327
|
-
}
|
|
332328
|
-
const fetchDepositAddresses = parsePayloadForAction(ctx, FetchDepositAddressesPayloadSchema);
|
|
332329
|
-
if (fetchDepositAddresses === null)
|
|
332881
|
+
const orderValue = parsePayloadForAction(ctx, CreateOrderPayloadSchema);
|
|
332882
|
+
if (orderValue === null)
|
|
332330
332883
|
return;
|
|
332331
|
-
|
|
332332
|
-
|
|
332333
|
-
depositNetwork = await resolveTransferNetwork(broker, symbol2, fetchDepositAddresses.chain);
|
|
332334
|
-
} catch (error48) {
|
|
332335
|
-
const message = getErrorMessage(error48);
|
|
332336
|
-
return ctx.wrappedCallback({
|
|
332337
|
-
code: stableGrpcErrorCode(message) ?? grpc7.status.INVALID_ARGUMENT,
|
|
332338
|
-
message
|
|
332339
|
-
}, null);
|
|
332340
|
-
}
|
|
332341
|
-
const depositValidation = validateDeposit(policy, cex3, depositNetwork.brokerNetworkId, symbol2);
|
|
332342
|
-
if (!depositValidation.valid) {
|
|
332884
|
+
const isPassiveOrder = orderValue.orderIntent === "passive_only";
|
|
332885
|
+
if (isPassiveOrder && orderValue.orderType !== "limit") {
|
|
332343
332886
|
return ctx.wrappedCallback({
|
|
332344
|
-
code:
|
|
332345
|
-
message:
|
|
332887
|
+
code: grpc6.status.INVALID_ARGUMENT,
|
|
332888
|
+
message: "ValidationError: passive_only order intent requires a limit order"
|
|
332346
332889
|
}, null);
|
|
332347
332890
|
}
|
|
332891
|
+
const createOrderParams = {
|
|
332892
|
+
...orderValue.params,
|
|
332893
|
+
...orderValue.clientOrderId !== undefined && {
|
|
332894
|
+
clientOrderId: orderValue.clientOrderId
|
|
332895
|
+
},
|
|
332896
|
+
...isPassiveOrder && { postOnly: true }
|
|
332897
|
+
};
|
|
332898
|
+
let resolvedOrderTelemetry = {};
|
|
332899
|
+
let marketMetadataHash;
|
|
332900
|
+
let submission = "not_attempted";
|
|
332348
332901
|
try {
|
|
332349
|
-
|
|
332350
|
-
|
|
332351
|
-
|
|
332352
|
-
|
|
332902
|
+
if (!broker) {
|
|
332903
|
+
return ctx.wrappedCallback({
|
|
332904
|
+
code: grpc6.status.INVALID_ARGUMENT,
|
|
332905
|
+
message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
|
|
332906
|
+
}, null);
|
|
332907
|
+
}
|
|
332908
|
+
const resolution = await resolveOrderExecution(policy, broker, cex3, orderValue.fromToken, orderValue.toToken, orderValue.amount, orderValue.price, orderValue.marketType);
|
|
332909
|
+
if (!resolution.valid || !resolution.symbol || !resolution.side) {
|
|
332910
|
+
return ctx.wrappedCallback({
|
|
332911
|
+
code: grpc6.status.INVALID_ARGUMENT,
|
|
332912
|
+
message: resolution.error ?? "Order rejected by policy: market or limits not satisfied"
|
|
332913
|
+
}, null);
|
|
332914
|
+
}
|
|
332915
|
+
resolvedOrderTelemetry = {
|
|
332916
|
+
symbol: resolution.symbol,
|
|
332917
|
+
side: resolution.side,
|
|
332918
|
+
requestedQuantity: resolution.amountBase ?? orderValue.amount
|
|
332919
|
+
};
|
|
332920
|
+
if (selectedBrokerAccount?.label) {
|
|
332921
|
+
orderActivityTracker?.record(cex3, selectedBrokerAccount.label, resolution.symbol);
|
|
332922
|
+
}
|
|
332923
|
+
const telemetryIds = extractOrderTelemetryIds(createOrderParams);
|
|
332924
|
+
const submissionTimestamp = new Date().toISOString();
|
|
332925
|
+
marketMetadataHash = await captureMarketMetadataSnapshot(brokerArchiver, broker, {
|
|
332926
|
+
exchange: cex3,
|
|
332927
|
+
accountSelector: selectedBrokerAccount?.label,
|
|
332928
|
+
symbol: resolution.symbol,
|
|
332929
|
+
action: "CreateOrder",
|
|
332930
|
+
brokerObservedTimestamp: submissionTimestamp,
|
|
332931
|
+
...telemetryIds
|
|
332932
|
+
});
|
|
332933
|
+
submission = "in_flight";
|
|
332934
|
+
const order = await broker.createOrder(resolution.symbol, orderValue.orderType, resolution.side, resolution.amountBase ?? orderValue.amount, orderValue.price, createOrderParams);
|
|
332935
|
+
submission = "placed";
|
|
332936
|
+
const createOrderContext = {
|
|
332937
|
+
action: "CreateOrder",
|
|
332938
|
+
cex: cex3,
|
|
332939
|
+
accountLabel: selectedBrokerAccount?.label,
|
|
332940
|
+
symbol: resolvedOrderTelemetry.symbol,
|
|
332941
|
+
side: resolvedOrderTelemetry.side,
|
|
332942
|
+
orderType: orderValue.orderType,
|
|
332943
|
+
requestedQuantity: resolvedOrderTelemetry.requestedQuantity,
|
|
332944
|
+
requestedNotional: orderValue.amount * orderValue.price,
|
|
332945
|
+
orderAuthor: orderValue.orderAuthor,
|
|
332946
|
+
brokerObservedTimestamp: submissionTimestamp,
|
|
332947
|
+
...telemetryIds
|
|
332948
|
+
};
|
|
332949
|
+
emitOrderExecutionTelemetryInBackground(otelMetrics, createOrderContext, order);
|
|
332950
|
+
archiveOrderExecutionInBackground(brokerArchiver, createOrderContext, order, undefined, { marketMetadataHash });
|
|
332951
|
+
ctx.wrappedCallback(null, {
|
|
332952
|
+
result: JSON.stringify({
|
|
332953
|
+
...order,
|
|
332954
|
+
...isPassiveOrder && {
|
|
332955
|
+
passivePlacementOutcome: "accepted_passive"
|
|
332956
|
+
}
|
|
332353
332957
|
})
|
|
332354
|
-
] : await broker.fetchDepositAddressesByNetwork(symbol2, {
|
|
332355
|
-
network: depositNetwork.exchangeNetworkId,
|
|
332356
|
-
...fetchDepositAddresses.params ?? {}
|
|
332357
332958
|
});
|
|
332358
|
-
|
|
332359
|
-
|
|
332360
|
-
|
|
332361
|
-
|
|
332362
|
-
|
|
332363
|
-
|
|
332364
|
-
|
|
332365
|
-
|
|
332366
|
-
|
|
332959
|
+
} catch (error48) {
|
|
332960
|
+
rethrowArchiveDurabilityError(error48);
|
|
332961
|
+
safeLogRedactedError("Order Creation failed", error48);
|
|
332962
|
+
const failedCreateContext = {
|
|
332963
|
+
action: "CreateOrder",
|
|
332964
|
+
cex: cex3,
|
|
332965
|
+
accountLabel: selectedBrokerAccount?.label,
|
|
332966
|
+
symbol: resolvedOrderTelemetry.symbol ?? symbol2,
|
|
332967
|
+
side: resolvedOrderTelemetry.side,
|
|
332968
|
+
orderType: orderValue.orderType,
|
|
332969
|
+
requestedQuantity: resolvedOrderTelemetry.requestedQuantity ?? orderValue.amount,
|
|
332970
|
+
requestedNotional: orderValue.amount * orderValue.price,
|
|
332971
|
+
orderAuthor: orderValue.orderAuthor,
|
|
332972
|
+
...extractOrderTelemetryIds(createOrderParams)
|
|
332973
|
+
};
|
|
332974
|
+
emitOrderExecutionTelemetryInBackground(otelMetrics, failedCreateContext, undefined, error48);
|
|
332975
|
+
archiveOrderExecutionInBackground(brokerArchiver, failedCreateContext, undefined, error48, { marketMetadataHash });
|
|
332976
|
+
if (isPassiveOrder && submission === "in_flight") {
|
|
332977
|
+
const stableErrorCode = classifyPassiveOrderError(error48);
|
|
332978
|
+
return rejectWithGrpcError(ctx, error48, {
|
|
332979
|
+
message: `${stableErrorCode}: ${sanitizeErrorDetail(error48)}`,
|
|
332980
|
+
preferStableMessageOnly: true
|
|
332367
332981
|
});
|
|
332368
332982
|
}
|
|
332369
332983
|
ctx.wrappedCallback({
|
|
332370
|
-
code:
|
|
332371
|
-
message:
|
|
332984
|
+
code: grpc6.status.INTERNAL,
|
|
332985
|
+
message: `Order Creation failed: ${sanitizeErrorDetail(error48)}`
|
|
332372
332986
|
}, null);
|
|
332987
|
+
}
|
|
332988
|
+
}
|
|
332989
|
+
async function handleGetOrderDetails(ctx) {
|
|
332990
|
+
const {
|
|
332991
|
+
call,
|
|
332992
|
+
wrappedCallback,
|
|
332993
|
+
policy,
|
|
332994
|
+
brokers,
|
|
332995
|
+
metadata,
|
|
332996
|
+
normalizedCex,
|
|
332997
|
+
cex: cex3,
|
|
332998
|
+
symbol: symbol2,
|
|
332999
|
+
selectedBrokerAccount,
|
|
333000
|
+
broker,
|
|
333001
|
+
verity,
|
|
333002
|
+
applyVerityToBroker,
|
|
333003
|
+
useVerity,
|
|
333004
|
+
verityProverUrl,
|
|
333005
|
+
otelMetrics,
|
|
333006
|
+
brokerArchiver,
|
|
333007
|
+
orderActivityTracker
|
|
333008
|
+
} = ctx;
|
|
333009
|
+
const verityProof = verity.proof;
|
|
333010
|
+
const getOrderValue = parsePayloadForAction(ctx, GetOrderDetailsPayloadSchema);
|
|
333011
|
+
if (getOrderValue === null)
|
|
333012
|
+
return;
|
|
333013
|
+
try {
|
|
333014
|
+
if (!broker) {
|
|
333015
|
+
return ctx.wrappedCallback({
|
|
333016
|
+
code: grpc6.status.INVALID_ARGUMENT,
|
|
333017
|
+
message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
|
|
333018
|
+
}, null);
|
|
333019
|
+
}
|
|
333020
|
+
const orderDetails = await broker.fetchOrder(getOrderValue.orderId, symbol2, { ...getOrderValue.params });
|
|
333021
|
+
if (selectedBrokerAccount?.label && symbol2) {
|
|
333022
|
+
orderActivityTracker?.record(cex3, selectedBrokerAccount.label, symbol2);
|
|
333023
|
+
}
|
|
333024
|
+
const getOrderContext = {
|
|
333025
|
+
action: "GetOrderDetails",
|
|
333026
|
+
cex: cex3,
|
|
333027
|
+
accountLabel: selectedBrokerAccount?.label,
|
|
333028
|
+
symbol: symbol2,
|
|
333029
|
+
...extractOrderTelemetryIds(getOrderValue.params)
|
|
333030
|
+
};
|
|
333031
|
+
emitOrderExecutionTelemetryInBackground(otelMetrics, getOrderContext, orderDetails);
|
|
333032
|
+
archiveOrderExecutionInBackground(brokerArchiver, getOrderContext, orderDetails);
|
|
333033
|
+
ctx.wrappedCallback(null, {
|
|
333034
|
+
result: JSON.stringify({
|
|
333035
|
+
orderId: orderDetails.id,
|
|
333036
|
+
status: orderDetails.status,
|
|
333037
|
+
amount: orderDetails.amount,
|
|
333038
|
+
filled: orderDetails.filled,
|
|
333039
|
+
remaining: orderDetails.remaining,
|
|
333040
|
+
symbol: orderDetails.symbol,
|
|
333041
|
+
side: orderDetails.side,
|
|
333042
|
+
price: orderDetails.price
|
|
333043
|
+
})
|
|
333044
|
+
});
|
|
332373
333045
|
} catch (error48) {
|
|
332374
|
-
safeLogError(
|
|
332375
|
-
const
|
|
333046
|
+
safeLogError(`Error fetching order details from ${cex3}`, error48);
|
|
333047
|
+
const failedGetOrderContext = {
|
|
333048
|
+
action: "GetOrderDetails",
|
|
333049
|
+
cex: cex3,
|
|
333050
|
+
accountLabel: selectedBrokerAccount?.label,
|
|
333051
|
+
symbol: symbol2,
|
|
333052
|
+
...extractOrderTelemetryIds(getOrderValue.params)
|
|
333053
|
+
};
|
|
333054
|
+
emitOrderExecutionTelemetryInBackground(otelMetrics, failedGetOrderContext, undefined, error48);
|
|
333055
|
+
archiveOrderExecutionInBackground(brokerArchiver, failedGetOrderContext, undefined, error48);
|
|
332376
333056
|
ctx.wrappedCallback({
|
|
332377
|
-
code:
|
|
332378
|
-
message:
|
|
333057
|
+
code: grpc6.status.INTERNAL,
|
|
333058
|
+
message: `Failed to fetch order details from ${cex3}: ${sanitizeErrorDetail(error48)}`
|
|
332379
333059
|
}, null);
|
|
332380
333060
|
}
|
|
332381
333061
|
}
|
|
332382
|
-
async function
|
|
333062
|
+
async function handleCancelOrder(ctx) {
|
|
332383
333063
|
const {
|
|
332384
333064
|
call,
|
|
332385
333065
|
wrappedCallback,
|
|
@@ -332395,63 +333075,66 @@ async function handleFetchBalances(ctx) {
|
|
|
332395
333075
|
applyVerityToBroker,
|
|
332396
333076
|
useVerity,
|
|
332397
333077
|
verityProverUrl,
|
|
332398
|
-
otelMetrics
|
|
333078
|
+
otelMetrics,
|
|
333079
|
+
brokerArchiver,
|
|
333080
|
+
orderActivityTracker
|
|
332399
333081
|
} = ctx;
|
|
332400
333082
|
const verityProof = verity.proof;
|
|
333083
|
+
const cancelOrderValue = parsePayloadForAction(ctx, CancelOrderPayloadSchema);
|
|
333084
|
+
if (cancelOrderValue === null)
|
|
333085
|
+
return;
|
|
332401
333086
|
try {
|
|
332402
|
-
|
|
332403
|
-
const providedBalanceType = payload.balanceType;
|
|
332404
|
-
const balanceType = (providedBalanceType ?? "total").toString();
|
|
332405
|
-
const validBalanceTypes = new Set(["free", "used", "total"]);
|
|
332406
|
-
if (!validBalanceTypes.has(balanceType)) {
|
|
333087
|
+
if (!broker) {
|
|
332407
333088
|
return ctx.wrappedCallback({
|
|
332408
|
-
code:
|
|
332409
|
-
message: `
|
|
333089
|
+
code: grpc6.status.INVALID_ARGUMENT,
|
|
333090
|
+
message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
|
|
332410
333091
|
}, null);
|
|
332411
333092
|
}
|
|
332412
|
-
const
|
|
332413
|
-
|
|
332414
|
-
|
|
332415
|
-
|
|
332416
|
-
|
|
332417
|
-
params
|
|
332418
|
-
}
|
|
332419
|
-
|
|
332420
|
-
if (
|
|
332421
|
-
|
|
332422
|
-
responseBalances = partial2 ?? {};
|
|
332423
|
-
} else if (balanceType === "used") {
|
|
332424
|
-
const partial2 = await broker.fetchUsedBalance(params);
|
|
332425
|
-
responseBalances = partial2 ?? {};
|
|
332426
|
-
} else if (balanceType === "total") {
|
|
332427
|
-
const partial2 = await broker.fetchTotalBalance(params);
|
|
332428
|
-
responseBalances = partial2 ?? {};
|
|
332429
|
-
}
|
|
332430
|
-
if (symbol2) {
|
|
332431
|
-
if (typeof responseBalances[symbol2] === "number") {
|
|
332432
|
-
responseBalances = {
|
|
332433
|
-
[symbol2]: responseBalances[symbol2] ?? 0
|
|
332434
|
-
};
|
|
332435
|
-
} else {
|
|
332436
|
-
responseBalances = {};
|
|
332437
|
-
}
|
|
333093
|
+
const cancelOrderContext = {
|
|
333094
|
+
action: "CancelOrder",
|
|
333095
|
+
cex: cex3,
|
|
333096
|
+
accountLabel: selectedBrokerAccount?.label,
|
|
333097
|
+
symbol: symbol2,
|
|
333098
|
+
...extractOrderTelemetryIds(cancelOrderValue.params)
|
|
333099
|
+
};
|
|
333100
|
+
const cancelledOrder = await broker.cancelOrder(cancelOrderValue.orderId, symbol2, cancelOrderValue.params ?? {});
|
|
333101
|
+
if (selectedBrokerAccount?.label && symbol2) {
|
|
333102
|
+
orderActivityTracker?.record(cex3, selectedBrokerAccount.label, symbol2);
|
|
332438
333103
|
}
|
|
333104
|
+
emitOrderExecutionTelemetryInBackground(otelMetrics, cancelOrderContext, cancelledOrder);
|
|
333105
|
+
archiveOrderExecutionInBackground(brokerArchiver, cancelOrderContext, cancelledOrder);
|
|
332439
333106
|
ctx.wrappedCallback(null, {
|
|
332440
|
-
|
|
332441
|
-
result: JSON.stringify({
|
|
332442
|
-
balances: responseBalances,
|
|
332443
|
-
balanceType
|
|
332444
|
-
})
|
|
333107
|
+
result: JSON.stringify({ ...cancelledOrder })
|
|
332445
333108
|
});
|
|
332446
333109
|
} catch (error48) {
|
|
332447
|
-
safeLogError(`Error
|
|
333110
|
+
safeLogError(`Error cancelling order from ${cex3}`, error48);
|
|
333111
|
+
const failedCancelContext = {
|
|
333112
|
+
action: "CancelOrder",
|
|
333113
|
+
cex: cex3,
|
|
333114
|
+
accountLabel: selectedBrokerAccount?.label,
|
|
333115
|
+
symbol: symbol2,
|
|
333116
|
+
...extractOrderTelemetryIds(cancelOrderValue.params)
|
|
333117
|
+
};
|
|
333118
|
+
emitOrderExecutionTelemetryInBackground(otelMetrics, failedCancelContext, undefined, error48);
|
|
333119
|
+
archiveOrderExecutionInBackground(brokerArchiver, failedCancelContext, undefined, error48);
|
|
332448
333120
|
ctx.wrappedCallback({
|
|
332449
|
-
code:
|
|
332450
|
-
message: `Failed to
|
|
333121
|
+
code: grpc6.status.INTERNAL,
|
|
333122
|
+
message: `Failed to cancel order from ${cex3}: ${sanitizeErrorDetail(error48)}`
|
|
332451
333123
|
}, null);
|
|
332452
333124
|
}
|
|
332453
333125
|
}
|
|
332454
|
-
async function
|
|
333126
|
+
async function handleOrders(ctx) {
|
|
333127
|
+
if (ctx.action === Action.CreateOrder)
|
|
333128
|
+
return handleCreateOrder(ctx);
|
|
333129
|
+
if (ctx.action === Action.GetOrderDetails)
|
|
333130
|
+
return handleGetOrderDetails(ctx);
|
|
333131
|
+
if (ctx.action === Action.CancelOrder)
|
|
333132
|
+
return handleCancelOrder(ctx);
|
|
333133
|
+
}
|
|
333134
|
+
|
|
333135
|
+
// src/handlers/execute-action/pass-through.ts
|
|
333136
|
+
var grpc7 = __toESM(require_src3(), 1);
|
|
333137
|
+
async function handleFetchCurrency(ctx) {
|
|
332455
333138
|
const {
|
|
332456
333139
|
call,
|
|
332457
333140
|
wrappedCallback,
|
|
@@ -332477,241 +333160,213 @@ async function handleFetchTicker(ctx) {
|
|
|
332477
333160
|
}, null);
|
|
332478
333161
|
}
|
|
332479
333162
|
try {
|
|
332480
|
-
const
|
|
333163
|
+
const assetCode = symbol2.trim().toUpperCase();
|
|
333164
|
+
const currencyInfo = await fetchCurrencyMetadata(broker, assetCode);
|
|
333165
|
+
if (!currencyInfo) {
|
|
333166
|
+
return ctx.wrappedCallback({
|
|
333167
|
+
code: grpc7.status.NOT_FOUND,
|
|
333168
|
+
message: `venue_discovery_unavailable: currency not found for ${assetCode}`
|
|
333169
|
+
}, null);
|
|
333170
|
+
}
|
|
333171
|
+
const networkEvidence = buildTransferNetworkEvidence(currencyInfo);
|
|
332481
333172
|
ctx.wrappedCallback(null, {
|
|
332482
333173
|
proof: ctx.verity.proof,
|
|
332483
|
-
result: JSON.stringify(ticker)
|
|
332484
|
-
});
|
|
332485
|
-
} catch (error48) {
|
|
332486
|
-
safeLogError(`Error fetching ticker from ${cex3}`, error48);
|
|
332487
|
-
ctx.wrappedCallback({
|
|
332488
|
-
code: grpc7.status.INTERNAL,
|
|
332489
|
-
message: `Failed to fetch ticker from ${cex3}`
|
|
332490
|
-
}, null);
|
|
332491
|
-
}
|
|
332492
|
-
}
|
|
332493
|
-
async function handlePassThrough(ctx) {
|
|
332494
|
-
if (ctx.action === Action.FetchCurrency)
|
|
332495
|
-
return handleFetchCurrency(ctx);
|
|
332496
|
-
if (ctx.action === Action.FetchAccountId)
|
|
332497
|
-
return handleFetchAccountId(ctx);
|
|
332498
|
-
if (ctx.action === Action.FetchFees)
|
|
332499
|
-
return handleFetchFees(ctx);
|
|
332500
|
-
if (ctx.action === Action.FetchDepositAddresses)
|
|
332501
|
-
return handleFetchDepositAddresses(ctx);
|
|
332502
|
-
if (ctx.action === Action.FetchBalances)
|
|
332503
|
-
return handleFetchBalances(ctx);
|
|
332504
|
-
if (ctx.action === Action.FetchTicker)
|
|
332505
|
-
return handleFetchTicker(ctx);
|
|
332506
|
-
}
|
|
332507
|
-
|
|
332508
|
-
// src/handlers/execute-action/perp-config.ts
|
|
332509
|
-
var grpc8 = __toESM(require_src3(), 1);
|
|
332510
|
-
function exchangeSupports(broker, capability) {
|
|
332511
|
-
return broker.has?.[capability] === true;
|
|
332512
|
-
}
|
|
332513
|
-
function extractPerpConfigs(positions) {
|
|
332514
|
-
return positions.map((position) => ({
|
|
332515
|
-
symbol: typeof position.symbol === "string" ? position.symbol : undefined,
|
|
332516
|
-
leverage: typeof position.leverage === "number" ? position.leverage : undefined,
|
|
332517
|
-
marginMode: typeof position.marginMode === "string" ? position.marginMode : undefined
|
|
332518
|
-
}));
|
|
332519
|
-
}
|
|
332520
|
-
async function handleGetPerpConfigState(ctx) {
|
|
332521
|
-
const { wrappedCallback, cex: cex3, normalizedCex, broker } = ctx;
|
|
332522
|
-
const payload = parsePayloadForAction(ctx, GetPerpConfigStatePayloadSchema);
|
|
332523
|
-
if (payload === null) {
|
|
332524
|
-
return;
|
|
332525
|
-
}
|
|
332526
|
-
if (!broker) {
|
|
332527
|
-
return wrappedCallback({
|
|
332528
|
-
code: grpc8.status.INVALID_ARGUMENT,
|
|
332529
|
-
message: `Invalid CEX key: ${cex3}`
|
|
332530
|
-
}, null);
|
|
332531
|
-
}
|
|
332532
|
-
const exchange = broker;
|
|
332533
|
-
if (!exchangeSupports(exchange, "fetchPositions")) {
|
|
332534
|
-
return wrappedCallback({
|
|
332535
|
-
code: grpc8.status.UNIMPLEMENTED,
|
|
332536
|
-
message: `${normalizedCex} does not support fetchPositions`
|
|
332537
|
-
}, null);
|
|
332538
|
-
}
|
|
332539
|
-
try {
|
|
332540
|
-
const symbols = payload.symbol ? [payload.symbol] : undefined;
|
|
332541
|
-
const positions = await exchange.fetchPositions?.(symbols, payload.params);
|
|
332542
|
-
ctx.wrappedCallback(null, {
|
|
332543
333174
|
result: JSON.stringify({
|
|
333175
|
+
...currencyInfo,
|
|
332544
333176
|
exchange: normalizedCex,
|
|
332545
|
-
|
|
332546
|
-
|
|
333177
|
+
asset: assetCode,
|
|
333178
|
+
code: currencyInfo.code ?? assetCode,
|
|
333179
|
+
id: currencyInfo.id ?? null,
|
|
333180
|
+
networks: networkEvidence.networks,
|
|
333181
|
+
networkAliases: networkEvidence.aliases,
|
|
333182
|
+
raw: currencyInfo
|
|
332547
333183
|
})
|
|
332548
333184
|
});
|
|
332549
333185
|
} catch (error48) {
|
|
332550
|
-
safeLogError(`
|
|
333186
|
+
safeLogError(`Error fetching currency ${symbol2} from ${cex3}`, error48);
|
|
333187
|
+
const message = getErrorMessage(error48);
|
|
332551
333188
|
ctx.wrappedCallback({
|
|
332552
|
-
code:
|
|
332553
|
-
message:
|
|
333189
|
+
code: stableGrpcErrorCode(message) ?? mapCcxtErrorToGrpcStatus(error48) ?? grpc7.status.INTERNAL,
|
|
333190
|
+
message: message.startsWith("venue_discovery_unavailable:") ? message : `venue_discovery_unavailable: ${message}`
|
|
332554
333191
|
}, null);
|
|
332555
333192
|
}
|
|
332556
333193
|
}
|
|
332557
|
-
async function
|
|
332558
|
-
const {
|
|
332559
|
-
|
|
332560
|
-
|
|
332561
|
-
|
|
332562
|
-
|
|
332563
|
-
|
|
332564
|
-
|
|
332565
|
-
|
|
332566
|
-
|
|
332567
|
-
|
|
332568
|
-
|
|
332569
|
-
|
|
332570
|
-
|
|
332571
|
-
|
|
332572
|
-
|
|
332573
|
-
|
|
332574
|
-
|
|
332575
|
-
|
|
333194
|
+
async function handleFetchAccountId(ctx) {
|
|
333195
|
+
const {
|
|
333196
|
+
call,
|
|
333197
|
+
wrappedCallback,
|
|
333198
|
+
policy,
|
|
333199
|
+
brokers,
|
|
333200
|
+
metadata,
|
|
333201
|
+
normalizedCex,
|
|
333202
|
+
cex: cex3,
|
|
333203
|
+
symbol: symbol2,
|
|
333204
|
+
selectedBrokerAccount,
|
|
333205
|
+
broker,
|
|
333206
|
+
verity,
|
|
333207
|
+
applyVerityToBroker,
|
|
333208
|
+
useVerity,
|
|
333209
|
+
verityProverUrl,
|
|
333210
|
+
otelMetrics
|
|
333211
|
+
} = ctx;
|
|
333212
|
+
const verityProof = verity.proof;
|
|
332576
333213
|
try {
|
|
332577
|
-
const
|
|
332578
|
-
|
|
332579
|
-
|
|
332580
|
-
|
|
332581
|
-
ctx.wrappedCallback(null, {
|
|
332582
|
-
result: JSON.stringify({
|
|
332583
|
-
exchange: normalizedCex,
|
|
332584
|
-
symbol: payload.symbol,
|
|
332585
|
-
leverage: payload.leverage,
|
|
332586
|
-
marginMode: payload.marginMode ?? "cross",
|
|
332587
|
-
response
|
|
332588
|
-
})
|
|
333214
|
+
const accountId = await broker.fetchAccountId();
|
|
333215
|
+
return ctx.wrappedCallback(null, {
|
|
333216
|
+
proof: ctx.verity.proof,
|
|
333217
|
+
result: JSON.stringify({ accountId })
|
|
332589
333218
|
});
|
|
332590
333219
|
} catch (error48) {
|
|
332591
|
-
safeLogError(`
|
|
333220
|
+
safeLogError(`Error fetching account ID ${cex3}`, error48);
|
|
332592
333221
|
ctx.wrappedCallback({
|
|
332593
|
-
code:
|
|
332594
|
-
message: `
|
|
333222
|
+
code: grpc7.status.INTERNAL,
|
|
333223
|
+
message: `Error fetching account ID from ${cex3}`
|
|
332595
333224
|
}, null);
|
|
332596
333225
|
}
|
|
332597
333226
|
}
|
|
332598
|
-
async function
|
|
332599
|
-
|
|
332600
|
-
|
|
332601
|
-
|
|
332602
|
-
|
|
332603
|
-
|
|
333227
|
+
async function handleFetchFees(ctx) {
|
|
333228
|
+
const {
|
|
333229
|
+
call,
|
|
333230
|
+
wrappedCallback,
|
|
333231
|
+
policy,
|
|
333232
|
+
brokers,
|
|
333233
|
+
metadata,
|
|
333234
|
+
normalizedCex,
|
|
333235
|
+
cex: cex3,
|
|
333236
|
+
symbol: symbol2,
|
|
333237
|
+
selectedBrokerAccount,
|
|
333238
|
+
broker,
|
|
333239
|
+
verity,
|
|
333240
|
+
applyVerityToBroker,
|
|
333241
|
+
useVerity,
|
|
333242
|
+
verityProverUrl,
|
|
333243
|
+
otelMetrics
|
|
333244
|
+
} = ctx;
|
|
333245
|
+
const verityProof = verity.proof;
|
|
333246
|
+
if (!symbol2) {
|
|
333247
|
+
return ctx.wrappedCallback({
|
|
333248
|
+
code: grpc7.status.INVALID_ARGUMENT,
|
|
333249
|
+
message: `ValidationError: Symbol required`
|
|
333250
|
+
}, null);
|
|
332604
333251
|
}
|
|
332605
|
-
|
|
332606
|
-
|
|
332607
|
-
// src/handlers/execute-action/treasury-call.ts
|
|
332608
|
-
var grpc9 = __toESM(require_src3(), 1);
|
|
332609
|
-
async function handleTreasuryCall(ctx) {
|
|
332610
|
-
const { broker } = ctx;
|
|
332611
|
-
const callValue = parsePayloadForAction(ctx, CallPayloadSchema);
|
|
332612
|
-
if (callValue === null)
|
|
333252
|
+
const feesPayload = parsePayloadForAction(ctx, FetchFeesPayloadSchema);
|
|
333253
|
+
if (feesPayload === null)
|
|
332613
333254
|
return;
|
|
332614
|
-
|
|
332615
|
-
let marketMetadataHash;
|
|
333255
|
+
const includeAllFees = feesPayload.includeAllFees || feesPayload.includeFundingFees === true;
|
|
332616
333256
|
try {
|
|
332617
|
-
|
|
332618
|
-
|
|
332619
|
-
|
|
332620
|
-
|
|
332621
|
-
|
|
332622
|
-
|
|
332623
|
-
|
|
332624
|
-
|
|
332625
|
-
|
|
332626
|
-
|
|
332627
|
-
|
|
332628
|
-
|
|
332629
|
-
|
|
332630
|
-
|
|
332631
|
-
|
|
332632
|
-
|
|
332633
|
-
|
|
332634
|
-
|
|
332635
|
-
|
|
332636
|
-
|
|
332637
|
-
|
|
332638
|
-
|
|
332639
|
-
|
|
332640
|
-
|
|
332641
|
-
|
|
332642
|
-
|
|
332643
|
-
|
|
332644
|
-
|
|
332645
|
-
|
|
332646
|
-
|
|
332647
|
-
|
|
332648
|
-
|
|
332649
|
-
|
|
332650
|
-
|
|
332651
|
-
|
|
332652
|
-
|
|
332653
|
-
|
|
332654
|
-
|
|
332655
|
-
|
|
332656
|
-
|
|
332657
|
-
|
|
332658
|
-
|
|
332659
|
-
|
|
332660
|
-
|
|
332661
|
-
|
|
332662
|
-
|
|
332663
|
-
|
|
332664
|
-
|
|
332665
|
-
|
|
333257
|
+
await broker.loadMarkets();
|
|
333258
|
+
const fetchFundingFees = async (currencyCodes) => {
|
|
333259
|
+
let fundingFeeSource2 = "unavailable";
|
|
333260
|
+
const fundingFeesByCurrency2 = {};
|
|
333261
|
+
if (broker.has.fetchDepositWithdrawFees) {
|
|
333262
|
+
try {
|
|
333263
|
+
const feeMap = await broker.fetchDepositWithdrawFees(currencyCodes);
|
|
333264
|
+
for (const code of currencyCodes) {
|
|
333265
|
+
const feeInfo = feeMap[code];
|
|
333266
|
+
if (!feeInfo) {
|
|
333267
|
+
continue;
|
|
333268
|
+
}
|
|
333269
|
+
const fallbackFee = feeInfo.fee !== undefined || feeInfo.percentage !== undefined ? {
|
|
333270
|
+
fee: feeInfo.fee ?? null,
|
|
333271
|
+
percentage: feeInfo.percentage ?? null
|
|
333272
|
+
} : null;
|
|
333273
|
+
fundingFeesByCurrency2[code] = {
|
|
333274
|
+
deposit: feeInfo.deposit ?? fallbackFee,
|
|
333275
|
+
withdraw: feeInfo.withdraw ?? fallbackFee,
|
|
333276
|
+
networks: feeInfo.networks ?? {}
|
|
333277
|
+
};
|
|
333278
|
+
}
|
|
333279
|
+
if (Object.keys(fundingFeesByCurrency2).length > 0) {
|
|
333280
|
+
fundingFeeSource2 = "fetchDepositWithdrawFees";
|
|
333281
|
+
}
|
|
333282
|
+
} catch (error48) {
|
|
333283
|
+
safeLogError(`Error fetching deposit/withdraw fee map for ${symbol2} from ${cex3}`, error48);
|
|
333284
|
+
}
|
|
333285
|
+
}
|
|
333286
|
+
if (fundingFeeSource2 === "unavailable") {
|
|
333287
|
+
try {
|
|
333288
|
+
const currencies = await broker.fetchCurrencies();
|
|
333289
|
+
for (const code of currencyCodes) {
|
|
333290
|
+
const currency = currencies[code];
|
|
333291
|
+
if (!currency) {
|
|
333292
|
+
continue;
|
|
333293
|
+
}
|
|
333294
|
+
fundingFeesByCurrency2[code] = {
|
|
333295
|
+
deposit: {
|
|
333296
|
+
enabled: currency.deposit ?? null
|
|
333297
|
+
},
|
|
333298
|
+
withdraw: {
|
|
333299
|
+
enabled: currency.withdraw ?? null,
|
|
333300
|
+
fee: currency.fee ?? null,
|
|
333301
|
+
limits: currency.limits?.withdraw ?? null
|
|
333302
|
+
},
|
|
333303
|
+
networks: currency.networks ?? {}
|
|
333304
|
+
};
|
|
333305
|
+
}
|
|
333306
|
+
if (Object.keys(fundingFeesByCurrency2).length > 0) {
|
|
333307
|
+
fundingFeeSource2 = "currencies";
|
|
333308
|
+
}
|
|
333309
|
+
} catch (error48) {
|
|
333310
|
+
safeLogError(`Error fetching currency metadata for fees for ${symbol2} from ${cex3}`, error48);
|
|
333311
|
+
}
|
|
333312
|
+
}
|
|
333313
|
+
return { fundingFeeSource: fundingFeeSource2, fundingFeesByCurrency: fundingFeesByCurrency2 };
|
|
333314
|
+
};
|
|
333315
|
+
const isMarketSymbol = symbol2.includes("/");
|
|
333316
|
+
if (isMarketSymbol) {
|
|
333317
|
+
const market = await broker.market(symbol2);
|
|
333318
|
+
const generalFee = broker.fees ?? null;
|
|
333319
|
+
const feeStatus = broker.fees ? "available" : "unknown";
|
|
333320
|
+
if (!broker.fees) {
|
|
333321
|
+
log.warn(`Fee metadata unavailable for ${cex3}`, { symbol: symbol2 });
|
|
333322
|
+
}
|
|
333323
|
+
if (!includeAllFees) {
|
|
333324
|
+
return ctx.wrappedCallback(null, {
|
|
333325
|
+
proof: ctx.verity.proof,
|
|
333326
|
+
result: JSON.stringify({
|
|
333327
|
+
feeScope: "market",
|
|
333328
|
+
generalFee,
|
|
333329
|
+
feeStatus,
|
|
333330
|
+
market
|
|
333331
|
+
})
|
|
332666
333332
|
});
|
|
332667
333333
|
}
|
|
332668
|
-
|
|
332669
|
-
|
|
332670
|
-
|
|
332671
|
-
|
|
332672
|
-
|
|
332673
|
-
|
|
332674
|
-
|
|
332675
|
-
|
|
332676
|
-
|
|
332677
|
-
|
|
333334
|
+
const currencyCodes = Array.from(new Set([market.base, market.quote]));
|
|
333335
|
+
const { fundingFeeSource: fundingFeeSource2, fundingFeesByCurrency: fundingFeesByCurrency2 } = await fetchFundingFees(currencyCodes);
|
|
333336
|
+
return ctx.wrappedCallback(null, {
|
|
333337
|
+
proof: ctx.verity.proof,
|
|
333338
|
+
result: JSON.stringify({
|
|
333339
|
+
feeScope: "market+funding",
|
|
333340
|
+
generalFee,
|
|
333341
|
+
feeStatus,
|
|
333342
|
+
market,
|
|
333343
|
+
fundingFeeSource: fundingFeeSource2,
|
|
333344
|
+
fundingFeesByCurrency: fundingFeesByCurrency2
|
|
333345
|
+
})
|
|
332678
333346
|
});
|
|
332679
333347
|
}
|
|
332680
|
-
|
|
333348
|
+
const tokenCode = symbol2.toUpperCase();
|
|
333349
|
+
const { fundingFeeSource, fundingFeesByCurrency } = await fetchFundingFees([
|
|
333350
|
+
tokenCode
|
|
333351
|
+
]);
|
|
333352
|
+
return ctx.wrappedCallback(null, {
|
|
332681
333353
|
proof: ctx.verity.proof,
|
|
332682
|
-
result: JSON.stringify(
|
|
333354
|
+
result: JSON.stringify({
|
|
333355
|
+
feeScope: "token",
|
|
333356
|
+
symbol: tokenCode,
|
|
333357
|
+
fundingFeeSource,
|
|
333358
|
+
fundingFeesByCurrency
|
|
333359
|
+
})
|
|
332683
333360
|
});
|
|
332684
333361
|
} catch (error48) {
|
|
332685
|
-
|
|
332686
|
-
|
|
332687
|
-
|
|
332688
|
-
|
|
332689
|
-
}
|
|
332690
|
-
safeLogError("Call failed", error48);
|
|
332691
|
-
rejectWithGrpcError(ctx, error48, {
|
|
332692
|
-
message: getErrorMessage(error48),
|
|
332693
|
-
preferStableMessageOnly: true,
|
|
332694
|
-
appendClassName: true
|
|
332695
|
-
});
|
|
332696
|
-
}
|
|
332697
|
-
}
|
|
332698
|
-
function asNonEmptyString(value) {
|
|
332699
|
-
return typeof value === "string" && value.trim() ? value : undefined;
|
|
332700
|
-
}
|
|
332701
|
-
function asFiniteNumber(value) {
|
|
332702
|
-
if (typeof value === "number") {
|
|
332703
|
-
return Number.isFinite(value) ? value : undefined;
|
|
332704
|
-
}
|
|
332705
|
-
if (typeof value !== "string" || !value.trim()) {
|
|
332706
|
-
return;
|
|
333362
|
+
safeLogError(`Error fetching fees for ${symbol2} from ${cex3}`, error48);
|
|
333363
|
+
ctx.wrappedCallback({
|
|
333364
|
+
code: grpc7.status.INTERNAL,
|
|
333365
|
+
message: `Error fetching fees from ${cex3}`
|
|
333366
|
+
}, null);
|
|
332707
333367
|
}
|
|
332708
|
-
const parsed = Number(value);
|
|
332709
|
-
return Number.isFinite(parsed) ? parsed : undefined;
|
|
332710
333368
|
}
|
|
332711
|
-
|
|
332712
|
-
// src/handlers/execute-action/withdraw.ts
|
|
332713
|
-
var grpc10 = __toESM(require_src3(), 1);
|
|
332714
|
-
async function handleWithdraw(ctx) {
|
|
333369
|
+
async function handleFetchDepositAddresses(ctx) {
|
|
332715
333370
|
const {
|
|
332716
333371
|
call,
|
|
332717
333372
|
wrappedCallback,
|
|
@@ -332727,1183 +333382,998 @@ async function handleWithdraw(ctx) {
|
|
|
332727
333382
|
applyVerityToBroker,
|
|
332728
333383
|
useVerity,
|
|
332729
333384
|
verityProverUrl,
|
|
332730
|
-
otelMetrics
|
|
332731
|
-
brokerArchiver
|
|
333385
|
+
otelMetrics
|
|
332732
333386
|
} = ctx;
|
|
332733
333387
|
const verityProof = verity.proof;
|
|
332734
333388
|
if (!symbol2) {
|
|
332735
333389
|
return ctx.wrappedCallback({
|
|
332736
|
-
code:
|
|
333390
|
+
code: grpc7.status.INVALID_ARGUMENT,
|
|
332737
333391
|
message: `ValidationError: Symbol required`
|
|
332738
333392
|
}, null);
|
|
332739
333393
|
}
|
|
332740
|
-
const
|
|
332741
|
-
if (
|
|
333394
|
+
const fetchDepositAddresses = parsePayloadForAction(ctx, FetchDepositAddressesPayloadSchema);
|
|
333395
|
+
if (fetchDepositAddresses === null)
|
|
332742
333396
|
return;
|
|
332743
|
-
let
|
|
333397
|
+
let depositNetwork;
|
|
332744
333398
|
try {
|
|
332745
|
-
|
|
333399
|
+
depositNetwork = await resolveTransferNetwork(broker, symbol2, fetchDepositAddresses.chain);
|
|
332746
333400
|
} catch (error48) {
|
|
332747
333401
|
const message = getErrorMessage(error48);
|
|
332748
333402
|
return ctx.wrappedCallback({
|
|
332749
|
-
code: stableGrpcErrorCode(message) ??
|
|
333403
|
+
code: stableGrpcErrorCode(message) ?? grpc7.status.INVALID_ARGUMENT,
|
|
332750
333404
|
message
|
|
332751
333405
|
}, null);
|
|
332752
333406
|
}
|
|
332753
|
-
const
|
|
332754
|
-
if (!
|
|
332755
|
-
return ctx.wrappedCallback({
|
|
332756
|
-
code: grpc10.status.PERMISSION_DENIED,
|
|
332757
|
-
message: `policy_withdrawal_denied: ${transferValidation.error}`
|
|
332758
|
-
}, null);
|
|
332759
|
-
}
|
|
332760
|
-
const travelRule = resolveTravelRuleDecision(policy, cex3, transferValue.recipientAddress);
|
|
332761
|
-
if (travelRule.mode === "denied") {
|
|
333407
|
+
const depositValidation = validateDeposit(policy, cex3, depositNetwork.brokerNetworkId, symbol2);
|
|
333408
|
+
if (!depositValidation.valid) {
|
|
332762
333409
|
return ctx.wrappedCallback({
|
|
332763
|
-
code:
|
|
332764
|
-
message: `
|
|
333410
|
+
code: grpc7.status.PERMISSION_DENIED,
|
|
333411
|
+
message: `policy_deposit_denied: ${depositValidation.error}`
|
|
332765
333412
|
}, null);
|
|
332766
333413
|
}
|
|
332767
|
-
const withdrawOrderId = transferValue.params.withdrawOrderId;
|
|
332768
|
-
const clientWithdrawalId = typeof withdrawOrderId === "string" && withdrawOrderId.length > 0 ? withdrawOrderId : undefined;
|
|
332769
333414
|
try {
|
|
332770
|
-
const
|
|
332771
|
-
|
|
332772
|
-
|
|
332773
|
-
|
|
332774
|
-
network: withdrawNetwork.exchangeNetworkId,
|
|
332775
|
-
questionnaire: travelRule.questionnaire,
|
|
332776
|
-
params: transferValue.params
|
|
332777
|
-
}) : await broker.withdraw(symbol2, transferValue.amount, transferValue.recipientAddress, undefined, {
|
|
332778
|
-
...transferValue.params ?? {},
|
|
332779
|
-
network: withdrawNetwork.exchangeNetworkId
|
|
332780
|
-
});
|
|
332781
|
-
log.info(`Withdraw Result: ${JSON.stringify(transaction)}`);
|
|
332782
|
-
const normalized = normalizeCcxtTransactionForArchive(transaction);
|
|
332783
|
-
archiveTransferEventInBackground(brokerArchiver, {
|
|
332784
|
-
exchange: cex3,
|
|
332785
|
-
accountSelector: selectedBrokerAccount?.label,
|
|
332786
|
-
assetSymbol: normalized.assetSymbol ?? symbol2,
|
|
332787
|
-
transfer: {
|
|
332788
|
-
eventKind: "withdrawal",
|
|
332789
|
-
lifecycleAction: "submit_withdrawal",
|
|
332790
|
-
status: normalized.status,
|
|
332791
|
-
amount: normalized.amount ?? String(transferValue.amount),
|
|
332792
|
-
address: normalized.address ?? transferValue.recipientAddress,
|
|
332793
|
-
network: normalized.network ?? withdrawNetwork.exchangeNetworkId,
|
|
332794
|
-
externalId: normalized.externalId,
|
|
332795
|
-
clientWithdrawalId,
|
|
332796
|
-
txid: normalized.txid,
|
|
332797
|
-
feeAmount: normalized.feeAmount,
|
|
332798
|
-
feeCurrency: normalized.feeCurrency,
|
|
332799
|
-
exchangeTimestamp: normalized.exchangeTimestamp,
|
|
332800
|
-
payload: transaction
|
|
332801
|
-
}
|
|
332802
|
-
});
|
|
332803
|
-
ctx.wrappedCallback(null, {
|
|
332804
|
-
proof: ctx.verity.proof,
|
|
332805
|
-
result: JSON.stringify({
|
|
332806
|
-
...transaction,
|
|
332807
|
-
operatorAlias: withdrawNetwork.operatorAlias,
|
|
332808
|
-
brokerNetworkId: withdrawNetwork.brokerNetworkId,
|
|
332809
|
-
exchangeNetworkId: withdrawNetwork.exchangeNetworkId
|
|
333415
|
+
const depositAddresses = broker.has.fetchDepositAddress === true ? [
|
|
333416
|
+
await broker.fetchDepositAddress(symbol2, {
|
|
333417
|
+
network: depositNetwork.exchangeNetworkId,
|
|
333418
|
+
...fetchDepositAddresses.params ?? {}
|
|
332810
333419
|
})
|
|
333420
|
+
] : await broker.fetchDepositAddressesByNetwork(symbol2, {
|
|
333421
|
+
network: depositNetwork.exchangeNetworkId,
|
|
333422
|
+
...fetchDepositAddresses.params ?? {}
|
|
332811
333423
|
});
|
|
332812
|
-
|
|
332813
|
-
|
|
332814
|
-
|
|
332815
|
-
|
|
332816
|
-
|
|
332817
|
-
|
|
332818
|
-
|
|
332819
|
-
|
|
332820
|
-
|
|
332821
|
-
|
|
332822
|
-
|
|
332823
|
-
address: transferValue.recipientAddress,
|
|
332824
|
-
network: withdrawNetwork.exchangeNetworkId,
|
|
332825
|
-
clientWithdrawalId,
|
|
332826
|
-
errorSummary: getErrorMessage(error48),
|
|
332827
|
-
payload: { recipientAddress: transferValue.recipientAddress }
|
|
332828
|
-
}
|
|
332829
|
-
});
|
|
332830
|
-
const code = mapCcxtErrorToGrpcStatus(error48) ?? grpc10.status.INTERNAL;
|
|
333424
|
+
if (depositAddresses.length > 0) {
|
|
333425
|
+
return ctx.wrappedCallback(null, {
|
|
333426
|
+
proof: ctx.verity.proof,
|
|
333427
|
+
result: JSON.stringify(depositAddresses.map((depositAddress) => ({
|
|
333428
|
+
...depositAddress,
|
|
333429
|
+
operatorAlias: depositNetwork.operatorAlias,
|
|
333430
|
+
brokerNetworkId: depositNetwork.brokerNetworkId,
|
|
333431
|
+
exchangeNetworkId: depositNetwork.exchangeNetworkId
|
|
333432
|
+
})))
|
|
333433
|
+
});
|
|
333434
|
+
}
|
|
332831
333435
|
ctx.wrappedCallback({
|
|
332832
|
-
code,
|
|
332833
|
-
message:
|
|
333436
|
+
code: grpc7.status.INTERNAL,
|
|
333437
|
+
message: "Deposit confirmation failed"
|
|
332834
333438
|
}, null);
|
|
332835
|
-
}
|
|
332836
|
-
|
|
332837
|
-
|
|
332838
|
-
// src/handlers/execute-action/registry.ts
|
|
332839
|
-
var ACTION_HANDLERS = {
|
|
332840
|
-
[Action.Deposit]: handleDeposit,
|
|
332841
|
-
[Action.Withdraw]: handleWithdraw,
|
|
332842
|
-
[Action.Call]: handleTreasuryCall,
|
|
332843
|
-
[Action.InternalTransfer]: handleInternalTransfer,
|
|
332844
|
-
[Action.CreateOrder]: handleOrders,
|
|
332845
|
-
[Action.GetOrderDetails]: handleOrders,
|
|
332846
|
-
[Action.CancelOrder]: handleOrders,
|
|
332847
|
-
[Action.FetchCurrency]: handlePassThrough,
|
|
332848
|
-
[Action.FetchAccountId]: handlePassThrough,
|
|
332849
|
-
[Action.FetchFees]: handlePassThrough,
|
|
332850
|
-
[Action.FetchDepositAddresses]: handlePassThrough,
|
|
332851
|
-
[Action.FetchBalances]: handlePassThrough,
|
|
332852
|
-
[Action.FetchTicker]: handlePassThrough,
|
|
332853
|
-
[Action.GetPerpConfigState]: handlePerpConfig,
|
|
332854
|
-
[Action.SetPerpConfigState]: handlePerpConfig
|
|
332855
|
-
};
|
|
332856
|
-
async function dispatchExecuteAction(ctx) {
|
|
332857
|
-
const handler = ACTION_HANDLERS[ctx.action];
|
|
332858
|
-
if (!handler) {
|
|
333439
|
+
} catch (error48) {
|
|
333440
|
+
safeLogError("Fetch Deposit Addresses confirmation failed", error48);
|
|
333441
|
+
const message = getErrorMessage(error48);
|
|
332859
333442
|
ctx.wrappedCallback({
|
|
332860
|
-
code:
|
|
332861
|
-
message: "
|
|
333443
|
+
code: grpc7.status.INTERNAL,
|
|
333444
|
+
message: "Fetch Deposit Addresses confirmation failed: " + message
|
|
332862
333445
|
}, null);
|
|
332863
|
-
return;
|
|
332864
333446
|
}
|
|
332865
|
-
await handler(ctx);
|
|
332866
333447
|
}
|
|
332867
|
-
|
|
332868
|
-
// src/handlers/execute-action/handler.ts
|
|
332869
|
-
function createExecuteActionHandler(deps) {
|
|
333448
|
+
async function handleFetchBalances(ctx) {
|
|
332870
333449
|
const {
|
|
333450
|
+
call,
|
|
333451
|
+
wrappedCallback,
|
|
332871
333452
|
policy,
|
|
332872
333453
|
brokers,
|
|
332873
|
-
|
|
332874
|
-
|
|
332875
|
-
|
|
332876
|
-
|
|
332877
|
-
|
|
332878
|
-
|
|
332879
|
-
|
|
332880
|
-
|
|
332881
|
-
|
|
332882
|
-
|
|
332883
|
-
|
|
332884
|
-
|
|
332885
|
-
|
|
332886
|
-
|
|
332887
|
-
|
|
332888
|
-
|
|
332889
|
-
|
|
332890
|
-
|
|
332891
|
-
|
|
332892
|
-
|
|
332893
|
-
|
|
332894
|
-
}
|
|
332895
|
-
if (error48) {
|
|
332896
|
-
otelMetrics?.recordCounter("execute_action_errors_total", 1, {
|
|
332897
|
-
action: actionName,
|
|
332898
|
-
cex: cex3 || "unknown",
|
|
332899
|
-
error_type: error48.code ? grpc12.status[error48.code] || "unknown" : "unknown"
|
|
332900
|
-
});
|
|
332901
|
-
} else {
|
|
332902
|
-
otelMetrics?.recordCounter("execute_action_success_total", 1, {
|
|
332903
|
-
action: actionName,
|
|
332904
|
-
cex: cex3 || "unknown"
|
|
332905
|
-
});
|
|
332906
|
-
}
|
|
332907
|
-
}
|
|
332908
|
-
callback(error48, value);
|
|
332909
|
-
};
|
|
332910
|
-
try {
|
|
332911
|
-
log.info(`Request - ExecuteAction:`, { action, cex: cex3, symbol: symbol2 });
|
|
332912
|
-
otelMetrics?.recordCounter("execute_action_requests_total", 1, {
|
|
332913
|
-
action: getActionName(action),
|
|
332914
|
-
cex: cex3 || "unknown"
|
|
332915
|
-
});
|
|
332916
|
-
if (!authenticateRequest(call, whitelistIps)) {
|
|
332917
|
-
return wrappedCallback({
|
|
332918
|
-
code: grpc12.status.PERMISSION_DENIED,
|
|
332919
|
-
message: "Access denied: Unauthorized IP"
|
|
332920
|
-
}, null);
|
|
332921
|
-
}
|
|
332922
|
-
if (!action || !cex3) {
|
|
332923
|
-
return wrappedCallback({
|
|
332924
|
-
code: grpc12.status.INVALID_ARGUMENT,
|
|
332925
|
-
message: "`action` AND `cex` fields are required"
|
|
332926
|
-
}, null);
|
|
332927
|
-
}
|
|
332928
|
-
const normalizedCex = cex3.trim().toLowerCase();
|
|
332929
|
-
const metadata = call.metadata;
|
|
332930
|
-
const selectedBrokerAccount = selectBrokerAccountForCex(normalizedCex, brokers, metadata);
|
|
332931
|
-
const verity = { proof: "" };
|
|
332932
|
-
const applyVerityToBroker = (targetBroker) => {
|
|
332933
|
-
if (!useVerity)
|
|
332934
|
-
return;
|
|
332935
|
-
const override = buildHttpClientOverrideFromMetadata(metadata, verityProverUrl, (proof, notaryPubKey) => {
|
|
332936
|
-
verity.proof = proof;
|
|
332937
|
-
log.debug(`Verity proof:`, { proof, notaryPubKey });
|
|
332938
|
-
});
|
|
332939
|
-
targetBroker.setHttpClientOverride(override, verityHttpClientOverridePredicate);
|
|
332940
|
-
};
|
|
332941
|
-
const preludeCtx = {
|
|
332942
|
-
call,
|
|
332943
|
-
wrappedCallback,
|
|
332944
|
-
action,
|
|
332945
|
-
policy,
|
|
332946
|
-
brokers,
|
|
332947
|
-
metadata,
|
|
332948
|
-
normalizedCex,
|
|
332949
|
-
cex: cex3,
|
|
332950
|
-
symbol: symbol2,
|
|
332951
|
-
selectedBrokerAccount,
|
|
332952
|
-
broker: selectedBrokerAccount?.exchange ?? createBroker(normalizedCex, metadata),
|
|
332953
|
-
verity,
|
|
332954
|
-
applyVerityToBroker,
|
|
332955
|
-
useVerity,
|
|
332956
|
-
verityProverUrl,
|
|
332957
|
-
otelMetrics,
|
|
332958
|
-
brokerArchiver,
|
|
332959
|
-
orderActivityTracker,
|
|
332960
|
-
withdrawalObservationTracker
|
|
332961
|
-
};
|
|
332962
|
-
if (action === Action.Call) {
|
|
332963
|
-
const handled = await handleOrderBookCall(preludeCtx);
|
|
332964
|
-
if (handled)
|
|
332965
|
-
return;
|
|
332966
|
-
}
|
|
332967
|
-
const broker = selectedBrokerAccount?.exchange ?? createBroker(normalizedCex, metadata);
|
|
332968
|
-
if (!broker) {
|
|
332969
|
-
return wrappedCallback({
|
|
332970
|
-
code: grpc12.status.UNAUTHENTICATED,
|
|
332971
|
-
message: `This Exchange is not registered and No API metadata was found`
|
|
332972
|
-
}, null);
|
|
332973
|
-
}
|
|
332974
|
-
applyVerityToBroker(broker);
|
|
332975
|
-
const ctx = { ...preludeCtx, broker };
|
|
332976
|
-
await dispatchExecuteAction(ctx);
|
|
332977
|
-
} catch (error48) {
|
|
332978
|
-
safeLogError("ExecuteAction unhandled error", error48);
|
|
332979
|
-
return wrappedCallback({
|
|
332980
|
-
code: grpc12.status.INTERNAL,
|
|
332981
|
-
message: "ExecuteAction failed unexpectedly"
|
|
333454
|
+
metadata,
|
|
333455
|
+
normalizedCex,
|
|
333456
|
+
cex: cex3,
|
|
333457
|
+
symbol: symbol2,
|
|
333458
|
+
selectedBrokerAccount,
|
|
333459
|
+
broker,
|
|
333460
|
+
verity,
|
|
333461
|
+
applyVerityToBroker,
|
|
333462
|
+
useVerity,
|
|
333463
|
+
verityProverUrl,
|
|
333464
|
+
otelMetrics
|
|
333465
|
+
} = ctx;
|
|
333466
|
+
const verityProof = verity.proof;
|
|
333467
|
+
try {
|
|
333468
|
+
const payload = call.request.payload || {};
|
|
333469
|
+
const providedBalanceType = payload.balanceType;
|
|
333470
|
+
const balanceType = (providedBalanceType ?? "total").toString();
|
|
333471
|
+
const validBalanceTypes = new Set(["free", "used", "total"]);
|
|
333472
|
+
if (!validBalanceTypes.has(balanceType)) {
|
|
333473
|
+
return ctx.wrappedCallback({
|
|
333474
|
+
code: grpc7.status.INVALID_ARGUMENT,
|
|
333475
|
+
message: `ValidationError: invalid balanceType '${providedBalanceType}'. Expected one of: free | used | total`
|
|
332982
333476
|
}, null);
|
|
332983
333477
|
}
|
|
332984
|
-
|
|
332985
|
-
|
|
332986
|
-
|
|
332987
|
-
|
|
332988
|
-
|
|
332989
|
-
|
|
332990
|
-
#shuttingDown = false;
|
|
332991
|
-
register(broker, context2) {
|
|
332992
|
-
this.#brokers.set(broker, context2);
|
|
332993
|
-
if (this.#shuttingDown) {
|
|
332994
|
-
this.close(broker);
|
|
333478
|
+
const params = { ...payload };
|
|
333479
|
+
delete params.balanceType;
|
|
333480
|
+
const marketType = parseMarketType(params.marketType);
|
|
333481
|
+
delete params.marketType;
|
|
333482
|
+
if (params.type === undefined) {
|
|
333483
|
+
params.type = marketTypeToCcxtType(marketType);
|
|
332995
333484
|
}
|
|
332996
|
-
|
|
332997
|
-
|
|
332998
|
-
|
|
332999
|
-
|
|
333000
|
-
|
|
333485
|
+
let responseBalances = {};
|
|
333486
|
+
if (balanceType === "free") {
|
|
333487
|
+
const partial2 = await broker.fetchFreeBalance(params);
|
|
333488
|
+
responseBalances = partial2 ?? {};
|
|
333489
|
+
} else if (balanceType === "used") {
|
|
333490
|
+
const partial2 = await broker.fetchUsedBalance(params);
|
|
333491
|
+
responseBalances = partial2 ?? {};
|
|
333492
|
+
} else if (balanceType === "total") {
|
|
333493
|
+
const partial2 = await broker.fetchTotalBalance(params);
|
|
333494
|
+
responseBalances = partial2 ?? {};
|
|
333001
333495
|
}
|
|
333002
|
-
|
|
333003
|
-
|
|
333004
|
-
|
|
333005
|
-
|
|
333006
|
-
|
|
333007
|
-
|
|
333008
|
-
|
|
333009
|
-
await broker.close();
|
|
333010
|
-
log.debug("Request-scoped Subscribe broker closed", context2);
|
|
333011
|
-
return "closed";
|
|
333012
|
-
} catch (error48) {
|
|
333013
|
-
log.warn("Failed to close request-scoped Subscribe broker", {
|
|
333014
|
-
...context2,
|
|
333015
|
-
error: error48
|
|
333016
|
-
});
|
|
333017
|
-
return "failed";
|
|
333018
|
-
} finally {
|
|
333019
|
-
this.#closing.delete(broker);
|
|
333496
|
+
if (symbol2) {
|
|
333497
|
+
if (typeof responseBalances[symbol2] === "number") {
|
|
333498
|
+
responseBalances = {
|
|
333499
|
+
[symbol2]: responseBalances[symbol2] ?? 0
|
|
333500
|
+
};
|
|
333501
|
+
} else {
|
|
333502
|
+
responseBalances = {};
|
|
333020
333503
|
}
|
|
333021
|
-
})();
|
|
333022
|
-
this.#closing.set(broker, closing);
|
|
333023
|
-
return closing;
|
|
333024
|
-
}
|
|
333025
|
-
async closeAll() {
|
|
333026
|
-
this.#shuttingDown = true;
|
|
333027
|
-
let failed = 0;
|
|
333028
|
-
while (this.#brokers.size > 0 || this.#closing.size > 0) {
|
|
333029
|
-
const inFlight = [...this.#closing.values()];
|
|
333030
|
-
const fresh = [...this.#brokers.keys()].map((broker) => this.close(broker));
|
|
333031
|
-
const outcomes = await Promise.all([...fresh, ...inFlight]);
|
|
333032
|
-
failed += outcomes.filter((outcome) => outcome === "failed").length;
|
|
333033
|
-
}
|
|
333034
|
-
if (failed > 0) {
|
|
333035
|
-
throw new Error(`${failed} request-scoped Subscribe broker(s) failed to close`);
|
|
333036
333504
|
}
|
|
333505
|
+
ctx.wrappedCallback(null, {
|
|
333506
|
+
proof: ctx.verity.proof,
|
|
333507
|
+
result: JSON.stringify({
|
|
333508
|
+
balances: responseBalances,
|
|
333509
|
+
balanceType
|
|
333510
|
+
})
|
|
333511
|
+
});
|
|
333512
|
+
} catch (error48) {
|
|
333513
|
+
safeLogError(`Error fetching balance from ${cex3}`, error48);
|
|
333514
|
+
ctx.wrappedCallback({
|
|
333515
|
+
code: grpc7.status.INTERNAL,
|
|
333516
|
+
message: `Failed to fetch balance from ${cex3}`
|
|
333517
|
+
}, null);
|
|
333037
333518
|
}
|
|
333038
333519
|
}
|
|
333039
|
-
|
|
333040
|
-
|
|
333041
|
-
|
|
333042
|
-
|
|
333043
|
-
|
|
333044
|
-
|
|
333045
|
-
|
|
333046
|
-
|
|
333047
|
-
|
|
333048
|
-
|
|
333049
|
-
|
|
333050
|
-
|
|
333051
|
-
|
|
333052
|
-
|
|
333053
|
-
|
|
333054
|
-
|
|
333055
|
-
|
|
333056
|
-
|
|
333057
|
-
|
|
333058
|
-
|
|
333059
|
-
|
|
333060
|
-
|
|
333061
|
-
|
|
333062
|
-
|
|
333063
|
-
}
|
|
333064
|
-
const secret = getExchangeString(exchange, "secret");
|
|
333065
|
-
return {
|
|
333066
|
-
...params,
|
|
333067
|
-
signature: createHmac("sha256", secret).update(sortedQuery(params)).digest("hex")
|
|
333068
|
-
};
|
|
333069
|
-
}
|
|
333070
|
-
function getBinanceSpotWsApiUrl(exchange) {
|
|
333071
|
-
const urls = exchange.urls;
|
|
333072
|
-
return urls?.api?.ws?.["ws-api"]?.spot ?? BINANCE_SPOT_WS_API_URL;
|
|
333073
|
-
}
|
|
333074
|
-
function getRecord(value) {
|
|
333075
|
-
return typeof value === "object" && value !== null ? value : null;
|
|
333076
|
-
}
|
|
333077
|
-
function getMessage(value) {
|
|
333078
|
-
if (value instanceof Error) {
|
|
333079
|
-
return value.message;
|
|
333080
|
-
}
|
|
333081
|
-
if (typeof value === "string" && value.length > 0) {
|
|
333082
|
-
return value;
|
|
333520
|
+
async function handleFetchTicker(ctx) {
|
|
333521
|
+
const {
|
|
333522
|
+
call,
|
|
333523
|
+
wrappedCallback,
|
|
333524
|
+
policy,
|
|
333525
|
+
brokers,
|
|
333526
|
+
metadata,
|
|
333527
|
+
normalizedCex,
|
|
333528
|
+
cex: cex3,
|
|
333529
|
+
symbol: symbol2,
|
|
333530
|
+
selectedBrokerAccount,
|
|
333531
|
+
broker,
|
|
333532
|
+
verity,
|
|
333533
|
+
applyVerityToBroker,
|
|
333534
|
+
useVerity,
|
|
333535
|
+
verityProverUrl,
|
|
333536
|
+
otelMetrics
|
|
333537
|
+
} = ctx;
|
|
333538
|
+
const verityProof = verity.proof;
|
|
333539
|
+
if (!symbol2) {
|
|
333540
|
+
return ctx.wrappedCallback({
|
|
333541
|
+
code: grpc7.status.INVALID_ARGUMENT,
|
|
333542
|
+
message: `ValidationError: Symbol required`
|
|
333543
|
+
}, null);
|
|
333083
333544
|
}
|
|
333084
|
-
|
|
333085
|
-
|
|
333086
|
-
|
|
333087
|
-
|
|
333088
|
-
|
|
333089
|
-
|
|
333090
|
-
|
|
333091
|
-
}
|
|
333092
|
-
|
|
333093
|
-
|
|
333094
|
-
|
|
333095
|
-
|
|
333096
|
-
redacted = redacted.split(value).join("[redacted]");
|
|
333097
|
-
}
|
|
333545
|
+
try {
|
|
333546
|
+
const ticker = await broker.fetchTicker(symbol2);
|
|
333547
|
+
ctx.wrappedCallback(null, {
|
|
333548
|
+
proof: ctx.verity.proof,
|
|
333549
|
+
result: JSON.stringify(ticker)
|
|
333550
|
+
});
|
|
333551
|
+
} catch (error48) {
|
|
333552
|
+
safeLogError(`Error fetching ticker from ${cex3}`, error48);
|
|
333553
|
+
ctx.wrappedCallback({
|
|
333554
|
+
code: grpc7.status.INTERNAL,
|
|
333555
|
+
message: `Failed to fetch ticker from ${cex3}`
|
|
333556
|
+
}, null);
|
|
333098
333557
|
}
|
|
333099
|
-
return redacted.replace(/(\b(?:apiKey|secret|signature)\b\s*=\s*)[^\s&,;)]+/gi, "$1[redacted]").replace(/("(?:apiKey|secret|signature)"\s*:\s*")[^"]*(")/gi, "$1[redacted]$2");
|
|
333100
333558
|
}
|
|
333101
|
-
function
|
|
333102
|
-
|
|
333103
|
-
|
|
333104
|
-
|
|
333105
|
-
|
|
333559
|
+
async function handlePassThrough(ctx) {
|
|
333560
|
+
if (ctx.action === Action.FetchCurrency)
|
|
333561
|
+
return handleFetchCurrency(ctx);
|
|
333562
|
+
if (ctx.action === Action.FetchAccountId)
|
|
333563
|
+
return handleFetchAccountId(ctx);
|
|
333564
|
+
if (ctx.action === Action.FetchFees)
|
|
333565
|
+
return handleFetchFees(ctx);
|
|
333566
|
+
if (ctx.action === Action.FetchDepositAddresses)
|
|
333567
|
+
return handleFetchDepositAddresses(ctx);
|
|
333568
|
+
if (ctx.action === Action.FetchBalances)
|
|
333569
|
+
return handleFetchBalances(ctx);
|
|
333570
|
+
if (ctx.action === Action.FetchTicker)
|
|
333571
|
+
return handleFetchTicker(ctx);
|
|
333106
333572
|
}
|
|
333107
|
-
|
|
333108
|
-
|
|
333109
|
-
|
|
333110
|
-
|
|
333111
|
-
|
|
333112
|
-
const reason = value.toString("utf8");
|
|
333113
|
-
return reason.length > 0 ? reason : null;
|
|
333114
|
-
}
|
|
333115
|
-
if (value instanceof Uint8Array) {
|
|
333116
|
-
const reason = Buffer2.from(value).toString("utf8");
|
|
333117
|
-
return reason.length > 0 ? reason : null;
|
|
333118
|
-
}
|
|
333119
|
-
return null;
|
|
333573
|
+
|
|
333574
|
+
// src/handlers/execute-action/perp-config.ts
|
|
333575
|
+
var grpc8 = __toESM(require_src3(), 1);
|
|
333576
|
+
function exchangeSupports(broker, capability) {
|
|
333577
|
+
return broker.has?.[capability] === true;
|
|
333120
333578
|
}
|
|
333121
|
-
function
|
|
333122
|
-
|
|
333123
|
-
|
|
333124
|
-
|
|
333125
|
-
|
|
333126
|
-
|
|
333127
|
-
typeof code === "number" || typeof code === "string" ? `code=${code}` : null,
|
|
333128
|
-
safeReason ? `reason=${safeReason}` : null
|
|
333129
|
-
].filter((detail) => detail !== null);
|
|
333130
|
-
return new Error(details.length > 0 ? `Binance user-data WebSocket closed unexpectedly (${details.join(", ")})` : "Binance user-data WebSocket closed unexpectedly");
|
|
333579
|
+
function extractPerpConfigs(positions) {
|
|
333580
|
+
return positions.map((position) => ({
|
|
333581
|
+
symbol: typeof position.symbol === "string" ? position.symbol : undefined,
|
|
333582
|
+
leverage: typeof position.leverage === "number" ? position.leverage : undefined,
|
|
333583
|
+
marginMode: typeof position.marginMode === "string" ? position.marginMode : undefined
|
|
333584
|
+
}));
|
|
333131
333585
|
}
|
|
333132
|
-
function
|
|
333133
|
-
|
|
333134
|
-
|
|
333135
|
-
|
|
333136
|
-
|
|
333137
|
-
return data.toString("utf8");
|
|
333586
|
+
async function handleGetPerpConfigState(ctx) {
|
|
333587
|
+
const { wrappedCallback, cex: cex3, normalizedCex, broker } = ctx;
|
|
333588
|
+
const payload = parsePayloadForAction(ctx, GetPerpConfigStatePayloadSchema);
|
|
333589
|
+
if (payload === null) {
|
|
333590
|
+
return;
|
|
333138
333591
|
}
|
|
333139
|
-
if (
|
|
333140
|
-
return
|
|
333592
|
+
if (!broker) {
|
|
333593
|
+
return wrappedCallback({
|
|
333594
|
+
code: grpc8.status.INVALID_ARGUMENT,
|
|
333595
|
+
message: `Invalid CEX key: ${cex3}`
|
|
333596
|
+
}, null);
|
|
333141
333597
|
}
|
|
333142
|
-
|
|
333143
|
-
|
|
333598
|
+
const exchange = broker;
|
|
333599
|
+
if (!exchangeSupports(exchange, "fetchPositions")) {
|
|
333600
|
+
return wrappedCallback({
|
|
333601
|
+
code: grpc8.status.UNIMPLEMENTED,
|
|
333602
|
+
message: `${normalizedCex} does not support fetchPositions`
|
|
333603
|
+
}, null);
|
|
333144
333604
|
}
|
|
333145
|
-
|
|
333146
|
-
|
|
333605
|
+
try {
|
|
333606
|
+
const symbols = payload.symbol ? [payload.symbol] : undefined;
|
|
333607
|
+
const positions = await exchange.fetchPositions?.(symbols, payload.params);
|
|
333608
|
+
ctx.wrappedCallback(null, {
|
|
333609
|
+
result: JSON.stringify({
|
|
333610
|
+
exchange: normalizedCex,
|
|
333611
|
+
configs: extractPerpConfigs(positions ?? []),
|
|
333612
|
+
positions: positions ?? []
|
|
333613
|
+
})
|
|
333614
|
+
});
|
|
333615
|
+
} catch (error48) {
|
|
333616
|
+
safeLogError(`GetPerpConfigState failed for ${cex3}`, error48);
|
|
333617
|
+
ctx.wrappedCallback({
|
|
333618
|
+
code: grpc8.status.INTERNAL,
|
|
333619
|
+
message: `GetPerpConfigState failed: ${sanitizeErrorDetail(error48)}`
|
|
333620
|
+
}, null);
|
|
333147
333621
|
}
|
|
333148
|
-
return data;
|
|
333149
333622
|
}
|
|
333150
|
-
|
|
333151
|
-
|
|
333152
|
-
|
|
333153
|
-
|
|
333154
|
-
|
|
333155
|
-
requestId = `user-data-${Date.now()}-${userDataRequestCounter++}`;
|
|
333156
|
-
maxBufferedEvents;
|
|
333157
|
-
queue = [];
|
|
333158
|
-
waiters = [];
|
|
333159
|
-
closed = false;
|
|
333160
|
-
closeError = null;
|
|
333161
|
-
subscriptionId = null;
|
|
333162
|
-
constructor(exchange, options = {}) {
|
|
333163
|
-
this.exchange = exchange;
|
|
333164
|
-
this.maxBufferedEvents = options.maxBufferedEvents ?? DEFAULT_BINANCE_USER_DATA_MAX_BUFFERED_EVENTS;
|
|
333165
|
-
this.secretValues = [
|
|
333166
|
-
getOptionalExchangeString(exchange, "apiKey"),
|
|
333167
|
-
getOptionalExchangeString(exchange, "secret")
|
|
333168
|
-
].filter((value) => value !== null);
|
|
333169
|
-
this.ws = createWebSocket(getBinanceSpotWsApiUrl(exchange));
|
|
333170
|
-
this.ws.on("open", () => this.subscribe());
|
|
333171
|
-
this.ws.on("message", (data) => this.handleMessage(data));
|
|
333172
|
-
this.ws.on("error", (error48) => this.fail(formatBinanceUserDataWebSocketError(error48, this.secretValues)));
|
|
333173
|
-
this.ws.on("close", (code, reason) => this.handleClose(code, reason));
|
|
333174
|
-
}
|
|
333175
|
-
async* [Symbol.asyncIterator]() {
|
|
333176
|
-
while (true) {
|
|
333177
|
-
const event = await this.nextEvent();
|
|
333178
|
-
if (!event) {
|
|
333179
|
-
break;
|
|
333180
|
-
}
|
|
333181
|
-
yield event;
|
|
333182
|
-
}
|
|
333623
|
+
async function handleSetPerpConfigState(ctx) {
|
|
333624
|
+
const { wrappedCallback, cex: cex3, normalizedCex, broker } = ctx;
|
|
333625
|
+
const payload = parsePayloadForAction(ctx, SetPerpConfigStatePayloadSchema);
|
|
333626
|
+
if (payload === null) {
|
|
333627
|
+
return;
|
|
333183
333628
|
}
|
|
333184
|
-
|
|
333185
|
-
|
|
333186
|
-
|
|
333187
|
-
|
|
333188
|
-
|
|
333189
|
-
this.queue.length = 0;
|
|
333190
|
-
try {
|
|
333191
|
-
this.ws.close();
|
|
333192
|
-
} catch {}
|
|
333193
|
-
this.flushWaiters();
|
|
333629
|
+
if (!broker) {
|
|
333630
|
+
return wrappedCallback({
|
|
333631
|
+
code: grpc8.status.INVALID_ARGUMENT,
|
|
333632
|
+
message: `Invalid CEX key: ${cex3}`
|
|
333633
|
+
}, null);
|
|
333194
333634
|
}
|
|
333195
|
-
|
|
333196
|
-
|
|
333197
|
-
|
|
333198
|
-
|
|
333199
|
-
|
|
333635
|
+
const exchange = broker;
|
|
333636
|
+
if (!exchangeSupports(exchange, "setLeverage")) {
|
|
333637
|
+
return wrappedCallback({
|
|
333638
|
+
code: grpc8.status.UNIMPLEMENTED,
|
|
333639
|
+
message: `${normalizedCex} does not support setLeverage`
|
|
333640
|
+
}, null);
|
|
333200
333641
|
}
|
|
333201
|
-
|
|
333202
|
-
const
|
|
333203
|
-
|
|
333204
|
-
|
|
333205
|
-
timestamp: Date.now()
|
|
333642
|
+
try {
|
|
333643
|
+
const response = await exchange.setLeverage?.(payload.leverage, payload.symbol, {
|
|
333644
|
+
marginMode: payload.marginMode ?? "cross",
|
|
333645
|
+
...payload.params
|
|
333206
333646
|
});
|
|
333207
|
-
|
|
333208
|
-
|
|
333209
|
-
|
|
333210
|
-
|
|
333211
|
-
|
|
333647
|
+
ctx.wrappedCallback(null, {
|
|
333648
|
+
result: JSON.stringify({
|
|
333649
|
+
exchange: normalizedCex,
|
|
333650
|
+
symbol: payload.symbol,
|
|
333651
|
+
leverage: payload.leverage,
|
|
333652
|
+
marginMode: payload.marginMode ?? "cross",
|
|
333653
|
+
response
|
|
333654
|
+
})
|
|
333655
|
+
});
|
|
333656
|
+
} catch (error48) {
|
|
333657
|
+
safeLogError(`SetPerpConfigState failed for ${cex3}`, error48);
|
|
333658
|
+
ctx.wrappedCallback({
|
|
333659
|
+
code: grpc8.status.INTERNAL,
|
|
333660
|
+
message: `SetPerpConfigState failed: ${sanitizeErrorDetail(error48)}`
|
|
333661
|
+
}, null);
|
|
333212
333662
|
}
|
|
333213
|
-
|
|
333214
|
-
|
|
333215
|
-
|
|
333216
|
-
|
|
333217
|
-
let message;
|
|
333218
|
-
try {
|
|
333219
|
-
const decodedData = decodeMessageData(data);
|
|
333220
|
-
message = typeof decodedData === "string" ? JSON.parse(decodedData) : decodedData;
|
|
333221
|
-
} catch (error48) {
|
|
333222
|
-
this.fail(error48 instanceof Error ? error48 : new Error("Invalid Binance user-data message"));
|
|
333223
|
-
return;
|
|
333224
|
-
}
|
|
333225
|
-
if ("id" in message && message.id === this.requestId) {
|
|
333226
|
-
if (message.status !== 200) {
|
|
333227
|
-
this.fail(new Error(message.error?.msg ?? message.error?.message ?? `Binance user-data subscription failed with status ${message.status}`));
|
|
333228
|
-
return;
|
|
333229
|
-
}
|
|
333230
|
-
this.subscriptionId = message.result?.subscriptionId ?? null;
|
|
333231
|
-
return;
|
|
333232
|
-
}
|
|
333233
|
-
if ("status" in message && typeof message.status === "number" && message.status !== 200) {
|
|
333234
|
-
const errorMessage = message.error?.msg ?? message.error?.message ?? `Binance user-data request failed with status ${message.status}`;
|
|
333235
|
-
const errorCode2 = message.error?.code;
|
|
333236
|
-
this.fail(new Error(typeof errorCode2 === "number" ? `${errorMessage} (code ${errorCode2})` : errorMessage));
|
|
333237
|
-
return;
|
|
333238
|
-
}
|
|
333239
|
-
if (!("event" in message) || !message.event) {
|
|
333240
|
-
return;
|
|
333241
|
-
}
|
|
333242
|
-
const subscriptionId = message.subscriptionId ?? this.subscriptionId;
|
|
333243
|
-
if (subscriptionId === null || subscriptionId === undefined) {
|
|
333244
|
-
return;
|
|
333245
|
-
}
|
|
333246
|
-
this.push({ subscriptionId, event: message.event });
|
|
333663
|
+
}
|
|
333664
|
+
async function handlePerpConfig(ctx) {
|
|
333665
|
+
if (ctx.action === Action.GetPerpConfigState) {
|
|
333666
|
+
return handleGetPerpConfigState(ctx);
|
|
333247
333667
|
}
|
|
333248
|
-
|
|
333249
|
-
|
|
333250
|
-
|
|
333251
|
-
|
|
333252
|
-
|
|
333253
|
-
|
|
333254
|
-
|
|
333255
|
-
|
|
333668
|
+
if (ctx.action === Action.SetPerpConfigState) {
|
|
333669
|
+
return handleSetPerpConfigState(ctx);
|
|
333670
|
+
}
|
|
333671
|
+
}
|
|
333672
|
+
|
|
333673
|
+
// src/handlers/execute-action/treasury-call.ts
|
|
333674
|
+
var grpc9 = __toESM(require_src3(), 1);
|
|
333675
|
+
async function handleTreasuryCall(ctx) {
|
|
333676
|
+
const { broker } = ctx;
|
|
333677
|
+
const callValue = parsePayloadForAction(ctx, CallPayloadSchema);
|
|
333678
|
+
if (callValue === null)
|
|
333679
|
+
return;
|
|
333680
|
+
let createOrderContext;
|
|
333681
|
+
let marketMetadataHash;
|
|
333682
|
+
try {
|
|
333683
|
+
if (callValue.functionName.startsWith("_") || callValue.functionName.includes("constructor") || callValue.functionName.includes("prototype")) {
|
|
333684
|
+
return ctx.wrappedCallback({
|
|
333685
|
+
code: grpc9.status.PERMISSION_DENIED,
|
|
333686
|
+
message: "Access to the requested function is denied"
|
|
333687
|
+
}, null);
|
|
333256
333688
|
}
|
|
333257
|
-
|
|
333258
|
-
|
|
333259
|
-
|
|
333689
|
+
const argsArray = callArgs(callValue.args, callValue.params ?? {});
|
|
333690
|
+
const treasuryDiscovery = await handleTreasuryDiscoveryCall(broker, callValue.functionName, callValue.args, callValue.params ?? {});
|
|
333691
|
+
if (treasuryDiscovery.handled) {
|
|
333692
|
+
return ctx.wrappedCallback(null, {
|
|
333693
|
+
proof: ctx.verity.proof,
|
|
333694
|
+
result: JSON.stringify(treasuryDiscovery.result)
|
|
333695
|
+
});
|
|
333260
333696
|
}
|
|
333261
|
-
|
|
333262
|
-
|
|
333263
|
-
|
|
333264
|
-
|
|
333265
|
-
|
|
333266
|
-
|
|
333697
|
+
const fn = broker[callValue.functionName];
|
|
333698
|
+
if (typeof fn !== "function" || broker.has?.[callValue.functionName] === false) {
|
|
333699
|
+
return ctx.wrappedCallback({
|
|
333700
|
+
code: grpc9.status.INVALID_ARGUMENT,
|
|
333701
|
+
message: `Function not found on broker: ${callValue.functionName}`
|
|
333702
|
+
}, null);
|
|
333267
333703
|
}
|
|
333268
|
-
if (
|
|
333269
|
-
|
|
333704
|
+
if (callValue.functionName === "createOrder") {
|
|
333705
|
+
const [symbol2, orderType, side, quantity, price] = callValue.args;
|
|
333706
|
+
const requestedQuantity = asFiniteNumber(quantity);
|
|
333707
|
+
const requestedPrice = asFiniteNumber(price);
|
|
333708
|
+
const requestedNotional = requestedQuantity !== undefined && requestedPrice !== undefined ? asFiniteNumber(requestedQuantity * requestedPrice) : undefined;
|
|
333709
|
+
const telemetryIds = extractOrderTelemetryIds(callValue.params);
|
|
333710
|
+
const submissionTimestamp = new Date().toISOString();
|
|
333711
|
+
createOrderContext = {
|
|
333712
|
+
action: "CreateOrder",
|
|
333713
|
+
cex: ctx.cex,
|
|
333714
|
+
accountLabel: ctx.selectedBrokerAccount?.label,
|
|
333715
|
+
symbol: asNonEmptyString(symbol2),
|
|
333716
|
+
orderType: asNonEmptyString(orderType),
|
|
333717
|
+
side: asNonEmptyString(side),
|
|
333718
|
+
requestedQuantity,
|
|
333719
|
+
requestedNotional,
|
|
333720
|
+
orderAuthor: callValue.orderAuthor,
|
|
333721
|
+
brokerObservedTimestamp: submissionTimestamp,
|
|
333722
|
+
...telemetryIds
|
|
333723
|
+
};
|
|
333724
|
+
if (createOrderContext.symbol !== undefined) {
|
|
333725
|
+
marketMetadataHash = await captureMarketMetadataSnapshot(ctx.brokerArchiver, broker, {
|
|
333726
|
+
exchange: ctx.cex,
|
|
333727
|
+
accountSelector: ctx.selectedBrokerAccount?.label,
|
|
333728
|
+
symbol: createOrderContext.symbol,
|
|
333729
|
+
action: "CreateOrder",
|
|
333730
|
+
brokerObservedTimestamp: submissionTimestamp,
|
|
333731
|
+
...telemetryIds
|
|
333732
|
+
});
|
|
333733
|
+
}
|
|
333270
333734
|
}
|
|
333271
|
-
|
|
333272
|
-
|
|
333735
|
+
const result = await fn.apply(broker, argsArray);
|
|
333736
|
+
if (createOrderContext !== undefined) {
|
|
333737
|
+
emitOrderExecutionTelemetryInBackground(ctx.otelMetrics, createOrderContext, result);
|
|
333738
|
+
archiveOrderExecutionInBackground(ctx.brokerArchiver, createOrderContext, result, undefined, { marketMetadataHash });
|
|
333739
|
+
} else if (callValue.functionName === "fetchWithdrawals") {
|
|
333740
|
+
archiveWithdrawalObservationsInBackground(ctx.brokerArchiver, ctx.withdrawalObservationTracker, {
|
|
333741
|
+
exchange: ctx.normalizedCex,
|
|
333742
|
+
accountSelector: ctx.selectedBrokerAccount?.label,
|
|
333743
|
+
transactions: result
|
|
333744
|
+
});
|
|
333273
333745
|
}
|
|
333274
|
-
|
|
333275
|
-
|
|
333746
|
+
ctx.wrappedCallback(null, {
|
|
333747
|
+
proof: ctx.verity.proof,
|
|
333748
|
+
result: JSON.stringify(result)
|
|
333276
333749
|
});
|
|
333277
|
-
}
|
|
333278
|
-
|
|
333279
|
-
|
|
333280
|
-
|
|
333281
|
-
|
|
333282
|
-
this.closeError = error48;
|
|
333283
|
-
this.closed = true;
|
|
333284
|
-
this.queue.length = 0;
|
|
333285
|
-
this.flushWaiters();
|
|
333286
|
-
try {
|
|
333287
|
-
this.ws.close();
|
|
333288
|
-
} catch {}
|
|
333289
|
-
}
|
|
333290
|
-
flushWaiters() {
|
|
333291
|
-
const error48 = this.closeError;
|
|
333292
|
-
for (const waiter of this.waiters.splice(0)) {
|
|
333293
|
-
if (error48) {
|
|
333294
|
-
waiter.reject(error48);
|
|
333295
|
-
} else {
|
|
333296
|
-
waiter.resolve(null);
|
|
333297
|
-
}
|
|
333750
|
+
} catch (error48) {
|
|
333751
|
+
if (createOrderContext !== undefined) {
|
|
333752
|
+
rethrowArchiveDurabilityError(error48);
|
|
333753
|
+
emitOrderExecutionTelemetryInBackground(ctx.otelMetrics, createOrderContext, undefined, error48);
|
|
333754
|
+
archiveOrderExecutionInBackground(ctx.brokerArchiver, createOrderContext, undefined, error48, { marketMetadataHash });
|
|
333298
333755
|
}
|
|
333756
|
+
safeLogError("Call failed", error48);
|
|
333757
|
+
rejectWithGrpcError(ctx, error48, {
|
|
333758
|
+
message: getErrorMessage(error48),
|
|
333759
|
+
preferStableMessageOnly: true,
|
|
333760
|
+
appendClassName: true
|
|
333761
|
+
});
|
|
333299
333762
|
}
|
|
333300
333763
|
}
|
|
333301
|
-
function
|
|
333302
|
-
return
|
|
333303
|
-
}
|
|
333304
|
-
function isBinanceOrderUserDataEvent(event) {
|
|
333305
|
-
return event.e === "executionReport" || event.e === "listStatus";
|
|
333306
|
-
}
|
|
333307
|
-
|
|
333308
|
-
// src/helpers/market-data-archive/ohlcv-bar-tracker.ts
|
|
333309
|
-
function isFiniteNumber(value) {
|
|
333310
|
-
return typeof value === "number" && Number.isFinite(value);
|
|
333764
|
+
function asNonEmptyString(value) {
|
|
333765
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
333311
333766
|
}
|
|
333312
|
-
function
|
|
333313
|
-
if (
|
|
333314
|
-
return
|
|
333315
|
-
}
|
|
333316
|
-
const [openTimeMs, open, high, low, close, volume, quoteVolume] = value;
|
|
333317
|
-
if (!isFiniteNumber(openTimeMs) || !isFiniteNumber(open) || !isFiniteNumber(high) || !isFiniteNumber(low) || !isFiniteNumber(close) || !isFiniteNumber(volume)) {
|
|
333318
|
-
return null;
|
|
333767
|
+
function asFiniteNumber(value) {
|
|
333768
|
+
if (typeof value === "number") {
|
|
333769
|
+
return Number.isFinite(value) ? value : undefined;
|
|
333319
333770
|
}
|
|
333320
|
-
|
|
333321
|
-
|
|
333322
|
-
open,
|
|
333323
|
-
high,
|
|
333324
|
-
low,
|
|
333325
|
-
close,
|
|
333326
|
-
volume
|
|
333327
|
-
};
|
|
333328
|
-
if (isFiniteNumber(quoteVolume)) {
|
|
333329
|
-
bar.quoteVolume = quoteVolume;
|
|
333771
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
333772
|
+
return;
|
|
333330
333773
|
}
|
|
333331
|
-
|
|
333774
|
+
const parsed = Number(value);
|
|
333775
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
333332
333776
|
}
|
|
333333
|
-
|
|
333334
|
-
|
|
333335
|
-
|
|
333777
|
+
|
|
333778
|
+
// src/handlers/execute-action/withdraw.ts
|
|
333779
|
+
var grpc10 = __toESM(require_src3(), 1);
|
|
333780
|
+
async function handleWithdraw(ctx) {
|
|
333781
|
+
const {
|
|
333782
|
+
call,
|
|
333783
|
+
wrappedCallback,
|
|
333784
|
+
policy,
|
|
333785
|
+
brokers,
|
|
333786
|
+
metadata,
|
|
333787
|
+
normalizedCex,
|
|
333788
|
+
cex: cex3,
|
|
333789
|
+
symbol: symbol2,
|
|
333790
|
+
selectedBrokerAccount,
|
|
333791
|
+
broker,
|
|
333792
|
+
verity,
|
|
333793
|
+
applyVerityToBroker,
|
|
333794
|
+
useVerity,
|
|
333795
|
+
verityProverUrl,
|
|
333796
|
+
otelMetrics,
|
|
333797
|
+
brokerArchiver
|
|
333798
|
+
} = ctx;
|
|
333799
|
+
const verityProof = verity.proof;
|
|
333800
|
+
if (!symbol2) {
|
|
333801
|
+
return ctx.wrappedCallback({
|
|
333802
|
+
code: grpc10.status.INVALID_ARGUMENT,
|
|
333803
|
+
message: `ValidationError: Symbol required`
|
|
333804
|
+
}, null);
|
|
333336
333805
|
}
|
|
333337
|
-
const
|
|
333338
|
-
|
|
333339
|
-
|
|
333340
|
-
|
|
333341
|
-
|
|
333342
|
-
|
|
333343
|
-
|
|
333806
|
+
const transferValue = parsePayloadForAction(ctx, WithdrawPayloadSchema);
|
|
333807
|
+
if (transferValue === null)
|
|
333808
|
+
return;
|
|
333809
|
+
let withdrawNetwork;
|
|
333810
|
+
try {
|
|
333811
|
+
withdrawNetwork = await resolveTransferNetwork(broker, symbol2, transferValue.chain);
|
|
333812
|
+
} catch (error48) {
|
|
333813
|
+
const message = getErrorMessage(error48);
|
|
333814
|
+
return ctx.wrappedCallback({
|
|
333815
|
+
code: stableGrpcErrorCode(message) ?? grpc10.status.INVALID_ARGUMENT,
|
|
333816
|
+
message
|
|
333817
|
+
}, null);
|
|
333344
333818
|
}
|
|
333345
|
-
|
|
333346
|
-
|
|
333347
|
-
|
|
333348
|
-
|
|
333349
|
-
|
|
333350
|
-
|
|
333351
|
-
const bars = extractOhlcvBars(payload);
|
|
333352
|
-
if (bars.length === 0) {
|
|
333353
|
-
return [];
|
|
333354
|
-
}
|
|
333355
|
-
if (bars.length === 1) {
|
|
333356
|
-
const [bar] = bars;
|
|
333357
|
-
return bar ? this.processSingleBar(bar, brokerVersion) : [];
|
|
333358
|
-
}
|
|
333359
|
-
return this.processBatch(bars, brokerVersion);
|
|
333819
|
+
const transferValidation = validateWithdraw(policy, cex3, withdrawNetwork.brokerNetworkId, transferValue.recipientAddress, transferValue.amount, symbol2);
|
|
333820
|
+
if (!transferValidation.valid) {
|
|
333821
|
+
return ctx.wrappedCallback({
|
|
333822
|
+
code: grpc10.status.PERMISSION_DENIED,
|
|
333823
|
+
message: `policy_withdrawal_denied: ${transferValidation.error}`
|
|
333824
|
+
}, null);
|
|
333360
333825
|
}
|
|
333361
|
-
|
|
333362
|
-
|
|
333363
|
-
|
|
333364
|
-
|
|
333365
|
-
|
|
333366
|
-
|
|
333367
|
-
candidates.push({
|
|
333368
|
-
bar: this.lastBar,
|
|
333369
|
-
isClosed: true,
|
|
333370
|
-
brokerVersion
|
|
333371
|
-
});
|
|
333372
|
-
}
|
|
333373
|
-
candidates.push({
|
|
333374
|
-
bar: currentBar,
|
|
333375
|
-
isClosed: false,
|
|
333376
|
-
brokerVersion
|
|
333377
|
-
});
|
|
333378
|
-
this.lastOpenTimeMs = currentBar.openTimeMs;
|
|
333379
|
-
this.lastBar = currentBar;
|
|
333380
|
-
return candidates;
|
|
333826
|
+
const travelRule = resolveTravelRuleDecision(policy, cex3, transferValue.recipientAddress);
|
|
333827
|
+
if (travelRule.mode === "denied") {
|
|
333828
|
+
return ctx.wrappedCallback({
|
|
333829
|
+
code: grpc10.status.FAILED_PRECONDITION,
|
|
333830
|
+
message: `travel_rule_denied: ${travelRule.error}`
|
|
333831
|
+
}, null);
|
|
333381
333832
|
}
|
|
333382
|
-
|
|
333383
|
-
|
|
333384
|
-
|
|
333385
|
-
|
|
333386
|
-
|
|
333387
|
-
|
|
333388
|
-
|
|
333389
|
-
|
|
333390
|
-
|
|
333391
|
-
|
|
333392
|
-
|
|
333393
|
-
|
|
333394
|
-
|
|
333395
|
-
|
|
333396
|
-
|
|
333397
|
-
|
|
333398
|
-
|
|
333399
|
-
|
|
333400
|
-
|
|
333401
|
-
|
|
333402
|
-
|
|
333403
|
-
|
|
333404
|
-
|
|
333405
|
-
|
|
333406
|
-
|
|
333407
|
-
|
|
333408
|
-
|
|
333409
|
-
|
|
333410
|
-
|
|
333411
|
-
|
|
333412
|
-
|
|
333413
|
-
|
|
333833
|
+
const withdrawOrderId = transferValue.params.withdrawOrderId;
|
|
333834
|
+
const clientWithdrawalId = typeof withdrawOrderId === "string" && withdrawOrderId.length > 0 ? withdrawOrderId : undefined;
|
|
333835
|
+
try {
|
|
333836
|
+
const transaction = travelRule.mode === "localentity" ? await withdrawViaLocalEntity(broker, {
|
|
333837
|
+
code: symbol2,
|
|
333838
|
+
amount: transferValue.amount,
|
|
333839
|
+
address: transferValue.recipientAddress,
|
|
333840
|
+
network: withdrawNetwork.exchangeNetworkId,
|
|
333841
|
+
questionnaire: travelRule.questionnaire,
|
|
333842
|
+
params: transferValue.params
|
|
333843
|
+
}) : await broker.withdraw(symbol2, transferValue.amount, transferValue.recipientAddress, undefined, {
|
|
333844
|
+
...transferValue.params ?? {},
|
|
333845
|
+
network: withdrawNetwork.exchangeNetworkId
|
|
333846
|
+
});
|
|
333847
|
+
log.info(`Withdraw Result: ${JSON.stringify(transaction)}`);
|
|
333848
|
+
const normalized = normalizeCcxtTransactionForArchive(transaction);
|
|
333849
|
+
archiveTransferEventInBackground(brokerArchiver, {
|
|
333850
|
+
exchange: cex3,
|
|
333851
|
+
accountSelector: selectedBrokerAccount?.label,
|
|
333852
|
+
assetSymbol: normalized.assetSymbol ?? symbol2,
|
|
333853
|
+
transfer: {
|
|
333854
|
+
eventKind: "withdrawal",
|
|
333855
|
+
lifecycleAction: "submit_withdrawal",
|
|
333856
|
+
status: normalized.status,
|
|
333857
|
+
amount: normalized.amount ?? String(transferValue.amount),
|
|
333858
|
+
address: normalized.address ?? transferValue.recipientAddress,
|
|
333859
|
+
network: normalized.network ?? withdrawNetwork.exchangeNetworkId,
|
|
333860
|
+
externalId: normalized.externalId,
|
|
333861
|
+
clientWithdrawalId,
|
|
333862
|
+
txid: normalized.txid,
|
|
333863
|
+
feeAmount: normalized.feeAmount,
|
|
333864
|
+
feeCurrency: normalized.feeCurrency,
|
|
333865
|
+
exchangeTimestamp: normalized.exchangeTimestamp,
|
|
333866
|
+
payload: transaction
|
|
333867
|
+
}
|
|
333868
|
+
});
|
|
333869
|
+
ctx.wrappedCallback(null, {
|
|
333870
|
+
proof: ctx.verity.proof,
|
|
333871
|
+
result: JSON.stringify({
|
|
333872
|
+
...transaction,
|
|
333873
|
+
operatorAlias: withdrawNetwork.operatorAlias,
|
|
333874
|
+
brokerNetworkId: withdrawNetwork.brokerNetworkId,
|
|
333875
|
+
exchangeNetworkId: withdrawNetwork.exchangeNetworkId
|
|
333876
|
+
})
|
|
333877
|
+
});
|
|
333878
|
+
} catch (error48) {
|
|
333879
|
+
safeLogError("Withdraw failed", error48);
|
|
333880
|
+
archiveTransferEventInBackground(brokerArchiver, {
|
|
333881
|
+
exchange: cex3,
|
|
333882
|
+
accountSelector: selectedBrokerAccount?.label,
|
|
333883
|
+
assetSymbol: symbol2,
|
|
333884
|
+
transfer: {
|
|
333885
|
+
eventKind: "withdrawal",
|
|
333886
|
+
lifecycleAction: "submit_withdrawal",
|
|
333887
|
+
status: "failed",
|
|
333888
|
+
amount: String(transferValue.amount),
|
|
333889
|
+
address: transferValue.recipientAddress,
|
|
333890
|
+
network: withdrawNetwork.exchangeNetworkId,
|
|
333891
|
+
clientWithdrawalId,
|
|
333892
|
+
errorSummary: getErrorMessage(error48),
|
|
333893
|
+
payload: { recipientAddress: transferValue.recipientAddress }
|
|
333414
333894
|
}
|
|
333415
|
-
}
|
|
333416
|
-
candidates.push({
|
|
333417
|
-
bar: lastBarToProcess,
|
|
333418
|
-
isClosed: false,
|
|
333419
|
-
brokerVersion
|
|
333420
333895
|
});
|
|
333421
|
-
|
|
333422
|
-
|
|
333423
|
-
|
|
333424
|
-
|
|
333425
|
-
}
|
|
333426
|
-
|
|
333427
|
-
// src/helpers/market-data-archive/orderbook-sampler.ts
|
|
333428
|
-
var DEFAULT_ORDERBOOK_INTERVAL_MS = 1000;
|
|
333429
|
-
function getOrderbookIntervalMs() {
|
|
333430
|
-
const raw = process.env.CEX_BROKER_ORDERBOOK_INTERVAL_MS ?? process.env.CEX_BROKER_ORDERBOOK_TOB_INTERVAL_MS;
|
|
333431
|
-
if (!raw) {
|
|
333432
|
-
return DEFAULT_ORDERBOOK_INTERVAL_MS;
|
|
333896
|
+
const code = mapCcxtErrorToGrpcStatus(error48) ?? grpc10.status.INTERNAL;
|
|
333897
|
+
ctx.wrappedCallback({
|
|
333898
|
+
code,
|
|
333899
|
+
message: `Withdraw failed: ${sanitizeErrorDetail(error48)}`
|
|
333900
|
+
}, null);
|
|
333433
333901
|
}
|
|
333434
|
-
const parsed = Number.parseInt(raw, 10);
|
|
333435
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_ORDERBOOK_INTERVAL_MS;
|
|
333436
|
-
}
|
|
333437
|
-
function isMarketArchiveEnabled() {
|
|
333438
|
-
return process.env.CEX_BROKER_MARKET_ARCHIVE_ENABLED !== "false";
|
|
333439
333902
|
}
|
|
333440
333903
|
|
|
333441
|
-
|
|
333442
|
-
|
|
333443
|
-
|
|
333444
|
-
|
|
333445
|
-
|
|
333446
|
-
|
|
333447
|
-
|
|
333448
|
-
|
|
333449
|
-
|
|
333450
|
-
|
|
333451
|
-
|
|
333452
|
-
|
|
333453
|
-
|
|
333454
|
-
|
|
333455
|
-
|
|
333456
|
-
|
|
333904
|
+
// src/handlers/execute-action/registry.ts
|
|
333905
|
+
var ACTION_HANDLERS = {
|
|
333906
|
+
[Action.Deposit]: handleDeposit,
|
|
333907
|
+
[Action.Withdraw]: handleWithdraw,
|
|
333908
|
+
[Action.Call]: handleTreasuryCall,
|
|
333909
|
+
[Action.InternalTransfer]: handleInternalTransfer,
|
|
333910
|
+
[Action.CreateOrder]: handleOrders,
|
|
333911
|
+
[Action.GetOrderDetails]: handleOrders,
|
|
333912
|
+
[Action.CancelOrder]: handleOrders,
|
|
333913
|
+
[Action.FetchCurrency]: handlePassThrough,
|
|
333914
|
+
[Action.FetchAccountId]: handlePassThrough,
|
|
333915
|
+
[Action.FetchFees]: handlePassThrough,
|
|
333916
|
+
[Action.FetchDepositAddresses]: handlePassThrough,
|
|
333917
|
+
[Action.FetchBalances]: handlePassThrough,
|
|
333918
|
+
[Action.FetchTicker]: handlePassThrough,
|
|
333919
|
+
[Action.GetPerpConfigState]: handlePerpConfig,
|
|
333920
|
+
[Action.SetPerpConfigState]: handlePerpConfig
|
|
333921
|
+
};
|
|
333922
|
+
async function dispatchExecuteAction(ctx) {
|
|
333923
|
+
const handler = ACTION_HANDLERS[ctx.action];
|
|
333924
|
+
if (!handler) {
|
|
333925
|
+
ctx.wrappedCallback({
|
|
333926
|
+
code: grpc11.status.INVALID_ARGUMENT,
|
|
333927
|
+
message: "Invalid Action"
|
|
333928
|
+
}, null);
|
|
333929
|
+
return;
|
|
333457
333930
|
}
|
|
333931
|
+
await handler(ctx);
|
|
333458
333932
|
}
|
|
333459
333933
|
|
|
333460
|
-
// src/
|
|
333461
|
-
function
|
|
333462
|
-
|
|
333463
|
-
|
|
333464
|
-
|
|
333465
|
-
if (isFiniteNumber2(value)) {
|
|
333466
|
-
return value;
|
|
333467
|
-
}
|
|
333468
|
-
if (typeof value === "string") {
|
|
333469
|
-
const parsed = Number.parseFloat(value);
|
|
333470
|
-
if (Number.isFinite(parsed)) {
|
|
333471
|
-
return parsed;
|
|
333472
|
-
}
|
|
333473
|
-
}
|
|
333474
|
-
return;
|
|
333475
|
-
}
|
|
333476
|
-
function toStringId(value) {
|
|
333477
|
-
if (typeof value === "string" && value.trim()) {
|
|
333478
|
-
return value.trim();
|
|
333479
|
-
}
|
|
333480
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
333481
|
-
return String(value);
|
|
333482
|
-
}
|
|
333483
|
-
return;
|
|
333484
|
-
}
|
|
333485
|
-
function scalarTimestampMs(value, fallbackMs) {
|
|
333486
|
-
const numeric = toNumber2(value);
|
|
333487
|
-
if (numeric !== undefined) {
|
|
333488
|
-
return numeric < 1000000000000 ? numeric * 1000 : numeric;
|
|
333489
|
-
}
|
|
333490
|
-
if (typeof value === "string") {
|
|
333491
|
-
const parsed = Date.parse(value);
|
|
333492
|
-
if (Number.isFinite(parsed)) {
|
|
333493
|
-
return parsed;
|
|
333494
|
-
}
|
|
333495
|
-
}
|
|
333496
|
-
return fallbackMs;
|
|
333497
|
-
}
|
|
333498
|
-
function parseTrade(value, fallbackMs = Date.now()) {
|
|
333499
|
-
const record2 = asRecord(value);
|
|
333500
|
-
if (!record2) {
|
|
333501
|
-
return null;
|
|
333502
|
-
}
|
|
333503
|
-
const tradeId = toStringId(record2.id);
|
|
333504
|
-
const price = toNumber2(record2.price);
|
|
333505
|
-
const amount = toNumber2(record2.amount);
|
|
333506
|
-
const side = typeof record2.side === "string" ? record2.side.toLowerCase() : undefined;
|
|
333507
|
-
if (!tradeId || price === undefined || amount === undefined || !side) {
|
|
333508
|
-
return null;
|
|
333509
|
-
}
|
|
333510
|
-
const parsed = {
|
|
333511
|
-
tradeId,
|
|
333512
|
-
eventTimeMs: scalarTimestampMs(record2.timestamp, fallbackMs),
|
|
333513
|
-
side,
|
|
333514
|
-
price,
|
|
333515
|
-
amount
|
|
333516
|
-
};
|
|
333517
|
-
const cost = toNumber2(record2.cost);
|
|
333518
|
-
if (cost !== undefined) {
|
|
333519
|
-
parsed.cost = cost;
|
|
333520
|
-
}
|
|
333521
|
-
if (typeof record2.takerOrMaker === "string") {
|
|
333522
|
-
parsed.takerOrMaker = record2.takerOrMaker;
|
|
333523
|
-
}
|
|
333524
|
-
return parsed;
|
|
333525
|
-
}
|
|
333526
|
-
function extractTrades(payload, fallbackMs = Date.now()) {
|
|
333527
|
-
if (Array.isArray(payload)) {
|
|
333528
|
-
return payload.map((entry) => parseTrade(entry, fallbackMs)).filter((entry) => entry !== null);
|
|
333529
|
-
}
|
|
333530
|
-
const single = parseTrade(payload, fallbackMs);
|
|
333531
|
-
return single ? [single] : [];
|
|
333934
|
+
// src/handlers/execute-action/handler.ts
|
|
333935
|
+
function isPublicMarketDataAction(action, payload) {
|
|
333936
|
+
if (action !== Action.Call)
|
|
333937
|
+
return false;
|
|
333938
|
+
return isOrderBookCallMethod(payload?.method ?? payload?.functionName);
|
|
333532
333939
|
}
|
|
333533
|
-
function
|
|
333534
|
-
const
|
|
333535
|
-
|
|
333536
|
-
|
|
333537
|
-
|
|
333538
|
-
|
|
333539
|
-
|
|
333540
|
-
|
|
333541
|
-
|
|
333542
|
-
|
|
333543
|
-
|
|
333544
|
-
|
|
333545
|
-
|
|
333546
|
-
|
|
333547
|
-
|
|
333548
|
-
|
|
333549
|
-
|
|
333550
|
-
|
|
333551
|
-
|
|
333552
|
-
|
|
333553
|
-
|
|
333554
|
-
|
|
333555
|
-
|
|
333556
|
-
|
|
333557
|
-
|
|
333940
|
+
function createExecuteActionHandler(deps) {
|
|
333941
|
+
const {
|
|
333942
|
+
policy,
|
|
333943
|
+
brokers,
|
|
333944
|
+
whitelistIps,
|
|
333945
|
+
useVerity,
|
|
333946
|
+
verityProverUrl,
|
|
333947
|
+
otelMetrics,
|
|
333948
|
+
brokerArchiver,
|
|
333949
|
+
orderActivityTracker
|
|
333950
|
+
} = deps;
|
|
333951
|
+
const withdrawalObservationTracker = deps.withdrawalObservationTracker ?? new WithdrawalObservationTracker;
|
|
333952
|
+
return async (call, callback) => {
|
|
333953
|
+
const startTime = Date.now();
|
|
333954
|
+
const { action: rawAction, cex: cex3, symbol: symbol2 } = call.request;
|
|
333955
|
+
const action = resolveAction(rawAction);
|
|
333956
|
+
let actionCompleted = false;
|
|
333957
|
+
const wrappedCallback = (error48, value) => {
|
|
333958
|
+
if (!actionCompleted) {
|
|
333959
|
+
actionCompleted = true;
|
|
333960
|
+
const latency = Date.now() - startTime;
|
|
333961
|
+
const actionName = getActionName(action);
|
|
333962
|
+
otelMetrics?.recordHistogram("execute_action_duration_ms", latency, {
|
|
333963
|
+
action: actionName,
|
|
333964
|
+
cex: cex3 || "unknown"
|
|
333965
|
+
});
|
|
333966
|
+
if (error48) {
|
|
333967
|
+
otelMetrics?.recordCounter("execute_action_errors_total", 1, {
|
|
333968
|
+
action: actionName,
|
|
333969
|
+
cex: cex3 || "unknown",
|
|
333970
|
+
error_type: error48.code ? grpc12.status[error48.code] || "unknown" : "unknown"
|
|
333971
|
+
});
|
|
333972
|
+
} else {
|
|
333973
|
+
otelMetrics?.recordCounter("execute_action_success_total", 1, {
|
|
333974
|
+
action: actionName,
|
|
333975
|
+
cex: cex3 || "unknown"
|
|
333976
|
+
});
|
|
333977
|
+
}
|
|
333978
|
+
}
|
|
333979
|
+
callback(error48, value);
|
|
333980
|
+
};
|
|
333981
|
+
try {
|
|
333982
|
+
log.info(`Request - ExecuteAction:`, { action, cex: cex3, symbol: symbol2 });
|
|
333983
|
+
otelMetrics?.recordCounter("execute_action_requests_total", 1, {
|
|
333984
|
+
action: getActionName(action),
|
|
333985
|
+
cex: cex3 || "unknown"
|
|
333986
|
+
});
|
|
333987
|
+
if (!authenticateRequest(call, whitelistIps)) {
|
|
333988
|
+
return wrappedCallback({
|
|
333989
|
+
code: grpc12.status.PERMISSION_DENIED,
|
|
333990
|
+
message: "Access denied: Unauthorized IP"
|
|
333991
|
+
}, null);
|
|
333992
|
+
}
|
|
333993
|
+
if (!action || !cex3) {
|
|
333994
|
+
return wrappedCallback({
|
|
333995
|
+
code: grpc12.status.INVALID_ARGUMENT,
|
|
333996
|
+
message: "`action` AND `cex` fields are required"
|
|
333997
|
+
}, null);
|
|
333998
|
+
}
|
|
333999
|
+
const normalizedCex = cex3.trim().toLowerCase();
|
|
334000
|
+
const metadata = call.metadata;
|
|
334001
|
+
const selectedBrokerAccount = selectBrokerAccountForCex(normalizedCex, brokers, metadata);
|
|
334002
|
+
const broker = selectedBrokerAccount?.exchange ?? createBroker(normalizedCex, call.metadata) ?? (isPublicMarketDataAction(action, call.request.payload) ? createPublicBroker(normalizedCex) : null);
|
|
334003
|
+
if (!broker) {
|
|
334004
|
+
return wrappedCallback({
|
|
334005
|
+
code: grpc12.status.UNAUTHENTICATED,
|
|
334006
|
+
message: `This Exchange is not registered and No API metadata was found`
|
|
334007
|
+
}, null);
|
|
334008
|
+
}
|
|
334009
|
+
const verity = { proof: "" };
|
|
334010
|
+
const applyVerityToBroker = (targetBroker) => {
|
|
334011
|
+
if (!useVerity)
|
|
334012
|
+
return;
|
|
334013
|
+
const override = buildHttpClientOverrideFromMetadata(metadata, verityProverUrl, (proof, notaryPubKey) => {
|
|
334014
|
+
verity.proof = proof;
|
|
334015
|
+
log.debug(`Verity proof:`, { proof, notaryPubKey });
|
|
334016
|
+
});
|
|
334017
|
+
targetBroker.setHttpClientOverride(override, verityHttpClientOverridePredicate);
|
|
334018
|
+
};
|
|
334019
|
+
const preludeCtx = {
|
|
334020
|
+
call,
|
|
334021
|
+
wrappedCallback,
|
|
334022
|
+
action,
|
|
334023
|
+
policy,
|
|
334024
|
+
brokers,
|
|
334025
|
+
metadata,
|
|
334026
|
+
normalizedCex,
|
|
334027
|
+
cex: cex3,
|
|
334028
|
+
symbol: symbol2,
|
|
334029
|
+
selectedBrokerAccount,
|
|
334030
|
+
broker,
|
|
334031
|
+
verity,
|
|
334032
|
+
applyVerityToBroker,
|
|
334033
|
+
useVerity,
|
|
334034
|
+
verityProverUrl,
|
|
334035
|
+
otelMetrics,
|
|
334036
|
+
brokerArchiver,
|
|
334037
|
+
orderActivityTracker,
|
|
334038
|
+
withdrawalObservationTracker
|
|
334039
|
+
};
|
|
334040
|
+
if (action === Action.Call) {
|
|
334041
|
+
const handled = await handleOrderBookCall(preludeCtx);
|
|
334042
|
+
if (handled)
|
|
334043
|
+
return;
|
|
334044
|
+
}
|
|
334045
|
+
applyVerityToBroker(broker);
|
|
334046
|
+
const ctx = { ...preludeCtx, broker };
|
|
334047
|
+
await dispatchExecuteAction(ctx);
|
|
334048
|
+
} catch (error48) {
|
|
334049
|
+
safeLogError("ExecuteAction unhandled error", error48);
|
|
334050
|
+
return wrappedCallback({
|
|
334051
|
+
code: grpc12.status.INTERNAL,
|
|
334052
|
+
message: "ExecuteAction failed unexpectedly"
|
|
334053
|
+
}, null);
|
|
333558
334054
|
}
|
|
333559
|
-
}
|
|
333560
|
-
return parsed;
|
|
334055
|
+
};
|
|
333561
334056
|
}
|
|
333562
|
-
|
|
333563
|
-
|
|
333564
|
-
|
|
333565
|
-
|
|
333566
|
-
|
|
333567
|
-
|
|
333568
|
-
|
|
333569
|
-
|
|
334057
|
+
// src/handlers/subscribe/broker-lifecycle.ts
|
|
334058
|
+
class SubscribeBrokerLifecycle {
|
|
334059
|
+
#brokers = new Map;
|
|
334060
|
+
#closing = new Map;
|
|
334061
|
+
#shuttingDown = false;
|
|
334062
|
+
register(broker, context2) {
|
|
334063
|
+
this.#brokers.set(broker, context2);
|
|
334064
|
+
if (this.#shuttingDown) {
|
|
334065
|
+
this.close(broker);
|
|
334066
|
+
}
|
|
333570
334067
|
}
|
|
333571
|
-
|
|
333572
|
-
|
|
333573
|
-
|
|
334068
|
+
close(broker) {
|
|
334069
|
+
const existing = this.#closing.get(broker);
|
|
334070
|
+
if (existing) {
|
|
334071
|
+
return existing;
|
|
334072
|
+
}
|
|
334073
|
+
const context2 = this.#brokers.get(broker) ?? {
|
|
334074
|
+
cex: "unknown",
|
|
334075
|
+
symbol: "unknown"
|
|
334076
|
+
};
|
|
334077
|
+
this.#brokers.delete(broker);
|
|
334078
|
+
const closing = (async () => {
|
|
334079
|
+
try {
|
|
334080
|
+
await broker.close();
|
|
334081
|
+
log.debug("Request-scoped Subscribe broker closed", context2);
|
|
334082
|
+
return "closed";
|
|
334083
|
+
} catch (error48) {
|
|
334084
|
+
log.warn("Failed to close request-scoped Subscribe broker", {
|
|
334085
|
+
...context2,
|
|
334086
|
+
error: error48
|
|
334087
|
+
});
|
|
334088
|
+
return "failed";
|
|
334089
|
+
} finally {
|
|
334090
|
+
this.#closing.delete(broker);
|
|
334091
|
+
}
|
|
334092
|
+
})();
|
|
334093
|
+
this.#closing.set(broker, closing);
|
|
334094
|
+
return closing;
|
|
333574
334095
|
}
|
|
333575
|
-
|
|
333576
|
-
|
|
333577
|
-
|
|
333578
|
-
|
|
333579
|
-
|
|
333580
|
-
|
|
333581
|
-
|
|
333582
|
-
|
|
334096
|
+
async closeAll() {
|
|
334097
|
+
this.#shuttingDown = true;
|
|
334098
|
+
let failed = 0;
|
|
334099
|
+
while (this.#brokers.size > 0 || this.#closing.size > 0) {
|
|
334100
|
+
const inFlight = [...this.#closing.values()];
|
|
334101
|
+
const fresh = [...this.#brokers.keys()].map((broker) => this.close(broker));
|
|
334102
|
+
const outcomes = await Promise.all([...fresh, ...inFlight]);
|
|
334103
|
+
failed += outcomes.filter((outcome) => outcome === "failed").length;
|
|
333583
334104
|
}
|
|
333584
|
-
|
|
333585
|
-
|
|
333586
|
-
if (price === undefined || size === undefined || !Number.isFinite(price) || !Number.isFinite(size)) {
|
|
333587
|
-
continue;
|
|
334105
|
+
if (failed > 0) {
|
|
334106
|
+
throw new Error(`${failed} request-scoped Subscribe broker(s) failed to close`);
|
|
333588
334107
|
}
|
|
333589
|
-
prices.push(price);
|
|
333590
|
-
sizes.push(size);
|
|
333591
334108
|
}
|
|
333592
|
-
return { prices, sizes };
|
|
333593
334109
|
}
|
|
334110
|
+
// src/handlers/subscribe/handler.ts
|
|
334111
|
+
var grpc13 = __toESM(require_src3(), 1);
|
|
333594
334112
|
|
|
333595
|
-
// src/helpers/
|
|
333596
|
-
|
|
333597
|
-
|
|
333598
|
-
|
|
333599
|
-
|
|
333600
|
-
|
|
333601
|
-
|
|
333602
|
-
|
|
333603
|
-
|
|
333604
|
-
|
|
333605
|
-
|
|
333606
|
-
if (Number.isFinite(numeric)) {
|
|
333607
|
-
return numeric;
|
|
333608
|
-
}
|
|
333609
|
-
}
|
|
333610
|
-
const parsed = Date.parse(value);
|
|
333611
|
-
if (Number.isFinite(parsed)) {
|
|
333612
|
-
return parsed;
|
|
333613
|
-
}
|
|
334113
|
+
// src/helpers/binance-user-data-stream.ts
|
|
334114
|
+
import { Buffer as Buffer2 } from "node:buffer";
|
|
334115
|
+
import { createHmac } from "node:crypto";
|
|
334116
|
+
var BINANCE_SPOT_WS_API_URL = "wss://ws-api.binance.com:443/ws-api/v3";
|
|
334117
|
+
var DEFAULT_BINANCE_USER_DATA_MAX_BUFFERED_EVENTS = 16;
|
|
334118
|
+
var createWebSocket = (url3) => new wrapper_default(url3);
|
|
334119
|
+
var userDataRequestCounter = 0;
|
|
334120
|
+
function getExchangeString(exchange, key) {
|
|
334121
|
+
const value = exchange[key];
|
|
334122
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
334123
|
+
throw new Error(`Binance user-data stream requires exchange.${key}`);
|
|
333614
334124
|
}
|
|
333615
|
-
return
|
|
334125
|
+
return value;
|
|
333616
334126
|
}
|
|
333617
|
-
function
|
|
333618
|
-
|
|
333619
|
-
return value;
|
|
333620
|
-
}
|
|
333621
|
-
if (typeof value === "string" && /^\d+$/.test(value)) {
|
|
333622
|
-
return Number.parseInt(value, 10);
|
|
333623
|
-
}
|
|
333624
|
-
return;
|
|
334127
|
+
function sortedQuery(params) {
|
|
334128
|
+
return Object.entries(params).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`).join("&");
|
|
333625
334129
|
}
|
|
333626
|
-
function
|
|
333627
|
-
const
|
|
333628
|
-
if (
|
|
333629
|
-
return
|
|
333630
|
-
}
|
|
333631
|
-
const price = level[0];
|
|
333632
|
-
const size = level[1];
|
|
333633
|
-
if (price === undefined || size === undefined || !Number.isFinite(price) || !Number.isFinite(size)) {
|
|
333634
|
-
return null;
|
|
334130
|
+
function signUserDataStreamParams(exchange, params) {
|
|
334131
|
+
const signParams = exchange.signParams;
|
|
334132
|
+
if (typeof signParams === "function") {
|
|
334133
|
+
return signParams.call(exchange, params);
|
|
333635
334134
|
}
|
|
333636
|
-
|
|
334135
|
+
const secret = getExchangeString(exchange, "secret");
|
|
334136
|
+
return {
|
|
334137
|
+
...params,
|
|
334138
|
+
signature: createHmac("sha256", secret).update(sortedQuery(params)).digest("hex")
|
|
334139
|
+
};
|
|
333637
334140
|
}
|
|
333638
|
-
function
|
|
333639
|
-
|
|
333640
|
-
|
|
333641
|
-
}
|
|
333642
|
-
const mid = (bestBid + bestAsk) / 2;
|
|
333643
|
-
if (mid <= 0) {
|
|
333644
|
-
return 0;
|
|
333645
|
-
}
|
|
333646
|
-
return (bestAsk - bestBid) / mid * 1e4;
|
|
334141
|
+
function getBinanceSpotWsApiUrl(exchange) {
|
|
334142
|
+
const urls = exchange.urls;
|
|
334143
|
+
return urls?.api?.ws?.["ws-api"]?.spot ?? BINANCE_SPOT_WS_API_URL;
|
|
333647
334144
|
}
|
|
333648
|
-
function
|
|
333649
|
-
return
|
|
333650
|
-
deploymentId: input.deploymentId,
|
|
333651
|
-
accountSelector: input.accountSelector,
|
|
333652
|
-
exchange: input.exchange,
|
|
333653
|
-
symbol: input.symbol,
|
|
333654
|
-
brokerObservedTimestamp: new Date(receivedTimeMs).toISOString()
|
|
333655
|
-
});
|
|
334145
|
+
function getRecord(value) {
|
|
334146
|
+
return typeof value === "object" && value !== null ? value : null;
|
|
333656
334147
|
}
|
|
333657
|
-
function
|
|
333658
|
-
|
|
333659
|
-
|
|
333660
|
-
if (!bid || !ask) {
|
|
333661
|
-
return null;
|
|
334148
|
+
function getMessage(value) {
|
|
334149
|
+
if (value instanceof Error) {
|
|
334150
|
+
return value.message;
|
|
333662
334151
|
}
|
|
333663
|
-
|
|
333664
|
-
|
|
333665
|
-
const asks = splitOrderBookSide(input.snapshot.asks, archiveDepthLimit);
|
|
333666
|
-
if (bids.prices.length === 0 || asks.prices.length === 0) {
|
|
333667
|
-
return null;
|
|
334152
|
+
if (typeof value === "string" && value.length > 0) {
|
|
334153
|
+
return value;
|
|
333668
334154
|
}
|
|
333669
|
-
const
|
|
333670
|
-
const
|
|
333671
|
-
|
|
333672
|
-
const sequence = parseSequence(input.snapshot.sequence);
|
|
333673
|
-
return {
|
|
333674
|
-
table: "market_data.orderbook_snapshots",
|
|
333675
|
-
row: compactUndefined3({
|
|
333676
|
-
...buildOrderbookArchiveTags(input, receivedTimeMs),
|
|
333677
|
-
asset_type: input.assetType,
|
|
333678
|
-
event_time_ms: eventTimeMs,
|
|
333679
|
-
received_time_ms: receivedTimeMs,
|
|
333680
|
-
best_bid: bid.price,
|
|
333681
|
-
best_ask: ask.price,
|
|
333682
|
-
bid_size: bid.size,
|
|
333683
|
-
ask_size: ask.size,
|
|
333684
|
-
mid,
|
|
333685
|
-
spread_bps: computeSpreadBps(bid.price, ask.price),
|
|
333686
|
-
depth_limit: archiveDepthLimit,
|
|
333687
|
-
bid_levels: bids.prices.length,
|
|
333688
|
-
ask_levels: asks.prices.length,
|
|
333689
|
-
bids_price: bids.prices,
|
|
333690
|
-
bids_size: bids.sizes,
|
|
333691
|
-
asks_price: asks.prices,
|
|
333692
|
-
asks_size: asks.sizes,
|
|
333693
|
-
sequence
|
|
333694
|
-
})
|
|
333695
|
-
};
|
|
333696
|
-
}
|
|
333697
|
-
function buildCandleRow(input) {
|
|
333698
|
-
const { context: context2, bar, isClosed, brokerVersion, receivedTimestamp } = input;
|
|
333699
|
-
const tags = buildCommonArchiveTags({
|
|
333700
|
-
deploymentId: context2.deploymentId,
|
|
333701
|
-
accountSelector: context2.accountSelector,
|
|
333702
|
-
exchange: context2.exchange,
|
|
333703
|
-
symbol: context2.symbol,
|
|
333704
|
-
brokerObservedTimestamp: new Date(receivedTimestamp).toISOString()
|
|
333705
|
-
});
|
|
333706
|
-
return {
|
|
333707
|
-
table: "market_data.candles",
|
|
333708
|
-
row: compactUndefined3({
|
|
333709
|
-
...tags,
|
|
333710
|
-
asset_type: context2.assetType,
|
|
333711
|
-
timeframe: context2.timeframe ?? "1m",
|
|
333712
|
-
open_time_ms: bar.openTimeMs,
|
|
333713
|
-
open: bar.open,
|
|
333714
|
-
high: bar.high,
|
|
333715
|
-
low: bar.low,
|
|
333716
|
-
close: bar.close,
|
|
333717
|
-
volume: bar.volume,
|
|
333718
|
-
quote_volume: bar.quoteVolume,
|
|
333719
|
-
is_closed: isClosed ? 1 : 0,
|
|
333720
|
-
broker_version: brokerVersion
|
|
333721
|
-
})
|
|
333722
|
-
};
|
|
334155
|
+
const record2 = getRecord(value);
|
|
334156
|
+
const message = record2?.message;
|
|
334157
|
+
return typeof message === "string" && message.length > 0 ? message : null;
|
|
333723
334158
|
}
|
|
333724
|
-
function
|
|
333725
|
-
const
|
|
333726
|
-
|
|
333727
|
-
const tags = buildCommonArchiveTags({
|
|
333728
|
-
deploymentId: input.deploymentId,
|
|
333729
|
-
accountSelector: input.accountSelector,
|
|
333730
|
-
exchange: input.exchange,
|
|
333731
|
-
symbol: input.symbol,
|
|
333732
|
-
brokerObservedTimestamp: new Date(receivedTimeMs).toISOString()
|
|
333733
|
-
});
|
|
333734
|
-
return {
|
|
333735
|
-
table: "market_data.cex_stream_events",
|
|
333736
|
-
row: compactUndefined3({
|
|
333737
|
-
...tags,
|
|
333738
|
-
asset_type: input.assetType,
|
|
333739
|
-
stream_type: input.streamType,
|
|
333740
|
-
event_time_ms: input.eventTimeMs ?? receivedTimeMs,
|
|
333741
|
-
received_time_ms: receivedTimeMs,
|
|
333742
|
-
payload_json: JSON.stringify(redactedPayload)
|
|
333743
|
-
})
|
|
333744
|
-
};
|
|
334159
|
+
function getOptionalExchangeString(exchange, key) {
|
|
334160
|
+
const value = exchange[key];
|
|
334161
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
333745
334162
|
}
|
|
333746
|
-
function
|
|
333747
|
-
|
|
333748
|
-
const
|
|
333749
|
-
|
|
333750
|
-
|
|
333751
|
-
|
|
333752
|
-
|
|
333753
|
-
|
|
333754
|
-
});
|
|
333755
|
-
return {
|
|
333756
|
-
table: "market_data.cex_ticker_events",
|
|
333757
|
-
row: compactUndefined3({
|
|
333758
|
-
...tags,
|
|
333759
|
-
asset_type: input.assetType,
|
|
333760
|
-
event_time_ms: ticker.eventTimeMs,
|
|
333761
|
-
received_time_ms: receivedTimeMs,
|
|
333762
|
-
last: ticker.last,
|
|
333763
|
-
bid: ticker.bid,
|
|
333764
|
-
ask: ticker.ask,
|
|
333765
|
-
high: ticker.high,
|
|
333766
|
-
low: ticker.low,
|
|
333767
|
-
open: ticker.open,
|
|
333768
|
-
close: ticker.close,
|
|
333769
|
-
base_volume: ticker.baseVolume,
|
|
333770
|
-
quote_volume: ticker.quoteVolume,
|
|
333771
|
-
change: ticker.change,
|
|
333772
|
-
percentage: ticker.percentage,
|
|
333773
|
-
payload_json: JSON.stringify(redactStreamPayload(input.payload))
|
|
333774
|
-
})
|
|
333775
|
-
};
|
|
334163
|
+
function redactDiagnosticMessage(message, secretValues) {
|
|
334164
|
+
let redacted = message;
|
|
334165
|
+
for (const value of secretValues) {
|
|
334166
|
+
if (value.length > 0) {
|
|
334167
|
+
redacted = redacted.split(value).join("[redacted]");
|
|
334168
|
+
}
|
|
334169
|
+
}
|
|
334170
|
+
return redacted.replace(/(\b(?:apiKey|secret|signature)\b\s*=\s*)[^\s&,;)]+/gi, "$1[redacted]").replace(/("(?:apiKey|secret|signature)"\s*:\s*")[^"]*(")/gi, "$1[redacted]$2");
|
|
333776
334171
|
}
|
|
333777
|
-
function
|
|
333778
|
-
const
|
|
333779
|
-
const
|
|
333780
|
-
|
|
333781
|
-
|
|
333782
|
-
exchange: input.exchange,
|
|
333783
|
-
symbol: input.symbol,
|
|
333784
|
-
brokerObservedTimestamp: new Date(receivedTimeMs).toISOString()
|
|
333785
|
-
});
|
|
333786
|
-
return {
|
|
333787
|
-
table: "market_data.cex_trades",
|
|
333788
|
-
row: compactUndefined3({
|
|
333789
|
-
...tags,
|
|
333790
|
-
asset_type: input.assetType,
|
|
333791
|
-
trade_id: trade.tradeId,
|
|
333792
|
-
event_time_ms: trade.eventTimeMs,
|
|
333793
|
-
received_time_ms: receivedTimeMs,
|
|
333794
|
-
side: trade.side,
|
|
333795
|
-
price: trade.price,
|
|
333796
|
-
amount: trade.amount,
|
|
333797
|
-
cost: trade.cost,
|
|
333798
|
-
taker_or_maker: trade.takerOrMaker
|
|
333799
|
-
})
|
|
333800
|
-
};
|
|
334172
|
+
function formatBinanceUserDataWebSocketError(event, secretValues) {
|
|
334173
|
+
const record2 = getRecord(event);
|
|
334174
|
+
const message = getMessage(record2?.error) ?? getMessage(record2?.message) ?? getMessage(event);
|
|
334175
|
+
const safeMessage = message === null ? null : redactDiagnosticMessage(message, secretValues);
|
|
334176
|
+
return new Error(safeMessage ? `Binance user-data WebSocket error: ${safeMessage}` : "Binance user-data WebSocket error");
|
|
333801
334177
|
}
|
|
333802
|
-
|
|
333803
|
-
|
|
333804
|
-
|
|
333805
|
-
|
|
333806
|
-
|
|
333807
|
-
|
|
334178
|
+
function getCloseReason(value) {
|
|
334179
|
+
if (typeof value === "string") {
|
|
334180
|
+
return value.length > 0 ? value : null;
|
|
334181
|
+
}
|
|
334182
|
+
if (Buffer2.isBuffer(value)) {
|
|
334183
|
+
const reason = value.toString("utf8");
|
|
334184
|
+
return reason.length > 0 ? reason : null;
|
|
334185
|
+
}
|
|
334186
|
+
if (value instanceof Uint8Array) {
|
|
334187
|
+
const reason = Buffer2.from(value).toString("utf8");
|
|
334188
|
+
return reason.length > 0 ? reason : null;
|
|
334189
|
+
}
|
|
334190
|
+
return null;
|
|
333808
334191
|
}
|
|
333809
|
-
function
|
|
333810
|
-
|
|
333811
|
-
|
|
333812
|
-
|
|
333813
|
-
|
|
333814
|
-
|
|
334192
|
+
function formatBinanceUserDataWebSocketClose(codeOrEvent, reasonOrUndefined, secretValues) {
|
|
334193
|
+
const record2 = getRecord(codeOrEvent);
|
|
334194
|
+
const code = record2 ? record2.code : codeOrEvent;
|
|
334195
|
+
const reason = getCloseReason(record2 ? record2.reason : reasonOrUndefined);
|
|
334196
|
+
const safeReason = reason === null ? null : redactDiagnosticMessage(reason, secretValues);
|
|
334197
|
+
const details = [
|
|
334198
|
+
typeof code === "number" || typeof code === "string" ? `code=${code}` : null,
|
|
334199
|
+
safeReason ? `reason=${safeReason}` : null
|
|
334200
|
+
].filter((detail) => detail !== null);
|
|
334201
|
+
return new Error(details.length > 0 ? `Binance user-data WebSocket closed unexpectedly (${details.join(", ")})` : "Binance user-data WebSocket closed unexpectedly");
|
|
333815
334202
|
}
|
|
333816
|
-
function
|
|
333817
|
-
|
|
333818
|
-
|
|
333819
|
-
if (!isMarketArchiveEnabled() || !archiver?.isEnabled()) {
|
|
333820
|
-
return;
|
|
334203
|
+
function decodeMessageData(data) {
|
|
334204
|
+
if (typeof data === "string") {
|
|
334205
|
+
return data;
|
|
333821
334206
|
}
|
|
333822
|
-
if (
|
|
333823
|
-
|
|
333824
|
-
return;
|
|
334207
|
+
if (Buffer2.isBuffer(data)) {
|
|
334208
|
+
return data.toString("utf8");
|
|
333825
334209
|
}
|
|
333826
|
-
|
|
333827
|
-
|
|
333828
|
-
|
|
333829
|
-
|
|
333830
|
-
|
|
333831
|
-
|
|
334210
|
+
if (data instanceof ArrayBuffer) {
|
|
334211
|
+
return Buffer2.from(data).toString("utf8");
|
|
334212
|
+
}
|
|
334213
|
+
if (ArrayBuffer.isView(data)) {
|
|
334214
|
+
return Buffer2.from(data.buffer, data.byteOffset, data.byteLength).toString("utf8");
|
|
334215
|
+
}
|
|
334216
|
+
if (Array.isArray(data) && data.every((item) => Buffer2.isBuffer(item))) {
|
|
334217
|
+
return Buffer2.concat(data).toString("utf8");
|
|
334218
|
+
}
|
|
334219
|
+
return data;
|
|
334220
|
+
}
|
|
334221
|
+
|
|
334222
|
+
class BinanceSpotUserDataStream {
|
|
334223
|
+
exchange;
|
|
334224
|
+
ws;
|
|
334225
|
+
secretValues;
|
|
334226
|
+
requestId = `user-data-${Date.now()}-${userDataRequestCounter++}`;
|
|
334227
|
+
maxBufferedEvents;
|
|
334228
|
+
queue = [];
|
|
334229
|
+
waiters = [];
|
|
334230
|
+
closed = false;
|
|
334231
|
+
closeError = null;
|
|
334232
|
+
subscriptionId = null;
|
|
334233
|
+
constructor(exchange, options = {}) {
|
|
334234
|
+
this.exchange = exchange;
|
|
334235
|
+
this.maxBufferedEvents = options.maxBufferedEvents ?? DEFAULT_BINANCE_USER_DATA_MAX_BUFFERED_EVENTS;
|
|
334236
|
+
this.secretValues = [
|
|
334237
|
+
getOptionalExchangeString(exchange, "apiKey"),
|
|
334238
|
+
getOptionalExchangeString(exchange, "secret")
|
|
334239
|
+
].filter((value) => value !== null);
|
|
334240
|
+
this.ws = createWebSocket(getBinanceSpotWsApiUrl(exchange));
|
|
334241
|
+
this.ws.on("open", () => this.subscribe());
|
|
334242
|
+
this.ws.on("message", (data) => this.handleMessage(data));
|
|
334243
|
+
this.ws.on("error", (error48) => this.fail(formatBinanceUserDataWebSocketError(error48, this.secretValues)));
|
|
334244
|
+
this.ws.on("close", (code, reason) => this.handleClose(code, reason));
|
|
334245
|
+
}
|
|
334246
|
+
async* [Symbol.asyncIterator]() {
|
|
334247
|
+
while (true) {
|
|
334248
|
+
const event = await this.nextEvent();
|
|
334249
|
+
if (!event) {
|
|
334250
|
+
break;
|
|
333832
334251
|
}
|
|
333833
|
-
|
|
333834
|
-
rethrowArchiveDurabilityError(error48);
|
|
333835
|
-
log.warn("Failed to archive orderbook snapshot", { error: error48 });
|
|
334252
|
+
yield event;
|
|
333836
334253
|
}
|
|
333837
|
-
});
|
|
333838
|
-
}
|
|
333839
|
-
function archiveOhlcvInBackground(archiver, otelMetrics, tracker, input) {
|
|
333840
|
-
const labels = watchLabels("ohlcv", input);
|
|
333841
|
-
recordWatchMetric(otelMetrics, "cex_watch_frames_received_total", labels);
|
|
333842
|
-
if (!isMarketArchiveEnabled() || !archiver?.isEnabled()) {
|
|
333843
|
-
return;
|
|
333844
334254
|
}
|
|
333845
|
-
|
|
334255
|
+
close() {
|
|
334256
|
+
if (this.closed) {
|
|
334257
|
+
return;
|
|
334258
|
+
}
|
|
334259
|
+
this.closed = true;
|
|
334260
|
+
this.queue.length = 0;
|
|
333846
334261
|
try {
|
|
333847
|
-
|
|
333848
|
-
|
|
333849
|
-
|
|
333850
|
-
|
|
333851
|
-
|
|
333852
|
-
|
|
333853
|
-
|
|
333854
|
-
receivedTimestamp: input.receivedTimestamp
|
|
333855
|
-
});
|
|
333856
|
-
archiver.enqueue(row);
|
|
333857
|
-
}
|
|
333858
|
-
if (candidates.length > 0) {
|
|
333859
|
-
recordWatchMetric(otelMetrics, "cex_watch_frames_archived_total", labels);
|
|
333860
|
-
}
|
|
333861
|
-
} catch (error48) {
|
|
333862
|
-
rethrowArchiveDurabilityError(error48);
|
|
333863
|
-
log.warn("Failed to archive OHLCV candle", { error: error48 });
|
|
334262
|
+
this.ws.close();
|
|
334263
|
+
} catch {}
|
|
334264
|
+
this.flushWaiters();
|
|
334265
|
+
}
|
|
334266
|
+
handleClose(code, reason) {
|
|
334267
|
+
if (this.closed) {
|
|
334268
|
+
return;
|
|
333864
334269
|
}
|
|
333865
|
-
|
|
333866
|
-
}
|
|
333867
|
-
function createOrderbookSampler() {
|
|
333868
|
-
return new OrderbookSampler;
|
|
333869
|
-
}
|
|
333870
|
-
function createOhlcvBarTracker() {
|
|
333871
|
-
return new OhlcvBarTracker;
|
|
333872
|
-
}
|
|
333873
|
-
function archiveMarketRowsInBackground(archiver, otelMetrics, stream4, input, enqueueRows) {
|
|
333874
|
-
const labels = watchLabels(stream4, input);
|
|
333875
|
-
recordWatchMetric(otelMetrics, "cex_watch_frames_received_total", labels);
|
|
333876
|
-
if (!isMarketArchiveEnabled() || !archiver?.isEnabled()) {
|
|
333877
|
-
return;
|
|
334270
|
+
this.fail(formatBinanceUserDataWebSocketClose(code, reason, this.secretValues));
|
|
333878
334271
|
}
|
|
333879
|
-
|
|
334272
|
+
subscribe() {
|
|
334273
|
+
const apiKey = getExchangeString(this.exchange, "apiKey");
|
|
334274
|
+
const signedParams = signUserDataStreamParams(this.exchange, {
|
|
334275
|
+
apiKey,
|
|
334276
|
+
timestamp: Date.now()
|
|
334277
|
+
});
|
|
334278
|
+
this.ws.send(JSON.stringify({
|
|
334279
|
+
id: this.requestId,
|
|
334280
|
+
method: "userDataStream.subscribe.signature",
|
|
334281
|
+
params: signedParams
|
|
334282
|
+
}));
|
|
334283
|
+
}
|
|
334284
|
+
handleMessage(data) {
|
|
334285
|
+
if (this.closed) {
|
|
334286
|
+
return;
|
|
334287
|
+
}
|
|
334288
|
+
let message;
|
|
333880
334289
|
try {
|
|
333881
|
-
const
|
|
333882
|
-
|
|
333883
|
-
|
|
334290
|
+
const decodedData = decodeMessageData(data);
|
|
334291
|
+
message = typeof decodedData === "string" ? JSON.parse(decodedData) : decodedData;
|
|
334292
|
+
} catch (error48) {
|
|
334293
|
+
this.fail(error48 instanceof Error ? error48 : new Error("Invalid Binance user-data message"));
|
|
334294
|
+
return;
|
|
334295
|
+
}
|
|
334296
|
+
if ("id" in message && message.id === this.requestId) {
|
|
334297
|
+
if (message.status !== 200) {
|
|
334298
|
+
this.fail(new Error(message.error?.msg ?? message.error?.message ?? `Binance user-data subscription failed with status ${message.status}`));
|
|
334299
|
+
return;
|
|
333884
334300
|
}
|
|
333885
|
-
|
|
333886
|
-
|
|
334301
|
+
this.subscriptionId = message.result?.subscriptionId ?? null;
|
|
334302
|
+
return;
|
|
334303
|
+
}
|
|
334304
|
+
if ("status" in message && typeof message.status === "number" && message.status !== 200) {
|
|
334305
|
+
const errorMessage = message.error?.msg ?? message.error?.message ?? `Binance user-data request failed with status ${message.status}`;
|
|
334306
|
+
const errorCode2 = message.error?.code;
|
|
334307
|
+
this.fail(new Error(typeof errorCode2 === "number" ? `${errorMessage} (code ${errorCode2})` : errorMessage));
|
|
334308
|
+
return;
|
|
334309
|
+
}
|
|
334310
|
+
if (!("event" in message) || !message.event) {
|
|
334311
|
+
return;
|
|
334312
|
+
}
|
|
334313
|
+
const subscriptionId = message.subscriptionId ?? this.subscriptionId;
|
|
334314
|
+
if (subscriptionId === null || subscriptionId === undefined) {
|
|
334315
|
+
return;
|
|
334316
|
+
}
|
|
334317
|
+
this.push({ subscriptionId, event: message.event });
|
|
334318
|
+
}
|
|
334319
|
+
push(event) {
|
|
334320
|
+
if (this.closed) {
|
|
334321
|
+
return;
|
|
334322
|
+
}
|
|
334323
|
+
const waiter = this.waiters.shift();
|
|
334324
|
+
if (waiter) {
|
|
334325
|
+
waiter.resolve(event);
|
|
334326
|
+
return;
|
|
334327
|
+
}
|
|
334328
|
+
if (this.queue.length >= this.maxBufferedEvents) {
|
|
334329
|
+
this.fail(new Error(`Binance user-data stream buffered event limit exceeded (${this.maxBufferedEvents}); downstream consumer is not keeping up`));
|
|
334330
|
+
return;
|
|
334331
|
+
}
|
|
334332
|
+
this.queue.push(event);
|
|
334333
|
+
}
|
|
334334
|
+
nextEvent() {
|
|
334335
|
+
const event = this.queue.shift();
|
|
334336
|
+
if (event) {
|
|
334337
|
+
return Promise.resolve(event);
|
|
334338
|
+
}
|
|
334339
|
+
if (this.closeError) {
|
|
334340
|
+
return Promise.reject(this.closeError);
|
|
334341
|
+
}
|
|
334342
|
+
if (this.closed) {
|
|
334343
|
+
return Promise.resolve(null);
|
|
334344
|
+
}
|
|
334345
|
+
return new Promise((resolve, reject) => {
|
|
334346
|
+
this.waiters.push({ resolve, reject });
|
|
334347
|
+
});
|
|
334348
|
+
}
|
|
334349
|
+
fail(error48) {
|
|
334350
|
+
if (this.closeError) {
|
|
334351
|
+
return;
|
|
334352
|
+
}
|
|
334353
|
+
this.closeError = error48;
|
|
334354
|
+
this.closed = true;
|
|
334355
|
+
this.queue.length = 0;
|
|
334356
|
+
this.flushWaiters();
|
|
334357
|
+
try {
|
|
334358
|
+
this.ws.close();
|
|
334359
|
+
} catch {}
|
|
334360
|
+
}
|
|
334361
|
+
flushWaiters() {
|
|
334362
|
+
const error48 = this.closeError;
|
|
334363
|
+
for (const waiter of this.waiters.splice(0)) {
|
|
334364
|
+
if (error48) {
|
|
334365
|
+
waiter.reject(error48);
|
|
334366
|
+
} else {
|
|
334367
|
+
waiter.resolve(null);
|
|
333887
334368
|
}
|
|
333888
|
-
} catch (error48) {
|
|
333889
|
-
rethrowArchiveDurabilityError(error48);
|
|
333890
|
-
log.warn(`Failed to archive ${stream4} market data`, { error: error48 });
|
|
333891
334369
|
}
|
|
333892
|
-
}
|
|
333893
|
-
}
|
|
333894
|
-
function archiveTradesInBackground(archiver, otelMetrics, input) {
|
|
333895
|
-
archiveMarketRowsInBackground(archiver, otelMetrics, "trades", input, () => extractTrades(input.payload, input.receivedTimestamp).map((trade) => buildCexTradeRow(input, trade)));
|
|
334370
|
+
}
|
|
333896
334371
|
}
|
|
333897
|
-
function
|
|
333898
|
-
|
|
333899
|
-
const ticker = parseTicker(input.payload, input.receivedTimestamp);
|
|
333900
|
-
return ticker ? [buildCexTickerEventRow(input, ticker)] : [];
|
|
333901
|
-
});
|
|
334372
|
+
function isBinanceBalanceUserDataEvent(event) {
|
|
334373
|
+
return event.e === "outboundAccountPosition" || event.e === "balanceUpdate" || event.e === "externalLockUpdate";
|
|
333902
334374
|
}
|
|
333903
|
-
function
|
|
333904
|
-
|
|
333905
|
-
buildCexStreamEventRow(input)
|
|
333906
|
-
]);
|
|
334375
|
+
function isBinanceOrderUserDataEvent(event) {
|
|
334376
|
+
return event.e === "executionReport" || event.e === "listStatus";
|
|
333907
334377
|
}
|
|
333908
334378
|
// src/helpers/market-data-archive/ohlcv-bootstrap.ts
|
|
333909
334379
|
var DEFAULT_OHLCV_BOOTSTRAP_LIMIT = 100;
|
|
@@ -333942,6 +334412,7 @@ async function bootstrapOhlcvHistory(broker, archiver, otelMetrics, tracker, inp
|
|
|
333942
334412
|
const receivedTimestamp = Date.now();
|
|
333943
334413
|
archiveOhlcvInBackground(archiver, otelMetrics, tracker, {
|
|
333944
334414
|
...input,
|
|
334415
|
+
sourceMode: "broker_bootstrap_fetch_v1",
|
|
333945
334416
|
payload,
|
|
333946
334417
|
receivedTimestamp
|
|
333947
334418
|
});
|
|
@@ -335001,7 +335472,7 @@ class CEXBroker {
|
|
|
335001
335472
|
if (this.otelMetrics?.isOtelEnabled()) {
|
|
335002
335473
|
await this.otelMetrics.initialize();
|
|
335003
335474
|
}
|
|
335004
|
-
this.server = getServer(this.policy, this.brokers, this.whitelistIps, this.useVerity, this.#verityProverUrl, this.otelMetrics, this.brokerArchiver, this.orderActivityTracker, this.withdrawalObservationTracker);
|
|
335475
|
+
this.server = getServer(this.policy, this.brokers, this.whitelistIps, this.useVerity, this.#verityProverUrl, this.otelMetrics, this.brokerArchiver, this.orderActivityTracker, this.withdrawalObservationTracker, undefined);
|
|
335005
335476
|
this.server.bindAsync(`0.0.0.0:${this.port}`, grpc15.ServerCredentials.createInsecure(), (err2, port) => {
|
|
335006
335477
|
if (err2) {
|
|
335007
335478
|
log.error(err2);
|