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