@usherlabs/cex-broker 0.2.40 → 0.2.42
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/dist/commands/cli.js +928 -332
- package/dist/helpers/balance-update-archive-consumer.d.ts +16 -0
- package/dist/helpers/broker-execution-archive/index.d.ts +2 -2
- package/dist/helpers/broker-execution-archive/rows.d.ts +25 -0
- package/dist/helpers/broker-execution-archive/types.d.ts +5 -3
- package/dist/helpers/broker-execution-archive/writer.d.ts +31 -1
- package/dist/helpers/user-asset-archive-poller.d.ts +19 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +928 -332
- package/dist/index.js.map +11 -9
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,25 +4,43 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
function __accessProp(key) {
|
|
8
|
+
return this[key];
|
|
9
|
+
}
|
|
10
|
+
var __toESMCache_node;
|
|
11
|
+
var __toESMCache_esm;
|
|
7
12
|
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
+
var canCache = mod != null && typeof mod === "object";
|
|
14
|
+
if (canCache) {
|
|
15
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
+
var cached = cache.get(mod);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
}
|
|
8
20
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
21
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
22
|
for (let key of __getOwnPropNames(mod))
|
|
11
23
|
if (!__hasOwnProp.call(to, key))
|
|
12
24
|
__defProp(to, key, {
|
|
13
|
-
get: (
|
|
25
|
+
get: __accessProp.bind(mod, key),
|
|
14
26
|
enumerable: true
|
|
15
27
|
});
|
|
28
|
+
if (canCache)
|
|
29
|
+
cache.set(mod, to);
|
|
16
30
|
return to;
|
|
17
31
|
};
|
|
18
32
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
33
|
+
var __returnValue = (v) => v;
|
|
34
|
+
function __exportSetter(name, newValue) {
|
|
35
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
36
|
+
}
|
|
19
37
|
var __export = (target, all) => {
|
|
20
38
|
for (var name in all)
|
|
21
39
|
__defProp(target, name, {
|
|
22
40
|
get: all[name],
|
|
23
41
|
enumerable: true,
|
|
24
42
|
configurable: true,
|
|
25
|
-
set: (
|
|
43
|
+
set: __exportSetter.bind(all, name)
|
|
26
44
|
});
|
|
27
45
|
};
|
|
28
46
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -26363,7 +26381,7 @@ var require_tracestate_impl = __commonJS((exports) => {
|
|
|
26363
26381
|
const value = listMember.slice(i2 + 1, part.length);
|
|
26364
26382
|
if ((0, tracestate_validators_1.validateKey)(key) && (0, tracestate_validators_1.validateValue)(value)) {
|
|
26365
26383
|
agg.set(key, value);
|
|
26366
|
-
}
|
|
26384
|
+
}
|
|
26367
26385
|
}
|
|
26368
26386
|
return agg;
|
|
26369
26387
|
}, new Map);
|
|
@@ -35496,7 +35514,7 @@ var require_TraceState = __commonJS((exports) => {
|
|
|
35496
35514
|
const value = listMember.slice(i2 + 1, part.length);
|
|
35497
35515
|
if ((0, validators_1.validateKey)(key) && (0, validators_1.validateValue)(value)) {
|
|
35498
35516
|
agg.set(key, value);
|
|
35499
|
-
}
|
|
35517
|
+
}
|
|
35500
35518
|
}
|
|
35501
35519
|
return agg;
|
|
35502
35520
|
}, new Map);
|
|
@@ -290507,6 +290525,20 @@ function validateDeposit(policy, exchange, network, ticker) {
|
|
|
290507
290525
|
return { valid: true };
|
|
290508
290526
|
}
|
|
290509
290527
|
|
|
290528
|
+
// src/helpers/broker-execution-archive/rows.ts
|
|
290529
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
290530
|
+
|
|
290531
|
+
// src/helpers/shared/guards.ts
|
|
290532
|
+
function isRecord(value) {
|
|
290533
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
290534
|
+
}
|
|
290535
|
+
function asRecord(value) {
|
|
290536
|
+
return isRecord(value) ? value : undefined;
|
|
290537
|
+
}
|
|
290538
|
+
|
|
290539
|
+
// src/helpers/broker-execution-archive/redact.ts
|
|
290540
|
+
import { createHash } from "node:crypto";
|
|
290541
|
+
|
|
290510
290542
|
// src/helpers/shared/errors.ts
|
|
290511
290543
|
var MAX_ERROR_DETAIL_LENGTH = 512;
|
|
290512
290544
|
var REDACTED_ERROR_MESSAGE = "redacted_error";
|
|
@@ -290566,226 +290598,7 @@ function safeLogRedactedError(context2, error) {
|
|
|
290566
290598
|
}
|
|
290567
290599
|
}
|
|
290568
290600
|
|
|
290569
|
-
// src/helpers/shared/guards.ts
|
|
290570
|
-
function isRecord(value) {
|
|
290571
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
290572
|
-
}
|
|
290573
|
-
function asRecord(value) {
|
|
290574
|
-
return isRecord(value) ? value : undefined;
|
|
290575
|
-
}
|
|
290576
|
-
|
|
290577
|
-
// src/helpers/order-telemetry.ts
|
|
290578
|
-
var NUMERIC_METRICS = [
|
|
290579
|
-
["requestedQuantity", "cex_market_action_requested_quantity"],
|
|
290580
|
-
["requestedNotional", "cex_market_action_requested_notional"],
|
|
290581
|
-
["executedBaseQuantity", "cex_market_action_executed_base_quantity"],
|
|
290582
|
-
["executedQuoteQuantity", "cex_market_action_executed_quote_quantity"],
|
|
290583
|
-
["averageExecutionPrice", "cex_market_action_average_execution_price"],
|
|
290584
|
-
["filledAmount", "cex_market_action_filled_amount"],
|
|
290585
|
-
["remainingAmount", "cex_market_action_remaining_amount"],
|
|
290586
|
-
["feeAmount", "cex_market_action_fee_amount"],
|
|
290587
|
-
["feeRate", "cex_market_action_fee_rate"]
|
|
290588
|
-
];
|
|
290589
|
-
async function emitOrderExecutionTelemetry(otelMetrics, context2, order, error) {
|
|
290590
|
-
try {
|
|
290591
|
-
const telemetry = buildOrderExecutionTelemetry(context2, order, error);
|
|
290592
|
-
log.info("CEX market action execution telemetry", telemetry);
|
|
290593
|
-
const labels = {
|
|
290594
|
-
action: telemetry.action,
|
|
290595
|
-
cex: telemetry.cex,
|
|
290596
|
-
account: telemetry.accountLabel,
|
|
290597
|
-
symbol: telemetry.symbol,
|
|
290598
|
-
side: telemetry.side,
|
|
290599
|
-
order_type: telemetry.orderType,
|
|
290600
|
-
status: telemetry.status
|
|
290601
|
-
};
|
|
290602
|
-
await otelMetrics?.recordCounter("cex_market_action_executions_total", 1, {
|
|
290603
|
-
...labels,
|
|
290604
|
-
result: error ? "error" : "ok"
|
|
290605
|
-
});
|
|
290606
|
-
for (const [key, metricName] of NUMERIC_METRICS) {
|
|
290607
|
-
const value = telemetry[key];
|
|
290608
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
290609
|
-
await otelMetrics?.recordHistogram(metricName, value, labels);
|
|
290610
|
-
}
|
|
290611
|
-
}
|
|
290612
|
-
return telemetry;
|
|
290613
|
-
} catch (telemetryError) {
|
|
290614
|
-
try {
|
|
290615
|
-
log.error("Failed to emit CEX order telemetry", {
|
|
290616
|
-
error: telemetryError
|
|
290617
|
-
});
|
|
290618
|
-
} catch {}
|
|
290619
|
-
return;
|
|
290620
|
-
}
|
|
290621
|
-
}
|
|
290622
|
-
function buildOrderExecutionTelemetry(context2, order, error) {
|
|
290623
|
-
const record = asRecord(order);
|
|
290624
|
-
const info = asRecord(record?.info);
|
|
290625
|
-
const fees = getFees(record, info);
|
|
290626
|
-
const fee = summarizeFees(fees);
|
|
290627
|
-
const executedBaseQuantity = firstNumber(record?.filled, info?.executedQty, info?.cumExecQty) ?? computeFilledFromAmount(record);
|
|
290628
|
-
const executedQuoteQuantity = firstNumber(record?.cost, info?.cummulativeQuoteQty, info?.cumQuote, info?.cumExecValue);
|
|
290629
|
-
const averageExecutionPrice = firstNumber(record?.average, info?.avgPrice) ?? computeAveragePrice(executedBaseQuantity, executedQuoteQuantity);
|
|
290630
|
-
const exchangeTimestamp = normalizeTimestamp(firstValue(record?.timestamp, info?.time, info?.transactTime, record?.datetime));
|
|
290631
|
-
const status = firstString(record?.status, info?.status) ?? (error ? "failed" : "unknown");
|
|
290632
|
-
const errorRecord = error instanceof Error ? error : undefined;
|
|
290633
|
-
return compactUndefined({
|
|
290634
|
-
event: "cex_market_action_execution",
|
|
290635
|
-
action: context2.action,
|
|
290636
|
-
cex: context2.cex.trim().toLowerCase() || "unknown",
|
|
290637
|
-
accountLabel: context2.accountLabel ?? "unknown",
|
|
290638
|
-
symbol: firstString(record?.symbol, info?.symbol, context2.symbol) ?? "unknown",
|
|
290639
|
-
side: firstString(record?.side, info?.side, context2.side) ?? "unknown",
|
|
290640
|
-
orderType: firstString(record?.type, info?.type, context2.orderType) ?? "unknown",
|
|
290641
|
-
orderId: firstString(record?.id, info?.orderId, info?.orderID),
|
|
290642
|
-
orderAuthor: context2.orderAuthor,
|
|
290643
|
-
clientOrderId: firstString(context2.clientOrderId, record?.clientOrderId, record?.clientOrderID, record?.clientOid, info?.clientOrderId, info?.clientOrderID, info?.clientOid),
|
|
290644
|
-
idempotencyId: context2.idempotencyId,
|
|
290645
|
-
makerActionId: context2.makerActionId,
|
|
290646
|
-
status: status.toLowerCase(),
|
|
290647
|
-
requestedQuantity: context2.requestedQuantity ?? firstNumber(record?.amount),
|
|
290648
|
-
requestedNotional: context2.requestedNotional,
|
|
290649
|
-
executedBaseQuantity,
|
|
290650
|
-
executedQuoteQuantity,
|
|
290651
|
-
averageExecutionPrice,
|
|
290652
|
-
filledAmount: firstNumber(record?.filled, info?.executedQty),
|
|
290653
|
-
remainingAmount: firstNumber(record?.remaining, info?.remainingQty),
|
|
290654
|
-
feeAmount: fee.amount,
|
|
290655
|
-
feeCurrency: fee.currency,
|
|
290656
|
-
feeRate: fee.rate,
|
|
290657
|
-
exchangeTimestamp,
|
|
290658
|
-
brokerObservedTimestamp: context2.brokerObservedTimestamp ?? new Date().toISOString(),
|
|
290659
|
-
errorType: errorRecord?.name,
|
|
290660
|
-
errorMessage: errorRecord ? REDACTED_ERROR_MESSAGE : undefined
|
|
290661
|
-
});
|
|
290662
|
-
}
|
|
290663
|
-
function emitOrderExecutionTelemetryInBackground(otelMetrics, context2, order, error) {
|
|
290664
|
-
emitOrderExecutionTelemetry(otelMetrics, context2, order, error).catch((telemetryError) => {
|
|
290665
|
-
try {
|
|
290666
|
-
log.warn("Telemetry emit failed", { error: telemetryError });
|
|
290667
|
-
} catch {
|
|
290668
|
-
console.warn("Telemetry emit failed", telemetryError);
|
|
290669
|
-
}
|
|
290670
|
-
});
|
|
290671
|
-
}
|
|
290672
|
-
function extractOrderTelemetryIds(params) {
|
|
290673
|
-
const record = params ?? {};
|
|
290674
|
-
return {
|
|
290675
|
-
clientOrderId: firstString(record.clientOrderId, record.clientOrderID, record.newClientOrderId, record.clientOid),
|
|
290676
|
-
idempotencyId: firstString(record.idempotencyId, record.idempotencyID, record.idempotencyKey, record.requestId, record.requestID),
|
|
290677
|
-
makerActionId: firstString(record.makerActionId, record.maker_action_id, record.actionId, record.action_id)
|
|
290678
|
-
};
|
|
290679
|
-
}
|
|
290680
|
-
function firstValue(...values2) {
|
|
290681
|
-
return values2.find((value) => value !== undefined && value !== null);
|
|
290682
|
-
}
|
|
290683
|
-
function firstString(...values2) {
|
|
290684
|
-
for (const value of values2) {
|
|
290685
|
-
if (typeof value === "string" && value.trim()) {
|
|
290686
|
-
return value.trim();
|
|
290687
|
-
}
|
|
290688
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
290689
|
-
return String(value);
|
|
290690
|
-
}
|
|
290691
|
-
}
|
|
290692
|
-
return;
|
|
290693
|
-
}
|
|
290694
|
-
function firstNumber(...values2) {
|
|
290695
|
-
for (const value of values2) {
|
|
290696
|
-
const numberValue = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
|
|
290697
|
-
if (Number.isFinite(numberValue)) {
|
|
290698
|
-
return numberValue;
|
|
290699
|
-
}
|
|
290700
|
-
}
|
|
290701
|
-
return;
|
|
290702
|
-
}
|
|
290703
|
-
function computeFilledFromAmount(record) {
|
|
290704
|
-
const amount = firstNumber(record?.amount);
|
|
290705
|
-
const remaining = firstNumber(record?.remaining);
|
|
290706
|
-
if (amount === undefined || remaining === undefined) {
|
|
290707
|
-
return;
|
|
290708
|
-
}
|
|
290709
|
-
return amount - remaining;
|
|
290710
|
-
}
|
|
290711
|
-
function computeAveragePrice(executedBaseQuantity, executedQuoteQuantity) {
|
|
290712
|
-
if (executedBaseQuantity === undefined || executedQuoteQuantity === undefined || executedBaseQuantity === 0) {
|
|
290713
|
-
return;
|
|
290714
|
-
}
|
|
290715
|
-
return executedQuoteQuantity / executedBaseQuantity;
|
|
290716
|
-
}
|
|
290717
|
-
function normalizeTimestamp(value) {
|
|
290718
|
-
if (typeof value === "string" && value.trim()) {
|
|
290719
|
-
return value.trim();
|
|
290720
|
-
}
|
|
290721
|
-
const timestamp = firstNumber(value);
|
|
290722
|
-
if (timestamp === undefined) {
|
|
290723
|
-
return;
|
|
290724
|
-
}
|
|
290725
|
-
const date = new Date(timestamp);
|
|
290726
|
-
return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
|
|
290727
|
-
}
|
|
290728
|
-
function getFees(record, info) {
|
|
290729
|
-
const fees = [];
|
|
290730
|
-
if (record?.fee)
|
|
290731
|
-
fees.push(record.fee);
|
|
290732
|
-
if (Array.isArray(record?.fees))
|
|
290733
|
-
fees.push(...record.fees);
|
|
290734
|
-
if (Array.isArray(record?.trades)) {
|
|
290735
|
-
for (const trade of record.trades) {
|
|
290736
|
-
const tradeRecord = asRecord(trade);
|
|
290737
|
-
if (tradeRecord?.fee)
|
|
290738
|
-
fees.push(tradeRecord.fee);
|
|
290739
|
-
if (Array.isArray(tradeRecord?.fees))
|
|
290740
|
-
fees.push(...tradeRecord.fees);
|
|
290741
|
-
}
|
|
290742
|
-
}
|
|
290743
|
-
if (Array.isArray(info?.fills)) {
|
|
290744
|
-
for (const fill of info.fills) {
|
|
290745
|
-
const fillRecord = asRecord(fill);
|
|
290746
|
-
const commission = firstNumber(fillRecord?.commission);
|
|
290747
|
-
if (commission !== undefined) {
|
|
290748
|
-
fees.push({
|
|
290749
|
-
cost: commission,
|
|
290750
|
-
currency: firstString(fillRecord?.commissionAsset)
|
|
290751
|
-
});
|
|
290752
|
-
}
|
|
290753
|
-
}
|
|
290754
|
-
}
|
|
290755
|
-
return fees;
|
|
290756
|
-
}
|
|
290757
|
-
function summarizeFees(fees) {
|
|
290758
|
-
let amount = 0;
|
|
290759
|
-
let amountFound = false;
|
|
290760
|
-
let currency;
|
|
290761
|
-
let rate;
|
|
290762
|
-
for (const rawFee of fees) {
|
|
290763
|
-
const fee = asRecord(rawFee);
|
|
290764
|
-
if (!fee)
|
|
290765
|
-
continue;
|
|
290766
|
-
const cost = firstNumber(fee.cost, fee.amount);
|
|
290767
|
-
if (cost !== undefined) {
|
|
290768
|
-
amount += cost;
|
|
290769
|
-
amountFound = true;
|
|
290770
|
-
}
|
|
290771
|
-
currency ??= firstString(fee.currency);
|
|
290772
|
-
rate ??= firstNumber(fee.rate);
|
|
290773
|
-
}
|
|
290774
|
-
return {
|
|
290775
|
-
amount: amountFound ? amount : undefined,
|
|
290776
|
-
currency,
|
|
290777
|
-
rate
|
|
290778
|
-
};
|
|
290779
|
-
}
|
|
290780
|
-
function compactUndefined(record) {
|
|
290781
|
-
return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
|
|
290782
|
-
}
|
|
290783
|
-
|
|
290784
|
-
// src/helpers/broker-execution-archive/rows.ts
|
|
290785
|
-
import { createHash as createHash2 } from "node:crypto";
|
|
290786
|
-
|
|
290787
290601
|
// src/helpers/broker-execution-archive/redact.ts
|
|
290788
|
-
import { createHash } from "node:crypto";
|
|
290789
290602
|
var SECRET_KEY_PATTERN = /\b(api[_-]?key|api[_-]?secret|secret|signature|passphrase|password|token|credential)\b/i;
|
|
290790
290603
|
var SECRET_VALUE_PATTERN = /(\b(?:apiKey|api_key|apiSecret|api_secret|secret|signature|passphrase|password|token)\b\s*[=:]\s*)[^\s&,;)}\]]+/gi;
|
|
290791
290604
|
var SECRET_JSON_PATTERN = /("(?:apiKey|api_key|apiSecret|api_secret|secret|signature|passphrase|password|token)"\s*:\s*")[^"]*(")/gi;
|
|
@@ -290858,9 +290671,11 @@ var ARCHIVE_SCHEMA_VERSION = "1";
|
|
|
290858
290671
|
var FILL_EVENT_KIND = "trade_history_fill";
|
|
290859
290672
|
var ACCOUNT_BALANCE_SCOPE = "spot";
|
|
290860
290673
|
var ACCOUNT_BALANCE_PRECISION_BASIS = "ccxt_normalized_number";
|
|
290674
|
+
var USER_ASSET_BALANCE_SCOPE = "user_asset";
|
|
290675
|
+
var USER_ASSET_PRECISION_BASIS = "venue_raw_string";
|
|
290861
290676
|
|
|
290862
290677
|
// src/helpers/broker-execution-archive/rows.ts
|
|
290863
|
-
function
|
|
290678
|
+
function firstString(...values2) {
|
|
290864
290679
|
for (const value of values2) {
|
|
290865
290680
|
if (typeof value === "string" && value.trim()) {
|
|
290866
290681
|
return value.trim();
|
|
@@ -290871,7 +290686,7 @@ function firstString2(...values2) {
|
|
|
290871
290686
|
}
|
|
290872
290687
|
return;
|
|
290873
290688
|
}
|
|
290874
|
-
function
|
|
290689
|
+
function firstNumber(...values2) {
|
|
290875
290690
|
for (const value of values2) {
|
|
290876
290691
|
const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
|
|
290877
290692
|
if (Number.isFinite(numeric)) {
|
|
@@ -290880,11 +290695,11 @@ function firstNumber2(...values2) {
|
|
|
290880
290695
|
}
|
|
290881
290696
|
return;
|
|
290882
290697
|
}
|
|
290883
|
-
function
|
|
290698
|
+
function normalizeTimestamp(value) {
|
|
290884
290699
|
if (typeof value === "string" && value.trim()) {
|
|
290885
290700
|
return value.trim();
|
|
290886
290701
|
}
|
|
290887
|
-
const ms =
|
|
290702
|
+
const ms = firstNumber(value);
|
|
290888
290703
|
if (ms === undefined) {
|
|
290889
290704
|
return;
|
|
290890
290705
|
}
|
|
@@ -290991,7 +290806,7 @@ function normalizeCcxtBalanceForArchive(balance) {
|
|
|
290991
290806
|
}
|
|
290992
290807
|
}
|
|
290993
290808
|
return {
|
|
290994
|
-
exchangeTimestamp:
|
|
290809
|
+
exchangeTimestamp: normalizeTimestamp(record.timestamp ?? record.datetime),
|
|
290995
290810
|
reportedAssets: [...reportedAssets].sort(),
|
|
290996
290811
|
assetEntryAssets: [...assetEntryAssets].sort(),
|
|
290997
290812
|
freeBalances: sortedBalanceMap(maps.free),
|
|
@@ -291014,7 +290829,7 @@ function buildAccountBalanceSnapshotRow(input) {
|
|
|
291014
290829
|
})).digest("hex");
|
|
291015
290830
|
return {
|
|
291016
290831
|
table: "broker_account.balance_snapshots",
|
|
291017
|
-
row:
|
|
290832
|
+
row: compactUndefined({
|
|
291018
290833
|
broker_observed_timestamp: tags.broker_observed_timestamp,
|
|
291019
290834
|
exchange_timestamp: balance.exchangeTimestamp,
|
|
291020
290835
|
source: tags.source,
|
|
@@ -291036,7 +290851,101 @@ function buildAccountBalanceSnapshotRow(input) {
|
|
|
291036
290851
|
})
|
|
291037
290852
|
};
|
|
291038
290853
|
}
|
|
291039
|
-
|
|
290854
|
+
var USER_ASSET_BUCKETS = {
|
|
290855
|
+
freeBalances: "free",
|
|
290856
|
+
lockedBalances: "locked",
|
|
290857
|
+
freezeBalances: "freeze",
|
|
290858
|
+
withdrawingBalances: "withdrawing"
|
|
290859
|
+
};
|
|
290860
|
+
function userAssetQuantity(value) {
|
|
290861
|
+
if (typeof value === "number") {
|
|
290862
|
+
return decimalString(value);
|
|
290863
|
+
}
|
|
290864
|
+
if (typeof value !== "string") {
|
|
290865
|
+
return;
|
|
290866
|
+
}
|
|
290867
|
+
const trimmed = value.trim();
|
|
290868
|
+
if (!trimmed || !Number.isFinite(Number(trimmed))) {
|
|
290869
|
+
return;
|
|
290870
|
+
}
|
|
290871
|
+
return trimmed;
|
|
290872
|
+
}
|
|
290873
|
+
function normalizeBinanceUserAssetsForArchive(response) {
|
|
290874
|
+
if (!Array.isArray(response)) {
|
|
290875
|
+
throw new Error("binance_user_asset_malformed_response: getUserAsset did not return an array");
|
|
290876
|
+
}
|
|
290877
|
+
const reportedAssets = [];
|
|
290878
|
+
const incompleteAssets = [];
|
|
290879
|
+
const maps = {
|
|
290880
|
+
freeBalances: {},
|
|
290881
|
+
lockedBalances: {},
|
|
290882
|
+
freezeBalances: {},
|
|
290883
|
+
withdrawingBalances: {}
|
|
290884
|
+
};
|
|
290885
|
+
for (const entry of response) {
|
|
290886
|
+
const record = asRecord(entry);
|
|
290887
|
+
const asset = typeof record?.asset === "string" ? record.asset.trim() : undefined;
|
|
290888
|
+
if (!record || !asset) {
|
|
290889
|
+
throw new Error("binance_user_asset_malformed_response: getUserAsset entry has no asset key");
|
|
290890
|
+
}
|
|
290891
|
+
if (reportedAssets.includes(asset)) {
|
|
290892
|
+
throw new Error(`binance_user_asset_malformed_response: getUserAsset returned duplicate entries for ${asset}`);
|
|
290893
|
+
}
|
|
290894
|
+
reportedAssets.push(asset);
|
|
290895
|
+
let complete = true;
|
|
290896
|
+
for (const [field, venueKey] of Object.entries(USER_ASSET_BUCKETS)) {
|
|
290897
|
+
const quantity = userAssetQuantity(record[venueKey]);
|
|
290898
|
+
if (quantity === undefined) {
|
|
290899
|
+
complete = false;
|
|
290900
|
+
continue;
|
|
290901
|
+
}
|
|
290902
|
+
maps[field][asset] = quantity;
|
|
290903
|
+
}
|
|
290904
|
+
if (!complete) {
|
|
290905
|
+
incompleteAssets.push(asset);
|
|
290906
|
+
}
|
|
290907
|
+
}
|
|
290908
|
+
return {
|
|
290909
|
+
reportedAssets: reportedAssets.sort(),
|
|
290910
|
+
incompleteAssets: incompleteAssets.sort(),
|
|
290911
|
+
freeBalances: sortedBalanceMap(maps.freeBalances),
|
|
290912
|
+
lockedBalances: sortedBalanceMap(maps.lockedBalances),
|
|
290913
|
+
freezeBalances: sortedBalanceMap(maps.freezeBalances),
|
|
290914
|
+
withdrawingBalances: sortedBalanceMap(maps.withdrawingBalances)
|
|
290915
|
+
};
|
|
290916
|
+
}
|
|
290917
|
+
function buildUserAssetSnapshotRow(input) {
|
|
290918
|
+
const { tags, userAssets } = input;
|
|
290919
|
+
const observationId = createHash2("sha256").update(JSON.stringify({
|
|
290920
|
+
deployment_id: tags.deployment_id,
|
|
290921
|
+
exchange: tags.exchange,
|
|
290922
|
+
account_selector: tags.account_selector,
|
|
290923
|
+
balance_scope: USER_ASSET_BALANCE_SCOPE,
|
|
290924
|
+
broker_observed_timestamp: tags.broker_observed_timestamp,
|
|
290925
|
+
userAssets
|
|
290926
|
+
})).digest("hex");
|
|
290927
|
+
return {
|
|
290928
|
+
table: "broker_account.user_asset_snapshots",
|
|
290929
|
+
row: {
|
|
290930
|
+
broker_observed_timestamp: tags.broker_observed_timestamp,
|
|
290931
|
+
source: tags.source,
|
|
290932
|
+
deployment_id: tags.deployment_id,
|
|
290933
|
+
schema_version: ARCHIVE_SCHEMA_VERSION,
|
|
290934
|
+
exchange: tags.exchange,
|
|
290935
|
+
account_selector: tags.account_selector,
|
|
290936
|
+
balance_scope: USER_ASSET_BALANCE_SCOPE,
|
|
290937
|
+
observation_id: observationId,
|
|
290938
|
+
reported_assets: userAssets.reportedAssets,
|
|
290939
|
+
incomplete_assets: userAssets.incompleteAssets,
|
|
290940
|
+
free_balances: userAssets.freeBalances,
|
|
290941
|
+
locked_balances: userAssets.lockedBalances,
|
|
290942
|
+
freeze_balances: userAssets.freezeBalances,
|
|
290943
|
+
withdrawing_balances: userAssets.withdrawingBalances,
|
|
290944
|
+
precision_basis: USER_ASSET_PRECISION_BASIS
|
|
290945
|
+
}
|
|
290946
|
+
};
|
|
290947
|
+
}
|
|
290948
|
+
function compactUndefined(record) {
|
|
291040
290949
|
return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
|
|
291041
290950
|
}
|
|
291042
290951
|
function buildCommonArchiveTags(input) {
|
|
@@ -291053,7 +290962,7 @@ function buildOrderEventArchiveRow(input) {
|
|
|
291053
290962
|
const { tags, telemetry, action } = input;
|
|
291054
290963
|
return {
|
|
291055
290964
|
table: "broker_execution.order_events",
|
|
291056
|
-
row:
|
|
290965
|
+
row: compactUndefined({
|
|
291057
290966
|
...tags,
|
|
291058
290967
|
event_kind: input.eventKind ?? "execute_action",
|
|
291059
290968
|
action,
|
|
@@ -291090,35 +290999,35 @@ function buildSubscribeStreamArchiveRow(input) {
|
|
|
291090
290999
|
const info = asRecord(record?.info);
|
|
291091
291000
|
return {
|
|
291092
291001
|
table: "broker_execution.order_events",
|
|
291093
|
-
row:
|
|
291002
|
+
row: compactUndefined({
|
|
291094
291003
|
...input.tags,
|
|
291095
291004
|
event_kind: "subscribe_stream",
|
|
291096
291005
|
subscription_type: input.subscriptionType,
|
|
291097
|
-
order_id:
|
|
291098
|
-
client_order_id:
|
|
291099
|
-
status:
|
|
291006
|
+
order_id: firstString(record?.id, record?.orderId, record?.i, info?.orderId, info?.i),
|
|
291007
|
+
client_order_id: firstString(record?.clientOrderId, record?.clientOrderID, record?.c, info?.clientOrderId, info?.c),
|
|
291008
|
+
status: firstString(record?.status, record?.X, info?.status, info?.X)?.toLowerCase(),
|
|
291100
291009
|
payload_json: JSON.stringify(redactedPayload)
|
|
291101
291010
|
})
|
|
291102
291011
|
};
|
|
291103
291012
|
}
|
|
291104
291013
|
function quantityString(...values2) {
|
|
291105
|
-
return
|
|
291014
|
+
return firstString(...values2);
|
|
291106
291015
|
}
|
|
291107
291016
|
function extractBinanceInternalTransferId(response) {
|
|
291108
291017
|
const record = asRecord(response);
|
|
291109
|
-
return
|
|
291018
|
+
return firstString(record?.txnId, record?.tranId);
|
|
291110
291019
|
}
|
|
291111
291020
|
function buildTransferEventArchiveRow(input) {
|
|
291112
291021
|
const { tags, transfer } = input;
|
|
291113
291022
|
return {
|
|
291114
291023
|
table: "broker_execution.transfer_events",
|
|
291115
|
-
row:
|
|
291024
|
+
row: compactUndefined({
|
|
291116
291025
|
...tags,
|
|
291117
291026
|
schema_version: ARCHIVE_SCHEMA_VERSION,
|
|
291118
291027
|
event_kind: transfer.eventKind,
|
|
291119
291028
|
lifecycle_action: transfer.lifecycleAction,
|
|
291120
291029
|
status: transfer.status ?? "",
|
|
291121
|
-
asset_symbol: tags.symbol,
|
|
291030
|
+
asset_symbol: transfer.assetSymbol ?? tags.symbol,
|
|
291122
291031
|
amount: transfer.amount,
|
|
291123
291032
|
address: transfer.address,
|
|
291124
291033
|
network: transfer.network,
|
|
@@ -291138,18 +291047,18 @@ function normalizeCcxtTransactionForArchive(transaction) {
|
|
|
291138
291047
|
const record = asRecord(transaction);
|
|
291139
291048
|
const info = asRecord(record?.info);
|
|
291140
291049
|
const fee = asRecord(record?.fee);
|
|
291141
|
-
return
|
|
291142
|
-
externalId:
|
|
291143
|
-
clientWithdrawalId:
|
|
291144
|
-
txid:
|
|
291145
|
-
address:
|
|
291146
|
-
network:
|
|
291050
|
+
return compactUndefined({
|
|
291051
|
+
externalId: firstString(record?.id, info?.id, record?.txid, info?.txId),
|
|
291052
|
+
clientWithdrawalId: firstString(info?.withdrawOrderId),
|
|
291053
|
+
txid: firstString(record?.txid, info?.txId, info?.txid, info?.tx_hash),
|
|
291054
|
+
address: firstString(record?.address, record?.addressTo, info?.address),
|
|
291055
|
+
network: firstString(record?.network, info?.network),
|
|
291147
291056
|
amount: quantityString(info?.amount, record?.amount),
|
|
291148
|
-
assetSymbol:
|
|
291149
|
-
status:
|
|
291057
|
+
assetSymbol: firstString(record?.currency, info?.coin, info?.asset),
|
|
291058
|
+
status: firstString(record?.status, info?.status)?.toLowerCase(),
|
|
291150
291059
|
feeAmount: quantityString(fee?.cost, record?.feeCost),
|
|
291151
|
-
feeCurrency:
|
|
291152
|
-
exchangeTimestamp:
|
|
291060
|
+
feeCurrency: firstString(fee?.currency, record?.feeCurrency),
|
|
291061
|
+
exchangeTimestamp: normalizeTimestamp(firstValueForTransfer(record?.timestamp, record?.datetime, info?.applyTime))
|
|
291153
291062
|
});
|
|
291154
291063
|
}
|
|
291155
291064
|
function firstValueForTransfer(...values2) {
|
|
@@ -291159,7 +291068,7 @@ function buildFillEventArchiveRow(input) {
|
|
|
291159
291068
|
const { tags, fill } = input;
|
|
291160
291069
|
return {
|
|
291161
291070
|
table: "broker_execution.fill_events",
|
|
291162
|
-
row:
|
|
291071
|
+
row: compactUndefined({
|
|
291163
291072
|
...tags,
|
|
291164
291073
|
schema_version: ARCHIVE_SCHEMA_VERSION,
|
|
291165
291074
|
event_kind: FILL_EVENT_KIND,
|
|
@@ -291185,18 +291094,18 @@ function normalizeCcxtTradeForArchive(trade) {
|
|
|
291185
291094
|
const info = asRecord(record?.info);
|
|
291186
291095
|
const fee = asRecord(record?.fee);
|
|
291187
291096
|
return {
|
|
291188
|
-
orderId:
|
|
291189
|
-
clientOrderId:
|
|
291190
|
-
fillId:
|
|
291191
|
-
side:
|
|
291192
|
-
orderType:
|
|
291097
|
+
orderId: firstString(record?.order, info?.orderId, info?.orderID),
|
|
291098
|
+
clientOrderId: firstString(record?.clientOrderId, info?.clientOrderId, info?.origClientOrderId),
|
|
291099
|
+
fillId: firstString(record?.id, info?.id, info?.tradeId),
|
|
291100
|
+
side: firstString(record?.side, info?.side)?.toLowerCase(),
|
|
291101
|
+
orderType: firstString(record?.type, info?.type)?.toLowerCase(),
|
|
291193
291102
|
price: quantityString(info?.price, record?.price),
|
|
291194
291103
|
baseQuantity: quantityString(info?.qty, record?.amount),
|
|
291195
291104
|
quoteQuantity: quantityString(info?.quoteQty, record?.cost),
|
|
291196
291105
|
feeAmount: quantityString(fee?.cost, info?.commission),
|
|
291197
|
-
feeCurrency:
|
|
291106
|
+
feeCurrency: firstString(fee?.currency, info?.commissionAsset),
|
|
291198
291107
|
feeRate: quantityString(fee?.rate),
|
|
291199
|
-
exchangeTimestamp:
|
|
291108
|
+
exchangeTimestamp: normalizeTimestamp(firstValueForTransfer(record?.timestamp, record?.datetime, info?.time)),
|
|
291200
291109
|
payload: trade
|
|
291201
291110
|
};
|
|
291202
291111
|
}
|
|
@@ -291205,7 +291114,7 @@ function buildMarketMetadataSnapshotRow(input) {
|
|
|
291205
291114
|
const metadataHash = hashMarketMetadata(redactedSnapshot);
|
|
291206
291115
|
return {
|
|
291207
291116
|
table: "broker_execution.market_metadata_snapshots",
|
|
291208
|
-
row:
|
|
291117
|
+
row: compactUndefined({
|
|
291209
291118
|
...input.tags,
|
|
291210
291119
|
client_order_id: input.clientOrderId,
|
|
291211
291120
|
order_id: input.orderId,
|
|
@@ -291217,10 +291126,209 @@ function buildMarketMetadataSnapshotRow(input) {
|
|
|
291217
291126
|
};
|
|
291218
291127
|
}
|
|
291219
291128
|
|
|
291129
|
+
// src/helpers/order-telemetry.ts
|
|
291130
|
+
var NUMERIC_METRICS = [
|
|
291131
|
+
["requestedQuantity", "cex_market_action_requested_quantity"],
|
|
291132
|
+
["requestedNotional", "cex_market_action_requested_notional"],
|
|
291133
|
+
["executedBaseQuantity", "cex_market_action_executed_base_quantity"],
|
|
291134
|
+
["executedQuoteQuantity", "cex_market_action_executed_quote_quantity"],
|
|
291135
|
+
["averageExecutionPrice", "cex_market_action_average_execution_price"],
|
|
291136
|
+
["filledAmount", "cex_market_action_filled_amount"],
|
|
291137
|
+
["remainingAmount", "cex_market_action_remaining_amount"],
|
|
291138
|
+
["feeAmount", "cex_market_action_fee_amount"],
|
|
291139
|
+
["feeRate", "cex_market_action_fee_rate"]
|
|
291140
|
+
];
|
|
291141
|
+
async function emitOrderExecutionTelemetry(otelMetrics, context2, order, error) {
|
|
291142
|
+
try {
|
|
291143
|
+
const telemetry = buildOrderExecutionTelemetry(context2, order, error);
|
|
291144
|
+
log.info("CEX market action execution telemetry", telemetry);
|
|
291145
|
+
const labels = {
|
|
291146
|
+
action: telemetry.action,
|
|
291147
|
+
cex: telemetry.cex,
|
|
291148
|
+
account: telemetry.accountLabel,
|
|
291149
|
+
symbol: telemetry.symbol,
|
|
291150
|
+
side: telemetry.side,
|
|
291151
|
+
order_type: telemetry.orderType,
|
|
291152
|
+
status: telemetry.status
|
|
291153
|
+
};
|
|
291154
|
+
await otelMetrics?.recordCounter("cex_market_action_executions_total", 1, {
|
|
291155
|
+
...labels,
|
|
291156
|
+
result: error ? "error" : "ok"
|
|
291157
|
+
});
|
|
291158
|
+
for (const [key, metricName] of NUMERIC_METRICS) {
|
|
291159
|
+
const value = telemetry[key];
|
|
291160
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
291161
|
+
await otelMetrics?.recordHistogram(metricName, value, labels);
|
|
291162
|
+
}
|
|
291163
|
+
}
|
|
291164
|
+
return telemetry;
|
|
291165
|
+
} catch (telemetryError) {
|
|
291166
|
+
try {
|
|
291167
|
+
log.error("Failed to emit CEX order telemetry", {
|
|
291168
|
+
error: telemetryError
|
|
291169
|
+
});
|
|
291170
|
+
} catch {}
|
|
291171
|
+
return;
|
|
291172
|
+
}
|
|
291173
|
+
}
|
|
291174
|
+
function buildOrderExecutionTelemetry(context2, order, error) {
|
|
291175
|
+
const record = asRecord(order);
|
|
291176
|
+
const info = asRecord(record?.info);
|
|
291177
|
+
const fees = getFees(record, info);
|
|
291178
|
+
const fee = summarizeFees(fees);
|
|
291179
|
+
const executedBaseQuantity = firstNumber2(record?.filled, info?.executedQty, info?.cumExecQty) ?? computeFilledFromAmount(record);
|
|
291180
|
+
const executedQuoteQuantity = firstNumber2(record?.cost, info?.cummulativeQuoteQty, info?.cumQuote, info?.cumExecValue);
|
|
291181
|
+
const averageExecutionPrice = firstNumber2(record?.average, info?.avgPrice) ?? computeAveragePrice(executedBaseQuantity, executedQuoteQuantity);
|
|
291182
|
+
const exchangeTimestamp = normalizeTimestamp(firstValue(record?.timestamp, info?.time, info?.transactTime, record?.datetime));
|
|
291183
|
+
const status = firstString2(record?.status, info?.status) ?? (error ? "failed" : "unknown");
|
|
291184
|
+
const errorRecord = error instanceof Error ? error : undefined;
|
|
291185
|
+
return compactUndefined2({
|
|
291186
|
+
event: "cex_market_action_execution",
|
|
291187
|
+
action: context2.action,
|
|
291188
|
+
cex: context2.cex.trim().toLowerCase() || "unknown",
|
|
291189
|
+
accountLabel: context2.accountLabel ?? "unknown",
|
|
291190
|
+
symbol: firstString2(record?.symbol, info?.symbol, context2.symbol) ?? "unknown",
|
|
291191
|
+
side: firstString2(record?.side, info?.side, context2.side) ?? "unknown",
|
|
291192
|
+
orderType: firstString2(record?.type, info?.type, context2.orderType) ?? "unknown",
|
|
291193
|
+
orderId: firstString2(record?.id, info?.orderId, info?.orderID),
|
|
291194
|
+
orderAuthor: context2.orderAuthor,
|
|
291195
|
+
clientOrderId: firstString2(context2.clientOrderId, record?.clientOrderId, record?.clientOrderID, record?.clientOid, info?.clientOrderId, info?.clientOrderID, info?.clientOid),
|
|
291196
|
+
idempotencyId: context2.idempotencyId,
|
|
291197
|
+
makerActionId: context2.makerActionId,
|
|
291198
|
+
status: status.toLowerCase(),
|
|
291199
|
+
requestedQuantity: context2.requestedQuantity ?? firstNumber2(record?.amount),
|
|
291200
|
+
requestedNotional: context2.requestedNotional,
|
|
291201
|
+
executedBaseQuantity,
|
|
291202
|
+
executedQuoteQuantity,
|
|
291203
|
+
averageExecutionPrice,
|
|
291204
|
+
filledAmount: firstNumber2(record?.filled, info?.executedQty),
|
|
291205
|
+
remainingAmount: firstNumber2(record?.remaining, info?.remainingQty),
|
|
291206
|
+
feeAmount: fee.amount,
|
|
291207
|
+
feeCurrency: fee.currency,
|
|
291208
|
+
feeRate: fee.rate,
|
|
291209
|
+
exchangeTimestamp,
|
|
291210
|
+
brokerObservedTimestamp: context2.brokerObservedTimestamp ?? new Date().toISOString(),
|
|
291211
|
+
errorType: errorRecord?.name,
|
|
291212
|
+
errorMessage: errorRecord ? REDACTED_ERROR_MESSAGE : undefined
|
|
291213
|
+
});
|
|
291214
|
+
}
|
|
291215
|
+
function emitOrderExecutionTelemetryInBackground(otelMetrics, context2, order, error) {
|
|
291216
|
+
emitOrderExecutionTelemetry(otelMetrics, context2, order, error).catch((telemetryError) => {
|
|
291217
|
+
try {
|
|
291218
|
+
log.warn("Telemetry emit failed", { error: telemetryError });
|
|
291219
|
+
} catch {
|
|
291220
|
+
console.warn("Telemetry emit failed", telemetryError);
|
|
291221
|
+
}
|
|
291222
|
+
});
|
|
291223
|
+
}
|
|
291224
|
+
function extractOrderTelemetryIds(params) {
|
|
291225
|
+
const record = params ?? {};
|
|
291226
|
+
return {
|
|
291227
|
+
clientOrderId: firstString2(record.clientOrderId, record.clientOrderID, record.newClientOrderId, record.clientOid),
|
|
291228
|
+
idempotencyId: firstString2(record.idempotencyId, record.idempotencyID, record.idempotencyKey, record.requestId, record.requestID),
|
|
291229
|
+
makerActionId: firstString2(record.makerActionId, record.maker_action_id, record.actionId, record.action_id)
|
|
291230
|
+
};
|
|
291231
|
+
}
|
|
291232
|
+
function firstValue(...values2) {
|
|
291233
|
+
return values2.find((value) => value !== undefined && value !== null);
|
|
291234
|
+
}
|
|
291235
|
+
function firstString2(...values2) {
|
|
291236
|
+
for (const value of values2) {
|
|
291237
|
+
if (typeof value === "string" && value.trim()) {
|
|
291238
|
+
return value.trim();
|
|
291239
|
+
}
|
|
291240
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
291241
|
+
return String(value);
|
|
291242
|
+
}
|
|
291243
|
+
}
|
|
291244
|
+
return;
|
|
291245
|
+
}
|
|
291246
|
+
function firstNumber2(...values2) {
|
|
291247
|
+
for (const value of values2) {
|
|
291248
|
+
const numberValue = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
|
|
291249
|
+
if (Number.isFinite(numberValue)) {
|
|
291250
|
+
return numberValue;
|
|
291251
|
+
}
|
|
291252
|
+
}
|
|
291253
|
+
return;
|
|
291254
|
+
}
|
|
291255
|
+
function computeFilledFromAmount(record) {
|
|
291256
|
+
const amount = firstNumber2(record?.amount);
|
|
291257
|
+
const remaining = firstNumber2(record?.remaining);
|
|
291258
|
+
if (amount === undefined || remaining === undefined) {
|
|
291259
|
+
return;
|
|
291260
|
+
}
|
|
291261
|
+
return amount - remaining;
|
|
291262
|
+
}
|
|
291263
|
+
function computeAveragePrice(executedBaseQuantity, executedQuoteQuantity) {
|
|
291264
|
+
if (executedBaseQuantity === undefined || executedQuoteQuantity === undefined || executedBaseQuantity === 0) {
|
|
291265
|
+
return;
|
|
291266
|
+
}
|
|
291267
|
+
return executedQuoteQuantity / executedBaseQuantity;
|
|
291268
|
+
}
|
|
291269
|
+
function getFees(record, info) {
|
|
291270
|
+
const fees = [];
|
|
291271
|
+
if (record?.fee)
|
|
291272
|
+
fees.push(record.fee);
|
|
291273
|
+
if (Array.isArray(record?.fees))
|
|
291274
|
+
fees.push(...record.fees);
|
|
291275
|
+
if (Array.isArray(record?.trades)) {
|
|
291276
|
+
for (const trade of record.trades) {
|
|
291277
|
+
const tradeRecord = asRecord(trade);
|
|
291278
|
+
if (tradeRecord?.fee)
|
|
291279
|
+
fees.push(tradeRecord.fee);
|
|
291280
|
+
if (Array.isArray(tradeRecord?.fees))
|
|
291281
|
+
fees.push(...tradeRecord.fees);
|
|
291282
|
+
}
|
|
291283
|
+
}
|
|
291284
|
+
if (Array.isArray(info?.fills)) {
|
|
291285
|
+
for (const fill of info.fills) {
|
|
291286
|
+
const fillRecord = asRecord(fill);
|
|
291287
|
+
const commission = firstNumber2(fillRecord?.commission);
|
|
291288
|
+
if (commission !== undefined) {
|
|
291289
|
+
fees.push({
|
|
291290
|
+
cost: commission,
|
|
291291
|
+
currency: firstString2(fillRecord?.commissionAsset)
|
|
291292
|
+
});
|
|
291293
|
+
}
|
|
291294
|
+
}
|
|
291295
|
+
}
|
|
291296
|
+
return fees;
|
|
291297
|
+
}
|
|
291298
|
+
function summarizeFees(fees) {
|
|
291299
|
+
let amount = 0;
|
|
291300
|
+
let amountFound = false;
|
|
291301
|
+
let currency;
|
|
291302
|
+
let rate;
|
|
291303
|
+
for (const rawFee of fees) {
|
|
291304
|
+
const fee = asRecord(rawFee);
|
|
291305
|
+
if (!fee)
|
|
291306
|
+
continue;
|
|
291307
|
+
const cost = firstNumber2(fee.cost, fee.amount);
|
|
291308
|
+
if (cost !== undefined) {
|
|
291309
|
+
amount += cost;
|
|
291310
|
+
amountFound = true;
|
|
291311
|
+
}
|
|
291312
|
+
currency ??= firstString2(fee.currency);
|
|
291313
|
+
rate ??= firstNumber2(fee.rate);
|
|
291314
|
+
}
|
|
291315
|
+
return {
|
|
291316
|
+
amount: amountFound ? amount : undefined,
|
|
291317
|
+
currency,
|
|
291318
|
+
rate
|
|
291319
|
+
};
|
|
291320
|
+
}
|
|
291321
|
+
function compactUndefined2(record) {
|
|
291322
|
+
return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
|
|
291323
|
+
}
|
|
291324
|
+
|
|
291220
291325
|
// src/helpers/broker-execution-archive/writer.ts
|
|
291221
291326
|
var import_api_logs2 = __toESM(require_src4(), 1);
|
|
291327
|
+
import { randomUUID } from "node:crypto";
|
|
291222
291328
|
import { closeSync, fsyncSync, openSync, writeSync } from "node:fs";
|
|
291223
|
-
import {
|
|
291329
|
+
import {
|
|
291330
|
+
request as httpRequest2
|
|
291331
|
+
} from "node:http";
|
|
291224
291332
|
import { request as httpsRequest2 } from "node:https";
|
|
291225
291333
|
var BROKER_EXECUTION_ARCHIVE_TABLES = new Set([
|
|
291226
291334
|
"broker_execution.order_events",
|
|
@@ -291248,6 +291356,7 @@ var DEFAULT_BATCH_SIZE = 10;
|
|
|
291248
291356
|
var DEFAULT_FLUSH_INTERVAL_MS = 1000;
|
|
291249
291357
|
var DEFAULT_FORWARDER_TIMEOUT_MS = 3000;
|
|
291250
291358
|
var SHED_WARN_INTERVAL_MS = 60000;
|
|
291359
|
+
var MAX_PINNED_BATCH_ATTEMPTS = 10;
|
|
291251
291360
|
var MARKET_FEEDS = new Set(["ORDERBOOK", "TICKER", "TRADES", "OHLCV"]);
|
|
291252
291361
|
function archiveFeed(row) {
|
|
291253
291362
|
const declared = row.row.feed ?? row.row.stream_type;
|
|
@@ -291293,14 +291402,26 @@ class BrokerExecutionArchiver {
|
|
|
291293
291402
|
flushIntervalMs;
|
|
291294
291403
|
forwarderTimeoutMs;
|
|
291295
291404
|
queue = [];
|
|
291405
|
+
enqueueTimes = new WeakMap;
|
|
291296
291406
|
stats = {
|
|
291297
291407
|
enqueued: 0,
|
|
291298
291408
|
shed: 0,
|
|
291299
291409
|
flushed: 0,
|
|
291300
291410
|
forwarderFailures: 0
|
|
291301
291411
|
};
|
|
291412
|
+
pendingRetry = null;
|
|
291302
291413
|
flushTimer = null;
|
|
291303
291414
|
flushInFlight = null;
|
|
291415
|
+
inFlightBatch = null;
|
|
291416
|
+
inFlightStartedAtMs = null;
|
|
291417
|
+
lastFailureAtMs = null;
|
|
291418
|
+
lastShedAtMs = null;
|
|
291419
|
+
lastSuccessAtMs = null;
|
|
291420
|
+
lastFailureEventSequence = null;
|
|
291421
|
+
lastShedEventSequence = null;
|
|
291422
|
+
lastSuccessEventSequence = null;
|
|
291423
|
+
lastSinkLatencyMs = null;
|
|
291424
|
+
healthEventSequence = 0;
|
|
291304
291425
|
lastShedWarnAtMs = 0;
|
|
291305
291426
|
closing = false;
|
|
291306
291427
|
closed = false;
|
|
@@ -291334,6 +291455,7 @@ class BrokerExecutionArchiver {
|
|
|
291334
291455
|
}
|
|
291335
291456
|
try {
|
|
291336
291457
|
this.flushTimer = setInterval(() => {
|
|
291458
|
+
this.emitArchiveHealthMetrics();
|
|
291337
291459
|
this.flush();
|
|
291338
291460
|
}, this.flushIntervalMs);
|
|
291339
291461
|
this.flushTimer.unref?.();
|
|
@@ -291377,32 +291499,34 @@ class BrokerExecutionArchiver {
|
|
|
291377
291499
|
row: { ...row.row, source: this.source }
|
|
291378
291500
|
};
|
|
291379
291501
|
if (this.queue.length >= this.maxQueueSize) {
|
|
291380
|
-
const
|
|
291381
|
-
if (
|
|
291382
|
-
this.appendLossRecords([
|
|
291502
|
+
const shedEntry = this.queue[0];
|
|
291503
|
+
if (shedEntry) {
|
|
291504
|
+
this.appendLossRecords([shedEntry], "queue_shed");
|
|
291383
291505
|
this.queue.shift();
|
|
291384
291506
|
}
|
|
291385
291507
|
this.stats.shed += 1;
|
|
291508
|
+
this.recordHealthEvent("shed");
|
|
291386
291509
|
this.recordArchiveMetric("cex_archive_rows_shed_total", {
|
|
291387
|
-
table:
|
|
291510
|
+
table: shedEntry?.table ?? "unknown",
|
|
291388
291511
|
source: this.source,
|
|
291389
|
-
feed:
|
|
291512
|
+
feed: shedEntry ? archiveFeed(shedEntry) : "NON_MARKET"
|
|
291390
291513
|
});
|
|
291391
291514
|
this.recordArchiveMetric("cex_archive_queue_saturated_rows_total", {
|
|
291392
|
-
table:
|
|
291515
|
+
table: shedEntry?.table ?? "unknown",
|
|
291393
291516
|
source: this.source,
|
|
291394
|
-
feed:
|
|
291517
|
+
feed: shedEntry ? archiveFeed(shedEntry) : "NON_MARKET"
|
|
291395
291518
|
});
|
|
291396
291519
|
const now3 = Date.now();
|
|
291397
291520
|
if (now3 - this.lastShedWarnAtMs >= SHED_WARN_INTERVAL_MS) {
|
|
291398
291521
|
log.warn("Archive queue full: shedding oldest rows", {
|
|
291399
291522
|
shed_total: this.stats.shed,
|
|
291400
291523
|
queue_max: this.maxQueueSize,
|
|
291401
|
-
table:
|
|
291524
|
+
table: shedEntry?.table ?? "unknown"
|
|
291402
291525
|
});
|
|
291403
291526
|
this.lastShedWarnAtMs = now3;
|
|
291404
291527
|
}
|
|
291405
291528
|
}
|
|
291529
|
+
this.enqueueTimes.set(archiveRow, Date.now());
|
|
291406
291530
|
this.queue.push(archiveRow);
|
|
291407
291531
|
this.stats.enqueued += 1;
|
|
291408
291532
|
this.recordArchiveMetric("cex_archive_rows_enqueued_total", {
|
|
@@ -291410,6 +291534,7 @@ class BrokerExecutionArchiver {
|
|
|
291410
291534
|
source: this.source,
|
|
291411
291535
|
feed: archiveFeed(archiveRow)
|
|
291412
291536
|
});
|
|
291537
|
+
this.emitArchiveHealthMetrics();
|
|
291413
291538
|
if (this.queue.length >= this.batchSize) {
|
|
291414
291539
|
this.flush();
|
|
291415
291540
|
}
|
|
@@ -291418,20 +291543,25 @@ class BrokerExecutionArchiver {
|
|
|
291418
291543
|
queueMicrotask(() => this.enqueue(row));
|
|
291419
291544
|
}
|
|
291420
291545
|
async flush() {
|
|
291421
|
-
if (!this.enabled || this.closed || this.queue.length === 0) {
|
|
291546
|
+
if (!this.enabled || this.closed || this.queue.length === 0 && this.pendingRetry === null) {
|
|
291422
291547
|
return;
|
|
291423
291548
|
}
|
|
291424
291549
|
if (this.flushInFlight) {
|
|
291425
291550
|
return this.flushInFlight;
|
|
291426
291551
|
}
|
|
291552
|
+
const inFlightState = {};
|
|
291427
291553
|
const inFlight = this.flushBatch().then(() => {
|
|
291428
291554
|
return;
|
|
291429
291555
|
}).finally(() => {
|
|
291556
|
+
if (this.flushInFlight !== inFlightState.promise) {
|
|
291557
|
+
return;
|
|
291558
|
+
}
|
|
291430
291559
|
this.flushInFlight = null;
|
|
291431
291560
|
if (!this.closed && !this.closing && this.enabled && this.queue.length >= this.batchSize) {
|
|
291432
291561
|
queueMicrotask(() => void this.flush());
|
|
291433
291562
|
}
|
|
291434
291563
|
});
|
|
291564
|
+
inFlightState.promise = inFlight;
|
|
291435
291565
|
this.flushInFlight = inFlight;
|
|
291436
291566
|
return inFlight;
|
|
291437
291567
|
}
|
|
@@ -291443,16 +291573,18 @@ class BrokerExecutionArchiver {
|
|
|
291443
291573
|
}
|
|
291444
291574
|
let closeError;
|
|
291445
291575
|
try {
|
|
291446
|
-
while (this.queue.length > 0 || this.flushInFlight) {
|
|
291576
|
+
while (this.pendingRetry !== null || this.queue.length > 0 || this.flushInFlight) {
|
|
291447
291577
|
if (this.flushInFlight) {
|
|
291448
291578
|
await this.flushInFlight;
|
|
291449
291579
|
continue;
|
|
291450
291580
|
}
|
|
291451
|
-
|
|
291452
|
-
|
|
291453
|
-
|
|
291454
|
-
|
|
291581
|
+
if (!await this.flushBatch()) {
|
|
291582
|
+
const undelivered = [
|
|
291583
|
+
...this.pendingRetry?.rows ?? [],
|
|
291584
|
+
...this.queue
|
|
291585
|
+
];
|
|
291455
291586
|
this.appendLossRecords(undelivered, "shutdown_forwarder_failure");
|
|
291587
|
+
this.pendingRetry = null;
|
|
291456
291588
|
this.queue.length = 0;
|
|
291457
291589
|
break;
|
|
291458
291590
|
}
|
|
@@ -291476,7 +291608,87 @@ class BrokerExecutionArchiver {
|
|
|
291476
291608
|
return { ...this.stats };
|
|
291477
291609
|
}
|
|
291478
291610
|
getQueueDepth() {
|
|
291479
|
-
return this.queue.length;
|
|
291611
|
+
return this.queue.length + (this.pendingRetry?.rows.length ?? 0);
|
|
291612
|
+
}
|
|
291613
|
+
getHealthSnapshot() {
|
|
291614
|
+
const now3 = Date.now();
|
|
291615
|
+
const oldestPendingAtMs = this.oldestPendingEnqueueAtMs();
|
|
291616
|
+
const oldestPendingAgeMs = oldestPendingAtMs === null ? 0 : Math.max(0, now3 - oldestPendingAtMs);
|
|
291617
|
+
const inFlightAgeMs = this.inFlightStartedAtMs === null ? 0 : Math.max(0, now3 - this.inFlightStartedAtMs);
|
|
291618
|
+
const lastUnhealthyEventSequence = Math.max(this.lastFailureEventSequence ?? Number.NEGATIVE_INFINITY, this.lastShedEventSequence ?? Number.NEGATIVE_INFINITY);
|
|
291619
|
+
const recoveredFromEvents = lastUnhealthyEventSequence === Number.NEGATIVE_INFINITY || this.lastSuccessEventSequence !== null && this.lastSuccessEventSequence > lastUnhealthyEventSequence;
|
|
291620
|
+
const healthy = this.enabled && !this.closing && !this.closed && this.queue.length < this.maxQueueSize && oldestPendingAgeMs <= this.forwarderTimeoutMs && inFlightAgeMs <= this.forwarderTimeoutMs && recoveredFromEvents;
|
|
291621
|
+
return {
|
|
291622
|
+
healthy,
|
|
291623
|
+
queue_depth: this.getQueueDepth(),
|
|
291624
|
+
oldest_pending_age_ms: oldestPendingAgeMs,
|
|
291625
|
+
shed_total: this.stats.shed,
|
|
291626
|
+
last_failure_at: this.lastFailureAtMs,
|
|
291627
|
+
last_shed_at: this.lastShedAtMs,
|
|
291628
|
+
last_success_at: this.lastSuccessAtMs,
|
|
291629
|
+
sink_latency_ms: this.lastSinkLatencyMs,
|
|
291630
|
+
sink_errors_total: this.stats.forwarderFailures,
|
|
291631
|
+
in_flight: this.inFlightBatch !== null,
|
|
291632
|
+
in_flight_age_ms: inFlightAgeMs
|
|
291633
|
+
};
|
|
291634
|
+
}
|
|
291635
|
+
oldestPendingEnqueueAtMs() {
|
|
291636
|
+
let oldest = null;
|
|
291637
|
+
for (const entry of this.pendingRetry?.rows ?? []) {
|
|
291638
|
+
const enqueuedAtMs = this.enqueueTimes.get(entry);
|
|
291639
|
+
if (enqueuedAtMs === undefined) {
|
|
291640
|
+
continue;
|
|
291641
|
+
}
|
|
291642
|
+
oldest = oldest === null ? enqueuedAtMs : Math.min(oldest, enqueuedAtMs);
|
|
291643
|
+
}
|
|
291644
|
+
for (const entry of this.queue) {
|
|
291645
|
+
const enqueuedAtMs = this.enqueueTimes.get(entry);
|
|
291646
|
+
if (enqueuedAtMs === undefined) {
|
|
291647
|
+
continue;
|
|
291648
|
+
}
|
|
291649
|
+
oldest = oldest === null ? enqueuedAtMs : Math.min(oldest, enqueuedAtMs);
|
|
291650
|
+
}
|
|
291651
|
+
for (const entry of this.inFlightBatch ?? []) {
|
|
291652
|
+
const enqueuedAtMs = this.enqueueTimes.get(entry);
|
|
291653
|
+
if (enqueuedAtMs === undefined) {
|
|
291654
|
+
continue;
|
|
291655
|
+
}
|
|
291656
|
+
oldest = oldest === null ? enqueuedAtMs : Math.min(oldest, enqueuedAtMs);
|
|
291657
|
+
}
|
|
291658
|
+
return oldest;
|
|
291659
|
+
}
|
|
291660
|
+
recordHealthEvent(event) {
|
|
291661
|
+
const eventSequence = ++this.healthEventSequence;
|
|
291662
|
+
const eventTimestampMs = Date.now();
|
|
291663
|
+
switch (event) {
|
|
291664
|
+
case "failure":
|
|
291665
|
+
this.lastFailureAtMs = eventTimestampMs;
|
|
291666
|
+
this.lastFailureEventSequence = eventSequence;
|
|
291667
|
+
return;
|
|
291668
|
+
case "shed":
|
|
291669
|
+
this.lastShedAtMs = eventTimestampMs;
|
|
291670
|
+
this.lastShedEventSequence = eventSequence;
|
|
291671
|
+
return;
|
|
291672
|
+
case "success":
|
|
291673
|
+
this.lastSuccessAtMs = eventTimestampMs;
|
|
291674
|
+
this.lastSuccessEventSequence = eventSequence;
|
|
291675
|
+
return;
|
|
291676
|
+
}
|
|
291677
|
+
}
|
|
291678
|
+
emitArchiveHealthMetrics() {
|
|
291679
|
+
const snapshot = this.getHealthSnapshot();
|
|
291680
|
+
const labels = { source: this.source };
|
|
291681
|
+
this.recordArchiveObservableGauge("cex_archive_health", snapshot.healthy ? 1 : 0, labels);
|
|
291682
|
+
this.recordArchiveGauge("cex_archive_queue_depth", snapshot.queue_depth, labels);
|
|
291683
|
+
this.recordArchiveGauge("cex_archive_oldest_pending_age_ms", snapshot.oldest_pending_age_ms, labels);
|
|
291684
|
+
this.recordArchiveGauge("cex_archive_shed_total", snapshot.shed_total, labels);
|
|
291685
|
+
this.recordArchiveGauge("cex_archive_last_failure_at", snapshot.last_failure_at ?? 0, labels);
|
|
291686
|
+
this.recordArchiveGauge("cex_archive_last_shed_at", snapshot.last_shed_at ?? 0, labels);
|
|
291687
|
+
this.recordArchiveGauge("cex_archive_last_success_at", snapshot.last_success_at ?? 0, labels);
|
|
291688
|
+
this.recordArchiveGauge("cex_archive_sink_latency_ms", snapshot.sink_latency_ms ?? 0, labels);
|
|
291689
|
+
this.recordArchiveGauge("cex_archive_sink_errors_total", snapshot.sink_errors_total, labels);
|
|
291690
|
+
this.recordArchiveGauge("cex_archive_in_flight", snapshot.in_flight ? 1 : 0, labels);
|
|
291691
|
+
this.recordArchiveGauge("cex_archive_in_flight_age_ms", snapshot.in_flight_age_ms, labels);
|
|
291480
291692
|
}
|
|
291481
291693
|
closeLossJournal() {
|
|
291482
291694
|
if (this.deadLetterFd === undefined) {
|
|
@@ -291491,27 +291703,6 @@ class BrokerExecutionArchiver {
|
|
|
291491
291703
|
this.deadLetterFd = undefined;
|
|
291492
291704
|
}
|
|
291493
291705
|
}
|
|
291494
|
-
enforceQueueBound() {
|
|
291495
|
-
while (this.queue.length > this.maxQueueSize) {
|
|
291496
|
-
const dropped = this.queue[0];
|
|
291497
|
-
if (!dropped) {
|
|
291498
|
-
return;
|
|
291499
|
-
}
|
|
291500
|
-
this.appendLossRecords([dropped], "queue_shed");
|
|
291501
|
-
this.queue.shift();
|
|
291502
|
-
this.stats.shed += 1;
|
|
291503
|
-
this.recordArchiveMetric("cex_archive_rows_shed_total", {
|
|
291504
|
-
table: dropped?.table ?? "unknown",
|
|
291505
|
-
source: this.source,
|
|
291506
|
-
feed: archiveFeed(dropped)
|
|
291507
|
-
});
|
|
291508
|
-
this.recordArchiveMetric("cex_archive_queue_saturated_rows_total", {
|
|
291509
|
-
table: dropped.table,
|
|
291510
|
-
source: this.source,
|
|
291511
|
-
feed: archiveFeed(dropped)
|
|
291512
|
-
});
|
|
291513
|
-
}
|
|
291514
|
-
}
|
|
291515
291706
|
appendLossRecords(rows, reason) {
|
|
291516
291707
|
if (rows.length === 0) {
|
|
291517
291708
|
return;
|
|
@@ -291556,10 +291747,14 @@ class BrokerExecutionArchiver {
|
|
|
291556
291747
|
}
|
|
291557
291748
|
}
|
|
291558
291749
|
async flushBatch() {
|
|
291559
|
-
const
|
|
291750
|
+
const pinned = this.pendingRetry;
|
|
291751
|
+
const batch = pinned ? pinned.rows : this.queue.splice(0, this.batchSize);
|
|
291560
291752
|
if (batch.length === 0) {
|
|
291561
291753
|
return true;
|
|
291562
291754
|
}
|
|
291755
|
+
const batchId = pinned?.batchId ?? randomUUID();
|
|
291756
|
+
this.inFlightBatch = batch;
|
|
291757
|
+
this.inFlightStartedAtMs = Date.now();
|
|
291563
291758
|
for (const entry of batch) {
|
|
291564
291759
|
if (isBrokerExecutionArchiveTable(entry.table)) {
|
|
291565
291760
|
this.emitOtelLog(entry);
|
|
@@ -291567,11 +291762,27 @@ class BrokerExecutionArchiver {
|
|
|
291567
291762
|
}
|
|
291568
291763
|
if (this.forwarderUrl) {
|
|
291569
291764
|
try {
|
|
291570
|
-
await this.postToForwarder(batch);
|
|
291765
|
+
await this.postToForwarder(batch, batchId);
|
|
291571
291766
|
} catch (error) {
|
|
291572
291767
|
this.stats.forwarderFailures += 1;
|
|
291573
|
-
this.
|
|
291574
|
-
this.
|
|
291768
|
+
this.recordHealthEvent("failure");
|
|
291769
|
+
this.lastSinkLatencyMs = Math.max(0, Date.now() - (this.inFlightStartedAtMs ?? Date.now()));
|
|
291770
|
+
const attempts = (pinned?.attempts ?? 0) + 1;
|
|
291771
|
+
try {
|
|
291772
|
+
if (attempts >= MAX_PINNED_BATCH_ATTEMPTS) {
|
|
291773
|
+
this.pendingRetry = null;
|
|
291774
|
+
this.appendLossRecords(batch, "retry_exhausted");
|
|
291775
|
+
log.warn("Broker execution archive gave up on a batch", {
|
|
291776
|
+
attempts,
|
|
291777
|
+
rows: batch.length
|
|
291778
|
+
});
|
|
291779
|
+
} else {
|
|
291780
|
+
this.pendingRetry = { batchId, rows: batch, attempts };
|
|
291781
|
+
}
|
|
291782
|
+
} finally {
|
|
291783
|
+
this.clearInFlightBatch(batch);
|
|
291784
|
+
this.emitArchiveHealthMetrics();
|
|
291785
|
+
}
|
|
291575
291786
|
this.recordArchiveMetric("cex_archive_forwarder_failures_total", {
|
|
291576
291787
|
count: batch.length
|
|
291577
291788
|
});
|
|
@@ -291579,15 +291790,28 @@ class BrokerExecutionArchiver {
|
|
|
291579
291790
|
return false;
|
|
291580
291791
|
}
|
|
291581
291792
|
}
|
|
291793
|
+
this.pendingRetry = null;
|
|
291582
291794
|
this.stats.flushed += batch.length;
|
|
291795
|
+
this.recordHealthEvent("success");
|
|
291796
|
+
this.lastSinkLatencyMs = Math.max(0, Date.now() - (this.inFlightStartedAtMs ?? Date.now()));
|
|
291797
|
+
this.clearInFlightBatch(batch);
|
|
291583
291798
|
this.recordFlushHealth(batch);
|
|
291799
|
+
this.emitArchiveHealthMetrics();
|
|
291584
291800
|
return true;
|
|
291585
291801
|
}
|
|
291802
|
+
clearInFlightBatch(batch) {
|
|
291803
|
+
if (this.inFlightBatch !== batch) {
|
|
291804
|
+
return;
|
|
291805
|
+
}
|
|
291806
|
+
this.inFlightBatch = null;
|
|
291807
|
+
this.inFlightStartedAtMs = null;
|
|
291808
|
+
}
|
|
291586
291809
|
recordFlushHealth(batch) {
|
|
291587
291810
|
const countByTable = new Map;
|
|
291588
291811
|
for (const entry of batch) {
|
|
291589
|
-
const
|
|
291590
|
-
const
|
|
291812
|
+
const row = entry;
|
|
291813
|
+
const key = `${row.table}\x00${archiveFeed(row)}`;
|
|
291814
|
+
const grouped = countByTable.get(key) ?? { row, count: 0 };
|
|
291591
291815
|
grouped.count += 1;
|
|
291592
291816
|
countByTable.set(key, grouped);
|
|
291593
291817
|
}
|
|
@@ -291605,9 +291829,14 @@ class BrokerExecutionArchiver {
|
|
|
291605
291829
|
await this.otelMetrics?.recordCounter(metricName, value, labels);
|
|
291606
291830
|
} catch {}
|
|
291607
291831
|
}
|
|
291608
|
-
async recordArchiveGauge(metricName, value) {
|
|
291832
|
+
async recordArchiveGauge(metricName, value, labels = {}) {
|
|
291833
|
+
try {
|
|
291834
|
+
await this.otelMetrics?.recordGauge(metricName, value, labels);
|
|
291835
|
+
} catch {}
|
|
291836
|
+
}
|
|
291837
|
+
async recordArchiveObservableGauge(metricName, value, labels) {
|
|
291609
291838
|
try {
|
|
291610
|
-
await this.otelMetrics?.
|
|
291839
|
+
await this.otelMetrics?.setObservableGauge(metricName, value, labels);
|
|
291611
291840
|
} catch {}
|
|
291612
291841
|
}
|
|
291613
291842
|
emitOtelLog(entry) {
|
|
@@ -291625,13 +291854,14 @@ class BrokerExecutionArchiver {
|
|
|
291625
291854
|
log.warn("Broker execution archive OTLP emit failed", { error });
|
|
291626
291855
|
}
|
|
291627
291856
|
}
|
|
291628
|
-
postToForwarder(batch) {
|
|
291857
|
+
postToForwarder(batch, batchId) {
|
|
291629
291858
|
if (!this.forwarderUrl || batch.length === 0) {
|
|
291630
291859
|
return Promise.resolve();
|
|
291631
291860
|
}
|
|
291632
291861
|
const body = JSON.stringify({
|
|
291633
291862
|
source: this.source,
|
|
291634
291863
|
deployment_id: this.deploymentId,
|
|
291864
|
+
batch_id: batchId,
|
|
291635
291865
|
rows: batch
|
|
291636
291866
|
});
|
|
291637
291867
|
const url2 = new URL(this.forwarderUrl);
|
|
@@ -291644,27 +291874,136 @@ class BrokerExecutionArchiver {
|
|
|
291644
291874
|
headers.authorization = `Bearer ${this.forwarderAuthToken}`;
|
|
291645
291875
|
}
|
|
291646
291876
|
return new Promise((resolve, reject) => {
|
|
291647
|
-
|
|
291648
|
-
|
|
291649
|
-
|
|
291650
|
-
|
|
291651
|
-
|
|
291652
|
-
|
|
291653
|
-
|
|
291654
|
-
|
|
291655
|
-
|
|
291656
|
-
|
|
291657
|
-
|
|
291658
|
-
|
|
291877
|
+
let req;
|
|
291878
|
+
let response;
|
|
291879
|
+
let settled = false;
|
|
291880
|
+
let requestClosed = false;
|
|
291881
|
+
let requestCloseFailureScheduled = false;
|
|
291882
|
+
let responseClosed = false;
|
|
291883
|
+
let responseEnded = false;
|
|
291884
|
+
let outerTimer;
|
|
291885
|
+
function cleanupRequestListeners() {
|
|
291886
|
+
req?.removeListener("error", onRequestError);
|
|
291887
|
+
req?.removeListener("timeout", onRequestTimeout);
|
|
291888
|
+
req?.removeListener("close", onRequestClose);
|
|
291889
|
+
}
|
|
291890
|
+
function cleanupResponseListeners() {
|
|
291891
|
+
response?.removeListener("data", onResponseData);
|
|
291892
|
+
response?.removeListener("end", onResponseEnd);
|
|
291893
|
+
response?.removeListener("aborted", onResponseAborted);
|
|
291894
|
+
response?.removeListener("error", onResponseError);
|
|
291895
|
+
response?.removeListener("close", onResponseClose);
|
|
291896
|
+
}
|
|
291897
|
+
function onRequestClose() {
|
|
291898
|
+
requestClosed = true;
|
|
291899
|
+
if (!settled && !response && !requestCloseFailureScheduled) {
|
|
291900
|
+
requestCloseFailureScheduled = true;
|
|
291901
|
+
queueMicrotask(() => {
|
|
291902
|
+
if (!settled && !response) {
|
|
291903
|
+
settle2(new Error("Archive forwarder request closed before response headers"), false, true);
|
|
291904
|
+
}
|
|
291905
|
+
});
|
|
291906
|
+
}
|
|
291907
|
+
if (settled) {
|
|
291908
|
+
cleanupRequestListeners();
|
|
291909
|
+
}
|
|
291910
|
+
}
|
|
291911
|
+
function onRequestError(error) {
|
|
291912
|
+
if (!settled) {
|
|
291913
|
+
settle2(error, true);
|
|
291914
|
+
} else {
|
|
291915
|
+
cleanupRequestListeners();
|
|
291916
|
+
}
|
|
291917
|
+
}
|
|
291918
|
+
function onRequestTimeout() {
|
|
291919
|
+
settle2(new Error("Archive forwarder socket timed out"), true);
|
|
291920
|
+
}
|
|
291921
|
+
function onResponseData() {}
|
|
291922
|
+
function onResponseEnd() {
|
|
291923
|
+
responseEnded = true;
|
|
291924
|
+
if (!response?.complete) {
|
|
291925
|
+
settle2(new Error("Archive forwarder response was incomplete"), true);
|
|
291926
|
+
return;
|
|
291927
|
+
}
|
|
291928
|
+
const status = response.statusCode ?? 0;
|
|
291929
|
+
if (status < 200 || status >= 300) {
|
|
291930
|
+
settle2(new Error(`Archive forwarder returned ${status} ${response.statusMessage ?? ""}`), false);
|
|
291931
|
+
return;
|
|
291932
|
+
}
|
|
291933
|
+
settle2(undefined, false);
|
|
291934
|
+
}
|
|
291935
|
+
function onResponseAborted() {
|
|
291936
|
+
settle2(new Error("Archive forwarder response was aborted"), true);
|
|
291937
|
+
}
|
|
291938
|
+
function onResponseError(error) {
|
|
291939
|
+
if (!settled) {
|
|
291940
|
+
settle2(error, true);
|
|
291941
|
+
}
|
|
291942
|
+
}
|
|
291943
|
+
function onResponseClose() {
|
|
291944
|
+
responseClosed = true;
|
|
291945
|
+
if (!settled && !responseEnded) {
|
|
291946
|
+
settle2(new Error("Archive forwarder response closed before completion"), true);
|
|
291947
|
+
return;
|
|
291948
|
+
}
|
|
291949
|
+
cleanupResponseListeners();
|
|
291950
|
+
}
|
|
291951
|
+
function settle2(error, destroyRequest, retainClosedRequestError = false) {
|
|
291952
|
+
if (settled) {
|
|
291953
|
+
return;
|
|
291954
|
+
}
|
|
291955
|
+
settled = true;
|
|
291956
|
+
if (outerTimer) {
|
|
291957
|
+
clearTimeout(outerTimer);
|
|
291958
|
+
outerTimer = undefined;
|
|
291959
|
+
}
|
|
291960
|
+
response?.removeListener("data", onResponseData);
|
|
291961
|
+
response?.removeListener("end", onResponseEnd);
|
|
291962
|
+
response?.removeListener("aborted", onResponseAborted);
|
|
291963
|
+
if (responseClosed) {
|
|
291964
|
+
cleanupResponseListeners();
|
|
291965
|
+
}
|
|
291966
|
+
if (destroyRequest && req && !requestClosed) {
|
|
291967
|
+
req.destroy(error);
|
|
291968
|
+
} else if (retainClosedRequestError && req && requestClosed) {
|
|
291969
|
+
req.removeListener("timeout", onRequestTimeout);
|
|
291970
|
+
req.removeListener("close", onRequestClose);
|
|
291971
|
+
} else if (!req || requestClosed) {
|
|
291972
|
+
cleanupRequestListeners();
|
|
291973
|
+
}
|
|
291974
|
+
if (error) {
|
|
291975
|
+
reject(error);
|
|
291976
|
+
} else {
|
|
291659
291977
|
resolve();
|
|
291978
|
+
}
|
|
291979
|
+
}
|
|
291980
|
+
outerTimer = setTimeout(() => {
|
|
291981
|
+
settle2(new Error("Archive forwarder request deadline exceeded"), true);
|
|
291982
|
+
}, this.forwarderTimeoutMs);
|
|
291983
|
+
try {
|
|
291984
|
+
req = doRequest(url2, {
|
|
291985
|
+
method: "POST",
|
|
291986
|
+
headers,
|
|
291987
|
+
timeout: this.forwarderTimeoutMs
|
|
291988
|
+
}, (nextResponse) => {
|
|
291989
|
+
response = nextResponse;
|
|
291990
|
+
response.on("data", onResponseData);
|
|
291991
|
+
response.on("end", onResponseEnd);
|
|
291992
|
+
response.on("aborted", onResponseAborted);
|
|
291993
|
+
response.on("error", onResponseError);
|
|
291994
|
+
response.on("close", onResponseClose);
|
|
291995
|
+
if (settled) {
|
|
291996
|
+
response.resume();
|
|
291997
|
+
}
|
|
291660
291998
|
});
|
|
291661
|
-
|
|
291662
|
-
|
|
291663
|
-
|
|
291664
|
-
req.
|
|
291665
|
-
|
|
291666
|
-
|
|
291667
|
-
|
|
291999
|
+
req.on("error", onRequestError);
|
|
292000
|
+
req.on("timeout", onRequestTimeout);
|
|
292001
|
+
req.on("close", onRequestClose);
|
|
292002
|
+
req.write(body);
|
|
292003
|
+
req.end();
|
|
292004
|
+
} catch (error) {
|
|
292005
|
+
settle2(error instanceof Error ? error : new Error(String(error)), true);
|
|
292006
|
+
}
|
|
291668
292007
|
});
|
|
291669
292008
|
}
|
|
291670
292009
|
}
|
|
@@ -292134,6 +292473,94 @@ class AccountBalanceArchivePoller {
|
|
|
292134
292473
|
}
|
|
292135
292474
|
}
|
|
292136
292475
|
|
|
292476
|
+
// src/helpers/balance-update-archive-consumer.ts
|
|
292477
|
+
class BalanceUpdateArchiveConsumer {
|
|
292478
|
+
params;
|
|
292479
|
+
#started = false;
|
|
292480
|
+
#stopping = false;
|
|
292481
|
+
#subscriptions = new Set;
|
|
292482
|
+
#runs = [];
|
|
292483
|
+
constructor(params) {
|
|
292484
|
+
this.params = params;
|
|
292485
|
+
}
|
|
292486
|
+
start() {
|
|
292487
|
+
if (this.#started || this.#stopping)
|
|
292488
|
+
return;
|
|
292489
|
+
this.#started = true;
|
|
292490
|
+
const targets = this.#targets();
|
|
292491
|
+
log.info("\uD83D\uDCB8 Balance-update archive consumer started", {
|
|
292492
|
+
accounts: targets.length
|
|
292493
|
+
});
|
|
292494
|
+
for (const target of targets) {
|
|
292495
|
+
this.#runs.push(this.#consume(target));
|
|
292496
|
+
}
|
|
292497
|
+
}
|
|
292498
|
+
async stop() {
|
|
292499
|
+
this.#stopping = true;
|
|
292500
|
+
for (const subscription of [...this.#subscriptions]) {
|
|
292501
|
+
subscription.close();
|
|
292502
|
+
}
|
|
292503
|
+
await Promise.all(this.#runs);
|
|
292504
|
+
}
|
|
292505
|
+
#targets() {
|
|
292506
|
+
const targets = [];
|
|
292507
|
+
for (const [exchange, pool] of Object.entries(this.params.brokers)) {
|
|
292508
|
+
for (const account of [pool.primary, ...pool.secondaryBrokers]) {
|
|
292509
|
+
targets.push({ exchange, accountSelector: account.label });
|
|
292510
|
+
}
|
|
292511
|
+
}
|
|
292512
|
+
return targets;
|
|
292513
|
+
}
|
|
292514
|
+
async#consume(target) {
|
|
292515
|
+
while (!this.#stopping) {
|
|
292516
|
+
let subscription;
|
|
292517
|
+
try {
|
|
292518
|
+
subscription = this.params.userDataStreamSupervisor.subscribe({
|
|
292519
|
+
exchange: target.exchange,
|
|
292520
|
+
accountSelector: target.accountSelector,
|
|
292521
|
+
kind: "balance"
|
|
292522
|
+
});
|
|
292523
|
+
this.#subscriptions.add(subscription);
|
|
292524
|
+
for await (const message of subscription) {
|
|
292525
|
+
if (this.#stopping)
|
|
292526
|
+
break;
|
|
292527
|
+
const event = message.event;
|
|
292528
|
+
if (event.e !== "balanceUpdate")
|
|
292529
|
+
continue;
|
|
292530
|
+
this.params.archiver.enqueue(buildTransferEventArchiveRow({
|
|
292531
|
+
tags: buildCommonArchiveTags({
|
|
292532
|
+
deploymentId: this.params.archiver.getDeploymentId(),
|
|
292533
|
+
accountSelector: target.accountSelector,
|
|
292534
|
+
exchange: target.exchange
|
|
292535
|
+
}),
|
|
292536
|
+
transfer: {
|
|
292537
|
+
eventKind: "balance_delta",
|
|
292538
|
+
lifecycleAction: "observe_balance_update",
|
|
292539
|
+
amount: typeof event.d === "string" ? event.d : undefined,
|
|
292540
|
+
assetSymbol: typeof event.a === "string" ? event.a : undefined,
|
|
292541
|
+
exchangeTimestamp: normalizeTimestamp(event.T),
|
|
292542
|
+
payload: event
|
|
292543
|
+
}
|
|
292544
|
+
}));
|
|
292545
|
+
}
|
|
292546
|
+
} catch (error) {
|
|
292547
|
+
if (!this.#stopping) {
|
|
292548
|
+
log.warn("Balance-update archive subscription failed", {
|
|
292549
|
+
exchange: target.exchange,
|
|
292550
|
+
accountSelector: target.accountSelector,
|
|
292551
|
+
errorType: error instanceof Error ? error.name : typeof error
|
|
292552
|
+
});
|
|
292553
|
+
}
|
|
292554
|
+
} finally {
|
|
292555
|
+
if (subscription) {
|
|
292556
|
+
this.#subscriptions.delete(subscription);
|
|
292557
|
+
subscription.close();
|
|
292558
|
+
}
|
|
292559
|
+
}
|
|
292560
|
+
}
|
|
292561
|
+
}
|
|
292562
|
+
}
|
|
292563
|
+
|
|
292137
292564
|
// src/helpers/deposit-archive-poller.ts
|
|
292138
292565
|
var DEFAULT_CONFIG2 = {
|
|
292139
292566
|
pollIntervalMs: 60000,
|
|
@@ -292343,7 +292770,7 @@ class DepositArchivePoller {
|
|
|
292343
292770
|
network: network === undefined ? undefined : String(network),
|
|
292344
292771
|
externalId: depositTxid,
|
|
292345
292772
|
txid: depositTxid,
|
|
292346
|
-
exchangeTimestamp:
|
|
292773
|
+
exchangeTimestamp: normalizeTimestamp(creditedAt),
|
|
292347
292774
|
payload: record
|
|
292348
292775
|
}
|
|
292349
292776
|
}));
|
|
@@ -293205,7 +293632,7 @@ function createOtelLogsFromEnv() {
|
|
|
293205
293632
|
}
|
|
293206
293633
|
|
|
293207
293634
|
// src/helpers/stream-health-publisher.ts
|
|
293208
|
-
import { createHash as createHash4, randomUUID } from "node:crypto";
|
|
293635
|
+
import { createHash as createHash4, randomUUID as randomUUID2 } from "node:crypto";
|
|
293209
293636
|
import {
|
|
293210
293637
|
closeSync as closeSync2,
|
|
293211
293638
|
fsyncSync as fsyncSync2,
|
|
@@ -293330,7 +293757,7 @@ class StreamHealthPublisher {
|
|
|
293330
293757
|
version: STATE_VERSION,
|
|
293331
293758
|
producerId: PRODUCER_ID,
|
|
293332
293759
|
producerEpoch: "1",
|
|
293333
|
-
runId:
|
|
293760
|
+
runId: randomUUID2(),
|
|
293334
293761
|
nextBatchSequence: "1",
|
|
293335
293762
|
nextStreamSequences: {}
|
|
293336
293763
|
};
|
|
@@ -293399,7 +293826,7 @@ class StreamHealthPublisher {
|
|
|
293399
293826
|
return;
|
|
293400
293827
|
if (this.#advanceRun) {
|
|
293401
293828
|
this.#state.producerEpoch = next(this.#state.producerEpoch);
|
|
293402
|
-
this.#state.runId =
|
|
293829
|
+
this.#state.runId = randomUUID2();
|
|
293403
293830
|
this.#state.nextBatchSequence = "1";
|
|
293404
293831
|
this.#state.nextStreamSequences = {};
|
|
293405
293832
|
this.#advanceRun = false;
|
|
@@ -293515,7 +293942,7 @@ class StreamHealthPublisher {
|
|
|
293515
293942
|
cause: error
|
|
293516
293943
|
});
|
|
293517
293944
|
}
|
|
293518
|
-
const temporary = `${this.#statePath}.${process.pid}.${
|
|
293945
|
+
const temporary = `${this.#statePath}.${process.pid}.${randomUUID2()}.tmp`;
|
|
293519
293946
|
let fd2;
|
|
293520
293947
|
try {
|
|
293521
293948
|
fd2 = openSync2(temporary, "wx", 384);
|
|
@@ -293560,6 +293987,143 @@ function streamHealthPublisherConfigFromEnv(env = process.env) {
|
|
|
293560
293987
|
};
|
|
293561
293988
|
}
|
|
293562
293989
|
|
|
293990
|
+
// src/helpers/user-asset-archive-poller.ts
|
|
293991
|
+
var DEFAULT_CONFIG4 = {
|
|
293992
|
+
pollIntervalMs: 60000
|
|
293993
|
+
};
|
|
293994
|
+
var USER_ASSET_EXCHANGE_ID = "binance";
|
|
293995
|
+
var metricLabels2 = (target) => ({
|
|
293996
|
+
exchange: target.exchangeId,
|
|
293997
|
+
account_selector: target.account.label,
|
|
293998
|
+
balance_scope: USER_ASSET_BALANCE_SCOPE
|
|
293999
|
+
});
|
|
294000
|
+
|
|
294001
|
+
class UserAssetArchivePoller {
|
|
294002
|
+
params;
|
|
294003
|
+
#timer = null;
|
|
294004
|
+
#stopped = false;
|
|
294005
|
+
#running = null;
|
|
294006
|
+
#lastSuccessMs = new Map;
|
|
294007
|
+
#config;
|
|
294008
|
+
constructor(params) {
|
|
294009
|
+
this.params = params;
|
|
294010
|
+
this.#config = { ...DEFAULT_CONFIG4, ...params.config };
|
|
294011
|
+
}
|
|
294012
|
+
start() {
|
|
294013
|
+
if (this.#timer || this.#stopped || !this.params.archiver.canPersistAccountBalanceSnapshots() || this.#targets().length === 0) {
|
|
294014
|
+
return;
|
|
294015
|
+
}
|
|
294016
|
+
log.info("\uD83E\uDDCA User asset archive poller started", {
|
|
294017
|
+
balanceScope: USER_ASSET_BALANCE_SCOPE
|
|
294018
|
+
});
|
|
294019
|
+
this.#schedule(0);
|
|
294020
|
+
}
|
|
294021
|
+
async stop() {
|
|
294022
|
+
this.#stopped = true;
|
|
294023
|
+
if (this.#timer) {
|
|
294024
|
+
clearTimeout(this.#timer);
|
|
294025
|
+
this.#timer = null;
|
|
294026
|
+
}
|
|
294027
|
+
await this.#running;
|
|
294028
|
+
}
|
|
294029
|
+
async pollAllOnce() {
|
|
294030
|
+
if (this.#stopped || this.#running || !this.params.archiver.canPersistAccountBalanceSnapshots()) {
|
|
294031
|
+
return false;
|
|
294032
|
+
}
|
|
294033
|
+
this.#running = this.#pollAllSequentially();
|
|
294034
|
+
try {
|
|
294035
|
+
return await this.#running;
|
|
294036
|
+
} finally {
|
|
294037
|
+
this.#running = null;
|
|
294038
|
+
}
|
|
294039
|
+
}
|
|
294040
|
+
#targets() {
|
|
294041
|
+
const targets = [];
|
|
294042
|
+
for (const [exchangeId, pool] of Object.entries(this.params.brokers)) {
|
|
294043
|
+
if (exchangeId.trim().toLowerCase() !== USER_ASSET_EXCHANGE_ID) {
|
|
294044
|
+
continue;
|
|
294045
|
+
}
|
|
294046
|
+
for (const account of [pool.primary, ...pool.secondaryBrokers]) {
|
|
294047
|
+
targets.push({ exchangeId, account });
|
|
294048
|
+
}
|
|
294049
|
+
}
|
|
294050
|
+
return targets;
|
|
294051
|
+
}
|
|
294052
|
+
async#pollAllSequentially() {
|
|
294053
|
+
for (const target of this.#targets()) {
|
|
294054
|
+
if (this.#stopped) {
|
|
294055
|
+
break;
|
|
294056
|
+
}
|
|
294057
|
+
await this.#pollOne(target);
|
|
294058
|
+
}
|
|
294059
|
+
return true;
|
|
294060
|
+
}
|
|
294061
|
+
async#pollOne(target) {
|
|
294062
|
+
const labels = metricLabels2(target);
|
|
294063
|
+
this.params.metrics?.recordCounter("cex_user_asset_poll_attempts_total", 1, labels);
|
|
294064
|
+
try {
|
|
294065
|
+
const exchange = target.account.exchange;
|
|
294066
|
+
if (typeof exchange.sapiV3PostAssetGetUserAsset !== "function") {
|
|
294067
|
+
throw new Error("binance_user_asset_unavailable: getUserAsset is not defined on this exchange instance");
|
|
294068
|
+
}
|
|
294069
|
+
const response = await exchange.sapiV3PostAssetGetUserAsset({});
|
|
294070
|
+
const observedAt = new Date;
|
|
294071
|
+
const normalized = normalizeBinanceUserAssetsForArchive(response);
|
|
294072
|
+
this.params.archiver.enqueue(buildUserAssetSnapshotRow({
|
|
294073
|
+
tags: buildCommonArchiveTags({
|
|
294074
|
+
deploymentId: this.params.archiver.getDeploymentId(),
|
|
294075
|
+
accountSelector: target.account.label,
|
|
294076
|
+
exchange: target.exchangeId,
|
|
294077
|
+
brokerObservedTimestamp: observedAt.toISOString()
|
|
294078
|
+
}),
|
|
294079
|
+
userAssets: normalized
|
|
294080
|
+
}));
|
|
294081
|
+
const successMs = Date.now();
|
|
294082
|
+
this.#lastSuccessMs.set(this.#targetKey(target), successMs);
|
|
294083
|
+
this.params.metrics?.recordCounter("cex_user_asset_poll_successes_total", 1, labels);
|
|
294084
|
+
this.params.metrics?.recordGauge("cex_user_asset_poll_last_success_timestamp_seconds", Math.floor(successMs / 1000), labels);
|
|
294085
|
+
this.#recordFreshness(labels, successMs, successMs);
|
|
294086
|
+
} catch (error) {
|
|
294087
|
+
rethrowArchiveDurabilityError(error);
|
|
294088
|
+
this.params.metrics?.recordCounter("cex_user_asset_poll_failures_total", 1, labels);
|
|
294089
|
+
const now3 = Date.now();
|
|
294090
|
+
const lastSuccess = this.#lastSuccessMs.get(this.#targetKey(target));
|
|
294091
|
+
if (lastSuccess !== undefined) {
|
|
294092
|
+
this.#recordFreshness(labels, lastSuccess, now3);
|
|
294093
|
+
}
|
|
294094
|
+
log.warn("User asset archive poll failed", {
|
|
294095
|
+
exchange: target.exchangeId,
|
|
294096
|
+
account: target.account.label,
|
|
294097
|
+
balanceScope: USER_ASSET_BALANCE_SCOPE,
|
|
294098
|
+
errorType: error instanceof Error ? error.name : "unknown"
|
|
294099
|
+
});
|
|
294100
|
+
}
|
|
294101
|
+
}
|
|
294102
|
+
#recordFreshness(labels, lastSuccessMs, nowMs) {
|
|
294103
|
+
this.params.metrics?.recordGauge("cex_user_asset_poll_freshness_seconds", Math.max(0, (nowMs - lastSuccessMs) / 1000), labels);
|
|
294104
|
+
}
|
|
294105
|
+
#targetKey(target) {
|
|
294106
|
+
return `${target.exchangeId}|${target.account.label}|${USER_ASSET_BALANCE_SCOPE}`;
|
|
294107
|
+
}
|
|
294108
|
+
#schedule(delayMs) {
|
|
294109
|
+
this.#timer = setTimeout(() => void this.#tick(), delayMs);
|
|
294110
|
+
this.#timer.unref?.();
|
|
294111
|
+
}
|
|
294112
|
+
async#tick() {
|
|
294113
|
+
this.#timer = null;
|
|
294114
|
+
try {
|
|
294115
|
+
await this.pollAllOnce();
|
|
294116
|
+
} catch (error) {
|
|
294117
|
+
rethrowArchiveDurabilityError(error);
|
|
294118
|
+
log.error("User asset archive poller tick failed", error);
|
|
294119
|
+
} finally {
|
|
294120
|
+
if (!this.#stopped) {
|
|
294121
|
+
this.#schedule(this.#config.pollIntervalMs);
|
|
294122
|
+
}
|
|
294123
|
+
}
|
|
294124
|
+
}
|
|
294125
|
+
}
|
|
294126
|
+
|
|
293563
294127
|
// src/helpers/binance-user-data-stream.ts
|
|
293564
294128
|
import { Buffer as Buffer2 } from "node:buffer";
|
|
293565
294129
|
import { createHmac } from "node:crypto";
|
|
@@ -305180,7 +305744,7 @@ function finalize(ctx, schema) {
|
|
|
305180
305744
|
result.$schema = "http://json-schema.org/draft-07/schema#";
|
|
305181
305745
|
} else if (ctx.target === "draft-04") {
|
|
305182
305746
|
result.$schema = "http://json-schema.org/draft-04/schema#";
|
|
305183
|
-
} else if (ctx.target === "openapi-3.0") {}
|
|
305747
|
+
} else if (ctx.target === "openapi-3.0") {}
|
|
305184
305748
|
if (ctx.external?.uri) {
|
|
305185
305749
|
const id2 = ctx.external.registry.get(schema)?.id;
|
|
305186
305750
|
if (!id2)
|
|
@@ -305428,7 +305992,7 @@ var literalProcessor = (schema, ctx, json3, _params) => {
|
|
|
305428
305992
|
if (val === undefined) {
|
|
305429
305993
|
if (ctx.unrepresentable === "throw") {
|
|
305430
305994
|
throw new Error("Literal `undefined` cannot be represented in JSON Schema");
|
|
305431
|
-
}
|
|
305995
|
+
}
|
|
305432
305996
|
} else if (typeof val === "bigint") {
|
|
305433
305997
|
if (ctx.unrepresentable === "throw") {
|
|
305434
305998
|
throw new Error("BigInt literals cannot be represented in JSON Schema");
|
|
@@ -308087,7 +308651,7 @@ async function handleDeposit(ctx) {
|
|
|
308087
308651
|
network: depositNetwork?.exchangeNetworkId,
|
|
308088
308652
|
externalId: depositTxid,
|
|
308089
308653
|
txid: depositTxid,
|
|
308090
|
-
exchangeTimestamp:
|
|
308654
|
+
exchangeTimestamp: normalizeTimestamp(creditedAt),
|
|
308091
308655
|
payload: deposit
|
|
308092
308656
|
}
|
|
308093
308657
|
});
|
|
@@ -311605,6 +312169,8 @@ class CEXBroker {
|
|
|
311605
312169
|
fillArchivePoller;
|
|
311606
312170
|
depositArchivePoller;
|
|
311607
312171
|
accountBalanceArchivePoller;
|
|
312172
|
+
userAssetArchivePoller;
|
|
312173
|
+
balanceUpdateArchiveConsumer;
|
|
311608
312174
|
userDataStreamSupervisor;
|
|
311609
312175
|
loadEnvConfig() {
|
|
311610
312176
|
log.info("\uD83D\uDD27 Loading CEX_BROKER_ environment variables:");
|
|
@@ -311758,6 +312324,14 @@ class CEXBroker {
|
|
|
311758
312324
|
await this.accountBalanceArchivePoller.stop();
|
|
311759
312325
|
this.accountBalanceArchivePoller = undefined;
|
|
311760
312326
|
}
|
|
312327
|
+
if (this.userAssetArchivePoller) {
|
|
312328
|
+
await this.userAssetArchivePoller.stop();
|
|
312329
|
+
this.userAssetArchivePoller = undefined;
|
|
312330
|
+
}
|
|
312331
|
+
if (this.balanceUpdateArchiveConsumer) {
|
|
312332
|
+
await this.balanceUpdateArchiveConsumer.stop();
|
|
312333
|
+
this.balanceUpdateArchiveConsumer = undefined;
|
|
312334
|
+
}
|
|
311761
312335
|
if (this.server) {
|
|
311762
312336
|
await this.server.forceShutdown();
|
|
311763
312337
|
}
|
|
@@ -311803,6 +312377,14 @@ class CEXBroker {
|
|
|
311803
312377
|
await this.accountBalanceArchivePoller.stop();
|
|
311804
312378
|
this.accountBalanceArchivePoller = undefined;
|
|
311805
312379
|
}
|
|
312380
|
+
if (this.userAssetArchivePoller) {
|
|
312381
|
+
await this.userAssetArchivePoller.stop();
|
|
312382
|
+
this.userAssetArchivePoller = undefined;
|
|
312383
|
+
}
|
|
312384
|
+
if (this.balanceUpdateArchiveConsumer) {
|
|
312385
|
+
await this.balanceUpdateArchiveConsumer.stop();
|
|
312386
|
+
this.balanceUpdateArchiveConsumer = undefined;
|
|
312387
|
+
}
|
|
311806
312388
|
log.info(`Running CEXBroker at ${new Date().toISOString()}`);
|
|
311807
312389
|
if (this.otelMetrics?.isOtelEnabled()) {
|
|
311808
312390
|
await this.otelMetrics.initialize();
|
|
@@ -311844,6 +312426,14 @@ class CEXBroker {
|
|
|
311844
312426
|
metrics: this.otelMetrics
|
|
311845
312427
|
});
|
|
311846
312428
|
this.depositArchivePoller.start();
|
|
312429
|
+
if (this.userDataStreamSupervisor) {
|
|
312430
|
+
this.balanceUpdateArchiveConsumer = new BalanceUpdateArchiveConsumer({
|
|
312431
|
+
brokers: this.brokers,
|
|
312432
|
+
archiver: this.brokerArchiver,
|
|
312433
|
+
userDataStreamSupervisor: this.userDataStreamSupervisor
|
|
312434
|
+
});
|
|
312435
|
+
this.balanceUpdateArchiveConsumer.start();
|
|
312436
|
+
}
|
|
311847
312437
|
}
|
|
311848
312438
|
if (this.brokerArchiver?.canPersistAccountBalanceSnapshots()) {
|
|
311849
312439
|
this.accountBalanceArchivePoller = new AccountBalanceArchivePoller({
|
|
@@ -311852,6 +312442,12 @@ class CEXBroker {
|
|
|
311852
312442
|
metrics: this.otelMetrics
|
|
311853
312443
|
});
|
|
311854
312444
|
this.accountBalanceArchivePoller.start();
|
|
312445
|
+
this.userAssetArchivePoller = new UserAssetArchivePoller({
|
|
312446
|
+
brokers: this.brokers,
|
|
312447
|
+
archiver: this.brokerArchiver,
|
|
312448
|
+
metrics: this.otelMetrics
|
|
312449
|
+
});
|
|
312450
|
+
this.userAssetArchivePoller.start();
|
|
311855
312451
|
}
|
|
311856
312452
|
return this;
|
|
311857
312453
|
}
|
|
@@ -311860,4 +312456,4 @@ export {
|
|
|
311860
312456
|
CEXBroker as default
|
|
311861
312457
|
};
|
|
311862
312458
|
|
|
311863
|
-
//# debugId=
|
|
312459
|
+
//# debugId=2BB56A1EBD617FCC64756E2164756E21
|