@usherlabs/cex-broker 0.2.18 → 0.2.20
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 +22 -1
- package/dist/commands/cli.js +2692 -1263
- package/dist/handlers/execute-action/context.d.ts +2 -0
- package/dist/handlers/execute-action/handler.d.ts +2 -0
- package/dist/handlers/subscribe/handler.d.ts +2 -0
- package/dist/helpers/broker-execution-archive/capture.d.ts +27 -0
- package/dist/helpers/broker-execution-archive/index.d.ts +5 -0
- package/dist/helpers/broker-execution-archive/redact.d.ts +7 -0
- package/dist/helpers/broker-execution-archive/rows.d.ts +31 -0
- package/dist/helpers/broker-execution-archive/types.d.ts +16 -0
- package/dist/helpers/broker-execution-archive/writer.d.ts +57 -0
- package/dist/helpers/market-data-archive/capture.d.ts +20 -0
- package/dist/helpers/market-data-archive/index.d.ts +9 -0
- package/dist/helpers/market-data-archive/ohlcv-bar-tracker.d.ts +11 -0
- package/dist/helpers/market-data-archive/ohlcv-bootstrap.d.ts +1 -0
- package/dist/helpers/market-data-archive/ohlcv-history.d.ts +8 -0
- package/dist/helpers/market-data-archive/orderbook-depth.d.ts +5 -0
- package/dist/helpers/market-data-archive/orderbook-sampler.d.ts +12 -0
- package/dist/helpers/market-data-archive/parse-stream.d.ts +26 -0
- package/dist/helpers/market-data-archive/rows.d.ts +18 -0
- package/dist/helpers/market-data-archive/types.d.ts +55 -0
- package/dist/helpers/order-telemetry.d.ts +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5659 -4231
- package/dist/index.js.map +26 -12
- package/dist/server.d.ts +2 -1
- package/package.json +9 -1
package/dist/commands/cli.js
CHANGED
|
@@ -313774,9 +313774,6 @@ function createBrokerPool(cfg) {
|
|
|
313774
313774
|
}
|
|
313775
313775
|
return pool;
|
|
313776
313776
|
}
|
|
313777
|
-
function selectBroker(brokers, metadata) {
|
|
313778
|
-
return selectBrokerAccount(brokers, metadata)?.exchange ?? null;
|
|
313779
|
-
}
|
|
313780
313777
|
function getCurrentBrokerSelector(metadata) {
|
|
313781
313778
|
const use_secondary_key = metadata.get("use-secondary-key");
|
|
313782
313779
|
if (!use_secondary_key || use_secondary_key.length === 0) {
|
|
@@ -314996,479 +314993,1257 @@ function validateDeposit(policy, exchange, network, ticker) {
|
|
|
314996
314993
|
return { valid: true };
|
|
314997
314994
|
}
|
|
314998
314995
|
|
|
314999
|
-
// src/helpers/
|
|
315000
|
-
|
|
315001
|
-
|
|
315002
|
-
|
|
315003
|
-
|
|
315004
|
-
|
|
315005
|
-
|
|
315006
|
-
var import_sdk_metrics = __toESM(require_src12(), 1);
|
|
315007
|
-
var DEFAULT_SERVICE = "cex-broker";
|
|
315008
|
-
var DEFAULT_OTLP_PORT = 4318;
|
|
315009
|
-
var EXPORT_INTERVAL_MS = 5000;
|
|
314996
|
+
// src/helpers/shared/guards.ts
|
|
314997
|
+
function isRecord(value) {
|
|
314998
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
314999
|
+
}
|
|
315000
|
+
function asRecord(value) {
|
|
315001
|
+
return isRecord(value) ? value : undefined;
|
|
315002
|
+
}
|
|
315010
315003
|
|
|
315011
|
-
|
|
315012
|
-
|
|
315013
|
-
|
|
315014
|
-
|
|
315015
|
-
|
|
315016
|
-
|
|
315017
|
-
|
|
315018
|
-
|
|
315019
|
-
|
|
315020
|
-
|
|
315021
|
-
|
|
315022
|
-
|
|
315004
|
+
// src/helpers/order-telemetry.ts
|
|
315005
|
+
var NUMERIC_METRICS = [
|
|
315006
|
+
["requestedQuantity", "cex_market_action_requested_quantity"],
|
|
315007
|
+
["requestedNotional", "cex_market_action_requested_notional"],
|
|
315008
|
+
["executedBaseQuantity", "cex_market_action_executed_base_quantity"],
|
|
315009
|
+
["executedQuoteQuantity", "cex_market_action_executed_quote_quantity"],
|
|
315010
|
+
["averageExecutionPrice", "cex_market_action_average_execution_price"],
|
|
315011
|
+
["filledAmount", "cex_market_action_filled_amount"],
|
|
315012
|
+
["remainingAmount", "cex_market_action_remaining_amount"],
|
|
315013
|
+
["feeAmount", "cex_market_action_fee_amount"],
|
|
315014
|
+
["feeRate", "cex_market_action_fee_rate"]
|
|
315015
|
+
];
|
|
315016
|
+
var REDACTED_ERROR_MESSAGE = "redacted_error";
|
|
315017
|
+
async function emitOrderExecutionTelemetry(otelMetrics, context2, order, error) {
|
|
315018
|
+
try {
|
|
315019
|
+
const telemetry = buildOrderExecutionTelemetry(context2, order, error);
|
|
315020
|
+
log.info("CEX market action execution telemetry", telemetry);
|
|
315021
|
+
const labels = {
|
|
315022
|
+
action: telemetry.action,
|
|
315023
|
+
cex: telemetry.cex,
|
|
315024
|
+
account: telemetry.accountLabel,
|
|
315025
|
+
symbol: telemetry.symbol,
|
|
315026
|
+
side: telemetry.side,
|
|
315027
|
+
order_type: telemetry.orderType,
|
|
315028
|
+
status: telemetry.status
|
|
315029
|
+
};
|
|
315030
|
+
await otelMetrics?.recordCounter("cex_market_action_executions_total", 1, {
|
|
315031
|
+
...labels,
|
|
315032
|
+
result: error ? "error" : "ok"
|
|
315033
|
+
});
|
|
315034
|
+
for (const [key, metricName] of NUMERIC_METRICS) {
|
|
315035
|
+
const value = telemetry[key];
|
|
315036
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
315037
|
+
await otelMetrics?.recordHistogram(metricName, value, labels);
|
|
315038
|
+
}
|
|
315023
315039
|
}
|
|
315040
|
+
return telemetry;
|
|
315041
|
+
} catch (telemetryError) {
|
|
315024
315042
|
try {
|
|
315025
|
-
|
|
315026
|
-
|
|
315027
|
-
|
|
315028
|
-
|
|
315029
|
-
|
|
315030
|
-
|
|
315031
|
-
|
|
315032
|
-
|
|
315043
|
+
log.error("Failed to emit CEX order telemetry", {
|
|
315044
|
+
error: telemetryError
|
|
315045
|
+
});
|
|
315046
|
+
} catch {}
|
|
315047
|
+
return;
|
|
315048
|
+
}
|
|
315049
|
+
}
|
|
315050
|
+
function buildOrderExecutionTelemetry(context2, order, error) {
|
|
315051
|
+
const record = asRecord(order);
|
|
315052
|
+
const info = asRecord(record?.info);
|
|
315053
|
+
const fees = getFees(record, info);
|
|
315054
|
+
const fee = summarizeFees(fees);
|
|
315055
|
+
const executedBaseQuantity = firstNumber(record?.filled, info?.executedQty, info?.cumExecQty) ?? computeFilledFromAmount(record);
|
|
315056
|
+
const executedQuoteQuantity = firstNumber(record?.cost, info?.cummulativeQuoteQty, info?.cumQuote, info?.cumExecValue);
|
|
315057
|
+
const averageExecutionPrice = firstNumber(record?.average, info?.avgPrice) ?? computeAveragePrice(executedBaseQuantity, executedQuoteQuantity);
|
|
315058
|
+
const exchangeTimestamp = normalizeTimestamp(firstValue(record?.timestamp, info?.time, info?.transactTime, record?.datetime));
|
|
315059
|
+
const status = firstString(record?.status, info?.status) ?? (error ? "failed" : "unknown");
|
|
315060
|
+
const errorRecord = error instanceof Error ? error : undefined;
|
|
315061
|
+
return compactUndefined({
|
|
315062
|
+
event: "cex_market_action_execution",
|
|
315063
|
+
action: context2.action,
|
|
315064
|
+
cex: context2.cex.trim().toLowerCase() || "unknown",
|
|
315065
|
+
accountLabel: context2.accountLabel ?? "unknown",
|
|
315066
|
+
symbol: firstString(record?.symbol, info?.symbol, context2.symbol) ?? "unknown",
|
|
315067
|
+
side: firstString(record?.side, info?.side, context2.side) ?? "unknown",
|
|
315068
|
+
orderType: firstString(record?.type, info?.type, context2.orderType) ?? "unknown",
|
|
315069
|
+
orderId: firstString(record?.id, info?.orderId, info?.orderID),
|
|
315070
|
+
clientOrderId: firstString(context2.clientOrderId, record?.clientOrderId, record?.clientOrderID, record?.clientOid, info?.clientOrderId, info?.clientOrderID, info?.clientOid),
|
|
315071
|
+
idempotencyId: context2.idempotencyId,
|
|
315072
|
+
makerActionId: context2.makerActionId,
|
|
315073
|
+
status: status.toLowerCase(),
|
|
315074
|
+
requestedQuantity: context2.requestedQuantity ?? firstNumber(record?.amount),
|
|
315075
|
+
requestedNotional: context2.requestedNotional,
|
|
315076
|
+
executedBaseQuantity,
|
|
315077
|
+
executedQuoteQuantity,
|
|
315078
|
+
averageExecutionPrice,
|
|
315079
|
+
filledAmount: firstNumber(record?.filled, info?.executedQty),
|
|
315080
|
+
remainingAmount: firstNumber(record?.remaining, info?.remainingQty),
|
|
315081
|
+
feeAmount: fee.amount,
|
|
315082
|
+
feeCurrency: fee.currency,
|
|
315083
|
+
feeRate: fee.rate,
|
|
315084
|
+
exchangeTimestamp,
|
|
315085
|
+
brokerObservedTimestamp: context2.brokerObservedTimestamp ?? new Date().toISOString(),
|
|
315086
|
+
errorType: errorRecord?.name,
|
|
315087
|
+
errorMessage: errorRecord ? REDACTED_ERROR_MESSAGE : undefined
|
|
315088
|
+
});
|
|
315089
|
+
}
|
|
315090
|
+
function emitOrderExecutionTelemetryInBackground(otelMetrics, context2, order, error) {
|
|
315091
|
+
emitOrderExecutionTelemetry(otelMetrics, context2, order, error).catch((telemetryError) => {
|
|
315092
|
+
try {
|
|
315093
|
+
log.warn("Telemetry emit failed", { error: telemetryError });
|
|
315094
|
+
} catch {
|
|
315095
|
+
console.warn("Telemetry emit failed", telemetryError);
|
|
315096
|
+
}
|
|
315097
|
+
});
|
|
315098
|
+
}
|
|
315099
|
+
function extractOrderTelemetryIds(params) {
|
|
315100
|
+
const record = params ?? {};
|
|
315101
|
+
return {
|
|
315102
|
+
clientOrderId: firstString(record.clientOrderId, record.clientOrderID, record.newClientOrderId, record.clientOid),
|
|
315103
|
+
idempotencyId: firstString(record.idempotencyId, record.idempotencyID, record.idempotencyKey, record.requestId, record.requestID),
|
|
315104
|
+
makerActionId: firstString(record.makerActionId, record.maker_action_id, record.actionId, record.action_id)
|
|
315105
|
+
};
|
|
315106
|
+
}
|
|
315107
|
+
function firstValue(...values2) {
|
|
315108
|
+
return values2.find((value) => value !== undefined && value !== null);
|
|
315109
|
+
}
|
|
315110
|
+
function firstString(...values2) {
|
|
315111
|
+
for (const value of values2) {
|
|
315112
|
+
if (typeof value === "string" && value.trim()) {
|
|
315113
|
+
return value.trim();
|
|
315114
|
+
}
|
|
315115
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
315116
|
+
return String(value);
|
|
315033
315117
|
}
|
|
315034
315118
|
}
|
|
315035
|
-
|
|
315036
|
-
|
|
315037
|
-
|
|
315038
|
-
|
|
315119
|
+
return;
|
|
315120
|
+
}
|
|
315121
|
+
function firstNumber(...values2) {
|
|
315122
|
+
for (const value of values2) {
|
|
315123
|
+
const numberValue = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
|
|
315124
|
+
if (Number.isFinite(numberValue)) {
|
|
315125
|
+
return numberValue;
|
|
315126
|
+
}
|
|
315039
315127
|
}
|
|
315040
|
-
|
|
315041
|
-
|
|
315128
|
+
return;
|
|
315129
|
+
}
|
|
315130
|
+
function computeFilledFromAmount(record) {
|
|
315131
|
+
const amount = firstNumber(record?.amount);
|
|
315132
|
+
const remaining = firstNumber(record?.remaining);
|
|
315133
|
+
if (amount === undefined || remaining === undefined) {
|
|
315134
|
+
return;
|
|
315042
315135
|
}
|
|
315043
|
-
|
|
315044
|
-
|
|
315136
|
+
return amount - remaining;
|
|
315137
|
+
}
|
|
315138
|
+
function computeAveragePrice(executedBaseQuantity, executedQuoteQuantity) {
|
|
315139
|
+
if (executedBaseQuantity === undefined || executedQuoteQuantity === undefined || executedBaseQuantity === 0) {
|
|
315140
|
+
return;
|
|
315045
315141
|
}
|
|
315046
|
-
|
|
315047
|
-
|
|
315048
|
-
|
|
315142
|
+
return executedQuoteQuantity / executedBaseQuantity;
|
|
315143
|
+
}
|
|
315144
|
+
function normalizeTimestamp(value) {
|
|
315145
|
+
if (typeof value === "string" && value.trim()) {
|
|
315146
|
+
return value.trim();
|
|
315147
|
+
}
|
|
315148
|
+
const timestamp = firstNumber(value);
|
|
315149
|
+
if (timestamp === undefined) {
|
|
315150
|
+
return;
|
|
315151
|
+
}
|
|
315152
|
+
const date = new Date(timestamp);
|
|
315153
|
+
return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
|
|
315154
|
+
}
|
|
315155
|
+
function getFees(record, info) {
|
|
315156
|
+
const fees = [];
|
|
315157
|
+
if (record?.fee)
|
|
315158
|
+
fees.push(record.fee);
|
|
315159
|
+
if (Array.isArray(record?.fees))
|
|
315160
|
+
fees.push(...record.fees);
|
|
315161
|
+
if (Array.isArray(record?.trades)) {
|
|
315162
|
+
for (const trade of record.trades) {
|
|
315163
|
+
const tradeRecord = asRecord(trade);
|
|
315164
|
+
if (tradeRecord?.fee)
|
|
315165
|
+
fees.push(tradeRecord.fee);
|
|
315166
|
+
if (Array.isArray(tradeRecord?.fees))
|
|
315167
|
+
fees.push(...tradeRecord.fees);
|
|
315049
315168
|
}
|
|
315050
|
-
|
|
315051
|
-
|
|
315052
|
-
|
|
315053
|
-
|
|
315054
|
-
|
|
315169
|
+
}
|
|
315170
|
+
if (Array.isArray(info?.fills)) {
|
|
315171
|
+
for (const fill of info.fills) {
|
|
315172
|
+
const fillRecord = asRecord(fill);
|
|
315173
|
+
const commission = firstNumber(fillRecord?.commission);
|
|
315174
|
+
if (commission !== undefined) {
|
|
315175
|
+
fees.push({
|
|
315176
|
+
cost: commission,
|
|
315177
|
+
currency: firstString(fillRecord?.commissionAsset)
|
|
315178
|
+
});
|
|
315179
|
+
}
|
|
315055
315180
|
}
|
|
315056
|
-
this.provider = null;
|
|
315057
|
-
this.isEnabled = false;
|
|
315058
|
-
this.onProviderClosed();
|
|
315059
315181
|
}
|
|
315182
|
+
return fees;
|
|
315060
315183
|
}
|
|
315061
|
-
function
|
|
315062
|
-
|
|
315063
|
-
|
|
315064
|
-
|
|
315065
|
-
|
|
315066
|
-
|
|
315184
|
+
function summarizeFees(fees) {
|
|
315185
|
+
let amount = 0;
|
|
315186
|
+
let amountFound = false;
|
|
315187
|
+
let currency;
|
|
315188
|
+
let rate;
|
|
315189
|
+
for (const rawFee of fees) {
|
|
315190
|
+
const fee = asRecord(rawFee);
|
|
315191
|
+
if (!fee)
|
|
315192
|
+
continue;
|
|
315193
|
+
const cost = firstNumber(fee.cost, fee.amount);
|
|
315194
|
+
if (cost !== undefined) {
|
|
315195
|
+
amount += cost;
|
|
315196
|
+
amountFound = true;
|
|
315067
315197
|
}
|
|
315198
|
+
currency ??= firstString(fee.currency);
|
|
315199
|
+
rate ??= firstNumber(fee.rate);
|
|
315068
315200
|
}
|
|
315069
|
-
return
|
|
315201
|
+
return {
|
|
315202
|
+
amount: amountFound ? amount : undefined,
|
|
315203
|
+
currency,
|
|
315204
|
+
rate
|
|
315205
|
+
};
|
|
315206
|
+
}
|
|
315207
|
+
function compactUndefined(record) {
|
|
315208
|
+
return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
|
|
315070
315209
|
}
|
|
315071
315210
|
|
|
315072
|
-
|
|
315073
|
-
|
|
315074
|
-
|
|
315075
|
-
|
|
315076
|
-
|
|
315211
|
+
// src/helpers/broker-execution-archive/redact.ts
|
|
315212
|
+
import { createHash } from "node:crypto";
|
|
315213
|
+
var SECRET_KEY_PATTERN = /\b(api[_-]?key|api[_-]?secret|secret|signature|passphrase|password|token|credential)\b/i;
|
|
315214
|
+
var SECRET_VALUE_PATTERN = /(\b(?:apiKey|api_key|apiSecret|api_secret|secret|signature|passphrase|password|token)\b\s*[=:]\s*)[^\s&,;)}\]]+/gi;
|
|
315215
|
+
var SECRET_JSON_PATTERN = /("(?:apiKey|api_key|apiSecret|api_secret|secret|signature|passphrase|password|token)"\s*:\s*")[^"]*(")/gi;
|
|
315216
|
+
function redactSecretLiterals(value, secretLiterals = []) {
|
|
315217
|
+
let redacted = value;
|
|
315218
|
+
for (const literal of secretLiterals) {
|
|
315219
|
+
if (literal.length > 0) {
|
|
315220
|
+
redacted = redacted.split(literal).join("[redacted]");
|
|
315221
|
+
}
|
|
315077
315222
|
}
|
|
315078
|
-
|
|
315079
|
-
|
|
315080
|
-
|
|
315081
|
-
|
|
315082
|
-
|
|
315083
|
-
exporter,
|
|
315084
|
-
exportIntervalMillis: EXPORT_INTERVAL_MS
|
|
315085
|
-
});
|
|
315086
|
-
const resource = import_resources.resourceFromAttributes({
|
|
315087
|
-
"service.name": serviceName
|
|
315088
|
-
});
|
|
315089
|
-
return new import_sdk_metrics.MeterProvider({
|
|
315090
|
-
resource,
|
|
315091
|
-
readers: [reader]
|
|
315092
|
-
});
|
|
315223
|
+
return redacted.replace(SECRET_VALUE_PATTERN, "$1[redacted]").replace(SECRET_JSON_PATTERN, "$1[redacted]$2");
|
|
315224
|
+
}
|
|
315225
|
+
function redactUnknownValue(value, secretLiterals) {
|
|
315226
|
+
if (value === null || value === undefined) {
|
|
315227
|
+
return value;
|
|
315093
315228
|
}
|
|
315094
|
-
|
|
315095
|
-
|
|
315229
|
+
if (typeof value === "string") {
|
|
315230
|
+
return redactSecretLiterals(value, secretLiterals);
|
|
315096
315231
|
}
|
|
315097
|
-
|
|
315098
|
-
return
|
|
315232
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
315233
|
+
return value;
|
|
315099
315234
|
}
|
|
315100
|
-
|
|
315101
|
-
|
|
315102
|
-
|
|
315235
|
+
if (Array.isArray(value)) {
|
|
315236
|
+
return value.map((entry) => redactUnknownValue(entry, secretLiterals));
|
|
315237
|
+
}
|
|
315238
|
+
if (typeof value === "object") {
|
|
315239
|
+
const record = value;
|
|
315240
|
+
const redacted = {};
|
|
315241
|
+
for (const [key, entry] of Object.entries(record)) {
|
|
315242
|
+
if (SECRET_KEY_PATTERN.test(key)) {
|
|
315243
|
+
redacted[key] = "[redacted]";
|
|
315244
|
+
continue;
|
|
315245
|
+
}
|
|
315246
|
+
redacted[key] = redactUnknownValue(entry, secretLiterals);
|
|
315103
315247
|
}
|
|
315248
|
+
return redacted;
|
|
315104
315249
|
}
|
|
315105
|
-
|
|
315106
|
-
|
|
315107
|
-
|
|
315108
|
-
|
|
315109
|
-
|
|
315110
|
-
|
|
315111
|
-
|
|
315112
|
-
} else if (metric.metric_type === "gauge") {
|
|
315113
|
-
await this.recordGauge(metric.metric_name, metric.value, labels, metric.service);
|
|
315114
|
-
} else {
|
|
315115
|
-
await this.recordHistogram(metric.metric_name, metric.value, labels, metric.service);
|
|
315116
|
-
}
|
|
315117
|
-
} catch {}
|
|
315250
|
+
return String(value);
|
|
315251
|
+
}
|
|
315252
|
+
function redactStreamPayload(payload, secretLiterals = []) {
|
|
315253
|
+
if (Array.isArray(payload)) {
|
|
315254
|
+
return {
|
|
315255
|
+
items: payload.map((entry) => redactUnknownValue(entry, secretLiterals))
|
|
315256
|
+
};
|
|
315118
315257
|
}
|
|
315119
|
-
|
|
315120
|
-
|
|
315258
|
+
const record = asRecord(payload);
|
|
315259
|
+
if (!record) {
|
|
315260
|
+
return {};
|
|
315261
|
+
}
|
|
315262
|
+
return redactUnknownValue(record, secretLiterals);
|
|
315263
|
+
}
|
|
315264
|
+
function hashMarketMetadata(payload) {
|
|
315265
|
+
if (payload === undefined || payload === null) {
|
|
315266
|
+
return;
|
|
315267
|
+
}
|
|
315268
|
+
try {
|
|
315269
|
+
const normalized = JSON.stringify(payload);
|
|
315270
|
+
if (!normalized || normalized === "{}") {
|
|
315121
315271
|
return;
|
|
315122
|
-
for (const m of metricsList) {
|
|
315123
|
-
await this.insertMetric(m);
|
|
315124
315272
|
}
|
|
315273
|
+
return createHash("sha256").update(normalized).digest("hex");
|
|
315274
|
+
} catch {
|
|
315275
|
+
return;
|
|
315125
315276
|
}
|
|
315126
|
-
|
|
315127
|
-
|
|
315128
|
-
|
|
315129
|
-
|
|
315130
|
-
|
|
315131
|
-
|
|
315132
|
-
|
|
315133
|
-
|
|
315134
|
-
|
|
315135
|
-
|
|
315136
|
-
|
|
315137
|
-
|
|
315138
|
-
|
|
315139
|
-
log.error("Failed to record counter:", error);
|
|
315277
|
+
}
|
|
315278
|
+
|
|
315279
|
+
// src/helpers/broker-execution-archive/types.ts
|
|
315280
|
+
var BROKER_WRITE_SOURCE = "broker_write";
|
|
315281
|
+
|
|
315282
|
+
// src/helpers/broker-execution-archive/rows.ts
|
|
315283
|
+
function firstString2(...values2) {
|
|
315284
|
+
for (const value of values2) {
|
|
315285
|
+
if (typeof value === "string" && value.trim()) {
|
|
315286
|
+
return value.trim();
|
|
315287
|
+
}
|
|
315288
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
315289
|
+
return String(value);
|
|
315140
315290
|
}
|
|
315141
315291
|
}
|
|
315142
|
-
|
|
315143
|
-
|
|
315144
|
-
|
|
315145
|
-
|
|
315292
|
+
return;
|
|
315293
|
+
}
|
|
315294
|
+
function compactUndefined2(record) {
|
|
315295
|
+
return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
|
|
315296
|
+
}
|
|
315297
|
+
function buildCommonArchiveTags(input) {
|
|
315298
|
+
return {
|
|
315299
|
+
source: BROKER_WRITE_SOURCE,
|
|
315300
|
+
deployment_id: input.deploymentId,
|
|
315301
|
+
account_selector: input.accountSelector ?? "unknown",
|
|
315302
|
+
exchange: input.exchange.trim().toLowerCase() || "unknown",
|
|
315303
|
+
symbol: input.symbol?.trim() || "unknown",
|
|
315304
|
+
broker_observed_timestamp: input.brokerObservedTimestamp ?? new Date().toISOString()
|
|
315305
|
+
};
|
|
315306
|
+
}
|
|
315307
|
+
function buildOrderEventArchiveRow(input) {
|
|
315308
|
+
const { tags, telemetry, action } = input;
|
|
315309
|
+
return {
|
|
315310
|
+
table: "broker_execution.order_events",
|
|
315311
|
+
row: compactUndefined2({
|
|
315312
|
+
...tags,
|
|
315313
|
+
event_kind: input.eventKind ?? "execute_action",
|
|
315314
|
+
action,
|
|
315315
|
+
subscription_type: input.subscriptionType,
|
|
315316
|
+
order_id: telemetry.orderId,
|
|
315317
|
+
client_order_id: telemetry.clientOrderId,
|
|
315318
|
+
idempotency_id: telemetry.idempotencyId,
|
|
315319
|
+
maker_action_id: telemetry.makerActionId,
|
|
315320
|
+
market_metadata_hash: input.marketMetadataHash,
|
|
315321
|
+
status: telemetry.status,
|
|
315322
|
+
side: telemetry.side,
|
|
315323
|
+
order_type: telemetry.orderType,
|
|
315324
|
+
requested_quantity: telemetry.requestedQuantity,
|
|
315325
|
+
requested_notional: telemetry.requestedNotional,
|
|
315326
|
+
executed_base_quantity: telemetry.executedBaseQuantity,
|
|
315327
|
+
executed_quote_quantity: telemetry.executedQuoteQuantity,
|
|
315328
|
+
average_execution_price: telemetry.averageExecutionPrice,
|
|
315329
|
+
filled_amount: telemetry.filledAmount,
|
|
315330
|
+
remaining_amount: telemetry.remainingAmount,
|
|
315331
|
+
fee_amount: telemetry.feeAmount,
|
|
315332
|
+
fee_currency: telemetry.feeCurrency,
|
|
315333
|
+
fee_rate: telemetry.feeRate,
|
|
315334
|
+
exchange_timestamp: telemetry.exchangeTimestamp,
|
|
315335
|
+
error_type: telemetry.errorType,
|
|
315336
|
+
error_message: telemetry.errorMessage,
|
|
315337
|
+
payload_json: JSON.stringify(telemetry)
|
|
315338
|
+
})
|
|
315339
|
+
};
|
|
315340
|
+
}
|
|
315341
|
+
function buildSubscribeStreamArchiveRow(input) {
|
|
315342
|
+
const redactedPayload = redactStreamPayload(input.streamPayload, input.secretLiterals);
|
|
315343
|
+
const record = asRecord(input.streamPayload);
|
|
315344
|
+
const info = asRecord(record?.info);
|
|
315345
|
+
return {
|
|
315346
|
+
table: "broker_execution.order_events",
|
|
315347
|
+
row: compactUndefined2({
|
|
315348
|
+
...input.tags,
|
|
315349
|
+
event_kind: "subscribe_stream",
|
|
315350
|
+
subscription_type: input.subscriptionType,
|
|
315351
|
+
order_id: firstString2(record?.id, record?.orderId, record?.i, info?.orderId, info?.i),
|
|
315352
|
+
client_order_id: firstString2(record?.clientOrderId, record?.clientOrderID, record?.c, info?.clientOrderId, info?.c),
|
|
315353
|
+
status: firstString2(record?.status, record?.X, info?.status, info?.X)?.toLowerCase(),
|
|
315354
|
+
payload_json: JSON.stringify(redactedPayload)
|
|
315355
|
+
})
|
|
315356
|
+
};
|
|
315357
|
+
}
|
|
315358
|
+
function buildMarketMetadataSnapshotRow(input) {
|
|
315359
|
+
const redactedSnapshot = redactStreamPayload(input.marketSnapshot);
|
|
315360
|
+
const metadataHash = hashMarketMetadata(redactedSnapshot);
|
|
315361
|
+
return {
|
|
315362
|
+
table: "broker_execution.market_metadata_snapshots",
|
|
315363
|
+
row: compactUndefined2({
|
|
315364
|
+
...input.tags,
|
|
315365
|
+
client_order_id: input.clientOrderId,
|
|
315366
|
+
order_id: input.orderId,
|
|
315367
|
+
maker_action_id: input.makerActionId,
|
|
315368
|
+
idempotency_id: input.idempotencyId,
|
|
315369
|
+
market_metadata_hash: metadataHash,
|
|
315370
|
+
snapshot_json: JSON.stringify(redactedSnapshot)
|
|
315371
|
+
})
|
|
315372
|
+
};
|
|
315373
|
+
}
|
|
315374
|
+
|
|
315375
|
+
// src/helpers/broker-execution-archive/capture.ts
|
|
315376
|
+
function archiveOrderExecutionInBackground(archiver, context2, order, error, options) {
|
|
315377
|
+
if (!archiver?.isEnabled()) {
|
|
315378
|
+
return;
|
|
315379
|
+
}
|
|
315380
|
+
queueMicrotask(() => {
|
|
315146
315381
|
try {
|
|
315147
|
-
|
|
315148
|
-
|
|
315149
|
-
|
|
315150
|
-
|
|
315151
|
-
|
|
315152
|
-
|
|
315153
|
-
|
|
315154
|
-
}
|
|
315155
|
-
|
|
315156
|
-
|
|
315157
|
-
|
|
315382
|
+
const telemetry = buildOrderExecutionTelemetry(context2, order, error);
|
|
315383
|
+
const tags = buildCommonArchiveTags({
|
|
315384
|
+
deploymentId: archiver.getDeploymentId(),
|
|
315385
|
+
accountSelector: context2.accountLabel,
|
|
315386
|
+
exchange: context2.cex,
|
|
315387
|
+
symbol: telemetry.symbol,
|
|
315388
|
+
brokerObservedTimestamp: telemetry.brokerObservedTimestamp
|
|
315389
|
+
});
|
|
315390
|
+
archiver.enqueue(buildOrderEventArchiveRow({
|
|
315391
|
+
tags,
|
|
315392
|
+
action: context2.action,
|
|
315393
|
+
telemetry,
|
|
315394
|
+
marketMetadataHash: options?.marketMetadataHash
|
|
315395
|
+
}));
|
|
315396
|
+
} catch (archiveError) {
|
|
315397
|
+
log.warn("Failed to archive order execution", { error: archiveError });
|
|
315158
315398
|
}
|
|
315399
|
+
});
|
|
315400
|
+
}
|
|
315401
|
+
function archiveSubscribeStreamInBackground(archiver, input) {
|
|
315402
|
+
if (!archiver?.isEnabled()) {
|
|
315403
|
+
return;
|
|
315159
315404
|
}
|
|
315160
|
-
|
|
315161
|
-
const provider = this.getProvider();
|
|
315162
|
-
if (!this.isOtelEnabled() || !provider)
|
|
315163
|
-
return;
|
|
315405
|
+
queueMicrotask(() => {
|
|
315164
315406
|
try {
|
|
315165
|
-
|
|
315166
|
-
|
|
315167
|
-
|
|
315168
|
-
|
|
315169
|
-
|
|
315170
|
-
}
|
|
315171
|
-
|
|
315172
|
-
|
|
315173
|
-
|
|
315407
|
+
const tags = buildCommonArchiveTags({
|
|
315408
|
+
deploymentId: archiver.getDeploymentId(),
|
|
315409
|
+
accountSelector: input.accountSelector,
|
|
315410
|
+
exchange: input.exchange,
|
|
315411
|
+
symbol: input.symbol
|
|
315412
|
+
});
|
|
315413
|
+
archiver.enqueue(buildSubscribeStreamArchiveRow({
|
|
315414
|
+
tags,
|
|
315415
|
+
subscriptionType: input.subscriptionType,
|
|
315416
|
+
streamPayload: input.streamPayload,
|
|
315417
|
+
secretLiterals: input.secretLiterals
|
|
315418
|
+
}));
|
|
315419
|
+
} catch (archiveError) {
|
|
315420
|
+
log.warn("Failed to archive subscribe stream event", {
|
|
315421
|
+
error: archiveError
|
|
315422
|
+
});
|
|
315174
315423
|
}
|
|
315175
|
-
}
|
|
315424
|
+
});
|
|
315176
315425
|
}
|
|
315177
|
-
|
|
315178
|
-
|
|
315179
|
-
|
|
315180
|
-
constructor(config) {
|
|
315181
|
-
super(config, "logs");
|
|
315426
|
+
async function captureMarketMetadataSnapshot(archiver, broker, input) {
|
|
315427
|
+
if (!archiver?.canPersistMarketMetadataSnapshot()) {
|
|
315428
|
+
return;
|
|
315182
315429
|
}
|
|
315183
|
-
|
|
315184
|
-
const
|
|
315185
|
-
|
|
315430
|
+
try {
|
|
315431
|
+
const fetchOrderBook = broker.fetchOrderBook;
|
|
315432
|
+
if (typeof fetchOrderBook !== "function") {
|
|
315433
|
+
return;
|
|
315434
|
+
}
|
|
315435
|
+
const orderBook = await fetchOrderBook.call(broker, input.symbol, 5);
|
|
315436
|
+
const record = asRecord(orderBook);
|
|
315437
|
+
const snapshot = {
|
|
315438
|
+
action: input.action,
|
|
315439
|
+
symbol: input.symbol,
|
|
315440
|
+
bids: record?.bids,
|
|
315441
|
+
asks: record?.asks,
|
|
315442
|
+
timestamp: record?.timestamp ?? Date.now(),
|
|
315443
|
+
datetime: record?.datetime,
|
|
315444
|
+
nonce: record?.nonce
|
|
315445
|
+
};
|
|
315446
|
+
const tags = buildCommonArchiveTags({
|
|
315447
|
+
deploymentId: archiver.getDeploymentId(),
|
|
315448
|
+
accountSelector: input.accountSelector,
|
|
315449
|
+
exchange: input.exchange,
|
|
315450
|
+
symbol: input.symbol,
|
|
315451
|
+
brokerObservedTimestamp: input.brokerObservedTimestamp
|
|
315186
315452
|
});
|
|
315187
|
-
const
|
|
315188
|
-
|
|
315189
|
-
|
|
315453
|
+
const row = buildMarketMetadataSnapshotRow({
|
|
315454
|
+
tags,
|
|
315455
|
+
clientOrderId: input.clientOrderId,
|
|
315456
|
+
orderId: input.orderId,
|
|
315457
|
+
makerActionId: input.makerActionId,
|
|
315458
|
+
idempotencyId: input.idempotencyId,
|
|
315459
|
+
marketSnapshot: snapshot
|
|
315190
315460
|
});
|
|
315191
|
-
|
|
315192
|
-
|
|
315193
|
-
|
|
315461
|
+
archiver.enqueue(row);
|
|
315462
|
+
const hash4 = row.row.market_metadata_hash;
|
|
315463
|
+
return typeof hash4 === "string" ? hash4 : undefined;
|
|
315464
|
+
} catch (archiveError) {
|
|
315465
|
+
log.warn("Failed to capture market metadata snapshot", {
|
|
315466
|
+
error: archiveError
|
|
315194
315467
|
});
|
|
315468
|
+
return;
|
|
315195
315469
|
}
|
|
315196
|
-
|
|
315197
|
-
|
|
315198
|
-
|
|
315199
|
-
|
|
315200
|
-
|
|
315201
|
-
|
|
315202
|
-
|
|
315203
|
-
|
|
315204
|
-
|
|
315470
|
+
}
|
|
315471
|
+
// src/helpers/broker-execution-archive/writer.ts
|
|
315472
|
+
var import_api_logs2 = __toESM(require_src7(), 1);
|
|
315473
|
+
import { request as httpRequest2 } from "node:http";
|
|
315474
|
+
import { request as httpsRequest2 } from "node:https";
|
|
315475
|
+
|
|
315476
|
+
// src/helpers/market-data-archive/types.ts
|
|
315477
|
+
var MARKET_ARCHIVE_TABLES = new Set([
|
|
315478
|
+
"market_data.orderbook_snapshots",
|
|
315479
|
+
"market_data.candles",
|
|
315480
|
+
"market_data.cex_stream_events",
|
|
315481
|
+
"market_data.cex_ticker_events",
|
|
315482
|
+
"market_data.cex_trades"
|
|
315483
|
+
]);
|
|
315484
|
+
function isMarketArchiveTable(table) {
|
|
315485
|
+
return MARKET_ARCHIVE_TABLES.has(table);
|
|
315486
|
+
}
|
|
315487
|
+
|
|
315488
|
+
// src/helpers/broker-execution-archive/writer.ts
|
|
315489
|
+
var DEFAULT_MAX_QUEUE_SIZE = 1e4;
|
|
315490
|
+
var DEFAULT_BATCH_SIZE = 10;
|
|
315491
|
+
var DEFAULT_FLUSH_INTERVAL_MS = 1000;
|
|
315492
|
+
var DEFAULT_FORWARDER_TIMEOUT_MS = 3000;
|
|
315493
|
+
var DEFAULT_ARCHIVE_FORWARDER_PATH = "/archive";
|
|
315494
|
+
var DEFAULT_ARCHIVE_FORWARDER_PORT = 8090;
|
|
315495
|
+
function isArchiveOtelLogsEnabled() {
|
|
315496
|
+
return process.env.CEX_BROKER_ARCHIVE_OTEL_LOGS_ENABLED === "true";
|
|
315497
|
+
}
|
|
315498
|
+
function resolveArchiveForwarderUrlFromEnv() {
|
|
315499
|
+
const explicit = process.env.CEX_BROKER_ARCHIVE_FORWARDER_URL?.trim();
|
|
315500
|
+
if (explicit) {
|
|
315501
|
+
return explicit;
|
|
315502
|
+
}
|
|
315503
|
+
const host = process.env.CEX_BROKER_ARCHIVE_FORWARDER_HOST?.trim() || process.env.CEX_BROKER_CLICKHOUSE_HOST?.trim();
|
|
315504
|
+
if (!host) {
|
|
315505
|
+
return;
|
|
315205
315506
|
}
|
|
315206
|
-
|
|
315207
|
-
|
|
315208
|
-
|
|
315507
|
+
const protocol = process.env.CEX_BROKER_CLICKHOUSE_PROTOCOL || "http";
|
|
315508
|
+
const port = parsePositiveInt(process.env.CEX_BROKER_ARCHIVE_FORWARDER_PORT, DEFAULT_ARCHIVE_FORWARDER_PORT);
|
|
315509
|
+
const path = process.env.CEX_BROKER_ARCHIVE_FORWARDER_PATH?.trim() || DEFAULT_ARCHIVE_FORWARDER_PATH;
|
|
315510
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
315511
|
+
return `${protocol}://${host}:${port}${normalizedPath}`;
|
|
315512
|
+
}
|
|
315513
|
+
|
|
315514
|
+
class BrokerExecutionArchiver {
|
|
315515
|
+
deploymentId;
|
|
315516
|
+
otelLogs;
|
|
315517
|
+
otelMetrics;
|
|
315518
|
+
forwarderUrl;
|
|
315519
|
+
maxQueueSize;
|
|
315520
|
+
batchSize;
|
|
315521
|
+
flushIntervalMs;
|
|
315522
|
+
forwarderTimeoutMs;
|
|
315523
|
+
queue = [];
|
|
315524
|
+
stats = {
|
|
315525
|
+
enqueued: 0,
|
|
315526
|
+
shed: 0,
|
|
315527
|
+
flushed: 0,
|
|
315528
|
+
forwarderFailures: 0
|
|
315529
|
+
};
|
|
315530
|
+
flushTimer = null;
|
|
315531
|
+
flushInFlight = null;
|
|
315532
|
+
closed = false;
|
|
315533
|
+
loggedMissingMarketForwarder = false;
|
|
315534
|
+
enabled;
|
|
315535
|
+
forwarderAuthToken;
|
|
315536
|
+
constructor(options) {
|
|
315537
|
+
this.deploymentId = options.deploymentId?.trim() || process.env.CEX_BROKER_DEPLOYMENT_ID?.trim() || "unknown";
|
|
315538
|
+
this.otelLogs = options.otelLogs;
|
|
315539
|
+
this.otelMetrics = options.otelMetrics;
|
|
315540
|
+
this.forwarderUrl = options.forwarderUrl?.trim();
|
|
315541
|
+
this.maxQueueSize = options.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;
|
|
315542
|
+
this.batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
315543
|
+
this.flushIntervalMs = options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
|
|
315544
|
+
this.forwarderTimeoutMs = options.forwarderTimeoutMs ?? DEFAULT_FORWARDER_TIMEOUT_MS;
|
|
315545
|
+
this.enabled = options.enabled;
|
|
315546
|
+
this.forwarderAuthToken = process.env.CEX_BROKER_ARCHIVE_FORWARDER_TOKEN?.trim() || undefined;
|
|
315547
|
+
if (this.enabled) {
|
|
315548
|
+
this.flushTimer = setInterval(() => {
|
|
315549
|
+
this.flush().catch((error) => {
|
|
315550
|
+
log.warn("Broker execution archive flush failed", { error });
|
|
315551
|
+
});
|
|
315552
|
+
}, this.flushIntervalMs);
|
|
315553
|
+
this.flushTimer.unref?.();
|
|
315209
315554
|
}
|
|
315210
|
-
this.logger.emit(record);
|
|
315211
315555
|
}
|
|
315212
|
-
|
|
315213
|
-
|
|
315214
|
-
if (config?.otlpEndpoint) {
|
|
315215
|
-
return {
|
|
315216
|
-
endpoint: normalizeOtlpEndpoint(config.otlpEndpoint),
|
|
315217
|
-
appendSignalPath: true
|
|
315218
|
-
};
|
|
315556
|
+
static disabled() {
|
|
315557
|
+
return new BrokerExecutionArchiver({ enabled: false });
|
|
315219
315558
|
}
|
|
315220
|
-
|
|
315221
|
-
|
|
315222
|
-
|
|
315223
|
-
|
|
315224
|
-
appendSignalPath: false
|
|
315225
|
-
};
|
|
315559
|
+
static create(options) {
|
|
315560
|
+
const hasSink = Boolean(options.otelLogs?.isOtelEnabled()) || Boolean(options.forwarderUrl);
|
|
315561
|
+
const enabled = process.env.CEX_BROKER_ARCHIVE_ENABLED !== "false" && hasSink;
|
|
315562
|
+
return new BrokerExecutionArchiver({ ...options, enabled });
|
|
315226
315563
|
}
|
|
315227
|
-
|
|
315228
|
-
return
|
|
315229
|
-
endpoint: normalizeOtlpEndpoint(process.env.OTEL_EXPORTER_OTLP_ENDPOINT),
|
|
315230
|
-
appendSignalPath: true
|
|
315231
|
-
};
|
|
315564
|
+
getDeploymentId() {
|
|
315565
|
+
return this.deploymentId;
|
|
315232
315566
|
}
|
|
315233
|
-
|
|
315234
|
-
|
|
315235
|
-
const port = config.port ?? DEFAULT_OTLP_PORT;
|
|
315236
|
-
return {
|
|
315237
|
-
endpoint: `${protocol}://${config.host}:${port}`,
|
|
315238
|
-
appendSignalPath: true
|
|
315239
|
-
};
|
|
315567
|
+
isEnabled() {
|
|
315568
|
+
return this.enabled && !this.closed && (Boolean(this.otelLogs) || Boolean(this.forwarderUrl));
|
|
315240
315569
|
}
|
|
315241
|
-
|
|
315242
|
-
|
|
315243
|
-
function appendOtlpPath(endpoint, signal, appendSignalPath) {
|
|
315244
|
-
if (!appendSignalPath) {
|
|
315245
|
-
return endpoint;
|
|
315570
|
+
canPersistMarketMetadataSnapshot() {
|
|
315571
|
+
return this.isEnabled() && (Boolean(this.otelLogs?.isOtelEnabled()) || Boolean(this.forwarderUrl));
|
|
315246
315572
|
}
|
|
315247
|
-
|
|
315248
|
-
|
|
315249
|
-
|
|
315250
|
-
|
|
315251
|
-
|
|
315252
|
-
|
|
315253
|
-
|
|
315254
|
-
|
|
315255
|
-
}
|
|
315256
|
-
|
|
315257
|
-
|
|
315258
|
-
|
|
315259
|
-
|
|
315260
|
-
|
|
315261
|
-
|
|
315262
|
-
|
|
315263
|
-
}
|
|
315264
|
-
|
|
315265
|
-
|
|
315266
|
-
|
|
315267
|
-
|
|
315268
|
-
|
|
315269
|
-
otlpEndpoint: otlpEndpoint.replace(/\/v1\/metrics\/?$/, ""),
|
|
315270
|
-
serviceName: process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE
|
|
315573
|
+
enqueue(row) {
|
|
315574
|
+
if (!this.enabled || this.closed || !this.otelLogs && !this.forwarderUrl) {
|
|
315575
|
+
return;
|
|
315576
|
+
}
|
|
315577
|
+
if (isMarketArchiveTable(row.table) && !this.forwarderUrl) {
|
|
315578
|
+
if (!this.loggedMissingMarketForwarder) {
|
|
315579
|
+
this.loggedMissingMarketForwarder = true;
|
|
315580
|
+
log.warn("Market data archive row dropped: configure CEX_BROKER_ARCHIVE_FORWARDER_URL or CEX_BROKER_CLICKHOUSE_HOST", { table: row.table });
|
|
315581
|
+
}
|
|
315582
|
+
return;
|
|
315583
|
+
}
|
|
315584
|
+
if (this.queue.length >= this.maxQueueSize) {
|
|
315585
|
+
this.queue.shift();
|
|
315586
|
+
this.stats.shed += 1;
|
|
315587
|
+
this.recordArchiveMetric("cex_archive_rows_shed_total", {
|
|
315588
|
+
table: row.table
|
|
315589
|
+
});
|
|
315590
|
+
}
|
|
315591
|
+
this.queue.push(row);
|
|
315592
|
+
this.stats.enqueued += 1;
|
|
315593
|
+
this.recordArchiveMetric("cex_archive_rows_enqueued_total", {
|
|
315594
|
+
table: row.table
|
|
315271
315595
|
});
|
|
315596
|
+
if (this.queue.length >= this.batchSize) {
|
|
315597
|
+
this.flush().catch((error) => {
|
|
315598
|
+
log.warn("Broker execution archive flush failed", { error });
|
|
315599
|
+
});
|
|
315600
|
+
}
|
|
315272
315601
|
}
|
|
315273
|
-
|
|
315274
|
-
|
|
315602
|
+
enqueueInBackground(row) {
|
|
315603
|
+
queueMicrotask(() => this.enqueue(row));
|
|
315275
315604
|
}
|
|
315276
|
-
|
|
315277
|
-
|
|
315278
|
-
|
|
315279
|
-
|
|
315280
|
-
|
|
315281
|
-
|
|
315282
|
-
|
|
315283
|
-
|
|
315284
|
-
|
|
315285
|
-
|
|
315286
|
-
|
|
315287
|
-
const genericEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
315288
|
-
const host = getOtelHostFromEnv();
|
|
315289
|
-
if (logsEndpoint) {
|
|
315290
|
-
return new OtelLogs({
|
|
315291
|
-
serviceName: process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE
|
|
315605
|
+
async flush() {
|
|
315606
|
+
if (!this.enabled || this.closed || this.queue.length === 0) {
|
|
315607
|
+
return;
|
|
315608
|
+
}
|
|
315609
|
+
if (this.flushInFlight) {
|
|
315610
|
+
return this.flushInFlight;
|
|
315611
|
+
}
|
|
315612
|
+
const inFlight = this.flushBatch().then(() => {
|
|
315613
|
+
return;
|
|
315614
|
+
}).finally(() => {
|
|
315615
|
+
this.flushInFlight = null;
|
|
315292
315616
|
});
|
|
315617
|
+
this.flushInFlight = inFlight;
|
|
315618
|
+
return inFlight;
|
|
315293
315619
|
}
|
|
315294
|
-
|
|
315295
|
-
|
|
315296
|
-
|
|
315297
|
-
|
|
315298
|
-
}
|
|
315620
|
+
async close() {
|
|
315621
|
+
if (this.flushTimer) {
|
|
315622
|
+
clearInterval(this.flushTimer);
|
|
315623
|
+
this.flushTimer = null;
|
|
315624
|
+
}
|
|
315625
|
+
while (this.queue.length > 0 || this.flushInFlight) {
|
|
315626
|
+
if (this.flushInFlight) {
|
|
315627
|
+
await this.flushInFlight;
|
|
315628
|
+
continue;
|
|
315629
|
+
}
|
|
315630
|
+
const depthBefore = this.queue.length;
|
|
315631
|
+
const flushed = await this.flushBatch();
|
|
315632
|
+
if (!flushed && this.queue.length >= depthBefore && depthBefore > 0) {
|
|
315633
|
+
const dropped = this.queue.length;
|
|
315634
|
+
this.queue.length = 0;
|
|
315635
|
+
log.warn(`Archive shutdown dropped ${dropped} undelivered row(s) after forwarder failure`);
|
|
315636
|
+
break;
|
|
315637
|
+
}
|
|
315638
|
+
}
|
|
315639
|
+
this.closed = true;
|
|
315299
315640
|
}
|
|
315300
|
-
|
|
315301
|
-
return
|
|
315641
|
+
getStats() {
|
|
315642
|
+
return { ...this.stats };
|
|
315302
315643
|
}
|
|
315303
|
-
|
|
315304
|
-
|
|
315305
|
-
host,
|
|
315306
|
-
port: port ?? DEFAULT_OTLP_PORT,
|
|
315307
|
-
protocol: getOtelProtocolFromEnv(),
|
|
315308
|
-
serviceName: process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE
|
|
315309
|
-
};
|
|
315310
|
-
return new OtelLogs(config);
|
|
315311
|
-
}
|
|
315312
|
-
|
|
315313
|
-
// src/server.ts
|
|
315314
|
-
var grpc14 = __toESM(require_src3(), 1);
|
|
315315
|
-
|
|
315316
|
-
// src/handlers/execute-action/deposit.ts
|
|
315317
|
-
var grpc3 = __toESM(require_src3(), 1);
|
|
315318
|
-
|
|
315319
|
-
// src/helpers/shared/errors.ts
|
|
315320
|
-
function getErrorMessage(error) {
|
|
315321
|
-
return error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
|
|
315322
|
-
}
|
|
315323
|
-
function safeLogError(context2, error) {
|
|
315324
|
-
try {
|
|
315325
|
-
log.error(context2, { error });
|
|
315326
|
-
} catch {
|
|
315327
|
-
console.error(context2, error);
|
|
315644
|
+
getQueueDepth() {
|
|
315645
|
+
return this.queue.length;
|
|
315328
315646
|
}
|
|
315329
|
-
|
|
315330
|
-
|
|
315331
|
-
|
|
315332
|
-
|
|
315333
|
-
|
|
315334
|
-
|
|
315335
|
-
|
|
315336
|
-
|
|
315337
|
-
}
|
|
315338
|
-
|
|
315339
|
-
// src/helpers/treasury-discovery.ts
|
|
315340
|
-
function callArgs(args, params) {
|
|
315341
|
-
const argsArray = Array.isArray(args) ? [...args] : [];
|
|
315342
|
-
if (params && Object.keys(params).length > 0) {
|
|
315343
|
-
argsArray.push(params);
|
|
315647
|
+
enforceQueueBound() {
|
|
315648
|
+
while (this.queue.length > this.maxQueueSize) {
|
|
315649
|
+
const dropped = this.queue.shift();
|
|
315650
|
+
this.stats.shed += 1;
|
|
315651
|
+
this.recordArchiveMetric("cex_archive_rows_shed_total", {
|
|
315652
|
+
table: dropped?.table ?? "unknown"
|
|
315653
|
+
});
|
|
315654
|
+
}
|
|
315344
315655
|
}
|
|
315345
|
-
|
|
315346
|
-
|
|
315347
|
-
|
|
315348
|
-
|
|
315349
|
-
if (functionName === "fetchMarkets") {
|
|
315350
|
-
if (typeof discoveryBroker.fetchMarkets === "function" && discoveryBroker.has?.fetchMarkets !== false) {
|
|
315351
|
-
return {
|
|
315352
|
-
handled: true,
|
|
315353
|
-
result: await discoveryBroker.fetchMarkets(...callArgs(args, params))
|
|
315354
|
-
};
|
|
315656
|
+
async flushBatch() {
|
|
315657
|
+
const batch = this.queue.splice(0, this.batchSize);
|
|
315658
|
+
if (batch.length === 0) {
|
|
315659
|
+
return true;
|
|
315355
315660
|
}
|
|
315356
|
-
|
|
315357
|
-
|
|
315358
|
-
|
|
315359
|
-
|
|
315661
|
+
for (const entry of batch) {
|
|
315662
|
+
if (!isMarketArchiveTable(entry.table)) {
|
|
315663
|
+
this.emitOtelLog(entry);
|
|
315664
|
+
}
|
|
315360
315665
|
}
|
|
315361
|
-
|
|
315666
|
+
if (this.forwarderUrl) {
|
|
315667
|
+
try {
|
|
315668
|
+
await this.postToForwarder(batch);
|
|
315669
|
+
} catch (error) {
|
|
315670
|
+
this.stats.forwarderFailures += 1;
|
|
315671
|
+
this.queue.push(...batch);
|
|
315672
|
+
this.enforceQueueBound();
|
|
315673
|
+
this.recordArchiveMetric("cex_archive_forwarder_failures_total", {
|
|
315674
|
+
count: batch.length
|
|
315675
|
+
});
|
|
315676
|
+
log.warn("Broker execution archive forwarder failed", { error });
|
|
315677
|
+
return false;
|
|
315678
|
+
}
|
|
315679
|
+
}
|
|
315680
|
+
this.stats.flushed += batch.length;
|
|
315681
|
+
return true;
|
|
315362
315682
|
}
|
|
315363
|
-
|
|
315364
|
-
|
|
315365
|
-
|
|
315366
|
-
|
|
315367
|
-
|
|
315368
|
-
|
|
315683
|
+
async recordArchiveMetric(metricName, labels) {
|
|
315684
|
+
try {
|
|
315685
|
+
await this.otelMetrics?.recordCounter(metricName, 1, labels);
|
|
315686
|
+
} catch {}
|
|
315687
|
+
}
|
|
315688
|
+
emitOtelLog(entry) {
|
|
315689
|
+
if (!this.otelLogs?.isOtelEnabled()) {
|
|
315690
|
+
return;
|
|
315369
315691
|
}
|
|
315370
|
-
|
|
315371
|
-
|
|
315692
|
+
try {
|
|
315693
|
+
this.otelLogs.emit({
|
|
315694
|
+
body: entry.table,
|
|
315695
|
+
severityNumber: import_api_logs2.SeverityNumber.INFO,
|
|
315696
|
+
severityText: "INFO",
|
|
315697
|
+
attributes: flattenArchiveAttributes(entry)
|
|
315698
|
+
});
|
|
315699
|
+
} catch (error) {
|
|
315700
|
+
log.warn("Broker execution archive OTLP emit failed", { error });
|
|
315372
315701
|
}
|
|
315373
|
-
throw new Error("venue_discovery_unavailable: fetchCurrencies unavailable on broker");
|
|
315374
315702
|
}
|
|
315375
|
-
|
|
315703
|
+
postToForwarder(batch) {
|
|
315704
|
+
if (!this.forwarderUrl || batch.length === 0) {
|
|
315705
|
+
return Promise.resolve();
|
|
315706
|
+
}
|
|
315707
|
+
const body = JSON.stringify({
|
|
315708
|
+
source: "broker_write",
|
|
315709
|
+
deployment_id: this.deploymentId,
|
|
315710
|
+
rows: batch
|
|
315711
|
+
});
|
|
315712
|
+
const url2 = new URL(this.forwarderUrl);
|
|
315713
|
+
const doRequest = url2.protocol === "http:" ? httpRequest2 : httpsRequest2;
|
|
315714
|
+
const headers = {
|
|
315715
|
+
"content-type": "application/json",
|
|
315716
|
+
"content-length": Buffer.byteLength(body)
|
|
315717
|
+
};
|
|
315718
|
+
if (this.forwarderAuthToken) {
|
|
315719
|
+
headers.authorization = `Bearer ${this.forwarderAuthToken}`;
|
|
315720
|
+
}
|
|
315721
|
+
return new Promise((resolve, reject) => {
|
|
315722
|
+
const req = doRequest(url2, {
|
|
315723
|
+
method: "POST",
|
|
315724
|
+
headers,
|
|
315725
|
+
timeout: this.forwarderTimeoutMs
|
|
315726
|
+
}, (res) => {
|
|
315727
|
+
res.on("data", () => {});
|
|
315728
|
+
res.on("end", () => {
|
|
315729
|
+
const status = res.statusCode ?? 0;
|
|
315730
|
+
if (status < 200 || status >= 300) {
|
|
315731
|
+
reject(new Error(`Archive forwarder returned ${status} ${res.statusMessage ?? ""}`));
|
|
315732
|
+
return;
|
|
315733
|
+
}
|
|
315734
|
+
resolve();
|
|
315735
|
+
});
|
|
315736
|
+
});
|
|
315737
|
+
req.on("error", reject);
|
|
315738
|
+
req.on("timeout", () => {
|
|
315739
|
+
req.destroy(new Error("Archive forwarder request timed out"));
|
|
315740
|
+
});
|
|
315741
|
+
req.write(body);
|
|
315742
|
+
req.end();
|
|
315743
|
+
});
|
|
315744
|
+
}
|
|
315376
315745
|
}
|
|
315377
|
-
|
|
315378
|
-
const
|
|
315379
|
-
|
|
315380
|
-
|
|
315381
|
-
|
|
315382
|
-
|
|
315746
|
+
function flattenArchiveAttributes(entry) {
|
|
315747
|
+
const attributes = {
|
|
315748
|
+
ch_table: entry.table
|
|
315749
|
+
};
|
|
315750
|
+
for (const [key, value] of Object.entries(entry.row)) {
|
|
315751
|
+
if (value === undefined || value === null) {
|
|
315752
|
+
continue;
|
|
315753
|
+
}
|
|
315754
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
315755
|
+
attributes[key] = value;
|
|
315756
|
+
} else {
|
|
315757
|
+
attributes[key] = JSON.stringify(value);
|
|
315758
|
+
}
|
|
315383
315759
|
}
|
|
315384
|
-
return
|
|
315760
|
+
return attributes;
|
|
315385
315761
|
}
|
|
315386
|
-
|
|
315387
|
-
|
|
315388
|
-
|
|
315389
|
-
|
|
315390
|
-
|
|
315391
|
-
|
|
315392
|
-
|
|
315393
|
-
|
|
315394
|
-
|
|
315395
|
-
|
|
315396
|
-
|
|
315762
|
+
function createBrokerExecutionArchiverFromEnv(otelLogs, otelMetrics) {
|
|
315763
|
+
const forwarderUrl = resolveArchiveForwarderUrlFromEnv();
|
|
315764
|
+
const archiveOtelLogs = isArchiveOtelLogsEnabled() ? otelLogs : undefined;
|
|
315765
|
+
return BrokerExecutionArchiver.create({
|
|
315766
|
+
otelLogs: archiveOtelLogs,
|
|
315767
|
+
otelMetrics,
|
|
315768
|
+
forwarderUrl,
|
|
315769
|
+
deploymentId: process.env.CEX_BROKER_DEPLOYMENT_ID,
|
|
315770
|
+
maxQueueSize: parsePositiveInt(process.env.CEX_BROKER_ARCHIVE_QUEUE_MAX, DEFAULT_MAX_QUEUE_SIZE),
|
|
315771
|
+
batchSize: parsePositiveInt(process.env.CEX_BROKER_ARCHIVE_BATCH_SIZE, DEFAULT_BATCH_SIZE),
|
|
315772
|
+
flushIntervalMs: parsePositiveInt(process.env.CEX_BROKER_ARCHIVE_FLUSH_INTERVAL_MS, DEFAULT_FLUSH_INTERVAL_MS)
|
|
315773
|
+
});
|
|
315774
|
+
}
|
|
315775
|
+
function parsePositiveInt(value, fallback) {
|
|
315776
|
+
if (!value) {
|
|
315777
|
+
return fallback;
|
|
315397
315778
|
}
|
|
315398
|
-
|
|
315779
|
+
const parsed = Number.parseInt(value, 10);
|
|
315780
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
315399
315781
|
}
|
|
315400
|
-
|
|
315401
|
-
|
|
315402
|
-
|
|
315403
|
-
|
|
315404
|
-
|
|
315405
|
-
|
|
315406
|
-
|
|
315407
|
-
|
|
315408
|
-
|
|
315409
|
-
|
|
315410
|
-
|
|
315411
|
-
|
|
315412
|
-
|
|
315413
|
-
|
|
315414
|
-
|
|
315415
|
-
|
|
315416
|
-
|
|
315417
|
-
|
|
315418
|
-
|
|
315419
|
-
|
|
315420
|
-
|
|
315421
|
-
|
|
315782
|
+
// src/helpers/otel.ts
|
|
315783
|
+
var import_api2 = __toESM(require_src5(), 1);
|
|
315784
|
+
var import_api_logs3 = __toESM(require_src7(), 1);
|
|
315785
|
+
var import_exporter_logs_otlp_http = __toESM(require_src14(), 1);
|
|
315786
|
+
var import_exporter_metrics_otlp_http = __toESM(require_src15(), 1);
|
|
315787
|
+
var import_resources = __toESM(require_src11(), 1);
|
|
315788
|
+
var import_sdk_logs = __toESM(require_src16(), 1);
|
|
315789
|
+
var import_sdk_metrics = __toESM(require_src12(), 1);
|
|
315790
|
+
var DEFAULT_SERVICE = "cex-broker";
|
|
315791
|
+
var DEFAULT_OTLP_PORT = 4318;
|
|
315792
|
+
var EXPORT_INTERVAL_MS = 5000;
|
|
315793
|
+
|
|
315794
|
+
class BaseOtelSignal {
|
|
315795
|
+
signal;
|
|
315796
|
+
provider = null;
|
|
315797
|
+
isEnabled = false;
|
|
315798
|
+
serviceName;
|
|
315799
|
+
constructor(config, signal) {
|
|
315800
|
+
this.signal = signal;
|
|
315801
|
+
this.serviceName = config?.serviceName ?? DEFAULT_SERVICE;
|
|
315802
|
+
const endpointResolution = resolveOtlpEndpoint(this.signal, config);
|
|
315803
|
+
if (!endpointResolution) {
|
|
315804
|
+
log.info(`OTel ${signal} disabled: no OTLP endpoint or host provided`);
|
|
315805
|
+
return;
|
|
315806
|
+
}
|
|
315807
|
+
try {
|
|
315808
|
+
this.provider = this.createProvider(endpointResolution.endpoint, this.serviceName, endpointResolution.appendSignalPath);
|
|
315809
|
+
this.onProviderCreated(this.provider);
|
|
315810
|
+
this.isEnabled = true;
|
|
315811
|
+
log.info(`OTel ${signal} enabled: ${endpointResolution.endpoint}`);
|
|
315812
|
+
} catch (error) {
|
|
315813
|
+
log.error(`Failed to initialize OTel ${signal}:`, error);
|
|
315814
|
+
this.isEnabled = false;
|
|
315815
|
+
this.provider = null;
|
|
315422
315816
|
}
|
|
315423
315817
|
}
|
|
315424
|
-
|
|
315425
|
-
}
|
|
315426
|
-
|
|
315427
|
-
|
|
315428
|
-
const brokerNetworkId = normalizeBrokerNetworkId(requestedAlias);
|
|
315429
|
-
let currencyInfo = null;
|
|
315430
|
-
try {
|
|
315431
|
-
currencyInfo = await fetchCurrencyMetadata(broker, assetCode);
|
|
315432
|
-
} catch (error) {
|
|
315433
|
-
safeLogError(`Network discovery failed for ${assetCode}/${operatorAlias}; using operator alias as exchange network id`, error);
|
|
315818
|
+
onProviderCreated(_provider) {}
|
|
315819
|
+
onProviderClosed() {}
|
|
315820
|
+
getProvider() {
|
|
315821
|
+
return this.provider;
|
|
315434
315822
|
}
|
|
315435
|
-
|
|
315436
|
-
|
|
315437
|
-
|
|
315438
|
-
|
|
315439
|
-
|
|
315823
|
+
getServiceName() {
|
|
315824
|
+
return this.serviceName;
|
|
315825
|
+
}
|
|
315826
|
+
isOtelEnabled() {
|
|
315827
|
+
return this.isEnabled && this.provider !== null;
|
|
315828
|
+
}
|
|
315829
|
+
async close() {
|
|
315830
|
+
if (!this.provider) {
|
|
315831
|
+
return;
|
|
315440
315832
|
}
|
|
315441
|
-
|
|
315833
|
+
try {
|
|
315834
|
+
await this.shutdownProvider(this.provider);
|
|
315835
|
+
log.info(`OTel ${this.signal} provider shut down`);
|
|
315836
|
+
} catch (error) {
|
|
315837
|
+
log.error(`Error shutting down OTel ${this.signal} provider:`, error);
|
|
315838
|
+
}
|
|
315839
|
+
this.provider = null;
|
|
315840
|
+
this.isEnabled = false;
|
|
315841
|
+
this.onProviderClosed();
|
|
315442
315842
|
}
|
|
315443
|
-
|
|
315444
|
-
|
|
315445
|
-
|
|
315446
|
-
|
|
315447
|
-
|
|
315448
|
-
|
|
315843
|
+
}
|
|
315844
|
+
function toAttributes(labels, service) {
|
|
315845
|
+
const attrs = { ...labels, service };
|
|
315846
|
+
for (const key of Object.keys(attrs)) {
|
|
315847
|
+
const v = attrs[key];
|
|
315848
|
+
if (typeof v !== "string" && typeof v !== "number") {
|
|
315849
|
+
attrs[key] = String(v);
|
|
315850
|
+
}
|
|
315851
|
+
}
|
|
315852
|
+
return attrs;
|
|
315449
315853
|
}
|
|
315450
315854
|
|
|
315451
|
-
|
|
315452
|
-
|
|
315453
|
-
|
|
315454
|
-
|
|
315455
|
-
|
|
315456
|
-
|
|
315457
|
-
|
|
315458
|
-
|
|
315459
|
-
|
|
315460
|
-
|
|
315461
|
-
|
|
315462
|
-
|
|
315463
|
-
|
|
315464
|
-
|
|
315465
|
-
|
|
315466
|
-
|
|
315467
|
-
|
|
315468
|
-
|
|
315469
|
-
|
|
315470
|
-
|
|
315471
|
-
|
|
315855
|
+
class OtelMetrics extends BaseOtelSignal {
|
|
315856
|
+
counters = new Map;
|
|
315857
|
+
histograms = new Map;
|
|
315858
|
+
constructor(config) {
|
|
315859
|
+
super(config, "metrics");
|
|
315860
|
+
}
|
|
315861
|
+
createProvider(endpoint, serviceName, appendSignalPath) {
|
|
315862
|
+
const exporter = new import_exporter_metrics_otlp_http.OTLPMetricExporter({
|
|
315863
|
+
url: appendOtlpPath(endpoint, "metrics", appendSignalPath)
|
|
315864
|
+
});
|
|
315865
|
+
const reader = new import_sdk_metrics.PeriodicExportingMetricReader({
|
|
315866
|
+
exporter,
|
|
315867
|
+
exportIntervalMillis: EXPORT_INTERVAL_MS
|
|
315868
|
+
});
|
|
315869
|
+
const resource = import_resources.resourceFromAttributes({
|
|
315870
|
+
"service.name": serviceName
|
|
315871
|
+
});
|
|
315872
|
+
return new import_sdk_metrics.MeterProvider({
|
|
315873
|
+
resource,
|
|
315874
|
+
readers: [reader]
|
|
315875
|
+
});
|
|
315876
|
+
}
|
|
315877
|
+
onProviderCreated(provider) {
|
|
315878
|
+
import_api2.metrics.setGlobalMeterProvider(provider);
|
|
315879
|
+
}
|
|
315880
|
+
shutdownProvider(provider) {
|
|
315881
|
+
return provider.shutdown();
|
|
315882
|
+
}
|
|
315883
|
+
async initialize() {
|
|
315884
|
+
if (this.isOtelEnabled()) {
|
|
315885
|
+
log.info("OTel metrics initialized (storage is handled by the collector)");
|
|
315886
|
+
}
|
|
315887
|
+
}
|
|
315888
|
+
async insertMetric(metric) {
|
|
315889
|
+
if (!this.isOtelEnabled())
|
|
315890
|
+
return;
|
|
315891
|
+
const labels = metric.labels ? JSON.parse(metric.labels) : {};
|
|
315892
|
+
try {
|
|
315893
|
+
if (metric.metric_type === "counter") {
|
|
315894
|
+
await this.recordCounter(metric.metric_name, metric.value, labels, metric.service);
|
|
315895
|
+
} else if (metric.metric_type === "gauge") {
|
|
315896
|
+
await this.recordGauge(metric.metric_name, metric.value, labels, metric.service);
|
|
315897
|
+
} else {
|
|
315898
|
+
await this.recordHistogram(metric.metric_name, metric.value, labels, metric.service);
|
|
315899
|
+
}
|
|
315900
|
+
} catch {}
|
|
315901
|
+
}
|
|
315902
|
+
async insertMetrics(metricsList) {
|
|
315903
|
+
if (!this.isOtelEnabled() || metricsList.length === 0)
|
|
315904
|
+
return;
|
|
315905
|
+
for (const m of metricsList) {
|
|
315906
|
+
await this.insertMetric(m);
|
|
315907
|
+
}
|
|
315908
|
+
}
|
|
315909
|
+
async recordCounter(metricName, value, labels, service = this.getServiceName()) {
|
|
315910
|
+
const provider = this.getProvider();
|
|
315911
|
+
if (!this.isOtelEnabled() || !provider)
|
|
315912
|
+
return;
|
|
315913
|
+
try {
|
|
315914
|
+
let counter = this.counters.get(metricName);
|
|
315915
|
+
if (!counter) {
|
|
315916
|
+
const meter = provider.getMeter("cex-broker-metrics", "1.0.0");
|
|
315917
|
+
counter = meter.createCounter(metricName, { description: metricName });
|
|
315918
|
+
this.counters.set(metricName, counter);
|
|
315919
|
+
}
|
|
315920
|
+
counter.add(value, toAttributes(labels, service));
|
|
315921
|
+
} catch (error) {
|
|
315922
|
+
log.error("Failed to record counter:", error);
|
|
315923
|
+
}
|
|
315924
|
+
}
|
|
315925
|
+
async recordGauge(metricName, value, labels, service = this.getServiceName()) {
|
|
315926
|
+
const provider = this.getProvider();
|
|
315927
|
+
if (!this.isOtelEnabled() || !provider)
|
|
315928
|
+
return;
|
|
315929
|
+
try {
|
|
315930
|
+
let hist = this.histograms.get(`gauge_${metricName}`);
|
|
315931
|
+
if (!hist) {
|
|
315932
|
+
const meter = provider.getMeter("cex-broker-metrics", "1.0.0");
|
|
315933
|
+
hist = meter.createHistogram(`${metricName}_gauge`, {
|
|
315934
|
+
description: metricName
|
|
315935
|
+
});
|
|
315936
|
+
this.histograms.set(`gauge_${metricName}`, hist);
|
|
315937
|
+
}
|
|
315938
|
+
hist.record(value, toAttributes(labels, service));
|
|
315939
|
+
} catch (error) {
|
|
315940
|
+
log.error("Failed to record gauge:", error);
|
|
315941
|
+
}
|
|
315942
|
+
}
|
|
315943
|
+
async recordHistogram(metricName, value, labels, service = this.getServiceName()) {
|
|
315944
|
+
const provider = this.getProvider();
|
|
315945
|
+
if (!this.isOtelEnabled() || !provider)
|
|
315946
|
+
return;
|
|
315947
|
+
try {
|
|
315948
|
+
let hist = this.histograms.get(metricName);
|
|
315949
|
+
if (!hist) {
|
|
315950
|
+
const meter = provider.getMeter("cex-broker-metrics", "1.0.0");
|
|
315951
|
+
hist = meter.createHistogram(metricName, { description: metricName });
|
|
315952
|
+
this.histograms.set(metricName, hist);
|
|
315953
|
+
}
|
|
315954
|
+
hist.record(value, toAttributes(labels, service));
|
|
315955
|
+
} catch (error) {
|
|
315956
|
+
log.error("Failed to record histogram:", error);
|
|
315957
|
+
}
|
|
315958
|
+
}
|
|
315959
|
+
}
|
|
315960
|
+
|
|
315961
|
+
class OtelLogs extends BaseOtelSignal {
|
|
315962
|
+
logger = null;
|
|
315963
|
+
constructor(config) {
|
|
315964
|
+
super(config, "logs");
|
|
315965
|
+
}
|
|
315966
|
+
createProvider(endpoint, serviceName, appendSignalPath) {
|
|
315967
|
+
const exporter = new import_exporter_logs_otlp_http.OTLPLogExporter({
|
|
315968
|
+
url: appendOtlpPath(endpoint, "logs", appendSignalPath)
|
|
315969
|
+
});
|
|
315970
|
+
const processor = new import_sdk_logs.BatchLogRecordProcessor(exporter);
|
|
315971
|
+
const resource = import_resources.resourceFromAttributes({
|
|
315972
|
+
"service.name": serviceName
|
|
315973
|
+
});
|
|
315974
|
+
return new import_sdk_logs.LoggerProvider({
|
|
315975
|
+
resource,
|
|
315976
|
+
processors: [processor]
|
|
315977
|
+
});
|
|
315978
|
+
}
|
|
315979
|
+
onProviderCreated(provider) {
|
|
315980
|
+
import_api_logs3.logs.setGlobalLoggerProvider(provider);
|
|
315981
|
+
this.logger = provider.getLogger("cex-broker-logs", "1.0.0");
|
|
315982
|
+
}
|
|
315983
|
+
shutdownProvider(provider) {
|
|
315984
|
+
return provider.forceFlush().then(() => provider.shutdown());
|
|
315985
|
+
}
|
|
315986
|
+
onProviderClosed() {
|
|
315987
|
+
this.logger = null;
|
|
315988
|
+
}
|
|
315989
|
+
emit(record) {
|
|
315990
|
+
if (!this.isOtelEnabled() || !this.logger) {
|
|
315991
|
+
return;
|
|
315992
|
+
}
|
|
315993
|
+
this.logger.emit(record);
|
|
315994
|
+
}
|
|
315995
|
+
}
|
|
315996
|
+
function resolveOtlpEndpoint(signal, config) {
|
|
315997
|
+
if (config?.otlpEndpoint) {
|
|
315998
|
+
return {
|
|
315999
|
+
endpoint: normalizeOtlpEndpoint(config.otlpEndpoint),
|
|
316000
|
+
appendSignalPath: true
|
|
316001
|
+
};
|
|
316002
|
+
}
|
|
316003
|
+
const signalEndpoint = signal === "metrics" ? process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT : process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT;
|
|
316004
|
+
if (signalEndpoint) {
|
|
316005
|
+
return {
|
|
316006
|
+
endpoint: signalEndpoint,
|
|
316007
|
+
appendSignalPath: false
|
|
316008
|
+
};
|
|
316009
|
+
}
|
|
316010
|
+
if (process.env.OTEL_EXPORTER_OTLP_ENDPOINT) {
|
|
316011
|
+
return {
|
|
316012
|
+
endpoint: normalizeOtlpEndpoint(process.env.OTEL_EXPORTER_OTLP_ENDPOINT),
|
|
316013
|
+
appendSignalPath: true
|
|
316014
|
+
};
|
|
316015
|
+
}
|
|
316016
|
+
if (config?.host) {
|
|
316017
|
+
const protocol = config.protocol || "http";
|
|
316018
|
+
const port = config.port ?? DEFAULT_OTLP_PORT;
|
|
316019
|
+
return {
|
|
316020
|
+
endpoint: `${protocol}://${config.host}:${port}`,
|
|
316021
|
+
appendSignalPath: true
|
|
316022
|
+
};
|
|
316023
|
+
}
|
|
316024
|
+
return null;
|
|
316025
|
+
}
|
|
316026
|
+
function appendOtlpPath(endpoint, signal, appendSignalPath) {
|
|
316027
|
+
if (!appendSignalPath) {
|
|
316028
|
+
return endpoint;
|
|
316029
|
+
}
|
|
316030
|
+
const baseEndpoint = normalizeOtlpEndpoint(endpoint);
|
|
316031
|
+
return `${baseEndpoint}/v1/${signal}`;
|
|
316032
|
+
}
|
|
316033
|
+
function normalizeOtlpEndpoint(endpoint) {
|
|
316034
|
+
return endpoint.replace(/\/v1\/(metrics|logs)\/?$/, "").replace(/\/+$/, "");
|
|
316035
|
+
}
|
|
316036
|
+
function getOtelHostFromEnv() {
|
|
316037
|
+
return process.env.CEX_BROKER_OTEL_HOST ?? process.env.CEX_BROKER_CLICKHOUSE_HOST;
|
|
316038
|
+
}
|
|
316039
|
+
function getOtelPortFromEnv() {
|
|
316040
|
+
const port = process.env.CEX_BROKER_OTEL_PORT ?? process.env.CEX_BROKER_CLICKHOUSE_PORT;
|
|
316041
|
+
return port ? Number.parseInt(port, 10) : undefined;
|
|
316042
|
+
}
|
|
316043
|
+
function getOtelProtocolFromEnv() {
|
|
316044
|
+
const protocol = process.env.CEX_BROKER_OTEL_PROTOCOL ?? process.env.CEX_BROKER_CLICKHOUSE_PROTOCOL;
|
|
316045
|
+
return protocol || "http";
|
|
316046
|
+
}
|
|
316047
|
+
function createOtelMetricsFromEnv() {
|
|
316048
|
+
const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
316049
|
+
const host = getOtelHostFromEnv();
|
|
316050
|
+
if (otlpEndpoint) {
|
|
316051
|
+
return new OtelMetrics({
|
|
316052
|
+
otlpEndpoint: otlpEndpoint.replace(/\/v1\/metrics\/?$/, ""),
|
|
316053
|
+
serviceName: process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE
|
|
316054
|
+
});
|
|
316055
|
+
}
|
|
316056
|
+
if (!host) {
|
|
316057
|
+
return new OtelMetrics;
|
|
316058
|
+
}
|
|
316059
|
+
const port = getOtelPortFromEnv();
|
|
316060
|
+
const config = {
|
|
316061
|
+
host,
|
|
316062
|
+
port: port ?? DEFAULT_OTLP_PORT,
|
|
316063
|
+
protocol: getOtelProtocolFromEnv(),
|
|
316064
|
+
serviceName: process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE
|
|
316065
|
+
};
|
|
316066
|
+
return new OtelMetrics(config);
|
|
316067
|
+
}
|
|
316068
|
+
function createOtelLogsFromEnv() {
|
|
316069
|
+
const logsEndpoint = process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT;
|
|
316070
|
+
const genericEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
316071
|
+
const host = getOtelHostFromEnv();
|
|
316072
|
+
if (logsEndpoint) {
|
|
316073
|
+
return new OtelLogs({
|
|
316074
|
+
serviceName: process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE
|
|
316075
|
+
});
|
|
316076
|
+
}
|
|
316077
|
+
if (genericEndpoint) {
|
|
316078
|
+
return new OtelLogs({
|
|
316079
|
+
otlpEndpoint: genericEndpoint,
|
|
316080
|
+
serviceName: process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE
|
|
316081
|
+
});
|
|
316082
|
+
}
|
|
316083
|
+
if (!host) {
|
|
316084
|
+
return new OtelLogs;
|
|
316085
|
+
}
|
|
316086
|
+
const port = getOtelPortFromEnv();
|
|
316087
|
+
const config = {
|
|
316088
|
+
host,
|
|
316089
|
+
port: port ?? DEFAULT_OTLP_PORT,
|
|
316090
|
+
protocol: getOtelProtocolFromEnv(),
|
|
316091
|
+
serviceName: process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE
|
|
316092
|
+
};
|
|
316093
|
+
return new OtelLogs(config);
|
|
316094
|
+
}
|
|
316095
|
+
|
|
316096
|
+
// src/server.ts
|
|
316097
|
+
var grpc14 = __toESM(require_src3(), 1);
|
|
316098
|
+
|
|
316099
|
+
// src/handlers/execute-action/deposit.ts
|
|
316100
|
+
var grpc3 = __toESM(require_src3(), 1);
|
|
316101
|
+
|
|
316102
|
+
// src/helpers/shared/errors.ts
|
|
316103
|
+
function getErrorMessage(error) {
|
|
316104
|
+
return error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
|
|
316105
|
+
}
|
|
316106
|
+
function safeLogError(context2, error) {
|
|
316107
|
+
try {
|
|
316108
|
+
log.error(context2, { error });
|
|
316109
|
+
} catch {
|
|
316110
|
+
console.error(context2, error);
|
|
316111
|
+
}
|
|
316112
|
+
}
|
|
316113
|
+
|
|
316114
|
+
// src/helpers/treasury-discovery.ts
|
|
316115
|
+
function callArgs(args, params) {
|
|
316116
|
+
const argsArray = Array.isArray(args) ? [...args] : [];
|
|
316117
|
+
if (params && Object.keys(params).length > 0) {
|
|
316118
|
+
argsArray.push(params);
|
|
316119
|
+
}
|
|
316120
|
+
return argsArray;
|
|
316121
|
+
}
|
|
316122
|
+
async function handleTreasuryDiscoveryCall(broker, functionName, args, params) {
|
|
316123
|
+
const discoveryBroker = broker;
|
|
316124
|
+
if (functionName === "fetchMarkets") {
|
|
316125
|
+
if (typeof discoveryBroker.fetchMarkets === "function" && discoveryBroker.has?.fetchMarkets !== false) {
|
|
316126
|
+
return {
|
|
316127
|
+
handled: true,
|
|
316128
|
+
result: await discoveryBroker.fetchMarkets(...callArgs(args, params))
|
|
316129
|
+
};
|
|
316130
|
+
}
|
|
316131
|
+
if (typeof discoveryBroker.loadMarkets === "function") {
|
|
316132
|
+
const loaded = await discoveryBroker.loadMarkets(false, params);
|
|
316133
|
+
const markets = loaded && typeof loaded === "object" && !Array.isArray(loaded) ? Object.values(loaded) : Object.values(discoveryBroker.markets ?? {});
|
|
316134
|
+
return { handled: true, result: markets };
|
|
316135
|
+
}
|
|
316136
|
+
throw new Error("venue_discovery_unavailable: fetchMarkets unavailable on broker");
|
|
316137
|
+
}
|
|
316138
|
+
if (functionName === "fetchCurrencies") {
|
|
316139
|
+
if (typeof discoveryBroker.fetchCurrencies === "function" && discoveryBroker.has?.fetchCurrencies !== false) {
|
|
316140
|
+
return {
|
|
316141
|
+
handled: true,
|
|
316142
|
+
result: await discoveryBroker.fetchCurrencies(...callArgs(args, params))
|
|
316143
|
+
};
|
|
316144
|
+
}
|
|
316145
|
+
if (discoveryBroker.currencies && Object.keys(discoveryBroker.currencies).length > 0) {
|
|
316146
|
+
return { handled: true, result: discoveryBroker.currencies };
|
|
316147
|
+
}
|
|
316148
|
+
throw new Error("venue_discovery_unavailable: fetchCurrencies unavailable on broker");
|
|
316149
|
+
}
|
|
316150
|
+
return { handled: false };
|
|
316151
|
+
}
|
|
316152
|
+
async function fetchCurrencyMetadata(broker, assetCode) {
|
|
316153
|
+
const discoveryBroker = broker;
|
|
316154
|
+
const normalizedAsset = assetCode.trim().toUpperCase();
|
|
316155
|
+
if (typeof discoveryBroker.fetchCurrencies === "function" && discoveryBroker.has?.fetchCurrencies !== false) {
|
|
316156
|
+
const currencies = await discoveryBroker.fetchCurrencies();
|
|
316157
|
+
return currencies[normalizedAsset];
|
|
316158
|
+
}
|
|
316159
|
+
return discoveryBroker.currencies?.[normalizedAsset];
|
|
316160
|
+
}
|
|
316161
|
+
|
|
316162
|
+
// src/helpers/transfer-network.ts
|
|
316163
|
+
function networkAliasSet(brokerNetworkId, networkKey) {
|
|
316164
|
+
const aliases = new Set([
|
|
316165
|
+
brokerNetworkId,
|
|
316166
|
+
networkKey.trim().toUpperCase()
|
|
316167
|
+
]);
|
|
316168
|
+
if (brokerNetworkId === "BNB") {
|
|
316169
|
+
aliases.add("BNB");
|
|
316170
|
+
aliases.add("BSC");
|
|
316171
|
+
aliases.add("BEP20");
|
|
316172
|
+
}
|
|
316173
|
+
return [...aliases].filter((alias) => alias.length > 0);
|
|
316174
|
+
}
|
|
316175
|
+
function buildTransferNetworkEvidence(currencyInfo) {
|
|
316176
|
+
const rawNetworks = isRecord(currencyInfo.networks) ? currencyInfo.networks : {};
|
|
316177
|
+
const networks = {};
|
|
316178
|
+
const aliases = {};
|
|
316179
|
+
for (const [networkKey, networkValue] of Object.entries(rawNetworks)) {
|
|
316180
|
+
const networkRecord = isRecord(networkValue) ? networkValue : {};
|
|
316181
|
+
const exchangeNetworkId = String(networkRecord.id ?? networkRecord.network ?? networkKey);
|
|
316182
|
+
const brokerNetworkId = normalizeBrokerNetworkId(String(networkRecord.network ?? networkKey));
|
|
316183
|
+
const evidence = {
|
|
316184
|
+
operatorAlias: networkKey,
|
|
316185
|
+
brokerNetworkId,
|
|
316186
|
+
exchangeNetworkId,
|
|
316187
|
+
networkKey
|
|
316188
|
+
};
|
|
316189
|
+
networks[networkKey] = {
|
|
316190
|
+
...networkRecord,
|
|
316191
|
+
operatorAlias: networkKey,
|
|
316192
|
+
brokerNetworkId,
|
|
316193
|
+
exchangeNetworkId
|
|
316194
|
+
};
|
|
316195
|
+
for (const alias of networkAliasSet(brokerNetworkId, networkKey)) {
|
|
316196
|
+
aliases[alias] = { ...evidence, operatorAlias: alias };
|
|
316197
|
+
}
|
|
316198
|
+
}
|
|
316199
|
+
return { networks, aliases };
|
|
316200
|
+
}
|
|
316201
|
+
async function resolveTransferNetwork(broker, assetCode, operatorAlias) {
|
|
316202
|
+
const requestedAlias = operatorAlias.trim().toUpperCase();
|
|
316203
|
+
const brokerNetworkId = normalizeBrokerNetworkId(requestedAlias);
|
|
316204
|
+
let currencyInfo = null;
|
|
316205
|
+
try {
|
|
316206
|
+
currencyInfo = await fetchCurrencyMetadata(broker, assetCode);
|
|
316207
|
+
} catch (error) {
|
|
316208
|
+
safeLogError(`Network discovery failed for ${assetCode}/${operatorAlias}; using operator alias as exchange network id`, error);
|
|
316209
|
+
}
|
|
316210
|
+
if (currencyInfo) {
|
|
316211
|
+
const evidence = buildTransferNetworkEvidence(currencyInfo);
|
|
316212
|
+
const resolved = evidence.aliases[requestedAlias] ?? evidence.aliases[brokerNetworkId];
|
|
316213
|
+
if (resolved) {
|
|
316214
|
+
return { ...resolved, operatorAlias: requestedAlias, brokerNetworkId };
|
|
316215
|
+
}
|
|
316216
|
+
throw new Error(`network_alias_unresolved: ${assetCode}/${requestedAlias} is not available in discovered transfer networks`);
|
|
316217
|
+
}
|
|
316218
|
+
return {
|
|
316219
|
+
operatorAlias: requestedAlias,
|
|
316220
|
+
brokerNetworkId,
|
|
316221
|
+
exchangeNetworkId: requestedAlias,
|
|
316222
|
+
networkKey: null
|
|
316223
|
+
};
|
|
316224
|
+
}
|
|
316225
|
+
|
|
316226
|
+
// node_modules/zod/v4/classic/external.js
|
|
316227
|
+
var exports_external = {};
|
|
316228
|
+
__export(exports_external, {
|
|
316229
|
+
xor: () => xor,
|
|
316230
|
+
xid: () => xid2,
|
|
316231
|
+
void: () => _void2,
|
|
316232
|
+
uuidv7: () => uuidv7,
|
|
316233
|
+
uuidv6: () => uuidv6,
|
|
316234
|
+
uuidv4: () => uuidv4,
|
|
316235
|
+
uuid: () => uuid5,
|
|
316236
|
+
util: () => exports_util,
|
|
316237
|
+
url: () => url2,
|
|
316238
|
+
uppercase: () => _uppercase,
|
|
316239
|
+
unknown: () => unknown,
|
|
316240
|
+
union: () => union,
|
|
316241
|
+
undefined: () => _undefined3,
|
|
316242
|
+
ulid: () => ulid2,
|
|
316243
|
+
uint64: () => uint64,
|
|
316244
|
+
uint32: () => uint32,
|
|
316245
|
+
tuple: () => tuple,
|
|
316246
|
+
trim: () => _trim,
|
|
315472
316247
|
treeifyError: () => treeifyError,
|
|
315473
316248
|
transform: () => transform,
|
|
315474
316249
|
toUpperCase: () => _toUpperCase,
|
|
@@ -329709,240 +330484,31 @@ async function handleInternalTransfer(ctx) {
|
|
|
329709
330484
|
result: JSON.stringify(result)
|
|
329710
330485
|
});
|
|
329711
330486
|
} catch (error48) {
|
|
329712
|
-
safeLogError("InternalTransfer failed", error48);
|
|
329713
|
-
if (error48 instanceof BrokerAccountPreconditionError) {
|
|
329714
|
-
return ctx.wrappedCallback({
|
|
329715
|
-
code: grpc5.status.FAILED_PRECONDITION,
|
|
329716
|
-
message: getErrorMessage(error48)
|
|
329717
|
-
}, null);
|
|
329718
|
-
}
|
|
329719
|
-
const msg = getErrorMessage(error48);
|
|
329720
|
-
let code;
|
|
329721
|
-
if (msg.includes("Unsupported transfer direction")) {
|
|
329722
|
-
code = grpc5.status.INVALID_ARGUMENT;
|
|
329723
|
-
} else if (msg.includes("unavailable in this CCXT build")) {
|
|
329724
|
-
code = grpc5.status.UNIMPLEMENTED;
|
|
329725
|
-
} else {
|
|
329726
|
-
code = mapCcxtErrorToGrpcStatus(error48) ?? grpc5.status.INTERNAL;
|
|
329727
|
-
}
|
|
329728
|
-
ctx.wrappedCallback({
|
|
329729
|
-
code,
|
|
329730
|
-
message: `InternalTransfer failed: ${msg}`
|
|
329731
|
-
}, null);
|
|
329732
|
-
}
|
|
329733
|
-
}
|
|
329734
|
-
|
|
329735
|
-
// src/handlers/execute-action/orders.ts
|
|
329736
|
-
var grpc6 = __toESM(require_src3(), 1);
|
|
329737
|
-
|
|
329738
|
-
// src/helpers/order-telemetry.ts
|
|
329739
|
-
var NUMERIC_METRICS = [
|
|
329740
|
-
["requestedQuantity", "cex_market_action_requested_quantity"],
|
|
329741
|
-
["requestedNotional", "cex_market_action_requested_notional"],
|
|
329742
|
-
["executedBaseQuantity", "cex_market_action_executed_base_quantity"],
|
|
329743
|
-
["executedQuoteQuantity", "cex_market_action_executed_quote_quantity"],
|
|
329744
|
-
["averageExecutionPrice", "cex_market_action_average_execution_price"],
|
|
329745
|
-
["filledAmount", "cex_market_action_filled_amount"],
|
|
329746
|
-
["remainingAmount", "cex_market_action_remaining_amount"],
|
|
329747
|
-
["feeAmount", "cex_market_action_fee_amount"],
|
|
329748
|
-
["feeRate", "cex_market_action_fee_rate"]
|
|
329749
|
-
];
|
|
329750
|
-
var REDACTED_ERROR_MESSAGE = "redacted_error";
|
|
329751
|
-
async function emitOrderExecutionTelemetry(otelMetrics, context2, order, error48) {
|
|
329752
|
-
try {
|
|
329753
|
-
const telemetry = buildOrderExecutionTelemetry(context2, order, error48);
|
|
329754
|
-
log.info("CEX market action execution telemetry", telemetry);
|
|
329755
|
-
const labels = {
|
|
329756
|
-
action: telemetry.action,
|
|
329757
|
-
cex: telemetry.cex,
|
|
329758
|
-
account: telemetry.accountLabel,
|
|
329759
|
-
symbol: telemetry.symbol,
|
|
329760
|
-
side: telemetry.side,
|
|
329761
|
-
order_type: telemetry.orderType,
|
|
329762
|
-
status: telemetry.status
|
|
329763
|
-
};
|
|
329764
|
-
await otelMetrics?.recordCounter("cex_market_action_executions_total", 1, {
|
|
329765
|
-
...labels,
|
|
329766
|
-
result: error48 ? "error" : "ok"
|
|
329767
|
-
});
|
|
329768
|
-
for (const [key, metricName] of NUMERIC_METRICS) {
|
|
329769
|
-
const value = telemetry[key];
|
|
329770
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
329771
|
-
await otelMetrics?.recordHistogram(metricName, value, labels);
|
|
329772
|
-
}
|
|
329773
|
-
}
|
|
329774
|
-
return telemetry;
|
|
329775
|
-
} catch (telemetryError) {
|
|
329776
|
-
try {
|
|
329777
|
-
log.error("Failed to emit CEX order telemetry", {
|
|
329778
|
-
error: telemetryError
|
|
329779
|
-
});
|
|
329780
|
-
} catch {}
|
|
329781
|
-
return;
|
|
329782
|
-
}
|
|
329783
|
-
}
|
|
329784
|
-
function buildOrderExecutionTelemetry(context2, order, error48) {
|
|
329785
|
-
const record2 = asRecord(order);
|
|
329786
|
-
const info = asRecord(record2?.info);
|
|
329787
|
-
const fees = getFees(record2, info);
|
|
329788
|
-
const fee = summarizeFees(fees);
|
|
329789
|
-
const executedBaseQuantity = firstNumber(record2?.filled, info?.executedQty, info?.cumExecQty) ?? computeFilledFromAmount(record2);
|
|
329790
|
-
const executedQuoteQuantity = firstNumber(record2?.cost, info?.cummulativeQuoteQty, info?.cumQuote, info?.cumExecValue);
|
|
329791
|
-
const averageExecutionPrice = firstNumber(record2?.average, info?.avgPrice) ?? computeAveragePrice(executedBaseQuantity, executedQuoteQuantity);
|
|
329792
|
-
const exchangeTimestamp = normalizeTimestamp(firstValue(record2?.timestamp, info?.time, info?.transactTime, record2?.datetime));
|
|
329793
|
-
const status6 = firstString(record2?.status, info?.status) ?? (error48 ? "failed" : "unknown");
|
|
329794
|
-
const errorRecord = error48 instanceof Error ? error48 : undefined;
|
|
329795
|
-
return compactUndefined({
|
|
329796
|
-
event: "cex_market_action_execution",
|
|
329797
|
-
action: context2.action,
|
|
329798
|
-
cex: context2.cex.trim().toLowerCase() || "unknown",
|
|
329799
|
-
accountLabel: context2.accountLabel ?? "unknown",
|
|
329800
|
-
symbol: firstString(record2?.symbol, info?.symbol, context2.symbol) ?? "unknown",
|
|
329801
|
-
side: firstString(record2?.side, info?.side, context2.side) ?? "unknown",
|
|
329802
|
-
orderType: firstString(record2?.type, info?.type, context2.orderType) ?? "unknown",
|
|
329803
|
-
orderId: firstString(record2?.id, info?.orderId, info?.orderID),
|
|
329804
|
-
clientOrderId: firstString(context2.clientOrderId, record2?.clientOrderId, record2?.clientOrderID, record2?.clientOid, info?.clientOrderId, info?.clientOrderID, info?.clientOid),
|
|
329805
|
-
idempotencyId: context2.idempotencyId,
|
|
329806
|
-
makerActionId: context2.makerActionId,
|
|
329807
|
-
status: status6.toLowerCase(),
|
|
329808
|
-
requestedQuantity: context2.requestedQuantity ?? firstNumber(record2?.amount),
|
|
329809
|
-
requestedNotional: context2.requestedNotional,
|
|
329810
|
-
executedBaseQuantity,
|
|
329811
|
-
executedQuoteQuantity,
|
|
329812
|
-
averageExecutionPrice,
|
|
329813
|
-
filledAmount: firstNumber(record2?.filled, info?.executedQty),
|
|
329814
|
-
remainingAmount: firstNumber(record2?.remaining, info?.remainingQty),
|
|
329815
|
-
feeAmount: fee.amount,
|
|
329816
|
-
feeCurrency: fee.currency,
|
|
329817
|
-
feeRate: fee.rate,
|
|
329818
|
-
exchangeTimestamp,
|
|
329819
|
-
brokerObservedTimestamp: context2.brokerObservedTimestamp ?? new Date().toISOString(),
|
|
329820
|
-
errorType: errorRecord?.name,
|
|
329821
|
-
errorMessage: errorRecord ? REDACTED_ERROR_MESSAGE : undefined
|
|
329822
|
-
});
|
|
329823
|
-
}
|
|
329824
|
-
function emitOrderExecutionTelemetryInBackground(otelMetrics, context2, order, error48) {
|
|
329825
|
-
emitOrderExecutionTelemetry(otelMetrics, context2, order, error48).catch((telemetryError) => {
|
|
329826
|
-
try {
|
|
329827
|
-
log.warn("Telemetry emit failed", { error: telemetryError });
|
|
329828
|
-
} catch {
|
|
329829
|
-
console.warn("Telemetry emit failed", telemetryError);
|
|
329830
|
-
}
|
|
329831
|
-
});
|
|
329832
|
-
}
|
|
329833
|
-
function extractOrderTelemetryIds(params) {
|
|
329834
|
-
const record2 = params ?? {};
|
|
329835
|
-
return {
|
|
329836
|
-
clientOrderId: firstString(record2.clientOrderId, record2.clientOrderID, record2.newClientOrderId, record2.clientOid),
|
|
329837
|
-
idempotencyId: firstString(record2.idempotencyId, record2.idempotencyID, record2.idempotencyKey, record2.requestId, record2.requestID),
|
|
329838
|
-
makerActionId: firstString(record2.makerActionId, record2.maker_action_id, record2.actionId, record2.action_id)
|
|
329839
|
-
};
|
|
329840
|
-
}
|
|
329841
|
-
function firstValue(...values2) {
|
|
329842
|
-
return values2.find((value) => value !== undefined && value !== null);
|
|
329843
|
-
}
|
|
329844
|
-
function firstString(...values2) {
|
|
329845
|
-
for (const value of values2) {
|
|
329846
|
-
if (typeof value === "string" && value.trim()) {
|
|
329847
|
-
return value.trim();
|
|
329848
|
-
}
|
|
329849
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
329850
|
-
return String(value);
|
|
329851
|
-
}
|
|
329852
|
-
}
|
|
329853
|
-
return;
|
|
329854
|
-
}
|
|
329855
|
-
function firstNumber(...values2) {
|
|
329856
|
-
for (const value of values2) {
|
|
329857
|
-
const numberValue = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
|
|
329858
|
-
if (Number.isFinite(numberValue)) {
|
|
329859
|
-
return numberValue;
|
|
329860
|
-
}
|
|
329861
|
-
}
|
|
329862
|
-
return;
|
|
329863
|
-
}
|
|
329864
|
-
function computeFilledFromAmount(record2) {
|
|
329865
|
-
const amount = firstNumber(record2?.amount);
|
|
329866
|
-
const remaining = firstNumber(record2?.remaining);
|
|
329867
|
-
if (amount === undefined || remaining === undefined) {
|
|
329868
|
-
return;
|
|
329869
|
-
}
|
|
329870
|
-
return amount - remaining;
|
|
329871
|
-
}
|
|
329872
|
-
function computeAveragePrice(executedBaseQuantity, executedQuoteQuantity) {
|
|
329873
|
-
if (executedBaseQuantity === undefined || executedQuoteQuantity === undefined || executedBaseQuantity === 0) {
|
|
329874
|
-
return;
|
|
329875
|
-
}
|
|
329876
|
-
return executedQuoteQuantity / executedBaseQuantity;
|
|
329877
|
-
}
|
|
329878
|
-
function normalizeTimestamp(value) {
|
|
329879
|
-
if (typeof value === "string" && value.trim()) {
|
|
329880
|
-
return value.trim();
|
|
329881
|
-
}
|
|
329882
|
-
const timestamp = firstNumber(value);
|
|
329883
|
-
if (timestamp === undefined) {
|
|
329884
|
-
return;
|
|
329885
|
-
}
|
|
329886
|
-
const date5 = new Date(timestamp);
|
|
329887
|
-
return Number.isNaN(date5.getTime()) ? undefined : date5.toISOString();
|
|
329888
|
-
}
|
|
329889
|
-
function getFees(record2, info) {
|
|
329890
|
-
const fees = [];
|
|
329891
|
-
if (record2?.fee)
|
|
329892
|
-
fees.push(record2.fee);
|
|
329893
|
-
if (Array.isArray(record2?.fees))
|
|
329894
|
-
fees.push(...record2.fees);
|
|
329895
|
-
if (Array.isArray(record2?.trades)) {
|
|
329896
|
-
for (const trade of record2.trades) {
|
|
329897
|
-
const tradeRecord = asRecord(trade);
|
|
329898
|
-
if (tradeRecord?.fee)
|
|
329899
|
-
fees.push(tradeRecord.fee);
|
|
329900
|
-
if (Array.isArray(tradeRecord?.fees))
|
|
329901
|
-
fees.push(...tradeRecord.fees);
|
|
329902
|
-
}
|
|
329903
|
-
}
|
|
329904
|
-
if (Array.isArray(info?.fills)) {
|
|
329905
|
-
for (const fill of info.fills) {
|
|
329906
|
-
const fillRecord = asRecord(fill);
|
|
329907
|
-
const commission = firstNumber(fillRecord?.commission);
|
|
329908
|
-
if (commission !== undefined) {
|
|
329909
|
-
fees.push({
|
|
329910
|
-
cost: commission,
|
|
329911
|
-
currency: firstString(fillRecord?.commissionAsset)
|
|
329912
|
-
});
|
|
329913
|
-
}
|
|
329914
|
-
}
|
|
329915
|
-
}
|
|
329916
|
-
return fees;
|
|
329917
|
-
}
|
|
329918
|
-
function summarizeFees(fees) {
|
|
329919
|
-
let amount = 0;
|
|
329920
|
-
let amountFound = false;
|
|
329921
|
-
let currency;
|
|
329922
|
-
let rate;
|
|
329923
|
-
for (const rawFee of fees) {
|
|
329924
|
-
const fee = asRecord(rawFee);
|
|
329925
|
-
if (!fee)
|
|
329926
|
-
continue;
|
|
329927
|
-
const cost = firstNumber(fee.cost, fee.amount);
|
|
329928
|
-
if (cost !== undefined) {
|
|
329929
|
-
amount += cost;
|
|
329930
|
-
amountFound = true;
|
|
330487
|
+
safeLogError("InternalTransfer failed", error48);
|
|
330488
|
+
if (error48 instanceof BrokerAccountPreconditionError) {
|
|
330489
|
+
return ctx.wrappedCallback({
|
|
330490
|
+
code: grpc5.status.FAILED_PRECONDITION,
|
|
330491
|
+
message: getErrorMessage(error48)
|
|
330492
|
+
}, null);
|
|
329931
330493
|
}
|
|
329932
|
-
|
|
329933
|
-
|
|
330494
|
+
const msg = getErrorMessage(error48);
|
|
330495
|
+
let code;
|
|
330496
|
+
if (msg.includes("Unsupported transfer direction")) {
|
|
330497
|
+
code = grpc5.status.INVALID_ARGUMENT;
|
|
330498
|
+
} else if (msg.includes("unavailable in this CCXT build")) {
|
|
330499
|
+
code = grpc5.status.UNIMPLEMENTED;
|
|
330500
|
+
} else {
|
|
330501
|
+
code = mapCcxtErrorToGrpcStatus(error48) ?? grpc5.status.INTERNAL;
|
|
330502
|
+
}
|
|
330503
|
+
ctx.wrappedCallback({
|
|
330504
|
+
code,
|
|
330505
|
+
message: `InternalTransfer failed: ${msg}`
|
|
330506
|
+
}, null);
|
|
329934
330507
|
}
|
|
329935
|
-
return {
|
|
329936
|
-
amount: amountFound ? amount : undefined,
|
|
329937
|
-
currency,
|
|
329938
|
-
rate
|
|
329939
|
-
};
|
|
329940
|
-
}
|
|
329941
|
-
function compactUndefined(record2) {
|
|
329942
|
-
return Object.fromEntries(Object.entries(record2).filter(([, value]) => value !== undefined));
|
|
329943
330508
|
}
|
|
329944
330509
|
|
|
329945
330510
|
// src/handlers/execute-action/orders.ts
|
|
330511
|
+
var grpc6 = __toESM(require_src3(), 1);
|
|
329946
330512
|
async function handleCreateOrder(ctx) {
|
|
329947
330513
|
const {
|
|
329948
330514
|
call,
|
|
@@ -329959,7 +330525,8 @@ async function handleCreateOrder(ctx) {
|
|
|
329959
330525
|
applyVerityToBroker,
|
|
329960
330526
|
useVerity,
|
|
329961
330527
|
verityProverUrl,
|
|
329962
|
-
otelMetrics
|
|
330528
|
+
otelMetrics,
|
|
330529
|
+
brokerArchiver
|
|
329963
330530
|
} = ctx;
|
|
329964
330531
|
const verityProof = verity.proof;
|
|
329965
330532
|
const orderValue = parsePayloadForAction(ctx, CreateOrderPayloadSchema);
|
|
@@ -329985,8 +330552,18 @@ async function handleCreateOrder(ctx) {
|
|
|
329985
330552
|
side: resolution.side,
|
|
329986
330553
|
requestedQuantity: resolution.amountBase ?? orderValue.amount
|
|
329987
330554
|
};
|
|
330555
|
+
const telemetryIds = extractOrderTelemetryIds(orderValue.params);
|
|
330556
|
+
const submissionTimestamp = new Date().toISOString();
|
|
330557
|
+
const marketMetadataHash = await captureMarketMetadataSnapshot(brokerArchiver, broker, {
|
|
330558
|
+
exchange: cex3,
|
|
330559
|
+
accountSelector: selectedBrokerAccount?.label,
|
|
330560
|
+
symbol: resolution.symbol,
|
|
330561
|
+
action: "CreateOrder",
|
|
330562
|
+
brokerObservedTimestamp: submissionTimestamp,
|
|
330563
|
+
...telemetryIds
|
|
330564
|
+
});
|
|
329988
330565
|
const order = await broker.createOrder(resolution.symbol, orderValue.orderType, resolution.side, resolution.amountBase ?? orderValue.amount, orderValue.price, orderValue.params ?? {});
|
|
329989
|
-
|
|
330566
|
+
const createOrderContext = {
|
|
329990
330567
|
action: "CreateOrder",
|
|
329991
330568
|
cex: cex3,
|
|
329992
330569
|
accountLabel: selectedBrokerAccount?.label,
|
|
@@ -329995,12 +330572,15 @@ async function handleCreateOrder(ctx) {
|
|
|
329995
330572
|
orderType: orderValue.orderType,
|
|
329996
330573
|
requestedQuantity: resolvedOrderTelemetry.requestedQuantity,
|
|
329997
330574
|
requestedNotional: orderValue.amount * orderValue.price,
|
|
329998
|
-
|
|
329999
|
-
|
|
330575
|
+
brokerObservedTimestamp: submissionTimestamp,
|
|
330576
|
+
...telemetryIds
|
|
330577
|
+
};
|
|
330578
|
+
emitOrderExecutionTelemetryInBackground(otelMetrics, createOrderContext, order);
|
|
330579
|
+
archiveOrderExecutionInBackground(brokerArchiver, createOrderContext, order, undefined, { marketMetadataHash });
|
|
330000
330580
|
ctx.wrappedCallback(null, { result: JSON.stringify({ ...order }) });
|
|
330001
330581
|
} catch (error48) {
|
|
330002
330582
|
safeLogError("Order Creation failed", error48);
|
|
330003
|
-
|
|
330583
|
+
const failedCreateContext = {
|
|
330004
330584
|
action: "CreateOrder",
|
|
330005
330585
|
cex: cex3,
|
|
330006
330586
|
accountLabel: selectedBrokerAccount?.label,
|
|
@@ -330010,7 +330590,9 @@ async function handleCreateOrder(ctx) {
|
|
|
330010
330590
|
requestedQuantity: resolvedOrderTelemetry.requestedQuantity ?? orderValue.amount,
|
|
330011
330591
|
requestedNotional: orderValue.amount * orderValue.price,
|
|
330012
330592
|
...extractOrderTelemetryIds(orderValue.params)
|
|
330013
|
-
}
|
|
330593
|
+
};
|
|
330594
|
+
emitOrderExecutionTelemetryInBackground(otelMetrics, failedCreateContext, undefined, error48);
|
|
330595
|
+
archiveOrderExecutionInBackground(brokerArchiver, failedCreateContext, undefined, error48);
|
|
330014
330596
|
ctx.wrappedCallback({
|
|
330015
330597
|
code: grpc6.status.INTERNAL,
|
|
330016
330598
|
message: "Order Creation failed"
|
|
@@ -330033,7 +330615,8 @@ async function handleGetOrderDetails(ctx) {
|
|
|
330033
330615
|
applyVerityToBroker,
|
|
330034
330616
|
useVerity,
|
|
330035
330617
|
verityProverUrl,
|
|
330036
|
-
otelMetrics
|
|
330618
|
+
otelMetrics,
|
|
330619
|
+
brokerArchiver
|
|
330037
330620
|
} = ctx;
|
|
330038
330621
|
const verityProof = verity.proof;
|
|
330039
330622
|
const getOrderValue = parsePayloadForAction(ctx, GetOrderDetailsPayloadSchema);
|
|
@@ -330047,13 +330630,15 @@ async function handleGetOrderDetails(ctx) {
|
|
|
330047
330630
|
}, null);
|
|
330048
330631
|
}
|
|
330049
330632
|
const orderDetails = await broker.fetchOrder(getOrderValue.orderId, symbol2, { ...getOrderValue.params });
|
|
330050
|
-
|
|
330633
|
+
const getOrderContext = {
|
|
330051
330634
|
action: "GetOrderDetails",
|
|
330052
330635
|
cex: cex3,
|
|
330053
330636
|
accountLabel: selectedBrokerAccount?.label,
|
|
330054
330637
|
symbol: symbol2,
|
|
330055
330638
|
...extractOrderTelemetryIds(getOrderValue.params)
|
|
330056
|
-
}
|
|
330639
|
+
};
|
|
330640
|
+
emitOrderExecutionTelemetryInBackground(otelMetrics, getOrderContext, orderDetails);
|
|
330641
|
+
archiveOrderExecutionInBackground(brokerArchiver, getOrderContext, orderDetails);
|
|
330057
330642
|
ctx.wrappedCallback(null, {
|
|
330058
330643
|
result: JSON.stringify({
|
|
330059
330644
|
orderId: orderDetails.id,
|
|
@@ -330068,6 +330653,15 @@ async function handleGetOrderDetails(ctx) {
|
|
|
330068
330653
|
});
|
|
330069
330654
|
} catch (error48) {
|
|
330070
330655
|
safeLogError(`Error fetching order details from ${cex3}`, error48);
|
|
330656
|
+
const failedGetOrderContext = {
|
|
330657
|
+
action: "GetOrderDetails",
|
|
330658
|
+
cex: cex3,
|
|
330659
|
+
accountLabel: selectedBrokerAccount?.label,
|
|
330660
|
+
symbol: symbol2,
|
|
330661
|
+
...extractOrderTelemetryIds(getOrderValue.params)
|
|
330662
|
+
};
|
|
330663
|
+
emitOrderExecutionTelemetryInBackground(otelMetrics, failedGetOrderContext, undefined, error48);
|
|
330664
|
+
archiveOrderExecutionInBackground(brokerArchiver, failedGetOrderContext, undefined, error48);
|
|
330071
330665
|
ctx.wrappedCallback({
|
|
330072
330666
|
code: grpc6.status.INTERNAL,
|
|
330073
330667
|
message: `Failed to fetch order details from ${cex3}`
|
|
@@ -330090,19 +330684,44 @@ async function handleCancelOrder(ctx) {
|
|
|
330090
330684
|
applyVerityToBroker,
|
|
330091
330685
|
useVerity,
|
|
330092
330686
|
verityProverUrl,
|
|
330093
|
-
otelMetrics
|
|
330687
|
+
otelMetrics,
|
|
330688
|
+
brokerArchiver
|
|
330094
330689
|
} = ctx;
|
|
330095
330690
|
const verityProof = verity.proof;
|
|
330096
330691
|
const cancelOrderValue = parsePayloadForAction(ctx, CancelOrderPayloadSchema);
|
|
330097
330692
|
if (cancelOrderValue === null)
|
|
330098
330693
|
return;
|
|
330099
330694
|
try {
|
|
330695
|
+
if (!broker) {
|
|
330696
|
+
return ctx.wrappedCallback({
|
|
330697
|
+
code: grpc6.status.INVALID_ARGUMENT,
|
|
330698
|
+
message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
|
|
330699
|
+
}, null);
|
|
330700
|
+
}
|
|
330701
|
+
const cancelOrderContext = {
|
|
330702
|
+
action: "CancelOrder",
|
|
330703
|
+
cex: cex3,
|
|
330704
|
+
accountLabel: selectedBrokerAccount?.label,
|
|
330705
|
+
symbol: symbol2,
|
|
330706
|
+
...extractOrderTelemetryIds(cancelOrderValue.params)
|
|
330707
|
+
};
|
|
330100
330708
|
const cancelledOrder = await broker.cancelOrder(cancelOrderValue.orderId, symbol2, cancelOrderValue.params ?? {});
|
|
330709
|
+
emitOrderExecutionTelemetryInBackground(otelMetrics, cancelOrderContext, cancelledOrder);
|
|
330710
|
+
archiveOrderExecutionInBackground(brokerArchiver, cancelOrderContext, cancelledOrder);
|
|
330101
330711
|
ctx.wrappedCallback(null, {
|
|
330102
330712
|
result: JSON.stringify({ ...cancelledOrder })
|
|
330103
330713
|
});
|
|
330104
330714
|
} catch (error48) {
|
|
330105
330715
|
safeLogError(`Error cancelling order from ${cex3}`, error48);
|
|
330716
|
+
const failedCancelContext = {
|
|
330717
|
+
action: "CancelOrder",
|
|
330718
|
+
cex: cex3,
|
|
330719
|
+
accountLabel: selectedBrokerAccount?.label,
|
|
330720
|
+
symbol: symbol2,
|
|
330721
|
+
...extractOrderTelemetryIds(cancelOrderValue.params)
|
|
330722
|
+
};
|
|
330723
|
+
emitOrderExecutionTelemetryInBackground(otelMetrics, failedCancelContext, undefined, error48);
|
|
330724
|
+
archiveOrderExecutionInBackground(brokerArchiver, failedCancelContext, undefined, error48);
|
|
330106
330725
|
ctx.wrappedCallback({
|
|
330107
330726
|
code: grpc6.status.INTERNAL,
|
|
330108
330727
|
message: `Failed to cancel order from ${cex3}`
|
|
@@ -330479,31 +331098,229 @@ async function handleFetchBalances(ctx) {
|
|
|
330479
331098
|
const partial2 = await broker.fetchTotalBalance(params);
|
|
330480
331099
|
responseBalances = partial2 ?? {};
|
|
330481
331100
|
}
|
|
330482
|
-
if (symbol2) {
|
|
330483
|
-
if (typeof responseBalances[symbol2] === "number") {
|
|
330484
|
-
responseBalances = {
|
|
330485
|
-
[symbol2]: responseBalances[symbol2] ?? 0
|
|
330486
|
-
};
|
|
330487
|
-
} else {
|
|
330488
|
-
responseBalances = {};
|
|
330489
|
-
}
|
|
331101
|
+
if (symbol2) {
|
|
331102
|
+
if (typeof responseBalances[symbol2] === "number") {
|
|
331103
|
+
responseBalances = {
|
|
331104
|
+
[symbol2]: responseBalances[symbol2] ?? 0
|
|
331105
|
+
};
|
|
331106
|
+
} else {
|
|
331107
|
+
responseBalances = {};
|
|
331108
|
+
}
|
|
331109
|
+
}
|
|
331110
|
+
ctx.wrappedCallback(null, {
|
|
331111
|
+
proof: ctx.verity.proof,
|
|
331112
|
+
result: JSON.stringify({
|
|
331113
|
+
balances: responseBalances,
|
|
331114
|
+
balanceType
|
|
331115
|
+
})
|
|
331116
|
+
});
|
|
331117
|
+
} catch (error48) {
|
|
331118
|
+
safeLogError(`Error fetching balance from ${cex3}`, error48);
|
|
331119
|
+
ctx.wrappedCallback({
|
|
331120
|
+
code: grpc7.status.INTERNAL,
|
|
331121
|
+
message: `Failed to fetch balance from ${cex3}`
|
|
331122
|
+
}, null);
|
|
331123
|
+
}
|
|
331124
|
+
}
|
|
331125
|
+
async function handleFetchTicker(ctx) {
|
|
331126
|
+
const {
|
|
331127
|
+
call,
|
|
331128
|
+
wrappedCallback,
|
|
331129
|
+
policy,
|
|
331130
|
+
brokers,
|
|
331131
|
+
metadata,
|
|
331132
|
+
normalizedCex,
|
|
331133
|
+
cex: cex3,
|
|
331134
|
+
symbol: symbol2,
|
|
331135
|
+
selectedBrokerAccount,
|
|
331136
|
+
broker,
|
|
331137
|
+
verity,
|
|
331138
|
+
applyVerityToBroker,
|
|
331139
|
+
useVerity,
|
|
331140
|
+
verityProverUrl,
|
|
331141
|
+
otelMetrics
|
|
331142
|
+
} = ctx;
|
|
331143
|
+
const verityProof = verity.proof;
|
|
331144
|
+
if (!symbol2) {
|
|
331145
|
+
return ctx.wrappedCallback({
|
|
331146
|
+
code: grpc7.status.INVALID_ARGUMENT,
|
|
331147
|
+
message: `ValidationError: Symbol required`
|
|
331148
|
+
}, null);
|
|
331149
|
+
}
|
|
331150
|
+
try {
|
|
331151
|
+
const ticker = await broker.fetchTicker(symbol2);
|
|
331152
|
+
ctx.wrappedCallback(null, {
|
|
331153
|
+
proof: ctx.verity.proof,
|
|
331154
|
+
result: JSON.stringify(ticker)
|
|
331155
|
+
});
|
|
331156
|
+
} catch (error48) {
|
|
331157
|
+
safeLogError(`Error fetching ticker from ${cex3}`, error48);
|
|
331158
|
+
ctx.wrappedCallback({
|
|
331159
|
+
code: grpc7.status.INTERNAL,
|
|
331160
|
+
message: `Failed to fetch ticker from ${cex3}`
|
|
331161
|
+
}, null);
|
|
331162
|
+
}
|
|
331163
|
+
}
|
|
331164
|
+
async function handlePassThrough(ctx) {
|
|
331165
|
+
if (ctx.action === Action.FetchCurrency)
|
|
331166
|
+
return handleFetchCurrency(ctx);
|
|
331167
|
+
if (ctx.action === Action.FetchAccountId)
|
|
331168
|
+
return handleFetchAccountId(ctx);
|
|
331169
|
+
if (ctx.action === Action.FetchFees)
|
|
331170
|
+
return handleFetchFees(ctx);
|
|
331171
|
+
if (ctx.action === Action.FetchDepositAddresses)
|
|
331172
|
+
return handleFetchDepositAddresses(ctx);
|
|
331173
|
+
if (ctx.action === Action.FetchBalances)
|
|
331174
|
+
return handleFetchBalances(ctx);
|
|
331175
|
+
if (ctx.action === Action.FetchTicker)
|
|
331176
|
+
return handleFetchTicker(ctx);
|
|
331177
|
+
}
|
|
331178
|
+
|
|
331179
|
+
// src/handlers/execute-action/perp-config.ts
|
|
331180
|
+
var grpc8 = __toESM(require_src3(), 1);
|
|
331181
|
+
function exchangeSupports(broker, capability) {
|
|
331182
|
+
return broker.has?.[capability] === true;
|
|
331183
|
+
}
|
|
331184
|
+
function extractPerpConfigs(positions) {
|
|
331185
|
+
return positions.map((position) => ({
|
|
331186
|
+
symbol: typeof position.symbol === "string" ? position.symbol : undefined,
|
|
331187
|
+
leverage: typeof position.leverage === "number" ? position.leverage : undefined,
|
|
331188
|
+
marginMode: typeof position.marginMode === "string" ? position.marginMode : undefined
|
|
331189
|
+
}));
|
|
331190
|
+
}
|
|
331191
|
+
async function handleGetPerpConfigState(ctx) {
|
|
331192
|
+
const { wrappedCallback, cex: cex3, normalizedCex, broker } = ctx;
|
|
331193
|
+
const payload = parsePayloadForAction(ctx, GetPerpConfigStatePayloadSchema);
|
|
331194
|
+
if (payload === null) {
|
|
331195
|
+
return;
|
|
331196
|
+
}
|
|
331197
|
+
if (!broker) {
|
|
331198
|
+
return wrappedCallback({
|
|
331199
|
+
code: grpc8.status.INVALID_ARGUMENT,
|
|
331200
|
+
message: `Invalid CEX key: ${cex3}`
|
|
331201
|
+
}, null);
|
|
331202
|
+
}
|
|
331203
|
+
const exchange = broker;
|
|
331204
|
+
if (!exchangeSupports(exchange, "fetchPositions")) {
|
|
331205
|
+
return wrappedCallback({
|
|
331206
|
+
code: grpc8.status.UNIMPLEMENTED,
|
|
331207
|
+
message: `${normalizedCex} does not support fetchPositions`
|
|
331208
|
+
}, null);
|
|
331209
|
+
}
|
|
331210
|
+
try {
|
|
331211
|
+
const symbols = payload.symbol ? [payload.symbol] : undefined;
|
|
331212
|
+
const positions = await exchange.fetchPositions?.(symbols, payload.params);
|
|
331213
|
+
ctx.wrappedCallback(null, {
|
|
331214
|
+
result: JSON.stringify({
|
|
331215
|
+
exchange: normalizedCex,
|
|
331216
|
+
configs: extractPerpConfigs(positions ?? []),
|
|
331217
|
+
positions: positions ?? []
|
|
331218
|
+
})
|
|
331219
|
+
});
|
|
331220
|
+
} catch (error48) {
|
|
331221
|
+
safeLogError(`GetPerpConfigState failed for ${cex3}`, error48);
|
|
331222
|
+
ctx.wrappedCallback({
|
|
331223
|
+
code: grpc8.status.INTERNAL,
|
|
331224
|
+
message: getErrorMessage(error48)
|
|
331225
|
+
}, null);
|
|
331226
|
+
}
|
|
331227
|
+
}
|
|
331228
|
+
async function handleSetPerpConfigState(ctx) {
|
|
331229
|
+
const { wrappedCallback, cex: cex3, normalizedCex, broker } = ctx;
|
|
331230
|
+
const payload = parsePayloadForAction(ctx, SetPerpConfigStatePayloadSchema);
|
|
331231
|
+
if (payload === null) {
|
|
331232
|
+
return;
|
|
331233
|
+
}
|
|
331234
|
+
if (!broker) {
|
|
331235
|
+
return wrappedCallback({
|
|
331236
|
+
code: grpc8.status.INVALID_ARGUMENT,
|
|
331237
|
+
message: `Invalid CEX key: ${cex3}`
|
|
331238
|
+
}, null);
|
|
331239
|
+
}
|
|
331240
|
+
const exchange = broker;
|
|
331241
|
+
if (!exchangeSupports(exchange, "setLeverage")) {
|
|
331242
|
+
return wrappedCallback({
|
|
331243
|
+
code: grpc8.status.UNIMPLEMENTED,
|
|
331244
|
+
message: `${normalizedCex} does not support setLeverage`
|
|
331245
|
+
}, null);
|
|
331246
|
+
}
|
|
331247
|
+
try {
|
|
331248
|
+
const response = await exchange.setLeverage?.(payload.leverage, payload.symbol, {
|
|
331249
|
+
marginMode: payload.marginMode ?? "cross",
|
|
331250
|
+
...payload.params
|
|
331251
|
+
});
|
|
331252
|
+
ctx.wrappedCallback(null, {
|
|
331253
|
+
result: JSON.stringify({
|
|
331254
|
+
exchange: normalizedCex,
|
|
331255
|
+
symbol: payload.symbol,
|
|
331256
|
+
leverage: payload.leverage,
|
|
331257
|
+
marginMode: payload.marginMode ?? "cross",
|
|
331258
|
+
response
|
|
331259
|
+
})
|
|
331260
|
+
});
|
|
331261
|
+
} catch (error48) {
|
|
331262
|
+
safeLogError(`SetPerpConfigState failed for ${cex3}`, error48);
|
|
331263
|
+
ctx.wrappedCallback({
|
|
331264
|
+
code: grpc8.status.INTERNAL,
|
|
331265
|
+
message: getErrorMessage(error48)
|
|
331266
|
+
}, null);
|
|
331267
|
+
}
|
|
331268
|
+
}
|
|
331269
|
+
async function handlePerpConfig(ctx) {
|
|
331270
|
+
if (ctx.action === Action.GetPerpConfigState) {
|
|
331271
|
+
return handleGetPerpConfigState(ctx);
|
|
331272
|
+
}
|
|
331273
|
+
if (ctx.action === Action.SetPerpConfigState) {
|
|
331274
|
+
return handleSetPerpConfigState(ctx);
|
|
331275
|
+
}
|
|
331276
|
+
}
|
|
331277
|
+
|
|
331278
|
+
// src/handlers/execute-action/treasury-call.ts
|
|
331279
|
+
var grpc9 = __toESM(require_src3(), 1);
|
|
331280
|
+
async function handleTreasuryCall(ctx) {
|
|
331281
|
+
const { broker } = ctx;
|
|
331282
|
+
const callValue = parsePayloadForAction(ctx, CallPayloadSchema);
|
|
331283
|
+
if (callValue === null)
|
|
331284
|
+
return;
|
|
331285
|
+
try {
|
|
331286
|
+
if (callValue.functionName.startsWith("_") || callValue.functionName.includes("constructor") || callValue.functionName.includes("prototype")) {
|
|
331287
|
+
return ctx.wrappedCallback({
|
|
331288
|
+
code: grpc9.status.PERMISSION_DENIED,
|
|
331289
|
+
message: "Access to the requested function is denied"
|
|
331290
|
+
}, null);
|
|
331291
|
+
}
|
|
331292
|
+
const argsArray = callArgs(callValue.args, callValue.params ?? {});
|
|
331293
|
+
const treasuryDiscovery = await handleTreasuryDiscoveryCall(broker, callValue.functionName, callValue.args, callValue.params ?? {});
|
|
331294
|
+
if (treasuryDiscovery.handled) {
|
|
331295
|
+
return ctx.wrappedCallback(null, {
|
|
331296
|
+
proof: ctx.verity.proof,
|
|
331297
|
+
result: JSON.stringify(treasuryDiscovery.result)
|
|
331298
|
+
});
|
|
331299
|
+
}
|
|
331300
|
+
const fn = broker[callValue.functionName];
|
|
331301
|
+
if (typeof fn !== "function" || broker.has?.[callValue.functionName] === false) {
|
|
331302
|
+
return ctx.wrappedCallback({
|
|
331303
|
+
code: grpc9.status.INVALID_ARGUMENT,
|
|
331304
|
+
message: `Function not found on broker: ${callValue.functionName}`
|
|
331305
|
+
}, null);
|
|
330490
331306
|
}
|
|
331307
|
+
const result = await fn.apply(broker, argsArray);
|
|
330491
331308
|
ctx.wrappedCallback(null, {
|
|
330492
331309
|
proof: ctx.verity.proof,
|
|
330493
|
-
result: JSON.stringify(
|
|
330494
|
-
balances: responseBalances,
|
|
330495
|
-
balanceType
|
|
330496
|
-
})
|
|
331310
|
+
result: JSON.stringify(result)
|
|
330497
331311
|
});
|
|
330498
331312
|
} catch (error48) {
|
|
330499
|
-
safeLogError(
|
|
330500
|
-
ctx
|
|
330501
|
-
|
|
330502
|
-
|
|
330503
|
-
}
|
|
331313
|
+
safeLogError("Call failed", error48);
|
|
331314
|
+
rejectWithGrpcError(ctx, error48, {
|
|
331315
|
+
message: getErrorMessage(error48),
|
|
331316
|
+
preferStableMessageOnly: true
|
|
331317
|
+
});
|
|
330504
331318
|
}
|
|
330505
331319
|
}
|
|
330506
|
-
|
|
331320
|
+
|
|
331321
|
+
// src/handlers/execute-action/withdraw.ts
|
|
331322
|
+
var grpc10 = __toESM(require_src3(), 1);
|
|
331323
|
+
async function handleWithdraw(ctx) {
|
|
330507
331324
|
const {
|
|
330508
331325
|
call,
|
|
330509
331326
|
wrappedCallback,
|
|
@@ -330524,671 +331341,1117 @@ async function handleFetchTicker(ctx) {
|
|
|
330524
331341
|
const verityProof = verity.proof;
|
|
330525
331342
|
if (!symbol2) {
|
|
330526
331343
|
return ctx.wrappedCallback({
|
|
330527
|
-
code:
|
|
331344
|
+
code: grpc10.status.INVALID_ARGUMENT,
|
|
330528
331345
|
message: `ValidationError: Symbol required`
|
|
330529
331346
|
}, null);
|
|
330530
331347
|
}
|
|
331348
|
+
const transferValue = parsePayloadForAction(ctx, WithdrawPayloadSchema);
|
|
331349
|
+
if (transferValue === null)
|
|
331350
|
+
return;
|
|
331351
|
+
let withdrawNetwork;
|
|
330531
331352
|
try {
|
|
330532
|
-
|
|
331353
|
+
withdrawNetwork = await resolveTransferNetwork(broker, symbol2, transferValue.chain);
|
|
331354
|
+
} catch (error48) {
|
|
331355
|
+
const message = getErrorMessage(error48);
|
|
331356
|
+
return ctx.wrappedCallback({
|
|
331357
|
+
code: stableGrpcErrorCode(message) ?? grpc10.status.INVALID_ARGUMENT,
|
|
331358
|
+
message
|
|
331359
|
+
}, null);
|
|
331360
|
+
}
|
|
331361
|
+
const transferValidation = validateWithdraw(policy, cex3, withdrawNetwork.brokerNetworkId, transferValue.recipientAddress, transferValue.amount, symbol2);
|
|
331362
|
+
if (!transferValidation.valid) {
|
|
331363
|
+
return ctx.wrappedCallback({
|
|
331364
|
+
code: grpc10.status.PERMISSION_DENIED,
|
|
331365
|
+
message: `policy_withdrawal_denied: ${transferValidation.error}`
|
|
331366
|
+
}, null);
|
|
331367
|
+
}
|
|
331368
|
+
const travelRule = resolveTravelRuleDecision(policy, cex3, transferValue.recipientAddress);
|
|
331369
|
+
if (travelRule.mode === "denied") {
|
|
331370
|
+
return ctx.wrappedCallback({
|
|
331371
|
+
code: grpc10.status.FAILED_PRECONDITION,
|
|
331372
|
+
message: `travel_rule_denied: ${travelRule.error}`
|
|
331373
|
+
}, null);
|
|
331374
|
+
}
|
|
331375
|
+
try {
|
|
331376
|
+
const transaction = travelRule.mode === "localentity" ? await withdrawViaLocalEntity(broker, {
|
|
331377
|
+
code: symbol2,
|
|
331378
|
+
amount: transferValue.amount,
|
|
331379
|
+
address: transferValue.recipientAddress,
|
|
331380
|
+
network: withdrawNetwork.exchangeNetworkId,
|
|
331381
|
+
questionnaire: travelRule.questionnaire,
|
|
331382
|
+
params: transferValue.params
|
|
331383
|
+
}) : await broker.withdraw(symbol2, transferValue.amount, transferValue.recipientAddress, undefined, {
|
|
331384
|
+
...transferValue.params ?? {},
|
|
331385
|
+
network: withdrawNetwork.exchangeNetworkId
|
|
331386
|
+
});
|
|
331387
|
+
log.info(`Withdraw Result: ${JSON.stringify(transaction)}`);
|
|
330533
331388
|
ctx.wrappedCallback(null, {
|
|
330534
331389
|
proof: ctx.verity.proof,
|
|
330535
|
-
result: JSON.stringify(
|
|
331390
|
+
result: JSON.stringify({
|
|
331391
|
+
...transaction,
|
|
331392
|
+
operatorAlias: withdrawNetwork.operatorAlias,
|
|
331393
|
+
brokerNetworkId: withdrawNetwork.brokerNetworkId,
|
|
331394
|
+
exchangeNetworkId: withdrawNetwork.exchangeNetworkId
|
|
331395
|
+
})
|
|
330536
331396
|
});
|
|
330537
331397
|
} catch (error48) {
|
|
330538
|
-
safeLogError(
|
|
331398
|
+
safeLogError("Withdraw failed", error48);
|
|
331399
|
+
const code = mapCcxtErrorToGrpcStatus(error48) ?? grpc10.status.INTERNAL;
|
|
330539
331400
|
ctx.wrappedCallback({
|
|
330540
|
-
code
|
|
330541
|
-
message: `
|
|
331401
|
+
code,
|
|
331402
|
+
message: `Withdraw failed: ${getErrorMessage(error48)}`
|
|
331403
|
+
}, null);
|
|
331404
|
+
}
|
|
331405
|
+
}
|
|
331406
|
+
|
|
331407
|
+
// src/handlers/execute-action/registry.ts
|
|
331408
|
+
var ACTION_HANDLERS = {
|
|
331409
|
+
[Action.Deposit]: handleDeposit,
|
|
331410
|
+
[Action.Withdraw]: handleWithdraw,
|
|
331411
|
+
[Action.Call]: handleTreasuryCall,
|
|
331412
|
+
[Action.InternalTransfer]: handleInternalTransfer,
|
|
331413
|
+
[Action.CreateOrder]: handleOrders,
|
|
331414
|
+
[Action.GetOrderDetails]: handleOrders,
|
|
331415
|
+
[Action.CancelOrder]: handleOrders,
|
|
331416
|
+
[Action.FetchCurrency]: handlePassThrough,
|
|
331417
|
+
[Action.FetchAccountId]: handlePassThrough,
|
|
331418
|
+
[Action.FetchFees]: handlePassThrough,
|
|
331419
|
+
[Action.FetchDepositAddresses]: handlePassThrough,
|
|
331420
|
+
[Action.FetchBalances]: handlePassThrough,
|
|
331421
|
+
[Action.FetchTicker]: handlePassThrough,
|
|
331422
|
+
[Action.GetPerpConfigState]: handlePerpConfig,
|
|
331423
|
+
[Action.SetPerpConfigState]: handlePerpConfig
|
|
331424
|
+
};
|
|
331425
|
+
async function dispatchExecuteAction(ctx) {
|
|
331426
|
+
const handler = ACTION_HANDLERS[ctx.action];
|
|
331427
|
+
if (!handler) {
|
|
331428
|
+
ctx.wrappedCallback({
|
|
331429
|
+
code: grpc11.status.INVALID_ARGUMENT,
|
|
331430
|
+
message: "Invalid Action"
|
|
330542
331431
|
}, null);
|
|
331432
|
+
return;
|
|
331433
|
+
}
|
|
331434
|
+
await handler(ctx);
|
|
331435
|
+
}
|
|
331436
|
+
|
|
331437
|
+
// src/handlers/execute-action/handler.ts
|
|
331438
|
+
function createExecuteActionHandler(deps) {
|
|
331439
|
+
const {
|
|
331440
|
+
policy,
|
|
331441
|
+
brokers,
|
|
331442
|
+
whitelistIps,
|
|
331443
|
+
useVerity,
|
|
331444
|
+
verityProverUrl,
|
|
331445
|
+
otelMetrics,
|
|
331446
|
+
brokerArchiver
|
|
331447
|
+
} = deps;
|
|
331448
|
+
return async (call, callback) => {
|
|
331449
|
+
const startTime = Date.now();
|
|
331450
|
+
const { action: rawAction, cex: cex3, symbol: symbol2 } = call.request;
|
|
331451
|
+
const action = resolveAction(rawAction);
|
|
331452
|
+
let actionCompleted = false;
|
|
331453
|
+
const wrappedCallback = (error48, value) => {
|
|
331454
|
+
if (!actionCompleted) {
|
|
331455
|
+
actionCompleted = true;
|
|
331456
|
+
const latency = Date.now() - startTime;
|
|
331457
|
+
const actionName = getActionName(action);
|
|
331458
|
+
otelMetrics?.recordHistogram("execute_action_duration_ms", latency, {
|
|
331459
|
+
action: actionName,
|
|
331460
|
+
cex: cex3 || "unknown"
|
|
331461
|
+
});
|
|
331462
|
+
if (error48) {
|
|
331463
|
+
otelMetrics?.recordCounter("execute_action_errors_total", 1, {
|
|
331464
|
+
action: actionName,
|
|
331465
|
+
cex: cex3 || "unknown",
|
|
331466
|
+
error_type: error48.code ? grpc12.status[error48.code] || "unknown" : "unknown"
|
|
331467
|
+
});
|
|
331468
|
+
} else {
|
|
331469
|
+
otelMetrics?.recordCounter("execute_action_success_total", 1, {
|
|
331470
|
+
action: actionName,
|
|
331471
|
+
cex: cex3 || "unknown"
|
|
331472
|
+
});
|
|
331473
|
+
}
|
|
331474
|
+
}
|
|
331475
|
+
callback(error48, value);
|
|
331476
|
+
};
|
|
331477
|
+
try {
|
|
331478
|
+
log.info(`Request - ExecuteAction:`, { action, cex: cex3, symbol: symbol2 });
|
|
331479
|
+
otelMetrics?.recordCounter("execute_action_requests_total", 1, {
|
|
331480
|
+
action: getActionName(action),
|
|
331481
|
+
cex: cex3 || "unknown"
|
|
331482
|
+
});
|
|
331483
|
+
if (!authenticateRequest(call, whitelistIps)) {
|
|
331484
|
+
return wrappedCallback({
|
|
331485
|
+
code: grpc12.status.PERMISSION_DENIED,
|
|
331486
|
+
message: "Access denied: Unauthorized IP"
|
|
331487
|
+
}, null);
|
|
331488
|
+
}
|
|
331489
|
+
if (!action || !cex3) {
|
|
331490
|
+
return wrappedCallback({
|
|
331491
|
+
code: grpc12.status.INVALID_ARGUMENT,
|
|
331492
|
+
message: "`action` AND `cex` fields are required"
|
|
331493
|
+
}, null);
|
|
331494
|
+
}
|
|
331495
|
+
const normalizedCex = cex3.trim().toLowerCase();
|
|
331496
|
+
const metadata = call.metadata;
|
|
331497
|
+
const selectedBrokerAccount = selectBrokerAccountForCex(normalizedCex, brokers, metadata);
|
|
331498
|
+
const verity = { proof: "" };
|
|
331499
|
+
const applyVerityToBroker = (targetBroker) => {
|
|
331500
|
+
if (!useVerity)
|
|
331501
|
+
return;
|
|
331502
|
+
const override = buildHttpClientOverrideFromMetadata(metadata, verityProverUrl, (proof, notaryPubKey) => {
|
|
331503
|
+
verity.proof = proof;
|
|
331504
|
+
log.debug(`Verity proof:`, { proof, notaryPubKey });
|
|
331505
|
+
});
|
|
331506
|
+
targetBroker.setHttpClientOverride(override, verityHttpClientOverridePredicate);
|
|
331507
|
+
};
|
|
331508
|
+
const preludeCtx = {
|
|
331509
|
+
call,
|
|
331510
|
+
wrappedCallback,
|
|
331511
|
+
action,
|
|
331512
|
+
policy,
|
|
331513
|
+
brokers,
|
|
331514
|
+
metadata,
|
|
331515
|
+
normalizedCex,
|
|
331516
|
+
cex: cex3,
|
|
331517
|
+
symbol: symbol2,
|
|
331518
|
+
selectedBrokerAccount,
|
|
331519
|
+
broker: selectedBrokerAccount?.exchange ?? createBroker(normalizedCex, metadata),
|
|
331520
|
+
verity,
|
|
331521
|
+
applyVerityToBroker,
|
|
331522
|
+
useVerity,
|
|
331523
|
+
verityProverUrl,
|
|
331524
|
+
otelMetrics,
|
|
331525
|
+
brokerArchiver
|
|
331526
|
+
};
|
|
331527
|
+
if (action === Action.Call) {
|
|
331528
|
+
const handled = await handleOrderBookCall(preludeCtx);
|
|
331529
|
+
if (handled)
|
|
331530
|
+
return;
|
|
331531
|
+
}
|
|
331532
|
+
const broker = selectedBrokerAccount?.exchange ?? createBroker(normalizedCex, metadata);
|
|
331533
|
+
if (!broker) {
|
|
331534
|
+
return wrappedCallback({
|
|
331535
|
+
code: grpc12.status.UNAUTHENTICATED,
|
|
331536
|
+
message: `This Exchange is not registered and No API metadata was found`
|
|
331537
|
+
}, null);
|
|
331538
|
+
}
|
|
331539
|
+
applyVerityToBroker(broker);
|
|
331540
|
+
const ctx = { ...preludeCtx, broker };
|
|
331541
|
+
await dispatchExecuteAction(ctx);
|
|
331542
|
+
} catch (error48) {
|
|
331543
|
+
safeLogError("ExecuteAction unhandled error", error48);
|
|
331544
|
+
return wrappedCallback({
|
|
331545
|
+
code: grpc12.status.INTERNAL,
|
|
331546
|
+
message: "ExecuteAction failed unexpectedly"
|
|
331547
|
+
}, null);
|
|
331548
|
+
}
|
|
331549
|
+
};
|
|
331550
|
+
}
|
|
331551
|
+
// src/handlers/subscribe/handler.ts
|
|
331552
|
+
var grpc13 = __toESM(require_src3(), 1);
|
|
331553
|
+
|
|
331554
|
+
// src/helpers/binance-user-data-stream.ts
|
|
331555
|
+
import { Buffer as Buffer2 } from "node:buffer";
|
|
331556
|
+
import { createHmac } from "node:crypto";
|
|
331557
|
+
var BINANCE_SPOT_WS_API_URL = "wss://ws-api.binance.com:443/ws-api/v3";
|
|
331558
|
+
var DEFAULT_BINANCE_USER_DATA_MAX_BUFFERED_EVENTS = 16;
|
|
331559
|
+
var createWebSocket = (url3) => new wrapper_default(url3);
|
|
331560
|
+
function getExchangeString(exchange, key) {
|
|
331561
|
+
const value = exchange[key];
|
|
331562
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
331563
|
+
throw new Error(`Binance user-data stream requires exchange.${key}`);
|
|
331564
|
+
}
|
|
331565
|
+
return value;
|
|
331566
|
+
}
|
|
331567
|
+
function sortedQuery(params) {
|
|
331568
|
+
return Object.entries(params).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`).join("&");
|
|
331569
|
+
}
|
|
331570
|
+
function signUserDataStreamParams(exchange, params) {
|
|
331571
|
+
const signParams = exchange.signParams;
|
|
331572
|
+
if (typeof signParams === "function") {
|
|
331573
|
+
return signParams.call(exchange, params);
|
|
330543
331574
|
}
|
|
331575
|
+
const secret = getExchangeString(exchange, "secret");
|
|
331576
|
+
return {
|
|
331577
|
+
...params,
|
|
331578
|
+
signature: createHmac("sha256", secret).update(sortedQuery(params)).digest("hex")
|
|
331579
|
+
};
|
|
330544
331580
|
}
|
|
330545
|
-
|
|
330546
|
-
|
|
330547
|
-
|
|
330548
|
-
if (ctx.action === Action.FetchAccountId)
|
|
330549
|
-
return handleFetchAccountId(ctx);
|
|
330550
|
-
if (ctx.action === Action.FetchFees)
|
|
330551
|
-
return handleFetchFees(ctx);
|
|
330552
|
-
if (ctx.action === Action.FetchDepositAddresses)
|
|
330553
|
-
return handleFetchDepositAddresses(ctx);
|
|
330554
|
-
if (ctx.action === Action.FetchBalances)
|
|
330555
|
-
return handleFetchBalances(ctx);
|
|
330556
|
-
if (ctx.action === Action.FetchTicker)
|
|
330557
|
-
return handleFetchTicker(ctx);
|
|
330558
|
-
}
|
|
330559
|
-
|
|
330560
|
-
// src/handlers/execute-action/perp-config.ts
|
|
330561
|
-
var grpc8 = __toESM(require_src3(), 1);
|
|
330562
|
-
function exchangeSupports(broker, capability) {
|
|
330563
|
-
return broker.has?.[capability] === true;
|
|
331581
|
+
function getBinanceSpotWsApiUrl(exchange) {
|
|
331582
|
+
const urls = exchange.urls;
|
|
331583
|
+
return urls?.api?.ws?.["ws-api"]?.spot ?? BINANCE_SPOT_WS_API_URL;
|
|
330564
331584
|
}
|
|
330565
|
-
function
|
|
330566
|
-
return
|
|
330567
|
-
symbol: typeof position.symbol === "string" ? position.symbol : undefined,
|
|
330568
|
-
leverage: typeof position.leverage === "number" ? position.leverage : undefined,
|
|
330569
|
-
marginMode: typeof position.marginMode === "string" ? position.marginMode : undefined
|
|
330570
|
-
}));
|
|
331585
|
+
function getRecord(value) {
|
|
331586
|
+
return typeof value === "object" && value !== null ? value : null;
|
|
330571
331587
|
}
|
|
330572
|
-
|
|
330573
|
-
|
|
330574
|
-
|
|
330575
|
-
if (payload === null) {
|
|
330576
|
-
return;
|
|
330577
|
-
}
|
|
330578
|
-
if (!broker) {
|
|
330579
|
-
return wrappedCallback({
|
|
330580
|
-
code: grpc8.status.INVALID_ARGUMENT,
|
|
330581
|
-
message: `Invalid CEX key: ${cex3}`
|
|
330582
|
-
}, null);
|
|
330583
|
-
}
|
|
330584
|
-
const exchange = broker;
|
|
330585
|
-
if (!exchangeSupports(exchange, "fetchPositions")) {
|
|
330586
|
-
return wrappedCallback({
|
|
330587
|
-
code: grpc8.status.UNIMPLEMENTED,
|
|
330588
|
-
message: `${normalizedCex} does not support fetchPositions`
|
|
330589
|
-
}, null);
|
|
331588
|
+
function getMessage(value) {
|
|
331589
|
+
if (value instanceof Error) {
|
|
331590
|
+
return value.message;
|
|
330590
331591
|
}
|
|
330591
|
-
|
|
330592
|
-
|
|
330593
|
-
const positions = await exchange.fetchPositions?.(symbols, payload.params);
|
|
330594
|
-
ctx.wrappedCallback(null, {
|
|
330595
|
-
result: JSON.stringify({
|
|
330596
|
-
exchange: normalizedCex,
|
|
330597
|
-
configs: extractPerpConfigs(positions ?? []),
|
|
330598
|
-
positions: positions ?? []
|
|
330599
|
-
})
|
|
330600
|
-
});
|
|
330601
|
-
} catch (error48) {
|
|
330602
|
-
safeLogError(`GetPerpConfigState failed for ${cex3}`, error48);
|
|
330603
|
-
ctx.wrappedCallback({
|
|
330604
|
-
code: grpc8.status.INTERNAL,
|
|
330605
|
-
message: getErrorMessage(error48)
|
|
330606
|
-
}, null);
|
|
331592
|
+
if (typeof value === "string" && value.length > 0) {
|
|
331593
|
+
return value;
|
|
330607
331594
|
}
|
|
331595
|
+
const record2 = getRecord(value);
|
|
331596
|
+
const message = record2?.message;
|
|
331597
|
+
return typeof message === "string" && message.length > 0 ? message : null;
|
|
330608
331598
|
}
|
|
330609
|
-
|
|
330610
|
-
const
|
|
330611
|
-
|
|
330612
|
-
|
|
330613
|
-
|
|
330614
|
-
|
|
330615
|
-
|
|
330616
|
-
|
|
330617
|
-
|
|
330618
|
-
|
|
330619
|
-
}, null);
|
|
330620
|
-
}
|
|
330621
|
-
const exchange = broker;
|
|
330622
|
-
if (!exchangeSupports(exchange, "setLeverage")) {
|
|
330623
|
-
return wrappedCallback({
|
|
330624
|
-
code: grpc8.status.UNIMPLEMENTED,
|
|
330625
|
-
message: `${normalizedCex} does not support setLeverage`
|
|
330626
|
-
}, null);
|
|
330627
|
-
}
|
|
330628
|
-
try {
|
|
330629
|
-
const response = await exchange.setLeverage?.(payload.leverage, payload.symbol, {
|
|
330630
|
-
marginMode: payload.marginMode ?? "cross",
|
|
330631
|
-
...payload.params
|
|
330632
|
-
});
|
|
330633
|
-
ctx.wrappedCallback(null, {
|
|
330634
|
-
result: JSON.stringify({
|
|
330635
|
-
exchange: normalizedCex,
|
|
330636
|
-
symbol: payload.symbol,
|
|
330637
|
-
leverage: payload.leverage,
|
|
330638
|
-
marginMode: payload.marginMode ?? "cross",
|
|
330639
|
-
response
|
|
330640
|
-
})
|
|
330641
|
-
});
|
|
330642
|
-
} catch (error48) {
|
|
330643
|
-
safeLogError(`SetPerpConfigState failed for ${cex3}`, error48);
|
|
330644
|
-
ctx.wrappedCallback({
|
|
330645
|
-
code: grpc8.status.INTERNAL,
|
|
330646
|
-
message: getErrorMessage(error48)
|
|
330647
|
-
}, null);
|
|
331599
|
+
function getOptionalExchangeString(exchange, key) {
|
|
331600
|
+
const value = exchange[key];
|
|
331601
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
331602
|
+
}
|
|
331603
|
+
function redactDiagnosticMessage(message, secretValues) {
|
|
331604
|
+
let redacted = message;
|
|
331605
|
+
for (const value of secretValues) {
|
|
331606
|
+
if (value.length > 0) {
|
|
331607
|
+
redacted = redacted.split(value).join("[redacted]");
|
|
331608
|
+
}
|
|
330648
331609
|
}
|
|
331610
|
+
return redacted.replace(/(\b(?:apiKey|secret|signature)\b\s*=\s*)[^\s&,;)]+/gi, "$1[redacted]").replace(/("(?:apiKey|secret|signature)"\s*:\s*")[^"]*(")/gi, "$1[redacted]$2");
|
|
330649
331611
|
}
|
|
330650
|
-
|
|
330651
|
-
|
|
330652
|
-
|
|
331612
|
+
function formatBinanceUserDataWebSocketError(event, secretValues) {
|
|
331613
|
+
const record2 = getRecord(event);
|
|
331614
|
+
const message = getMessage(record2?.error) ?? getMessage(record2?.message) ?? getMessage(event);
|
|
331615
|
+
const safeMessage = message === null ? null : redactDiagnosticMessage(message, secretValues);
|
|
331616
|
+
return new Error(safeMessage ? `Binance user-data WebSocket error: ${safeMessage}` : "Binance user-data WebSocket error");
|
|
331617
|
+
}
|
|
331618
|
+
function getCloseReason(value) {
|
|
331619
|
+
if (typeof value === "string") {
|
|
331620
|
+
return value.length > 0 ? value : null;
|
|
330653
331621
|
}
|
|
330654
|
-
if (
|
|
330655
|
-
|
|
331622
|
+
if (Buffer2.isBuffer(value)) {
|
|
331623
|
+
const reason = value.toString("utf8");
|
|
331624
|
+
return reason.length > 0 ? reason : null;
|
|
330656
331625
|
}
|
|
330657
|
-
|
|
330658
|
-
|
|
330659
|
-
|
|
330660
|
-
var grpc9 = __toESM(require_src3(), 1);
|
|
330661
|
-
async function handleTreasuryCall(ctx) {
|
|
330662
|
-
const { broker } = ctx;
|
|
330663
|
-
const callValue = parsePayloadForAction(ctx, CallPayloadSchema);
|
|
330664
|
-
if (callValue === null)
|
|
330665
|
-
return;
|
|
330666
|
-
try {
|
|
330667
|
-
if (callValue.functionName.startsWith("_") || callValue.functionName.includes("constructor") || callValue.functionName.includes("prototype")) {
|
|
330668
|
-
return ctx.wrappedCallback({
|
|
330669
|
-
code: grpc9.status.PERMISSION_DENIED,
|
|
330670
|
-
message: "Access to the requested function is denied"
|
|
330671
|
-
}, null);
|
|
330672
|
-
}
|
|
330673
|
-
const argsArray = callArgs(callValue.args, callValue.params ?? {});
|
|
330674
|
-
const treasuryDiscovery = await handleTreasuryDiscoveryCall(broker, callValue.functionName, callValue.args, callValue.params ?? {});
|
|
330675
|
-
if (treasuryDiscovery.handled) {
|
|
330676
|
-
return ctx.wrappedCallback(null, {
|
|
330677
|
-
proof: ctx.verity.proof,
|
|
330678
|
-
result: JSON.stringify(treasuryDiscovery.result)
|
|
330679
|
-
});
|
|
330680
|
-
}
|
|
330681
|
-
const fn = broker[callValue.functionName];
|
|
330682
|
-
if (typeof fn !== "function" || broker.has?.[callValue.functionName] === false) {
|
|
330683
|
-
return ctx.wrappedCallback({
|
|
330684
|
-
code: grpc9.status.INVALID_ARGUMENT,
|
|
330685
|
-
message: `Function not found on broker: ${callValue.functionName}`
|
|
330686
|
-
}, null);
|
|
330687
|
-
}
|
|
330688
|
-
const result = await fn.apply(broker, argsArray);
|
|
330689
|
-
ctx.wrappedCallback(null, {
|
|
330690
|
-
proof: ctx.verity.proof,
|
|
330691
|
-
result: JSON.stringify(result)
|
|
330692
|
-
});
|
|
330693
|
-
} catch (error48) {
|
|
330694
|
-
safeLogError("Call failed", error48);
|
|
330695
|
-
rejectWithGrpcError(ctx, error48, {
|
|
330696
|
-
message: getErrorMessage(error48),
|
|
330697
|
-
preferStableMessageOnly: true
|
|
330698
|
-
});
|
|
331626
|
+
if (value instanceof Uint8Array) {
|
|
331627
|
+
const reason = Buffer2.from(value).toString("utf8");
|
|
331628
|
+
return reason.length > 0 ? reason : null;
|
|
330699
331629
|
}
|
|
331630
|
+
return null;
|
|
330700
331631
|
}
|
|
330701
|
-
|
|
330702
|
-
|
|
330703
|
-
|
|
330704
|
-
|
|
330705
|
-
const
|
|
330706
|
-
|
|
330707
|
-
|
|
330708
|
-
|
|
330709
|
-
|
|
330710
|
-
|
|
330711
|
-
|
|
330712
|
-
|
|
330713
|
-
|
|
330714
|
-
|
|
330715
|
-
broker,
|
|
330716
|
-
verity,
|
|
330717
|
-
applyVerityToBroker,
|
|
330718
|
-
useVerity,
|
|
330719
|
-
verityProverUrl,
|
|
330720
|
-
otelMetrics
|
|
330721
|
-
} = ctx;
|
|
330722
|
-
const verityProof = verity.proof;
|
|
330723
|
-
if (!symbol2) {
|
|
330724
|
-
return ctx.wrappedCallback({
|
|
330725
|
-
code: grpc10.status.INVALID_ARGUMENT,
|
|
330726
|
-
message: `ValidationError: Symbol required`
|
|
330727
|
-
}, null);
|
|
331632
|
+
function formatBinanceUserDataWebSocketClose(codeOrEvent, reasonOrUndefined, secretValues) {
|
|
331633
|
+
const record2 = getRecord(codeOrEvent);
|
|
331634
|
+
const code = record2 ? record2.code : codeOrEvent;
|
|
331635
|
+
const reason = getCloseReason(record2 ? record2.reason : reasonOrUndefined);
|
|
331636
|
+
const safeReason = reason === null ? null : redactDiagnosticMessage(reason, secretValues);
|
|
331637
|
+
const details = [
|
|
331638
|
+
typeof code === "number" || typeof code === "string" ? `code=${code}` : null,
|
|
331639
|
+
safeReason ? `reason=${safeReason}` : null
|
|
331640
|
+
].filter((detail) => detail !== null);
|
|
331641
|
+
return new Error(details.length > 0 ? `Binance user-data WebSocket closed unexpectedly (${details.join(", ")})` : "Binance user-data WebSocket closed unexpectedly");
|
|
331642
|
+
}
|
|
331643
|
+
function decodeMessageData(data) {
|
|
331644
|
+
if (typeof data === "string") {
|
|
331645
|
+
return data;
|
|
330728
331646
|
}
|
|
330729
|
-
|
|
330730
|
-
|
|
330731
|
-
return;
|
|
330732
|
-
let withdrawNetwork;
|
|
330733
|
-
try {
|
|
330734
|
-
withdrawNetwork = await resolveTransferNetwork(broker, symbol2, transferValue.chain);
|
|
330735
|
-
} catch (error48) {
|
|
330736
|
-
const message = getErrorMessage(error48);
|
|
330737
|
-
return ctx.wrappedCallback({
|
|
330738
|
-
code: stableGrpcErrorCode(message) ?? grpc10.status.INVALID_ARGUMENT,
|
|
330739
|
-
message
|
|
330740
|
-
}, null);
|
|
331647
|
+
if (Buffer2.isBuffer(data)) {
|
|
331648
|
+
return data.toString("utf8");
|
|
330741
331649
|
}
|
|
330742
|
-
|
|
330743
|
-
|
|
330744
|
-
return ctx.wrappedCallback({
|
|
330745
|
-
code: grpc10.status.PERMISSION_DENIED,
|
|
330746
|
-
message: `policy_withdrawal_denied: ${transferValidation.error}`
|
|
330747
|
-
}, null);
|
|
331650
|
+
if (data instanceof ArrayBuffer) {
|
|
331651
|
+
return Buffer2.from(data).toString("utf8");
|
|
330748
331652
|
}
|
|
330749
|
-
|
|
330750
|
-
|
|
330751
|
-
return ctx.wrappedCallback({
|
|
330752
|
-
code: grpc10.status.FAILED_PRECONDITION,
|
|
330753
|
-
message: `travel_rule_denied: ${travelRule.error}`
|
|
330754
|
-
}, null);
|
|
331653
|
+
if (ArrayBuffer.isView(data)) {
|
|
331654
|
+
return Buffer2.from(data.buffer, data.byteOffset, data.byteLength).toString("utf8");
|
|
330755
331655
|
}
|
|
330756
|
-
|
|
330757
|
-
|
|
330758
|
-
code: symbol2,
|
|
330759
|
-
amount: transferValue.amount,
|
|
330760
|
-
address: transferValue.recipientAddress,
|
|
330761
|
-
network: withdrawNetwork.exchangeNetworkId,
|
|
330762
|
-
questionnaire: travelRule.questionnaire,
|
|
330763
|
-
params: transferValue.params
|
|
330764
|
-
}) : await broker.withdraw(symbol2, transferValue.amount, transferValue.recipientAddress, undefined, {
|
|
330765
|
-
...transferValue.params ?? {},
|
|
330766
|
-
network: withdrawNetwork.exchangeNetworkId
|
|
330767
|
-
});
|
|
330768
|
-
log.info(`Withdraw Result: ${JSON.stringify(transaction)}`);
|
|
330769
|
-
ctx.wrappedCallback(null, {
|
|
330770
|
-
proof: ctx.verity.proof,
|
|
330771
|
-
result: JSON.stringify({
|
|
330772
|
-
...transaction,
|
|
330773
|
-
operatorAlias: withdrawNetwork.operatorAlias,
|
|
330774
|
-
brokerNetworkId: withdrawNetwork.brokerNetworkId,
|
|
330775
|
-
exchangeNetworkId: withdrawNetwork.exchangeNetworkId
|
|
330776
|
-
})
|
|
330777
|
-
});
|
|
330778
|
-
} catch (error48) {
|
|
330779
|
-
safeLogError("Withdraw failed", error48);
|
|
330780
|
-
const code = mapCcxtErrorToGrpcStatus(error48) ?? grpc10.status.INTERNAL;
|
|
330781
|
-
ctx.wrappedCallback({
|
|
330782
|
-
code,
|
|
330783
|
-
message: `Withdraw failed: ${getErrorMessage(error48)}`
|
|
330784
|
-
}, null);
|
|
331656
|
+
if (Array.isArray(data) && data.every((item) => Buffer2.isBuffer(item))) {
|
|
331657
|
+
return Buffer2.concat(data).toString("utf8");
|
|
330785
331658
|
}
|
|
331659
|
+
return data;
|
|
330786
331660
|
}
|
|
330787
331661
|
|
|
330788
|
-
|
|
330789
|
-
|
|
330790
|
-
|
|
330791
|
-
|
|
330792
|
-
|
|
330793
|
-
|
|
330794
|
-
[
|
|
330795
|
-
[
|
|
330796
|
-
|
|
330797
|
-
|
|
330798
|
-
|
|
330799
|
-
|
|
330800
|
-
|
|
330801
|
-
|
|
330802
|
-
|
|
330803
|
-
|
|
330804
|
-
|
|
330805
|
-
|
|
330806
|
-
|
|
330807
|
-
|
|
330808
|
-
|
|
330809
|
-
|
|
330810
|
-
|
|
330811
|
-
message: "Invalid Action"
|
|
330812
|
-
}, null);
|
|
330813
|
-
return;
|
|
331662
|
+
class BinanceSpotUserDataStream {
|
|
331663
|
+
exchange;
|
|
331664
|
+
ws;
|
|
331665
|
+
secretValues;
|
|
331666
|
+
requestId = `user-data-${Date.now()}-${Math.random()}`;
|
|
331667
|
+
maxBufferedEvents;
|
|
331668
|
+
queue = [];
|
|
331669
|
+
waiters = [];
|
|
331670
|
+
closed = false;
|
|
331671
|
+
closeError = null;
|
|
331672
|
+
subscriptionId = null;
|
|
331673
|
+
constructor(exchange, options = {}) {
|
|
331674
|
+
this.exchange = exchange;
|
|
331675
|
+
this.maxBufferedEvents = options.maxBufferedEvents ?? DEFAULT_BINANCE_USER_DATA_MAX_BUFFERED_EVENTS;
|
|
331676
|
+
this.secretValues = [
|
|
331677
|
+
getOptionalExchangeString(exchange, "apiKey"),
|
|
331678
|
+
getOptionalExchangeString(exchange, "secret")
|
|
331679
|
+
].filter((value) => value !== null);
|
|
331680
|
+
this.ws = createWebSocket(getBinanceSpotWsApiUrl(exchange));
|
|
331681
|
+
this.ws.on("open", () => this.subscribe());
|
|
331682
|
+
this.ws.on("message", (data) => this.handleMessage(data));
|
|
331683
|
+
this.ws.on("error", (error48) => this.fail(formatBinanceUserDataWebSocketError(error48, this.secretValues)));
|
|
331684
|
+
this.ws.on("close", (code, reason) => this.handleClose(code, reason));
|
|
330814
331685
|
}
|
|
330815
|
-
|
|
330816
|
-
|
|
330817
|
-
|
|
330818
|
-
|
|
330819
|
-
|
|
330820
|
-
const {
|
|
330821
|
-
policy,
|
|
330822
|
-
brokers,
|
|
330823
|
-
whitelistIps,
|
|
330824
|
-
useVerity,
|
|
330825
|
-
verityProverUrl,
|
|
330826
|
-
otelMetrics
|
|
330827
|
-
} = deps;
|
|
330828
|
-
return async (call, callback) => {
|
|
330829
|
-
const startTime = Date.now();
|
|
330830
|
-
const { action: rawAction, cex: cex3, symbol: symbol2 } = call.request;
|
|
330831
|
-
const action = resolveAction(rawAction);
|
|
330832
|
-
let actionCompleted = false;
|
|
330833
|
-
const wrappedCallback = (error48, value) => {
|
|
330834
|
-
if (!actionCompleted) {
|
|
330835
|
-
actionCompleted = true;
|
|
330836
|
-
const latency = Date.now() - startTime;
|
|
330837
|
-
const actionName = getActionName(action);
|
|
330838
|
-
otelMetrics?.recordHistogram("execute_action_duration_ms", latency, {
|
|
330839
|
-
action: actionName,
|
|
330840
|
-
cex: cex3 || "unknown"
|
|
330841
|
-
});
|
|
330842
|
-
if (error48) {
|
|
330843
|
-
otelMetrics?.recordCounter("execute_action_errors_total", 1, {
|
|
330844
|
-
action: actionName,
|
|
330845
|
-
cex: cex3 || "unknown",
|
|
330846
|
-
error_type: error48.code ? grpc12.status[error48.code] || "unknown" : "unknown"
|
|
330847
|
-
});
|
|
330848
|
-
} else {
|
|
330849
|
-
otelMetrics?.recordCounter("execute_action_success_total", 1, {
|
|
330850
|
-
action: actionName,
|
|
330851
|
-
cex: cex3 || "unknown"
|
|
330852
|
-
});
|
|
330853
|
-
}
|
|
331686
|
+
async* [Symbol.asyncIterator]() {
|
|
331687
|
+
while (true) {
|
|
331688
|
+
const event = await this.nextEvent();
|
|
331689
|
+
if (!event) {
|
|
331690
|
+
break;
|
|
330854
331691
|
}
|
|
330855
|
-
|
|
330856
|
-
}
|
|
331692
|
+
yield event;
|
|
331693
|
+
}
|
|
331694
|
+
}
|
|
331695
|
+
close() {
|
|
331696
|
+
if (this.closed) {
|
|
331697
|
+
return;
|
|
331698
|
+
}
|
|
331699
|
+
this.closed = true;
|
|
331700
|
+
this.queue.length = 0;
|
|
330857
331701
|
try {
|
|
330858
|
-
|
|
330859
|
-
|
|
330860
|
-
|
|
330861
|
-
|
|
330862
|
-
|
|
330863
|
-
|
|
330864
|
-
|
|
330865
|
-
|
|
330866
|
-
|
|
330867
|
-
|
|
331702
|
+
this.ws.close();
|
|
331703
|
+
} catch {}
|
|
331704
|
+
this.flushWaiters();
|
|
331705
|
+
}
|
|
331706
|
+
handleClose(code, reason) {
|
|
331707
|
+
if (this.closed) {
|
|
331708
|
+
return;
|
|
331709
|
+
}
|
|
331710
|
+
this.fail(formatBinanceUserDataWebSocketClose(code, reason, this.secretValues));
|
|
331711
|
+
}
|
|
331712
|
+
subscribe() {
|
|
331713
|
+
const apiKey = getExchangeString(this.exchange, "apiKey");
|
|
331714
|
+
const signedParams = signUserDataStreamParams(this.exchange, {
|
|
331715
|
+
apiKey,
|
|
331716
|
+
timestamp: Date.now()
|
|
331717
|
+
});
|
|
331718
|
+
this.ws.send(JSON.stringify({
|
|
331719
|
+
id: this.requestId,
|
|
331720
|
+
method: "userDataStream.subscribe.signature",
|
|
331721
|
+
params: signedParams
|
|
331722
|
+
}));
|
|
331723
|
+
}
|
|
331724
|
+
handleMessage(data) {
|
|
331725
|
+
if (this.closed) {
|
|
331726
|
+
return;
|
|
331727
|
+
}
|
|
331728
|
+
let message;
|
|
331729
|
+
try {
|
|
331730
|
+
const decodedData = decodeMessageData(data);
|
|
331731
|
+
message = typeof decodedData === "string" ? JSON.parse(decodedData) : decodedData;
|
|
331732
|
+
} catch (error48) {
|
|
331733
|
+
this.fail(error48 instanceof Error ? error48 : new Error("Invalid Binance user-data message"));
|
|
331734
|
+
return;
|
|
331735
|
+
}
|
|
331736
|
+
if ("id" in message && message.id === this.requestId) {
|
|
331737
|
+
if (message.status !== 200) {
|
|
331738
|
+
this.fail(new Error(message.error?.msg ?? message.error?.message ?? `Binance user-data subscription failed with status ${message.status}`));
|
|
331739
|
+
return;
|
|
330868
331740
|
}
|
|
330869
|
-
|
|
330870
|
-
|
|
330871
|
-
|
|
330872
|
-
|
|
330873
|
-
|
|
331741
|
+
this.subscriptionId = message.result?.subscriptionId ?? null;
|
|
331742
|
+
return;
|
|
331743
|
+
}
|
|
331744
|
+
if (!("event" in message) || !message.event) {
|
|
331745
|
+
return;
|
|
331746
|
+
}
|
|
331747
|
+
const subscriptionId = message.subscriptionId ?? this.subscriptionId;
|
|
331748
|
+
if (subscriptionId === null || subscriptionId === undefined) {
|
|
331749
|
+
return;
|
|
331750
|
+
}
|
|
331751
|
+
this.push({ subscriptionId, event: message.event });
|
|
331752
|
+
}
|
|
331753
|
+
push(event) {
|
|
331754
|
+
if (this.closed) {
|
|
331755
|
+
return;
|
|
331756
|
+
}
|
|
331757
|
+
const waiter = this.waiters.shift();
|
|
331758
|
+
if (waiter) {
|
|
331759
|
+
waiter.resolve(event);
|
|
331760
|
+
return;
|
|
331761
|
+
}
|
|
331762
|
+
if (this.queue.length >= this.maxBufferedEvents) {
|
|
331763
|
+
this.fail(new Error(`Binance user-data stream buffered event limit exceeded (${this.maxBufferedEvents}); downstream consumer is not keeping up`));
|
|
331764
|
+
return;
|
|
331765
|
+
}
|
|
331766
|
+
this.queue.push(event);
|
|
331767
|
+
}
|
|
331768
|
+
nextEvent() {
|
|
331769
|
+
const event = this.queue.shift();
|
|
331770
|
+
if (event) {
|
|
331771
|
+
return Promise.resolve(event);
|
|
331772
|
+
}
|
|
331773
|
+
if (this.closeError) {
|
|
331774
|
+
return Promise.reject(this.closeError);
|
|
331775
|
+
}
|
|
331776
|
+
if (this.closed) {
|
|
331777
|
+
return Promise.resolve(null);
|
|
331778
|
+
}
|
|
331779
|
+
return new Promise((resolve, reject) => {
|
|
331780
|
+
this.waiters.push({ resolve, reject });
|
|
331781
|
+
});
|
|
331782
|
+
}
|
|
331783
|
+
fail(error48) {
|
|
331784
|
+
if (this.closeError) {
|
|
331785
|
+
return;
|
|
331786
|
+
}
|
|
331787
|
+
this.closeError = error48;
|
|
331788
|
+
this.closed = true;
|
|
331789
|
+
this.queue.length = 0;
|
|
331790
|
+
this.flushWaiters();
|
|
331791
|
+
try {
|
|
331792
|
+
this.ws.close();
|
|
331793
|
+
} catch {}
|
|
331794
|
+
}
|
|
331795
|
+
flushWaiters() {
|
|
331796
|
+
const error48 = this.closeError;
|
|
331797
|
+
for (const waiter of this.waiters.splice(0)) {
|
|
331798
|
+
if (error48) {
|
|
331799
|
+
waiter.reject(error48);
|
|
331800
|
+
} else {
|
|
331801
|
+
waiter.resolve(null);
|
|
330874
331802
|
}
|
|
330875
|
-
|
|
330876
|
-
|
|
330877
|
-
|
|
330878
|
-
|
|
330879
|
-
|
|
330880
|
-
|
|
330881
|
-
|
|
330882
|
-
|
|
330883
|
-
|
|
330884
|
-
|
|
331803
|
+
}
|
|
331804
|
+
}
|
|
331805
|
+
}
|
|
331806
|
+
function isBinanceBalanceUserDataEvent(event) {
|
|
331807
|
+
return event.e === "outboundAccountPosition" || event.e === "balanceUpdate" || event.e === "externalLockUpdate";
|
|
331808
|
+
}
|
|
331809
|
+
function isBinanceOrderUserDataEvent(event) {
|
|
331810
|
+
return event.e === "executionReport" || event.e === "listStatus";
|
|
331811
|
+
}
|
|
331812
|
+
|
|
331813
|
+
// src/helpers/market-data-archive/ohlcv-bar-tracker.ts
|
|
331814
|
+
function isFiniteNumber(value) {
|
|
331815
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
331816
|
+
}
|
|
331817
|
+
function parseOhlcvBar(value) {
|
|
331818
|
+
if (!Array.isArray(value) || value.length < 6) {
|
|
331819
|
+
return null;
|
|
331820
|
+
}
|
|
331821
|
+
const [openTimeMs, open, high, low, close, volume, quoteVolume] = value;
|
|
331822
|
+
if (!isFiniteNumber(openTimeMs) || !isFiniteNumber(open) || !isFiniteNumber(high) || !isFiniteNumber(low) || !isFiniteNumber(close) || !isFiniteNumber(volume)) {
|
|
331823
|
+
return null;
|
|
331824
|
+
}
|
|
331825
|
+
const bar = {
|
|
331826
|
+
openTimeMs,
|
|
331827
|
+
open,
|
|
331828
|
+
high,
|
|
331829
|
+
low,
|
|
331830
|
+
close,
|
|
331831
|
+
volume
|
|
331832
|
+
};
|
|
331833
|
+
if (isFiniteNumber(quoteVolume)) {
|
|
331834
|
+
bar.quoteVolume = quoteVolume;
|
|
331835
|
+
}
|
|
331836
|
+
return bar;
|
|
331837
|
+
}
|
|
331838
|
+
function extractOhlcvBars(payload) {
|
|
331839
|
+
if (!Array.isArray(payload) || payload.length === 0) {
|
|
331840
|
+
return [];
|
|
331841
|
+
}
|
|
331842
|
+
const rawBars = Array.isArray(payload[0]) ? payload : [payload];
|
|
331843
|
+
const byOpenTime = new Map;
|
|
331844
|
+
for (const entry of rawBars) {
|
|
331845
|
+
const bar = parseOhlcvBar(entry);
|
|
331846
|
+
if (bar) {
|
|
331847
|
+
byOpenTime.set(bar.openTimeMs, bar);
|
|
331848
|
+
}
|
|
331849
|
+
}
|
|
331850
|
+
return [...byOpenTime.values()].sort((a, b2) => a.openTimeMs - b2.openTimeMs);
|
|
331851
|
+
}
|
|
331852
|
+
class OhlcvBarTracker {
|
|
331853
|
+
lastOpenTimeMs = null;
|
|
331854
|
+
lastBar = null;
|
|
331855
|
+
process(payload, brokerVersion) {
|
|
331856
|
+
const bars = extractOhlcvBars(payload);
|
|
331857
|
+
if (bars.length === 0) {
|
|
331858
|
+
return [];
|
|
331859
|
+
}
|
|
331860
|
+
if (bars.length === 1) {
|
|
331861
|
+
const [bar] = bars;
|
|
331862
|
+
return bar ? this.processSingleBar(bar, brokerVersion) : [];
|
|
331863
|
+
}
|
|
331864
|
+
return this.processBatch(bars, brokerVersion);
|
|
331865
|
+
}
|
|
331866
|
+
processSingleBar(currentBar, brokerVersion) {
|
|
331867
|
+
if (this.lastOpenTimeMs !== null && currentBar.openTimeMs < this.lastOpenTimeMs) {
|
|
331868
|
+
return [];
|
|
331869
|
+
}
|
|
331870
|
+
const candidates = [];
|
|
331871
|
+
if (this.lastOpenTimeMs !== null && this.lastBar !== null && currentBar.openTimeMs !== this.lastOpenTimeMs) {
|
|
331872
|
+
candidates.push({
|
|
331873
|
+
bar: this.lastBar,
|
|
331874
|
+
isClosed: true,
|
|
331875
|
+
brokerVersion
|
|
331876
|
+
});
|
|
331877
|
+
}
|
|
331878
|
+
candidates.push({
|
|
331879
|
+
bar: currentBar,
|
|
331880
|
+
isClosed: false,
|
|
331881
|
+
brokerVersion
|
|
331882
|
+
});
|
|
331883
|
+
this.lastOpenTimeMs = currentBar.openTimeMs;
|
|
331884
|
+
this.lastBar = currentBar;
|
|
331885
|
+
return candidates;
|
|
331886
|
+
}
|
|
331887
|
+
processBatch(bars, brokerVersion) {
|
|
331888
|
+
const firstBar = bars[0];
|
|
331889
|
+
const lastBar = bars[bars.length - 1];
|
|
331890
|
+
if (!firstBar || !lastBar) {
|
|
331891
|
+
return [];
|
|
331892
|
+
}
|
|
331893
|
+
if (this.lastOpenTimeMs !== null && lastBar.openTimeMs < this.lastOpenTimeMs) {
|
|
331894
|
+
return [];
|
|
331895
|
+
}
|
|
331896
|
+
const candidates = [];
|
|
331897
|
+
if (this.lastBar !== null && this.lastOpenTimeMs !== null && !bars.some((bar) => bar.openTimeMs === this.lastOpenTimeMs) && this.lastOpenTimeMs < firstBar.openTimeMs) {
|
|
331898
|
+
candidates.push({
|
|
331899
|
+
bar: this.lastBar,
|
|
331900
|
+
isClosed: true,
|
|
331901
|
+
brokerVersion
|
|
331902
|
+
});
|
|
331903
|
+
}
|
|
331904
|
+
for (let index2 = 0;index2 < bars.length - 1; index2 += 1) {
|
|
331905
|
+
const bar = bars[index2];
|
|
331906
|
+
if (bar) {
|
|
331907
|
+
candidates.push({
|
|
331908
|
+
bar,
|
|
331909
|
+
isClosed: true,
|
|
331910
|
+
brokerVersion
|
|
330885
331911
|
});
|
|
330886
|
-
targetBroker.setHttpClientOverride(override, verityHttpClientOverridePredicate);
|
|
330887
|
-
};
|
|
330888
|
-
const preludeCtx = {
|
|
330889
|
-
call,
|
|
330890
|
-
wrappedCallback,
|
|
330891
|
-
action,
|
|
330892
|
-
policy,
|
|
330893
|
-
brokers,
|
|
330894
|
-
metadata,
|
|
330895
|
-
normalizedCex,
|
|
330896
|
-
cex: cex3,
|
|
330897
|
-
symbol: symbol2,
|
|
330898
|
-
selectedBrokerAccount,
|
|
330899
|
-
broker: selectedBrokerAccount?.exchange ?? createBroker(normalizedCex, metadata),
|
|
330900
|
-
verity,
|
|
330901
|
-
applyVerityToBroker,
|
|
330902
|
-
useVerity,
|
|
330903
|
-
verityProverUrl,
|
|
330904
|
-
otelMetrics
|
|
330905
|
-
};
|
|
330906
|
-
if (action === Action.Call) {
|
|
330907
|
-
const handled = await handleOrderBookCall(preludeCtx);
|
|
330908
|
-
if (handled)
|
|
330909
|
-
return;
|
|
330910
|
-
}
|
|
330911
|
-
const broker = selectedBrokerAccount?.exchange ?? createBroker(normalizedCex, metadata);
|
|
330912
|
-
if (!broker) {
|
|
330913
|
-
return wrappedCallback({
|
|
330914
|
-
code: grpc12.status.UNAUTHENTICATED,
|
|
330915
|
-
message: `This Exchange is not registered and No API metadata was found`
|
|
330916
|
-
}, null);
|
|
330917
331912
|
}
|
|
330918
|
-
applyVerityToBroker(broker);
|
|
330919
|
-
const ctx = { ...preludeCtx, broker };
|
|
330920
|
-
await dispatchExecuteAction(ctx);
|
|
330921
|
-
} catch (error48) {
|
|
330922
|
-
safeLogError("ExecuteAction unhandled error", error48);
|
|
330923
|
-
return wrappedCallback({
|
|
330924
|
-
code: grpc12.status.INTERNAL,
|
|
330925
|
-
message: "ExecuteAction failed unexpectedly"
|
|
330926
|
-
}, null);
|
|
330927
331913
|
}
|
|
330928
|
-
|
|
331914
|
+
candidates.push({
|
|
331915
|
+
bar: lastBar,
|
|
331916
|
+
isClosed: false,
|
|
331917
|
+
brokerVersion
|
|
331918
|
+
});
|
|
331919
|
+
this.lastOpenTimeMs = lastBar.openTimeMs;
|
|
331920
|
+
this.lastBar = lastBar;
|
|
331921
|
+
return candidates;
|
|
331922
|
+
}
|
|
330929
331923
|
}
|
|
330930
|
-
// src/handlers/subscribe/handler.ts
|
|
330931
|
-
var grpc13 = __toESM(require_src3(), 1);
|
|
330932
331924
|
|
|
330933
|
-
// src/helpers/
|
|
330934
|
-
|
|
330935
|
-
|
|
330936
|
-
|
|
330937
|
-
|
|
330938
|
-
|
|
330939
|
-
function getExchangeString(exchange, key) {
|
|
330940
|
-
const value = exchange[key];
|
|
330941
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
330942
|
-
throw new Error(`Binance user-data stream requires exchange.${key}`);
|
|
331925
|
+
// src/helpers/market-data-archive/orderbook-sampler.ts
|
|
331926
|
+
var DEFAULT_ORDERBOOK_INTERVAL_MS = 1000;
|
|
331927
|
+
function getOrderbookIntervalMs() {
|
|
331928
|
+
const raw = process.env.CEX_BROKER_ORDERBOOK_INTERVAL_MS ?? process.env.CEX_BROKER_ORDERBOOK_TOB_INTERVAL_MS;
|
|
331929
|
+
if (!raw) {
|
|
331930
|
+
return DEFAULT_ORDERBOOK_INTERVAL_MS;
|
|
330943
331931
|
}
|
|
330944
|
-
|
|
331932
|
+
const parsed = Number.parseInt(raw, 10);
|
|
331933
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_ORDERBOOK_INTERVAL_MS;
|
|
330945
331934
|
}
|
|
330946
|
-
function
|
|
330947
|
-
return
|
|
331935
|
+
function isMarketArchiveEnabled() {
|
|
331936
|
+
return process.env.CEX_BROKER_MARKET_ARCHIVE_ENABLED !== "false";
|
|
330948
331937
|
}
|
|
330949
|
-
|
|
330950
|
-
|
|
330951
|
-
|
|
330952
|
-
|
|
331938
|
+
|
|
331939
|
+
class OrderbookSampler {
|
|
331940
|
+
intervalMs;
|
|
331941
|
+
lastEmitMs = null;
|
|
331942
|
+
constructor(intervalMs = getOrderbookIntervalMs()) {
|
|
331943
|
+
this.intervalMs = intervalMs;
|
|
331944
|
+
}
|
|
331945
|
+
shouldEmit(nowMs = Date.now()) {
|
|
331946
|
+
if (this.lastEmitMs !== null && nowMs < this.lastEmitMs) {
|
|
331947
|
+
this.lastEmitMs = nowMs;
|
|
331948
|
+
return true;
|
|
331949
|
+
}
|
|
331950
|
+
if (this.lastEmitMs !== null && nowMs - this.lastEmitMs < this.intervalMs) {
|
|
331951
|
+
return false;
|
|
331952
|
+
}
|
|
331953
|
+
this.lastEmitMs = nowMs;
|
|
331954
|
+
return true;
|
|
330953
331955
|
}
|
|
330954
|
-
const secret = getExchangeString(exchange, "secret");
|
|
330955
|
-
return {
|
|
330956
|
-
...params,
|
|
330957
|
-
signature: createHmac("sha256", secret).update(sortedQuery(params)).digest("hex")
|
|
330958
|
-
};
|
|
330959
331956
|
}
|
|
330960
|
-
|
|
330961
|
-
|
|
330962
|
-
|
|
331957
|
+
|
|
331958
|
+
// src/helpers/market-data-archive/parse-stream.ts
|
|
331959
|
+
function isFiniteNumber2(value) {
|
|
331960
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
330963
331961
|
}
|
|
330964
|
-
function
|
|
330965
|
-
|
|
331962
|
+
function toNumber2(value) {
|
|
331963
|
+
if (isFiniteNumber2(value)) {
|
|
331964
|
+
return value;
|
|
331965
|
+
}
|
|
331966
|
+
if (typeof value === "string") {
|
|
331967
|
+
const parsed = Number.parseFloat(value);
|
|
331968
|
+
if (Number.isFinite(parsed)) {
|
|
331969
|
+
return parsed;
|
|
331970
|
+
}
|
|
331971
|
+
}
|
|
331972
|
+
return;
|
|
330966
331973
|
}
|
|
330967
|
-
function
|
|
330968
|
-
if (value
|
|
330969
|
-
return value.
|
|
331974
|
+
function toStringId(value) {
|
|
331975
|
+
if (typeof value === "string" && value.trim()) {
|
|
331976
|
+
return value.trim();
|
|
330970
331977
|
}
|
|
330971
|
-
if (typeof value === "
|
|
330972
|
-
return value;
|
|
331978
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
331979
|
+
return String(value);
|
|
330973
331980
|
}
|
|
330974
|
-
|
|
330975
|
-
const message = record2?.message;
|
|
330976
|
-
return typeof message === "string" && message.length > 0 ? message : null;
|
|
331981
|
+
return;
|
|
330977
331982
|
}
|
|
330978
|
-
function
|
|
330979
|
-
const
|
|
330980
|
-
|
|
331983
|
+
function scalarTimestampMs(value, fallbackMs) {
|
|
331984
|
+
const numeric = toNumber2(value);
|
|
331985
|
+
if (numeric !== undefined) {
|
|
331986
|
+
return numeric < 1000000000000 ? numeric * 1000 : numeric;
|
|
331987
|
+
}
|
|
331988
|
+
if (typeof value === "string") {
|
|
331989
|
+
const parsed = Date.parse(value);
|
|
331990
|
+
if (Number.isFinite(parsed)) {
|
|
331991
|
+
return parsed;
|
|
331992
|
+
}
|
|
331993
|
+
}
|
|
331994
|
+
return fallbackMs;
|
|
330981
331995
|
}
|
|
330982
|
-
function
|
|
330983
|
-
|
|
330984
|
-
|
|
330985
|
-
|
|
330986
|
-
|
|
331996
|
+
function parseTrade(value, fallbackMs = Date.now()) {
|
|
331997
|
+
const record2 = asRecord(value);
|
|
331998
|
+
if (!record2) {
|
|
331999
|
+
return null;
|
|
332000
|
+
}
|
|
332001
|
+
const tradeId = toStringId(record2.id);
|
|
332002
|
+
const price = toNumber2(record2.price);
|
|
332003
|
+
const amount = toNumber2(record2.amount);
|
|
332004
|
+
const side = typeof record2.side === "string" ? record2.side.toLowerCase() : undefined;
|
|
332005
|
+
if (!tradeId || price === undefined || amount === undefined || !side) {
|
|
332006
|
+
return null;
|
|
332007
|
+
}
|
|
332008
|
+
const parsed = {
|
|
332009
|
+
tradeId,
|
|
332010
|
+
eventTimeMs: scalarTimestampMs(record2.timestamp, fallbackMs),
|
|
332011
|
+
side,
|
|
332012
|
+
price,
|
|
332013
|
+
amount
|
|
332014
|
+
};
|
|
332015
|
+
const cost = toNumber2(record2.cost);
|
|
332016
|
+
if (cost !== undefined) {
|
|
332017
|
+
parsed.cost = cost;
|
|
332018
|
+
}
|
|
332019
|
+
if (typeof record2.takerOrMaker === "string") {
|
|
332020
|
+
parsed.takerOrMaker = record2.takerOrMaker;
|
|
332021
|
+
}
|
|
332022
|
+
return parsed;
|
|
332023
|
+
}
|
|
332024
|
+
function extractTrades(payload, fallbackMs = Date.now()) {
|
|
332025
|
+
if (Array.isArray(payload)) {
|
|
332026
|
+
return payload.map((entry) => parseTrade(entry, fallbackMs)).filter((entry) => entry !== null);
|
|
332027
|
+
}
|
|
332028
|
+
const single = parseTrade(payload, fallbackMs);
|
|
332029
|
+
return single ? [single] : [];
|
|
332030
|
+
}
|
|
332031
|
+
function parseTicker(value, fallbackMs) {
|
|
332032
|
+
const record2 = asRecord(value);
|
|
332033
|
+
if (!record2) {
|
|
332034
|
+
return null;
|
|
332035
|
+
}
|
|
332036
|
+
const parsed = {
|
|
332037
|
+
eventTimeMs: scalarTimestampMs(record2.timestamp, fallbackMs)
|
|
332038
|
+
};
|
|
332039
|
+
const fields = [
|
|
332040
|
+
["last", record2.last],
|
|
332041
|
+
["bid", record2.bid],
|
|
332042
|
+
["ask", record2.ask],
|
|
332043
|
+
["high", record2.high],
|
|
332044
|
+
["low", record2.low],
|
|
332045
|
+
["open", record2.open],
|
|
332046
|
+
["close", record2.close],
|
|
332047
|
+
["baseVolume", record2.baseVolume],
|
|
332048
|
+
["quoteVolume", record2.quoteVolume],
|
|
332049
|
+
["change", record2.change],
|
|
332050
|
+
["percentage", record2.percentage]
|
|
332051
|
+
];
|
|
332052
|
+
for (const [key, rawValue] of fields) {
|
|
332053
|
+
const numeric = toNumber2(rawValue);
|
|
332054
|
+
if (numeric !== undefined) {
|
|
332055
|
+
parsed[key] = numeric;
|
|
330987
332056
|
}
|
|
330988
332057
|
}
|
|
330989
|
-
return
|
|
332058
|
+
return parsed;
|
|
330990
332059
|
}
|
|
330991
|
-
|
|
330992
|
-
|
|
330993
|
-
|
|
330994
|
-
|
|
330995
|
-
|
|
332060
|
+
|
|
332061
|
+
// src/helpers/market-data-archive/orderbook-depth.ts
|
|
332062
|
+
var DEFAULT_ORDERBOOK_ARCHIVE_DEPTH_LIMIT = 25;
|
|
332063
|
+
var MAX_ORDERBOOK_ARCHIVE_DEPTH_LIMIT = 500;
|
|
332064
|
+
function getOrderbookArchiveDepthLimit() {
|
|
332065
|
+
const raw = process.env.CEX_BROKER_ORDERBOOK_ARCHIVE_DEPTH_LIMIT;
|
|
332066
|
+
if (!raw) {
|
|
332067
|
+
return DEFAULT_ORDERBOOK_ARCHIVE_DEPTH_LIMIT;
|
|
332068
|
+
}
|
|
332069
|
+
const parsed = Number.parseInt(raw, 10);
|
|
332070
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
332071
|
+
return DEFAULT_ORDERBOOK_ARCHIVE_DEPTH_LIMIT;
|
|
332072
|
+
}
|
|
332073
|
+
return Math.min(parsed, MAX_ORDERBOOK_ARCHIVE_DEPTH_LIMIT);
|
|
330996
332074
|
}
|
|
330997
|
-
function
|
|
330998
|
-
|
|
330999
|
-
|
|
332075
|
+
function splitOrderBookSide(levels, limit) {
|
|
332076
|
+
const prices = [];
|
|
332077
|
+
const sizes = [];
|
|
332078
|
+
for (const level of levels.slice(0, limit)) {
|
|
332079
|
+
if (!Array.isArray(level) || level.length < 2) {
|
|
332080
|
+
continue;
|
|
332081
|
+
}
|
|
332082
|
+
const price = level[0];
|
|
332083
|
+
const size = level[1];
|
|
332084
|
+
if (price === undefined || size === undefined || !Number.isFinite(price) || !Number.isFinite(size)) {
|
|
332085
|
+
continue;
|
|
332086
|
+
}
|
|
332087
|
+
prices.push(price);
|
|
332088
|
+
sizes.push(size);
|
|
331000
332089
|
}
|
|
331001
|
-
|
|
331002
|
-
|
|
331003
|
-
|
|
332090
|
+
return { prices, sizes };
|
|
332091
|
+
}
|
|
332092
|
+
|
|
332093
|
+
// src/helpers/market-data-archive/rows.ts
|
|
332094
|
+
function compactUndefined3(record2) {
|
|
332095
|
+
return Object.fromEntries(Object.entries(record2).filter(([, value]) => value !== undefined));
|
|
332096
|
+
}
|
|
332097
|
+
function scalarTimestampMs2(value) {
|
|
332098
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
332099
|
+
return value;
|
|
331004
332100
|
}
|
|
331005
|
-
if (value
|
|
331006
|
-
|
|
331007
|
-
|
|
332101
|
+
if (typeof value === "string") {
|
|
332102
|
+
if (/^\d+$/.test(value)) {
|
|
332103
|
+
const numeric = Number.parseInt(value, 10);
|
|
332104
|
+
if (Number.isFinite(numeric)) {
|
|
332105
|
+
return numeric;
|
|
332106
|
+
}
|
|
332107
|
+
}
|
|
332108
|
+
const parsed = Date.parse(value);
|
|
332109
|
+
if (Number.isFinite(parsed)) {
|
|
332110
|
+
return parsed;
|
|
332111
|
+
}
|
|
331008
332112
|
}
|
|
331009
|
-
return
|
|
332113
|
+
return Date.now();
|
|
331010
332114
|
}
|
|
331011
|
-
function
|
|
331012
|
-
|
|
331013
|
-
|
|
331014
|
-
|
|
331015
|
-
|
|
331016
|
-
|
|
331017
|
-
|
|
331018
|
-
|
|
331019
|
-
].filter((detail) => detail !== null);
|
|
331020
|
-
return new Error(details.length > 0 ? `Binance user-data WebSocket closed unexpectedly (${details.join(", ")})` : "Binance user-data WebSocket closed unexpectedly");
|
|
332115
|
+
function parseSequence(value) {
|
|
332116
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
332117
|
+
return value;
|
|
332118
|
+
}
|
|
332119
|
+
if (typeof value === "string" && /^\d+$/.test(value)) {
|
|
332120
|
+
return Number.parseInt(value, 10);
|
|
332121
|
+
}
|
|
332122
|
+
return;
|
|
331021
332123
|
}
|
|
331022
|
-
function
|
|
331023
|
-
|
|
331024
|
-
|
|
332124
|
+
function topOfBookLevel(levels) {
|
|
332125
|
+
const level = levels[0];
|
|
332126
|
+
if (!level || level.length < 2) {
|
|
332127
|
+
return null;
|
|
331025
332128
|
}
|
|
331026
|
-
|
|
331027
|
-
|
|
332129
|
+
const price = level[0];
|
|
332130
|
+
const size = level[1];
|
|
332131
|
+
if (price === undefined || size === undefined || !Number.isFinite(price) || !Number.isFinite(size)) {
|
|
332132
|
+
return null;
|
|
331028
332133
|
}
|
|
331029
|
-
|
|
331030
|
-
|
|
332134
|
+
return { price, size };
|
|
332135
|
+
}
|
|
332136
|
+
function computeSpreadBps(bestBid, bestAsk) {
|
|
332137
|
+
if (bestBid <= 0 || bestAsk <= 0 || bestAsk < bestBid) {
|
|
332138
|
+
return 0;
|
|
331031
332139
|
}
|
|
331032
|
-
|
|
331033
|
-
|
|
332140
|
+
const mid = (bestBid + bestAsk) / 2;
|
|
332141
|
+
if (mid <= 0) {
|
|
332142
|
+
return 0;
|
|
331034
332143
|
}
|
|
331035
|
-
|
|
331036
|
-
|
|
332144
|
+
return (bestAsk - bestBid) / mid * 1e4;
|
|
332145
|
+
}
|
|
332146
|
+
function buildOrderbookArchiveTags(input, receivedTimeMs) {
|
|
332147
|
+
return buildCommonArchiveTags({
|
|
332148
|
+
deploymentId: input.deploymentId,
|
|
332149
|
+
accountSelector: input.accountSelector,
|
|
332150
|
+
exchange: input.exchange,
|
|
332151
|
+
symbol: input.symbol,
|
|
332152
|
+
brokerObservedTimestamp: new Date(receivedTimeMs).toISOString()
|
|
332153
|
+
});
|
|
332154
|
+
}
|
|
332155
|
+
function buildOrderbookSnapshotRow(input) {
|
|
332156
|
+
const bid = topOfBookLevel(input.snapshot.bids);
|
|
332157
|
+
const ask = topOfBookLevel(input.snapshot.asks);
|
|
332158
|
+
if (!bid || !ask) {
|
|
332159
|
+
return null;
|
|
331037
332160
|
}
|
|
331038
|
-
|
|
332161
|
+
const archiveDepthLimit = getOrderbookArchiveDepthLimit();
|
|
332162
|
+
const bids = splitOrderBookSide(input.snapshot.bids, archiveDepthLimit);
|
|
332163
|
+
const asks = splitOrderBookSide(input.snapshot.asks, archiveDepthLimit);
|
|
332164
|
+
if (bids.prices.length === 0 || asks.prices.length === 0) {
|
|
332165
|
+
return null;
|
|
332166
|
+
}
|
|
332167
|
+
const eventTimeMs = scalarTimestampMs2(input.snapshot.timestamp);
|
|
332168
|
+
const receivedTimeMs = input.snapshot.receivedTimestamp;
|
|
332169
|
+
const mid = (bid.price + ask.price) / 2;
|
|
332170
|
+
const sequence = parseSequence(input.snapshot.sequence);
|
|
332171
|
+
return {
|
|
332172
|
+
table: "market_data.orderbook_snapshots",
|
|
332173
|
+
row: compactUndefined3({
|
|
332174
|
+
...buildOrderbookArchiveTags(input, receivedTimeMs),
|
|
332175
|
+
asset_type: input.assetType,
|
|
332176
|
+
event_time_ms: eventTimeMs,
|
|
332177
|
+
received_time_ms: receivedTimeMs,
|
|
332178
|
+
best_bid: bid.price,
|
|
332179
|
+
best_ask: ask.price,
|
|
332180
|
+
bid_size: bid.size,
|
|
332181
|
+
ask_size: ask.size,
|
|
332182
|
+
mid,
|
|
332183
|
+
spread_bps: computeSpreadBps(bid.price, ask.price),
|
|
332184
|
+
depth_limit: archiveDepthLimit,
|
|
332185
|
+
bid_levels: bids.prices.length,
|
|
332186
|
+
ask_levels: asks.prices.length,
|
|
332187
|
+
bids_price: bids.prices,
|
|
332188
|
+
bids_size: bids.sizes,
|
|
332189
|
+
asks_price: asks.prices,
|
|
332190
|
+
asks_size: asks.sizes,
|
|
332191
|
+
sequence
|
|
332192
|
+
})
|
|
332193
|
+
};
|
|
332194
|
+
}
|
|
332195
|
+
function buildCandleRow(input) {
|
|
332196
|
+
const { context: context2, bar, isClosed, brokerVersion, receivedTimestamp } = input;
|
|
332197
|
+
const tags = buildCommonArchiveTags({
|
|
332198
|
+
deploymentId: context2.deploymentId,
|
|
332199
|
+
accountSelector: context2.accountSelector,
|
|
332200
|
+
exchange: context2.exchange,
|
|
332201
|
+
symbol: context2.symbol,
|
|
332202
|
+
brokerObservedTimestamp: new Date(receivedTimestamp).toISOString()
|
|
332203
|
+
});
|
|
332204
|
+
return {
|
|
332205
|
+
table: "market_data.candles",
|
|
332206
|
+
row: compactUndefined3({
|
|
332207
|
+
...tags,
|
|
332208
|
+
asset_type: context2.assetType,
|
|
332209
|
+
timeframe: context2.timeframe ?? "1m",
|
|
332210
|
+
open_time_ms: bar.openTimeMs,
|
|
332211
|
+
open: bar.open,
|
|
332212
|
+
high: bar.high,
|
|
332213
|
+
low: bar.low,
|
|
332214
|
+
close: bar.close,
|
|
332215
|
+
volume: bar.volume,
|
|
332216
|
+
quote_volume: bar.quoteVolume,
|
|
332217
|
+
is_closed: isClosed ? 1 : 0,
|
|
332218
|
+
broker_version: brokerVersion
|
|
332219
|
+
})
|
|
332220
|
+
};
|
|
332221
|
+
}
|
|
332222
|
+
function buildCexStreamEventRow(input) {
|
|
332223
|
+
const receivedTimeMs = input.receivedTimestamp;
|
|
332224
|
+
const redactedPayload = redactStreamPayload(input.payload);
|
|
332225
|
+
const tags = buildCommonArchiveTags({
|
|
332226
|
+
deploymentId: input.deploymentId,
|
|
332227
|
+
accountSelector: input.accountSelector,
|
|
332228
|
+
exchange: input.exchange,
|
|
332229
|
+
symbol: input.symbol,
|
|
332230
|
+
brokerObservedTimestamp: new Date(receivedTimeMs).toISOString()
|
|
332231
|
+
});
|
|
332232
|
+
return {
|
|
332233
|
+
table: "market_data.cex_stream_events",
|
|
332234
|
+
row: compactUndefined3({
|
|
332235
|
+
...tags,
|
|
332236
|
+
asset_type: input.assetType,
|
|
332237
|
+
stream_type: input.streamType,
|
|
332238
|
+
event_time_ms: input.eventTimeMs ?? receivedTimeMs,
|
|
332239
|
+
received_time_ms: receivedTimeMs,
|
|
332240
|
+
payload_json: JSON.stringify(redactedPayload)
|
|
332241
|
+
})
|
|
332242
|
+
};
|
|
332243
|
+
}
|
|
332244
|
+
function buildCexTickerEventRow(input, ticker) {
|
|
332245
|
+
const receivedTimeMs = input.receivedTimestamp;
|
|
332246
|
+
const tags = buildCommonArchiveTags({
|
|
332247
|
+
deploymentId: input.deploymentId,
|
|
332248
|
+
accountSelector: input.accountSelector,
|
|
332249
|
+
exchange: input.exchange,
|
|
332250
|
+
symbol: input.symbol,
|
|
332251
|
+
brokerObservedTimestamp: new Date(receivedTimeMs).toISOString()
|
|
332252
|
+
});
|
|
332253
|
+
return {
|
|
332254
|
+
table: "market_data.cex_ticker_events",
|
|
332255
|
+
row: compactUndefined3({
|
|
332256
|
+
...tags,
|
|
332257
|
+
asset_type: input.assetType,
|
|
332258
|
+
event_time_ms: ticker.eventTimeMs,
|
|
332259
|
+
received_time_ms: receivedTimeMs,
|
|
332260
|
+
last: ticker.last,
|
|
332261
|
+
bid: ticker.bid,
|
|
332262
|
+
ask: ticker.ask,
|
|
332263
|
+
high: ticker.high,
|
|
332264
|
+
low: ticker.low,
|
|
332265
|
+
open: ticker.open,
|
|
332266
|
+
close: ticker.close,
|
|
332267
|
+
base_volume: ticker.baseVolume,
|
|
332268
|
+
quote_volume: ticker.quoteVolume,
|
|
332269
|
+
change: ticker.change,
|
|
332270
|
+
percentage: ticker.percentage,
|
|
332271
|
+
payload_json: JSON.stringify(redactStreamPayload(input.payload))
|
|
332272
|
+
})
|
|
332273
|
+
};
|
|
332274
|
+
}
|
|
332275
|
+
function buildCexTradeRow(input, trade) {
|
|
332276
|
+
const receivedTimeMs = input.receivedTimestamp;
|
|
332277
|
+
const tags = buildCommonArchiveTags({
|
|
332278
|
+
deploymentId: input.deploymentId,
|
|
332279
|
+
accountSelector: input.accountSelector,
|
|
332280
|
+
exchange: input.exchange,
|
|
332281
|
+
symbol: input.symbol,
|
|
332282
|
+
brokerObservedTimestamp: new Date(receivedTimeMs).toISOString()
|
|
332283
|
+
});
|
|
332284
|
+
return {
|
|
332285
|
+
table: "market_data.cex_trades",
|
|
332286
|
+
row: compactUndefined3({
|
|
332287
|
+
...tags,
|
|
332288
|
+
asset_type: input.assetType,
|
|
332289
|
+
trade_id: trade.tradeId,
|
|
332290
|
+
event_time_ms: trade.eventTimeMs,
|
|
332291
|
+
received_time_ms: receivedTimeMs,
|
|
332292
|
+
side: trade.side,
|
|
332293
|
+
price: trade.price,
|
|
332294
|
+
amount: trade.amount,
|
|
332295
|
+
cost: trade.cost,
|
|
332296
|
+
taker_or_maker: trade.takerOrMaker
|
|
332297
|
+
})
|
|
332298
|
+
};
|
|
331039
332299
|
}
|
|
331040
332300
|
|
|
331041
|
-
|
|
331042
|
-
|
|
331043
|
-
|
|
331044
|
-
|
|
331045
|
-
|
|
331046
|
-
|
|
331047
|
-
|
|
331048
|
-
|
|
331049
|
-
|
|
331050
|
-
|
|
331051
|
-
|
|
331052
|
-
|
|
331053
|
-
|
|
331054
|
-
|
|
331055
|
-
|
|
331056
|
-
|
|
331057
|
-
|
|
331058
|
-
|
|
331059
|
-
this.ws = createWebSocket(getBinanceSpotWsApiUrl(exchange));
|
|
331060
|
-
this.ws.on("open", () => this.subscribe());
|
|
331061
|
-
this.ws.on("message", (data) => this.handleMessage(data));
|
|
331062
|
-
this.ws.on("error", (error48) => this.fail(formatBinanceUserDataWebSocketError(error48, this.secretValues)));
|
|
331063
|
-
this.ws.on("close", (code, reason) => this.handleClose(code, reason));
|
|
332301
|
+
// src/helpers/market-data-archive/capture.ts
|
|
332302
|
+
async function recordWatchMetric(otelMetrics, metricName, labels) {
|
|
332303
|
+
try {
|
|
332304
|
+
await otelMetrics?.recordCounter(metricName, 1, labels);
|
|
332305
|
+
} catch {}
|
|
332306
|
+
}
|
|
332307
|
+
function watchLabels(stream4, input) {
|
|
332308
|
+
return {
|
|
332309
|
+
stream: stream4,
|
|
332310
|
+
exchange: input.exchange,
|
|
332311
|
+
symbol: input.symbol
|
|
332312
|
+
};
|
|
332313
|
+
}
|
|
332314
|
+
function archiveOrderbookInBackground(archiver, otelMetrics, input, options) {
|
|
332315
|
+
const labels = watchLabels("orderbook", input);
|
|
332316
|
+
recordWatchMetric(otelMetrics, "cex_watch_frames_received_total", labels);
|
|
332317
|
+
if (!isMarketArchiveEnabled() || !archiver?.isEnabled()) {
|
|
332318
|
+
return;
|
|
331064
332319
|
}
|
|
331065
|
-
|
|
331066
|
-
|
|
331067
|
-
|
|
331068
|
-
if (!event) {
|
|
331069
|
-
break;
|
|
331070
|
-
}
|
|
331071
|
-
yield event;
|
|
331072
|
-
}
|
|
332320
|
+
if (options?.sampledOut) {
|
|
332321
|
+
recordWatchMetric(otelMetrics, "cex_watch_frames_sampled_out_total", labels);
|
|
332322
|
+
return;
|
|
331073
332323
|
}
|
|
331074
|
-
|
|
331075
|
-
if (this.closed) {
|
|
331076
|
-
return;
|
|
331077
|
-
}
|
|
331078
|
-
this.closed = true;
|
|
331079
|
-
this.queue.length = 0;
|
|
332324
|
+
queueMicrotask(() => {
|
|
331080
332325
|
try {
|
|
331081
|
-
|
|
331082
|
-
|
|
331083
|
-
|
|
331084
|
-
|
|
331085
|
-
|
|
331086
|
-
|
|
331087
|
-
|
|
332326
|
+
const row = buildOrderbookSnapshotRow(input);
|
|
332327
|
+
if (row) {
|
|
332328
|
+
archiver.enqueue(row);
|
|
332329
|
+
recordWatchMetric(otelMetrics, "cex_watch_frames_archived_total", labels);
|
|
332330
|
+
}
|
|
332331
|
+
} catch (error48) {
|
|
332332
|
+
log.warn("Failed to archive orderbook snapshot", { error: error48 });
|
|
331088
332333
|
}
|
|
331089
|
-
|
|
331090
|
-
|
|
331091
|
-
|
|
331092
|
-
|
|
331093
|
-
|
|
331094
|
-
|
|
331095
|
-
|
|
331096
|
-
});
|
|
331097
|
-
this.ws.send(JSON.stringify({
|
|
331098
|
-
id: this.requestId,
|
|
331099
|
-
method: "userDataStream.subscribe.signature",
|
|
331100
|
-
params: signedParams
|
|
331101
|
-
}));
|
|
332334
|
+
});
|
|
332335
|
+
}
|
|
332336
|
+
function archiveOhlcvInBackground(archiver, otelMetrics, tracker, input) {
|
|
332337
|
+
const labels = watchLabels("ohlcv", input);
|
|
332338
|
+
recordWatchMetric(otelMetrics, "cex_watch_frames_received_total", labels);
|
|
332339
|
+
if (!isMarketArchiveEnabled() || !archiver?.isEnabled()) {
|
|
332340
|
+
return;
|
|
331102
332341
|
}
|
|
331103
|
-
|
|
331104
|
-
if (this.closed) {
|
|
331105
|
-
return;
|
|
331106
|
-
}
|
|
331107
|
-
let message;
|
|
332342
|
+
queueMicrotask(() => {
|
|
331108
332343
|
try {
|
|
331109
|
-
const
|
|
331110
|
-
|
|
332344
|
+
const candidates = tracker.process(input.payload, input.receivedTimestamp);
|
|
332345
|
+
for (const candidate of candidates) {
|
|
332346
|
+
const row = buildCandleRow({
|
|
332347
|
+
context: input,
|
|
332348
|
+
bar: candidate.bar,
|
|
332349
|
+
isClosed: candidate.isClosed,
|
|
332350
|
+
brokerVersion: candidate.brokerVersion,
|
|
332351
|
+
receivedTimestamp: input.receivedTimestamp
|
|
332352
|
+
});
|
|
332353
|
+
archiver.enqueue(row);
|
|
332354
|
+
}
|
|
332355
|
+
if (candidates.length > 0) {
|
|
332356
|
+
recordWatchMetric(otelMetrics, "cex_watch_frames_archived_total", labels);
|
|
332357
|
+
}
|
|
331111
332358
|
} catch (error48) {
|
|
331112
|
-
|
|
331113
|
-
return;
|
|
332359
|
+
log.warn("Failed to archive OHLCV candle", { error: error48 });
|
|
331114
332360
|
}
|
|
331115
|
-
|
|
331116
|
-
|
|
331117
|
-
|
|
331118
|
-
|
|
332361
|
+
});
|
|
332362
|
+
}
|
|
332363
|
+
function createOrderbookSampler() {
|
|
332364
|
+
return new OrderbookSampler;
|
|
332365
|
+
}
|
|
332366
|
+
function createOhlcvBarTracker() {
|
|
332367
|
+
return new OhlcvBarTracker;
|
|
332368
|
+
}
|
|
332369
|
+
function archiveMarketRowsInBackground(archiver, otelMetrics, stream4, input, enqueueRows) {
|
|
332370
|
+
const labels = watchLabels(stream4, input);
|
|
332371
|
+
recordWatchMetric(otelMetrics, "cex_watch_frames_received_total", labels);
|
|
332372
|
+
if (!isMarketArchiveEnabled() || !archiver?.isEnabled()) {
|
|
332373
|
+
return;
|
|
332374
|
+
}
|
|
332375
|
+
queueMicrotask(() => {
|
|
332376
|
+
try {
|
|
332377
|
+
const rows = enqueueRows();
|
|
332378
|
+
for (const row of rows) {
|
|
332379
|
+
archiver.enqueue(row);
|
|
331119
332380
|
}
|
|
331120
|
-
|
|
331121
|
-
|
|
331122
|
-
|
|
331123
|
-
|
|
331124
|
-
|
|
331125
|
-
}
|
|
331126
|
-
const subscriptionId = message.subscriptionId ?? this.subscriptionId;
|
|
331127
|
-
if (subscriptionId === null || subscriptionId === undefined) {
|
|
331128
|
-
return;
|
|
332381
|
+
if (rows.length > 0) {
|
|
332382
|
+
recordWatchMetric(otelMetrics, "cex_watch_frames_archived_total", labels);
|
|
332383
|
+
}
|
|
332384
|
+
} catch (error48) {
|
|
332385
|
+
log.warn(`Failed to archive ${stream4} market data`, { error: error48 });
|
|
331129
332386
|
}
|
|
331130
|
-
|
|
332387
|
+
});
|
|
332388
|
+
}
|
|
332389
|
+
function archiveTradesInBackground(archiver, otelMetrics, input) {
|
|
332390
|
+
archiveMarketRowsInBackground(archiver, otelMetrics, "trades", input, () => extractTrades(input.payload, input.receivedTimestamp).map((trade) => buildCexTradeRow(input, trade)));
|
|
332391
|
+
}
|
|
332392
|
+
function archiveTickerInBackground(archiver, otelMetrics, input) {
|
|
332393
|
+
archiveMarketRowsInBackground(archiver, otelMetrics, "ticker", input, () => {
|
|
332394
|
+
const ticker = parseTicker(input.payload, input.receivedTimestamp);
|
|
332395
|
+
return ticker ? [buildCexTickerEventRow(input, ticker)] : [];
|
|
332396
|
+
});
|
|
332397
|
+
}
|
|
332398
|
+
function archiveCexStreamEventInBackground(archiver, otelMetrics, input) {
|
|
332399
|
+
archiveMarketRowsInBackground(archiver, otelMetrics, "stream", input, () => [
|
|
332400
|
+
buildCexStreamEventRow(input)
|
|
332401
|
+
]);
|
|
332402
|
+
}
|
|
332403
|
+
// src/helpers/market-data-archive/ohlcv-bootstrap.ts
|
|
332404
|
+
var DEFAULT_OHLCV_BOOTSTRAP_LIMIT = 100;
|
|
332405
|
+
var MAX_OHLCV_BOOTSTRAP_LIMIT = 1000;
|
|
332406
|
+
function resolveOhlcvBootstrapLimit(optionValue) {
|
|
332407
|
+
const raw = optionValue?.trim() || process.env.CEX_BROKER_OHLCV_ARCHIVE_BOOTSTRAP_LIMIT?.trim();
|
|
332408
|
+
if (raw === "0" || raw?.toLowerCase() === "false") {
|
|
332409
|
+
return 0;
|
|
331131
332410
|
}
|
|
331132
|
-
|
|
331133
|
-
|
|
331134
|
-
return;
|
|
331135
|
-
}
|
|
331136
|
-
const waiter = this.waiters.shift();
|
|
331137
|
-
if (waiter) {
|
|
331138
|
-
waiter.resolve(event);
|
|
331139
|
-
return;
|
|
331140
|
-
}
|
|
331141
|
-
if (this.queue.length >= this.maxBufferedEvents) {
|
|
331142
|
-
this.fail(new Error(`Binance user-data stream buffered event limit exceeded (${this.maxBufferedEvents}); downstream consumer is not keeping up`));
|
|
331143
|
-
return;
|
|
331144
|
-
}
|
|
331145
|
-
this.queue.push(event);
|
|
332411
|
+
if (!raw) {
|
|
332412
|
+
return DEFAULT_OHLCV_BOOTSTRAP_LIMIT;
|
|
331146
332413
|
}
|
|
331147
|
-
|
|
331148
|
-
|
|
331149
|
-
|
|
331150
|
-
return Promise.resolve(event);
|
|
331151
|
-
}
|
|
331152
|
-
if (this.closeError) {
|
|
331153
|
-
return Promise.reject(this.closeError);
|
|
331154
|
-
}
|
|
331155
|
-
if (this.closed) {
|
|
331156
|
-
return Promise.resolve(null);
|
|
331157
|
-
}
|
|
331158
|
-
return new Promise((resolve, reject) => {
|
|
331159
|
-
this.waiters.push({ resolve, reject });
|
|
331160
|
-
});
|
|
332414
|
+
const parsed = Number.parseInt(raw, 10);
|
|
332415
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
332416
|
+
return DEFAULT_OHLCV_BOOTSTRAP_LIMIT;
|
|
331161
332417
|
}
|
|
331162
|
-
|
|
331163
|
-
|
|
331164
|
-
|
|
331165
|
-
|
|
331166
|
-
|
|
331167
|
-
|
|
331168
|
-
|
|
331169
|
-
|
|
331170
|
-
|
|
331171
|
-
|
|
331172
|
-
|
|
332418
|
+
return Math.min(parsed, MAX_OHLCV_BOOTSTRAP_LIMIT);
|
|
332419
|
+
}
|
|
332420
|
+
|
|
332421
|
+
// src/helpers/market-data-archive/ohlcv-history.ts
|
|
332422
|
+
function supportsFetchOhlcv(broker) {
|
|
332423
|
+
const fetchOHLCV = broker.fetchOHLCV;
|
|
332424
|
+
const hasValue = broker.has?.fetchOHLCV;
|
|
332425
|
+
return typeof fetchOHLCV === "function" && hasValue !== false;
|
|
332426
|
+
}
|
|
332427
|
+
async function bootstrapOhlcvHistory(broker, archiver, otelMetrics, tracker, input, options) {
|
|
332428
|
+
const limit = resolveOhlcvBootstrapLimit(options?.bootstrapLimit);
|
|
332429
|
+
if (limit <= 0 || !supportsFetchOhlcv(broker)) {
|
|
332430
|
+
return null;
|
|
331173
332431
|
}
|
|
331174
|
-
|
|
331175
|
-
const
|
|
331176
|
-
|
|
331177
|
-
|
|
331178
|
-
|
|
331179
|
-
} else {
|
|
331180
|
-
waiter.resolve(null);
|
|
331181
|
-
}
|
|
332432
|
+
try {
|
|
332433
|
+
const fetchOHLCV = broker.fetchOHLCV.bind(broker);
|
|
332434
|
+
const payload = await fetchOHLCV(input.symbol, input.timeframe ?? "1m", undefined, limit);
|
|
332435
|
+
if (!Array.isArray(payload) || payload.length === 0) {
|
|
332436
|
+
return null;
|
|
331182
332437
|
}
|
|
332438
|
+
const receivedTimestamp = Date.now();
|
|
332439
|
+
archiveOhlcvInBackground(archiver, otelMetrics, tracker, {
|
|
332440
|
+
...input,
|
|
332441
|
+
payload,
|
|
332442
|
+
receivedTimestamp
|
|
332443
|
+
});
|
|
332444
|
+
return payload;
|
|
332445
|
+
} catch (error48) {
|
|
332446
|
+
log.warn("OHLCV bootstrap fetch failed", {
|
|
332447
|
+
error: error48,
|
|
332448
|
+
symbol: input.symbol,
|
|
332449
|
+
exchange: input.exchange,
|
|
332450
|
+
timeframe: input.timeframe
|
|
332451
|
+
});
|
|
332452
|
+
return null;
|
|
331183
332453
|
}
|
|
331184
332454
|
}
|
|
331185
|
-
function isBinanceBalanceUserDataEvent(event) {
|
|
331186
|
-
return event.e === "outboundAccountPosition" || event.e === "balanceUpdate" || event.e === "externalLockUpdate";
|
|
331187
|
-
}
|
|
331188
|
-
function isBinanceOrderUserDataEvent(event) {
|
|
331189
|
-
return event.e === "executionReport" || event.e === "listStatus";
|
|
331190
|
-
}
|
|
331191
|
-
|
|
331192
332455
|
// src/handlers/subscribe/handler.ts
|
|
331193
332456
|
function isBinanceSpotAccountSubscription(cex3, subscriptionType, marketTypeInput) {
|
|
331194
332457
|
return cex3 === "binance" && parseMarketType(marketTypeInput) === "spot" && (subscriptionType === SubscriptionType.BALANCE || subscriptionType === SubscriptionType.ORDERS);
|
|
@@ -331257,7 +332520,7 @@ async function getBinanceMarketId(broker, symbol2) {
|
|
|
331257
332520
|
}
|
|
331258
332521
|
return symbol2.replace("/", "").toUpperCase();
|
|
331259
332522
|
}
|
|
331260
|
-
async function streamBinanceUserData(call, broker, symbol2, subscriptionType, isClosed) {
|
|
332523
|
+
async function streamBinanceUserData(call, broker, symbol2, subscriptionType, isClosed, archiveContext) {
|
|
331261
332524
|
const userDataStream = new BinanceSpotUserDataStream(broker);
|
|
331262
332525
|
call.once("close", () => userDataStream.close());
|
|
331263
332526
|
call.once("cancelled", () => userDataStream.close());
|
|
@@ -331282,37 +332545,78 @@ async function streamBinanceUserData(call, broker, symbol2, subscriptionType, is
|
|
|
331282
332545
|
continue;
|
|
331283
332546
|
}
|
|
331284
332547
|
}
|
|
332548
|
+
const receivedTimestamp = Date.now();
|
|
331285
332549
|
if (!await writeSubscribeFrame(call, isClosed, {
|
|
331286
332550
|
data: JSON.stringify({
|
|
331287
332551
|
subscriptionId: message.subscriptionId,
|
|
331288
332552
|
event
|
|
331289
332553
|
}),
|
|
331290
|
-
timestamp:
|
|
332554
|
+
timestamp: receivedTimestamp,
|
|
331291
332555
|
symbol: symbol2,
|
|
331292
332556
|
type: subscriptionType
|
|
331293
332557
|
})) {
|
|
331294
332558
|
break;
|
|
331295
332559
|
}
|
|
332560
|
+
const archiveSubscriptionType = subscriptionType === SubscriptionType.BALANCE ? "BALANCE" : "ORDERS";
|
|
332561
|
+
archiveSubscribeStreamInBackground(archiveContext?.archiver, {
|
|
332562
|
+
exchange: archiveContext?.exchange ?? "binance",
|
|
332563
|
+
accountSelector: archiveContext?.accountSelector,
|
|
332564
|
+
symbol: symbol2,
|
|
332565
|
+
subscriptionType: archiveSubscriptionType,
|
|
332566
|
+
streamPayload: event
|
|
332567
|
+
});
|
|
332568
|
+
if (archiveContext) {
|
|
332569
|
+
archiveCexStreamEventInBackground(archiveContext.archiver, archiveContext.otelMetrics, {
|
|
332570
|
+
deploymentId: archiveContext.deploymentId,
|
|
332571
|
+
exchange: archiveContext.exchange,
|
|
332572
|
+
symbol: symbol2,
|
|
332573
|
+
assetType: archiveContext.assetType,
|
|
332574
|
+
accountSelector: archiveContext.accountSelector,
|
|
332575
|
+
streamType: archiveSubscriptionType,
|
|
332576
|
+
payload: event,
|
|
332577
|
+
receivedTimestamp
|
|
332578
|
+
});
|
|
332579
|
+
}
|
|
331296
332580
|
}
|
|
331297
332581
|
} finally {
|
|
331298
332582
|
userDataStream.close();
|
|
331299
332583
|
}
|
|
331300
332584
|
}
|
|
331301
|
-
async function runCcxtSubscribeLoop(call, isClosed, symbol2, subscriptionType, watch) {
|
|
332585
|
+
async function runCcxtSubscribeLoop(call, isClosed, symbol2, subscriptionType, watch, archiveContext) {
|
|
331302
332586
|
while (!isClosed()) {
|
|
331303
332587
|
const data = await watch();
|
|
332588
|
+
const receivedTimestamp = Date.now();
|
|
331304
332589
|
if (!await writeSubscribeFrame(call, isClosed, {
|
|
331305
332590
|
data: JSON.stringify(data),
|
|
331306
|
-
timestamp:
|
|
332591
|
+
timestamp: receivedTimestamp,
|
|
331307
332592
|
symbol: symbol2,
|
|
331308
332593
|
type: subscriptionType
|
|
331309
332594
|
})) {
|
|
331310
332595
|
break;
|
|
331311
332596
|
}
|
|
332597
|
+
if (archiveContext?.archiveSubscriptionType) {
|
|
332598
|
+
archiveSubscribeStreamInBackground(archiveContext.archiver, {
|
|
332599
|
+
exchange: archiveContext.exchange,
|
|
332600
|
+
accountSelector: archiveContext.accountSelector,
|
|
332601
|
+
symbol: symbol2,
|
|
332602
|
+
subscriptionType: archiveContext.archiveSubscriptionType,
|
|
332603
|
+
streamPayload: data
|
|
332604
|
+
});
|
|
332605
|
+
archiveCexStreamEventInBackground(archiveContext.archiver, archiveContext.otelMetrics, {
|
|
332606
|
+
deploymentId: archiveContext.deploymentId,
|
|
332607
|
+
exchange: archiveContext.exchange,
|
|
332608
|
+
symbol: symbol2,
|
|
332609
|
+
assetType: archiveContext.assetType,
|
|
332610
|
+
accountSelector: archiveContext.accountSelector,
|
|
332611
|
+
streamType: archiveContext.archiveSubscriptionType,
|
|
332612
|
+
payload: data,
|
|
332613
|
+
receivedTimestamp
|
|
332614
|
+
});
|
|
332615
|
+
}
|
|
331312
332616
|
}
|
|
331313
332617
|
}
|
|
331314
332618
|
function createSubscribeHandler(deps) {
|
|
331315
|
-
const { brokers, whitelistIps, otelMetrics } = deps;
|
|
332619
|
+
const { brokers, whitelistIps, otelMetrics, brokerArchiver } = deps;
|
|
331316
332620
|
return async (call) => {
|
|
331317
332621
|
const subscribeStartTime = Date.now();
|
|
331318
332622
|
let streamClosed = false;
|
|
@@ -331378,7 +332682,9 @@ function createSubscribeHandler(deps) {
|
|
|
331378
332682
|
return;
|
|
331379
332683
|
}
|
|
331380
332684
|
const normalizedCex = cex3.trim().toLowerCase();
|
|
331381
|
-
const
|
|
332685
|
+
const brokerPool = brokers[normalizedCex];
|
|
332686
|
+
const selectedBrokerAccount = selectBrokerAccount(brokerPool, metadata);
|
|
332687
|
+
const selectedBroker = selectedBrokerAccount?.exchange ?? createBroker(normalizedCex, metadata);
|
|
331382
332688
|
const broker = selectedBroker ?? createPublicBroker(normalizedCex);
|
|
331383
332689
|
if (!broker) {
|
|
331384
332690
|
await writeSubscribeError(call, isStreamClosed, {
|
|
@@ -331392,8 +332698,19 @@ function createSubscribeHandler(deps) {
|
|
|
331392
332698
|
return;
|
|
331393
332699
|
}
|
|
331394
332700
|
const resolvedSymbol = await resolveSubscriptionSymbol(broker, symbol2, options?.marketType);
|
|
332701
|
+
const assetType = parseMarketType(options?.marketType);
|
|
332702
|
+
const deploymentId = brokerArchiver?.getDeploymentId() ?? "unknown";
|
|
332703
|
+
const streamArchiveContext = {
|
|
332704
|
+
archiver: brokerArchiver,
|
|
332705
|
+
otelMetrics,
|
|
332706
|
+
exchange: normalizedCex,
|
|
332707
|
+
accountSelector: selectedBrokerAccount?.label,
|
|
332708
|
+
deploymentId,
|
|
332709
|
+
assetType
|
|
332710
|
+
};
|
|
331395
332711
|
if (isBinanceSpotAccountSubscription(normalizedCex, subscriptionType, options?.marketType)) {
|
|
331396
|
-
|
|
332712
|
+
const accountBroker = selectedBrokerAccount?.exchange ?? selectedBroker;
|
|
332713
|
+
if (!accountBroker) {
|
|
331397
332714
|
await writeSubscribeError(call, isStreamClosed, {
|
|
331398
332715
|
data: JSON.stringify({
|
|
331399
332716
|
error: "Binance account subscriptions require API credentials"
|
|
@@ -331404,29 +332721,42 @@ function createSubscribeHandler(deps) {
|
|
|
331404
332721
|
});
|
|
331405
332722
|
return;
|
|
331406
332723
|
}
|
|
331407
|
-
await streamBinanceUserData(call,
|
|
332724
|
+
await streamBinanceUserData(call, accountBroker, resolvedSymbol, subscriptionType, isStreamClosed, streamArchiveContext);
|
|
331408
332725
|
return;
|
|
331409
332726
|
}
|
|
331410
332727
|
switch (subscriptionType) {
|
|
331411
332728
|
case SubscriptionType.ORDERBOOK:
|
|
331412
332729
|
try {
|
|
332730
|
+
const orderbookSampler = createOrderbookSampler();
|
|
331413
332731
|
while (!isStreamClosed()) {
|
|
331414
332732
|
const depthLimit = parseOptionalDepthLimit(options?.depthLimit ?? options?.limit);
|
|
331415
332733
|
const orderbook = depthLimit === undefined ? await broker.watchOrderBook(resolvedSymbol) : await broker.watchOrderBook(resolvedSymbol, depthLimit);
|
|
331416
332734
|
const receivedTimestamp = Date.now();
|
|
332735
|
+
const normalizedSnapshot = normalizeOrderBookSnapshot(orderbook, {
|
|
332736
|
+
exchange: normalizedCex,
|
|
332737
|
+
symbol: resolvedSymbol,
|
|
332738
|
+
depthLimit: depthLimit ?? Math.max(Array.isArray(orderbook?.bids) ? orderbook.bids.length : 0, Array.isArray(orderbook?.asks) ? orderbook.asks.length : 0),
|
|
332739
|
+
receivedTimestamp
|
|
332740
|
+
});
|
|
331417
332741
|
if (!await writeSubscribeFrame(call, isStreamClosed, {
|
|
331418
|
-
data: JSON.stringify(
|
|
331419
|
-
exchange: normalizedCex,
|
|
331420
|
-
symbol: resolvedSymbol,
|
|
331421
|
-
depthLimit: depthLimit ?? Math.max(Array.isArray(orderbook?.bids) ? orderbook.bids.length : 0, Array.isArray(orderbook?.asks) ? orderbook.asks.length : 0),
|
|
331422
|
-
receivedTimestamp
|
|
331423
|
-
})),
|
|
332742
|
+
data: JSON.stringify(normalizedSnapshot),
|
|
331424
332743
|
timestamp: receivedTimestamp,
|
|
331425
332744
|
symbol: resolvedSymbol,
|
|
331426
332745
|
type: subscriptionType
|
|
331427
332746
|
})) {
|
|
331428
332747
|
break;
|
|
331429
332748
|
}
|
|
332749
|
+
const shouldArchive = orderbookSampler.shouldEmit(receivedTimestamp);
|
|
332750
|
+
archiveOrderbookInBackground(brokerArchiver, otelMetrics, {
|
|
332751
|
+
deploymentId,
|
|
332752
|
+
exchange: normalizedCex,
|
|
332753
|
+
symbol: resolvedSymbol,
|
|
332754
|
+
assetType,
|
|
332755
|
+
accountSelector: selectedBrokerAccount?.label,
|
|
332756
|
+
snapshot: normalizedSnapshot
|
|
332757
|
+
}, {
|
|
332758
|
+
sampledOut: !shouldArchive
|
|
332759
|
+
});
|
|
331430
332760
|
}
|
|
331431
332761
|
} catch (error48) {
|
|
331432
332762
|
log.error(`Error fetching orderbook for ${resolvedSymbol} on ${cex3}:`, error48);
|
|
@@ -331443,7 +332773,27 @@ function createSubscribeHandler(deps) {
|
|
|
331443
332773
|
break;
|
|
331444
332774
|
case SubscriptionType.TRADES:
|
|
331445
332775
|
try {
|
|
331446
|
-
|
|
332776
|
+
while (!isStreamClosed()) {
|
|
332777
|
+
const data = await broker.watchTrades(resolvedSymbol);
|
|
332778
|
+
const receivedTimestamp = Date.now();
|
|
332779
|
+
if (!await writeSubscribeFrame(call, isStreamClosed, {
|
|
332780
|
+
data: JSON.stringify(data),
|
|
332781
|
+
timestamp: receivedTimestamp,
|
|
332782
|
+
symbol: resolvedSymbol,
|
|
332783
|
+
type: subscriptionType
|
|
332784
|
+
})) {
|
|
332785
|
+
break;
|
|
332786
|
+
}
|
|
332787
|
+
archiveTradesInBackground(brokerArchiver, otelMetrics, {
|
|
332788
|
+
deploymentId,
|
|
332789
|
+
exchange: normalizedCex,
|
|
332790
|
+
symbol: resolvedSymbol,
|
|
332791
|
+
assetType,
|
|
332792
|
+
accountSelector: selectedBrokerAccount?.label,
|
|
332793
|
+
payload: data,
|
|
332794
|
+
receivedTimestamp
|
|
332795
|
+
});
|
|
332796
|
+
}
|
|
331447
332797
|
} catch (error48) {
|
|
331448
332798
|
const message = getErrorMessage(error48);
|
|
331449
332799
|
log.error(`Error fetching trades for ${resolvedSymbol} on ${cex3}:`, error48);
|
|
@@ -331459,7 +332809,27 @@ function createSubscribeHandler(deps) {
|
|
|
331459
332809
|
break;
|
|
331460
332810
|
case SubscriptionType.TICKER:
|
|
331461
332811
|
try {
|
|
331462
|
-
|
|
332812
|
+
while (!isStreamClosed()) {
|
|
332813
|
+
const data = await broker.watchTicker(resolvedSymbol);
|
|
332814
|
+
const receivedTimestamp = Date.now();
|
|
332815
|
+
if (!await writeSubscribeFrame(call, isStreamClosed, {
|
|
332816
|
+
data: JSON.stringify(data),
|
|
332817
|
+
timestamp: receivedTimestamp,
|
|
332818
|
+
symbol: resolvedSymbol,
|
|
332819
|
+
type: subscriptionType
|
|
332820
|
+
})) {
|
|
332821
|
+
break;
|
|
332822
|
+
}
|
|
332823
|
+
archiveTickerInBackground(brokerArchiver, otelMetrics, {
|
|
332824
|
+
deploymentId,
|
|
332825
|
+
exchange: normalizedCex,
|
|
332826
|
+
symbol: resolvedSymbol,
|
|
332827
|
+
assetType,
|
|
332828
|
+
accountSelector: selectedBrokerAccount?.label,
|
|
332829
|
+
payload: data,
|
|
332830
|
+
receivedTimestamp
|
|
332831
|
+
});
|
|
332832
|
+
}
|
|
331463
332833
|
} catch (error48) {
|
|
331464
332834
|
const message = getErrorMessage(error48);
|
|
331465
332835
|
log.error(`Error fetching ticker for ${resolvedSymbol} on ${cex3}:`, error48);
|
|
@@ -331476,7 +332846,53 @@ function createSubscribeHandler(deps) {
|
|
|
331476
332846
|
case SubscriptionType.OHLCV:
|
|
331477
332847
|
try {
|
|
331478
332848
|
const timeframe = options?.timeframe || "1m";
|
|
331479
|
-
|
|
332849
|
+
const ohlcvBarTracker = createOhlcvBarTracker();
|
|
332850
|
+
const ohlcvArchiveInput = {
|
|
332851
|
+
deploymentId,
|
|
332852
|
+
exchange: normalizedCex,
|
|
332853
|
+
symbol: resolvedSymbol,
|
|
332854
|
+
assetType,
|
|
332855
|
+
timeframe,
|
|
332856
|
+
accountSelector: selectedBrokerAccount?.label,
|
|
332857
|
+
receivedTimestamp: Date.now(),
|
|
332858
|
+
payload: []
|
|
332859
|
+
};
|
|
332860
|
+
const bootstrapPayload = await bootstrapOhlcvHistory(broker, brokerArchiver, otelMetrics, ohlcvBarTracker, ohlcvArchiveInput, { bootstrapLimit: options?.bootstrapLimit });
|
|
332861
|
+
let ohlcvStreamActive = true;
|
|
332862
|
+
if (bootstrapPayload) {
|
|
332863
|
+
const bootstrapTimestamp = Date.now();
|
|
332864
|
+
const bootstrapSent = await writeSubscribeFrame(call, isStreamClosed, {
|
|
332865
|
+
data: JSON.stringify(bootstrapPayload),
|
|
332866
|
+
timestamp: bootstrapTimestamp,
|
|
332867
|
+
symbol: resolvedSymbol,
|
|
332868
|
+
type: subscriptionType
|
|
332869
|
+
});
|
|
332870
|
+
if (!bootstrapSent) {
|
|
332871
|
+
ohlcvStreamActive = false;
|
|
332872
|
+
}
|
|
332873
|
+
}
|
|
332874
|
+
while (ohlcvStreamActive && !isStreamClosed()) {
|
|
332875
|
+
const data = await broker.fetchOHLCVWs(resolvedSymbol, timeframe);
|
|
332876
|
+
const receivedTimestamp = Date.now();
|
|
332877
|
+
if (!await writeSubscribeFrame(call, isStreamClosed, {
|
|
332878
|
+
data: JSON.stringify(data),
|
|
332879
|
+
timestamp: receivedTimestamp,
|
|
332880
|
+
symbol: resolvedSymbol,
|
|
332881
|
+
type: subscriptionType
|
|
332882
|
+
})) {
|
|
332883
|
+
break;
|
|
332884
|
+
}
|
|
332885
|
+
archiveOhlcvInBackground(brokerArchiver, otelMetrics, ohlcvBarTracker, {
|
|
332886
|
+
deploymentId,
|
|
332887
|
+
exchange: normalizedCex,
|
|
332888
|
+
symbol: resolvedSymbol,
|
|
332889
|
+
assetType,
|
|
332890
|
+
timeframe,
|
|
332891
|
+
accountSelector: selectedBrokerAccount?.label,
|
|
332892
|
+
payload: data,
|
|
332893
|
+
receivedTimestamp
|
|
332894
|
+
});
|
|
332895
|
+
}
|
|
331480
332896
|
} catch (error48) {
|
|
331481
332897
|
log.error(`Error fetching OHLCV for ${resolvedSymbol} on ${cex3}:`, error48);
|
|
331482
332898
|
const message = getErrorMessage(error48);
|
|
@@ -331492,7 +332908,10 @@ function createSubscribeHandler(deps) {
|
|
|
331492
332908
|
break;
|
|
331493
332909
|
case SubscriptionType.BALANCE:
|
|
331494
332910
|
try {
|
|
331495
|
-
await runCcxtSubscribeLoop(call, isStreamClosed, symbol2, subscriptionType, () => broker.watchBalance()
|
|
332911
|
+
await runCcxtSubscribeLoop(call, isStreamClosed, symbol2, subscriptionType, () => broker.watchBalance(), {
|
|
332912
|
+
...streamArchiveContext,
|
|
332913
|
+
archiveSubscriptionType: "BALANCE"
|
|
332914
|
+
});
|
|
331496
332915
|
} catch (error48) {
|
|
331497
332916
|
const message = getErrorMessage(error48);
|
|
331498
332917
|
log.error(`Error fetching balance for ${cex3}:`, error48);
|
|
@@ -331508,7 +332927,10 @@ function createSubscribeHandler(deps) {
|
|
|
331508
332927
|
break;
|
|
331509
332928
|
case SubscriptionType.ORDERS:
|
|
331510
332929
|
try {
|
|
331511
|
-
await runCcxtSubscribeLoop(call, isStreamClosed, resolvedSymbol, subscriptionType, () => broker.watchOrders(resolvedSymbol)
|
|
332930
|
+
await runCcxtSubscribeLoop(call, isStreamClosed, resolvedSymbol, subscriptionType, () => broker.watchOrders(resolvedSymbol), {
|
|
332931
|
+
...streamArchiveContext,
|
|
332932
|
+
archiveSubscriptionType: "ORDERS"
|
|
332933
|
+
});
|
|
331512
332934
|
} catch (error48) {
|
|
331513
332935
|
log.error(`Error fetching orders for ${resolvedSymbol} on ${cex3}:`, error48);
|
|
331514
332936
|
const message = getErrorMessage(error48);
|
|
@@ -331689,7 +333111,7 @@ var CEX_BROKER_PACKAGE_DEFINITION = protoLoader.fromJSON(node_descriptor_default
|
|
|
331689
333111
|
// src/server.ts
|
|
331690
333112
|
var grpcObj = grpc14.loadPackageDefinition(CEX_BROKER_PACKAGE_DEFINITION);
|
|
331691
333113
|
var cexNode = grpcObj.cex_broker;
|
|
331692
|
-
function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, otelMetrics) {
|
|
333114
|
+
function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, otelMetrics, brokerArchiver) {
|
|
331693
333115
|
const server = new grpc14.Server;
|
|
331694
333116
|
server.addService(cexNode.cex_service.service, {
|
|
331695
333117
|
ExecuteAction: createExecuteActionHandler({
|
|
@@ -331698,12 +333120,14 @@ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, ot
|
|
|
331698
333120
|
whitelistIps,
|
|
331699
333121
|
useVerity,
|
|
331700
333122
|
verityProverUrl,
|
|
331701
|
-
otelMetrics
|
|
333123
|
+
otelMetrics,
|
|
333124
|
+
brokerArchiver
|
|
331702
333125
|
}),
|
|
331703
333126
|
Subscribe: createSubscribeHandler({
|
|
331704
333127
|
brokers,
|
|
331705
333128
|
whitelistIps,
|
|
331706
|
-
otelMetrics
|
|
333129
|
+
otelMetrics,
|
|
333130
|
+
brokerArchiver
|
|
331707
333131
|
})
|
|
331708
333132
|
});
|
|
331709
333133
|
return server;
|
|
@@ -331840,6 +333264,7 @@ class CEXBroker {
|
|
|
331840
333264
|
useVerity = false;
|
|
331841
333265
|
otelMetrics;
|
|
331842
333266
|
otelLogs;
|
|
333267
|
+
brokerArchiver;
|
|
331843
333268
|
depositReconciler;
|
|
331844
333269
|
loadEnvConfig() {
|
|
331845
333270
|
log.info("\uD83D\uDD27 Loading CEX_BROKER_ environment variables:");
|
|
@@ -331951,6 +333376,7 @@ class CEXBroker {
|
|
|
331951
333376
|
this.otelMetrics = createOtelMetricsFromEnv();
|
|
331952
333377
|
this.otelLogs = createOtelLogsFromEnv();
|
|
331953
333378
|
}
|
|
333379
|
+
this.brokerArchiver = createBrokerExecutionArchiverFromEnv(this.otelLogs, this.otelMetrics);
|
|
331954
333380
|
this.loadExchangeCredentials(apiCredentials);
|
|
331955
333381
|
this.whitelistIps = [
|
|
331956
333382
|
...(config2 ?? { whitelistIps: [] }).whitelistIps ?? [],
|
|
@@ -331983,6 +333409,9 @@ class CEXBroker {
|
|
|
331983
333409
|
if (this.server) {
|
|
331984
333410
|
await this.server.forceShutdown();
|
|
331985
333411
|
}
|
|
333412
|
+
if (this.brokerArchiver) {
|
|
333413
|
+
await this.brokerArchiver.close();
|
|
333414
|
+
}
|
|
331986
333415
|
if (this.otelMetrics) {
|
|
331987
333416
|
await this.otelMetrics.close();
|
|
331988
333417
|
}
|
|
@@ -332002,7 +333431,7 @@ class CEXBroker {
|
|
|
332002
333431
|
if (this.otelMetrics?.isOtelEnabled()) {
|
|
332003
333432
|
await this.otelMetrics.initialize();
|
|
332004
333433
|
}
|
|
332005
|
-
this.server = getServer(this.policy, this.brokers, this.whitelistIps, this.useVerity, this.#verityProverUrl, this.otelMetrics);
|
|
333434
|
+
this.server = getServer(this.policy, this.brokers, this.whitelistIps, this.useVerity, this.#verityProverUrl, this.otelMetrics, this.brokerArchiver);
|
|
332006
333435
|
this.server.bindAsync(`0.0.0.0:${this.port}`, grpc15.ServerCredentials.createInsecure(), (err2, port) => {
|
|
332007
333436
|
if (err2) {
|
|
332008
333437
|
log.error(err2);
|