@usherlabs/cex-broker 0.2.34 → 0.2.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/cli.js +323 -14
- package/dist/helpers/deposit-archive-poller.d.ts +32 -0
- package/dist/helpers/otel.d.ts +13 -1
- package/dist/helpers/passive-order.d.ts +2 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +324 -15
- package/dist/index.js.map +10 -9
- package/package.json +1 -1
package/dist/commands/cli.js
CHANGED
|
@@ -316534,8 +316534,256 @@ class AccountBalanceArchivePoller {
|
|
|
316534
316534
|
}
|
|
316535
316535
|
}
|
|
316536
316536
|
|
|
316537
|
-
// src/helpers/
|
|
316537
|
+
// src/helpers/deposit-archive-poller.ts
|
|
316538
316538
|
var DEFAULT_CONFIG2 = {
|
|
316539
|
+
pollIntervalMs: 60000,
|
|
316540
|
+
lookbackMs: 24 * 60 * 60 * 1000,
|
|
316541
|
+
depositsLimit: 50
|
|
316542
|
+
};
|
|
316543
|
+
var ALL_CURRENCIES_CODE = "*";
|
|
316544
|
+
function depositTimestamp(record) {
|
|
316545
|
+
const observedAt = depositField(record, [
|
|
316546
|
+
"timestamp",
|
|
316547
|
+
"creditedAt",
|
|
316548
|
+
"credited_at",
|
|
316549
|
+
"updated",
|
|
316550
|
+
"updatedAt",
|
|
316551
|
+
"datetime"
|
|
316552
|
+
]);
|
|
316553
|
+
const timestamp = typeof observedAt === "number" ? observedAt : typeof observedAt === "string" ? Date.parse(observedAt) : Number.NaN;
|
|
316554
|
+
return Number.isFinite(timestamp) ? timestamp : undefined;
|
|
316555
|
+
}
|
|
316556
|
+
function archivedDepositStatus(exchangeId, record) {
|
|
316557
|
+
const rawStatus = asRecord(record.info)?.status;
|
|
316558
|
+
if (exchangeId?.toLowerCase() === "binance" && String(rawStatus ?? "").trim() === "6") {
|
|
316559
|
+
return "credited_not_withdrawable";
|
|
316560
|
+
}
|
|
316561
|
+
return normalizeCcxtTransactionForArchive(record).status;
|
|
316562
|
+
}
|
|
316563
|
+
function nextDepositCursor(deposits, currentSince, depositsLimit, exchangeId) {
|
|
316564
|
+
if (deposits.length >= depositsLimit) {
|
|
316565
|
+
return currentSince;
|
|
316566
|
+
}
|
|
316567
|
+
let next = currentSince;
|
|
316568
|
+
let oldestPending = Number.POSITIVE_INFINITY;
|
|
316569
|
+
for (const deposit of deposits) {
|
|
316570
|
+
const record = asRecord(deposit);
|
|
316571
|
+
if (!record) {
|
|
316572
|
+
return currentSince;
|
|
316573
|
+
}
|
|
316574
|
+
const timestamp = depositTimestamp(record);
|
|
316575
|
+
const archiveStatus = archivedDepositStatus(exchangeId, record);
|
|
316576
|
+
const status = normalizeDepositStatus(depositField(record, ["status", "state"]));
|
|
316577
|
+
const isPending = archiveStatus === "credited_not_withdrawable" || status === "pending";
|
|
316578
|
+
if (timestamp === undefined) {
|
|
316579
|
+
if (isPending) {
|
|
316580
|
+
return currentSince;
|
|
316581
|
+
}
|
|
316582
|
+
continue;
|
|
316583
|
+
}
|
|
316584
|
+
if (isPending) {
|
|
316585
|
+
oldestPending = Math.min(oldestPending, timestamp);
|
|
316586
|
+
} else {
|
|
316587
|
+
next = Math.max(next, timestamp + 1);
|
|
316588
|
+
}
|
|
316589
|
+
}
|
|
316590
|
+
return Number.isFinite(oldestPending) ? Math.max(currentSince, oldestPending) : next;
|
|
316591
|
+
}
|
|
316592
|
+
|
|
316593
|
+
class DepositArchivePoller {
|
|
316594
|
+
params;
|
|
316595
|
+
#timer = null;
|
|
316596
|
+
#stopped = false;
|
|
316597
|
+
#running = null;
|
|
316598
|
+
#cursors = new Map;
|
|
316599
|
+
#lastArchivedByTarget = new Map;
|
|
316600
|
+
#unsupportedLogged = new Set;
|
|
316601
|
+
#config;
|
|
316602
|
+
constructor(params) {
|
|
316603
|
+
this.params = params;
|
|
316604
|
+
this.#config = { ...DEFAULT_CONFIG2, ...params.config };
|
|
316605
|
+
}
|
|
316606
|
+
start() {
|
|
316607
|
+
if (this.#timer || this.#stopped || !this.params.archiver.isEnabled()) {
|
|
316608
|
+
return;
|
|
316609
|
+
}
|
|
316610
|
+
log.info("\uD83D\uDCE5 Deposit archive poller started");
|
|
316611
|
+
this.#schedule(0);
|
|
316612
|
+
}
|
|
316613
|
+
async stop() {
|
|
316614
|
+
this.#stopped = true;
|
|
316615
|
+
if (this.#timer) {
|
|
316616
|
+
clearTimeout(this.#timer);
|
|
316617
|
+
this.#timer = null;
|
|
316618
|
+
}
|
|
316619
|
+
await this.#running;
|
|
316620
|
+
}
|
|
316621
|
+
async pollAllOnce() {
|
|
316622
|
+
if (this.#stopped || this.#running || !this.params.archiver.isEnabled()) {
|
|
316623
|
+
return false;
|
|
316624
|
+
}
|
|
316625
|
+
this.#running = this.#pollAllSequentially();
|
|
316626
|
+
try {
|
|
316627
|
+
return await this.#running;
|
|
316628
|
+
} finally {
|
|
316629
|
+
this.#running = null;
|
|
316630
|
+
}
|
|
316631
|
+
}
|
|
316632
|
+
#targets() {
|
|
316633
|
+
const targets = [];
|
|
316634
|
+
for (const [exchangeId, pool] of Object.entries(this.params.brokers)) {
|
|
316635
|
+
for (const account of [pool.primary, ...pool.secondaryBrokers]) {
|
|
316636
|
+
targets.push({
|
|
316637
|
+
exchangeId,
|
|
316638
|
+
account,
|
|
316639
|
+
code: ALL_CURRENCIES_CODE
|
|
316640
|
+
});
|
|
316641
|
+
}
|
|
316642
|
+
}
|
|
316643
|
+
return targets;
|
|
316644
|
+
}
|
|
316645
|
+
async#pollAllSequentially() {
|
|
316646
|
+
for (const target of this.#targets()) {
|
|
316647
|
+
if (this.#stopped) {
|
|
316648
|
+
break;
|
|
316649
|
+
}
|
|
316650
|
+
await this.#pollOne(target);
|
|
316651
|
+
}
|
|
316652
|
+
return true;
|
|
316653
|
+
}
|
|
316654
|
+
async#pollOne(target) {
|
|
316655
|
+
const exchange = target.account.exchange;
|
|
316656
|
+
const key = this.#targetKey(target);
|
|
316657
|
+
if (typeof exchange.fetchDeposits !== "function" || exchange.has?.fetchDeposits === false) {
|
|
316658
|
+
if (!this.#unsupportedLogged.has(key)) {
|
|
316659
|
+
this.#unsupportedLogged.add(key);
|
|
316660
|
+
log.info("Deposit archive poll skipped: fetchDeposits unsupported", {
|
|
316661
|
+
exchange: target.exchangeId,
|
|
316662
|
+
account: target.account.label
|
|
316663
|
+
});
|
|
316664
|
+
}
|
|
316665
|
+
return;
|
|
316666
|
+
}
|
|
316667
|
+
const since = this.#cursors.get(key) ?? Date.now() - this.#config.lookbackMs;
|
|
316668
|
+
let deposits;
|
|
316669
|
+
try {
|
|
316670
|
+
deposits = await exchange.fetchDeposits(undefined, since, this.#config.depositsLimit);
|
|
316671
|
+
} catch (error) {
|
|
316672
|
+
this.params.metrics?.recordCounter("cex_deposit_poller_errors_total", 1, { exchange: target.exchangeId });
|
|
316673
|
+
log.warn("Deposit archive poll failed", {
|
|
316674
|
+
exchange: target.exchangeId,
|
|
316675
|
+
account: target.account.label,
|
|
316676
|
+
error
|
|
316677
|
+
});
|
|
316678
|
+
return;
|
|
316679
|
+
}
|
|
316680
|
+
if (!Array.isArray(deposits) || deposits.length === 0) {
|
|
316681
|
+
return;
|
|
316682
|
+
}
|
|
316683
|
+
let archived = 0;
|
|
316684
|
+
for (const deposit of deposits) {
|
|
316685
|
+
const record = asRecord(deposit);
|
|
316686
|
+
if (!record) {
|
|
316687
|
+
continue;
|
|
316688
|
+
}
|
|
316689
|
+
const assetSymbol = depositField(record, ["currency", "code", "asset"]);
|
|
316690
|
+
const amount = depositField(record, ["amount"]);
|
|
316691
|
+
const address = depositField(record, [
|
|
316692
|
+
"address",
|
|
316693
|
+
"recipientAddress",
|
|
316694
|
+
"to",
|
|
316695
|
+
"destination"
|
|
316696
|
+
]);
|
|
316697
|
+
const txid = depositField(record, ["txid", "txId", "tx_hash", "txHash"]);
|
|
316698
|
+
const network = depositField(record, ["network", "chain"]);
|
|
316699
|
+
const archiveStatus = archivedDepositStatus(target.exchangeId, record);
|
|
316700
|
+
const creditedAt = depositField(record, [
|
|
316701
|
+
"creditedAt",
|
|
316702
|
+
"credited_at",
|
|
316703
|
+
"updated",
|
|
316704
|
+
"updatedAt",
|
|
316705
|
+
"timestamp",
|
|
316706
|
+
"datetime"
|
|
316707
|
+
]);
|
|
316708
|
+
const depositTxid = txid === undefined ? undefined : String(txid);
|
|
316709
|
+
const lastArchived = depositTxid === undefined ? undefined : this.#lastArchivedByTarget.get(key)?.get(depositTxid);
|
|
316710
|
+
if (lastArchived && lastArchived.status === archiveStatus) {
|
|
316711
|
+
continue;
|
|
316712
|
+
}
|
|
316713
|
+
this.params.archiver.enqueue(buildTransferEventArchiveRow({
|
|
316714
|
+
tags: buildCommonArchiveTags({
|
|
316715
|
+
deploymentId: this.params.archiver.getDeploymentId(),
|
|
316716
|
+
accountSelector: target.account.label,
|
|
316717
|
+
exchange: target.exchangeId,
|
|
316718
|
+
symbol: assetSymbol === undefined ? undefined : String(assetSymbol)
|
|
316719
|
+
}),
|
|
316720
|
+
transfer: {
|
|
316721
|
+
eventKind: "deposit",
|
|
316722
|
+
lifecycleAction: "observe_deposit",
|
|
316723
|
+
status: archiveStatus,
|
|
316724
|
+
amount: amount === undefined ? undefined : String(amount),
|
|
316725
|
+
address: address === undefined ? undefined : String(address),
|
|
316726
|
+
network: network === undefined ? undefined : String(network),
|
|
316727
|
+
externalId: depositTxid,
|
|
316728
|
+
txid: depositTxid,
|
|
316729
|
+
exchangeTimestamp: typeof creditedAt === "string" ? creditedAt : undefined,
|
|
316730
|
+
payload: record
|
|
316731
|
+
}
|
|
316732
|
+
}));
|
|
316733
|
+
if (depositTxid !== undefined) {
|
|
316734
|
+
let targetDeposits2 = this.#lastArchivedByTarget.get(key);
|
|
316735
|
+
if (!targetDeposits2) {
|
|
316736
|
+
targetDeposits2 = new Map;
|
|
316737
|
+
this.#lastArchivedByTarget.set(key, targetDeposits2);
|
|
316738
|
+
}
|
|
316739
|
+
targetDeposits2.set(depositTxid, {
|
|
316740
|
+
status: archiveStatus,
|
|
316741
|
+
timestamp: depositTimestamp(record)
|
|
316742
|
+
});
|
|
316743
|
+
}
|
|
316744
|
+
archived += 1;
|
|
316745
|
+
}
|
|
316746
|
+
if (archived > 0) {
|
|
316747
|
+
this.params.metrics?.recordCounter("cex_deposit_poller_deposits_archived_total", archived, { exchange: target.exchangeId });
|
|
316748
|
+
}
|
|
316749
|
+
const nextCursor = nextDepositCursor(deposits, since, this.#config.depositsLimit, target.exchangeId);
|
|
316750
|
+
this.#cursors.set(key, nextCursor);
|
|
316751
|
+
const targetDeposits = this.#lastArchivedByTarget.get(key);
|
|
316752
|
+
if (targetDeposits) {
|
|
316753
|
+
for (const [externalId, lastArchived] of targetDeposits) {
|
|
316754
|
+
if (lastArchived.timestamp !== undefined && lastArchived.timestamp < nextCursor) {
|
|
316755
|
+
targetDeposits.delete(externalId);
|
|
316756
|
+
}
|
|
316757
|
+
}
|
|
316758
|
+
if (targetDeposits.size === 0) {
|
|
316759
|
+
this.#lastArchivedByTarget.delete(key);
|
|
316760
|
+
}
|
|
316761
|
+
}
|
|
316762
|
+
}
|
|
316763
|
+
#targetKey(target) {
|
|
316764
|
+
return `${target.exchangeId}|${target.account.label}|${target.code}`;
|
|
316765
|
+
}
|
|
316766
|
+
#schedule(delayMs) {
|
|
316767
|
+
this.#timer = setTimeout(() => void this.#tick(), delayMs);
|
|
316768
|
+
this.#timer.unref?.();
|
|
316769
|
+
}
|
|
316770
|
+
async#tick() {
|
|
316771
|
+
this.#timer = null;
|
|
316772
|
+
try {
|
|
316773
|
+
await this.pollAllOnce();
|
|
316774
|
+
} catch (error) {
|
|
316775
|
+
rethrowArchiveDurabilityError(error);
|
|
316776
|
+
log.error("Deposit archive poller tick failed", error);
|
|
316777
|
+
} finally {
|
|
316778
|
+
if (!this.#stopped) {
|
|
316779
|
+
this.#schedule(this.#config.pollIntervalMs);
|
|
316780
|
+
}
|
|
316781
|
+
}
|
|
316782
|
+
}
|
|
316783
|
+
}
|
|
316784
|
+
|
|
316785
|
+
// src/helpers/fill-archive-poller.ts
|
|
316786
|
+
var DEFAULT_CONFIG3 = {
|
|
316539
316787
|
pollIntervalMs: 60000,
|
|
316540
316788
|
lookbackMs: 24 * 60 * 60 * 1000,
|
|
316541
316789
|
tradesLimit: 500
|
|
@@ -316560,7 +316808,7 @@ class FillArchivePoller {
|
|
|
316560
316808
|
#config;
|
|
316561
316809
|
constructor(params) {
|
|
316562
316810
|
this.params = params;
|
|
316563
|
-
this.#config = { ...
|
|
316811
|
+
this.#config = { ...DEFAULT_CONFIG3, ...params.config };
|
|
316564
316812
|
}
|
|
316565
316813
|
start() {
|
|
316566
316814
|
if (this.#timer || this.#stopped) {
|
|
@@ -316764,6 +317012,7 @@ function toAttributes(labels, service) {
|
|
|
316764
317012
|
class OtelMetrics extends BaseOtelSignal {
|
|
316765
317013
|
counters = new Map;
|
|
316766
317014
|
histograms = new Map;
|
|
317015
|
+
observableGauges = new Map;
|
|
316767
317016
|
constructor(config) {
|
|
316768
317017
|
super(config, "metrics");
|
|
316769
317018
|
}
|
|
@@ -316849,6 +317098,32 @@ class OtelMetrics extends BaseOtelSignal {
|
|
|
316849
317098
|
log.error("Failed to record gauge:", error);
|
|
316850
317099
|
}
|
|
316851
317100
|
}
|
|
317101
|
+
async setObservableGauge(metricName, value, labels, service = this.getServiceName()) {
|
|
317102
|
+
const provider = this.getProvider();
|
|
317103
|
+
if (!this.isOtelEnabled() || !provider)
|
|
317104
|
+
return;
|
|
317105
|
+
try {
|
|
317106
|
+
let state = this.observableGauges.get(metricName);
|
|
317107
|
+
if (!state) {
|
|
317108
|
+
const observations = new Map;
|
|
317109
|
+
const instrument = provider.getMeter("cex-broker-metrics", "1.0.0").createObservableGauge(metricName, { description: metricName });
|
|
317110
|
+
instrument.addCallback((result) => {
|
|
317111
|
+
for (const observation of observations.values()) {
|
|
317112
|
+
result.observe(observation.value, observation.attributes);
|
|
317113
|
+
}
|
|
317114
|
+
});
|
|
317115
|
+
state = { instrument, observations };
|
|
317116
|
+
this.observableGauges.set(metricName, state);
|
|
317117
|
+
}
|
|
317118
|
+
const attributes = toAttributes(labels, service);
|
|
317119
|
+
state.observations.set(stableAttributeKey(attributes), {
|
|
317120
|
+
value,
|
|
317121
|
+
attributes
|
|
317122
|
+
});
|
|
317123
|
+
} catch (error) {
|
|
317124
|
+
log.error("Failed to set observable gauge:", error);
|
|
317125
|
+
}
|
|
317126
|
+
}
|
|
316852
317127
|
async recordHistogram(metricName, value, labels, service = this.getServiceName()) {
|
|
316853
317128
|
const provider = this.getProvider();
|
|
316854
317129
|
if (!this.isOtelEnabled() || !provider)
|
|
@@ -316953,27 +317228,33 @@ function getOtelProtocolFromEnv() {
|
|
|
316953
317228
|
const protocol = process.env.CEX_BROKER_OTEL_PROTOCOL ?? process.env.CEX_BROKER_CLICKHOUSE_PROTOCOL;
|
|
316954
317229
|
return protocol || "http";
|
|
316955
317230
|
}
|
|
316956
|
-
function createOtelMetricsFromEnv() {
|
|
317231
|
+
function createOtelMetricsFromEnv(options = {}) {
|
|
316957
317232
|
const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
316958
|
-
const
|
|
317233
|
+
const serviceName = process.env.OTEL_SERVICE_NAME || options.defaultServiceName || DEFAULT_SERVICE;
|
|
316959
317234
|
if (otlpEndpoint) {
|
|
316960
317235
|
return new OtelMetrics({
|
|
316961
|
-
otlpEndpoint
|
|
316962
|
-
serviceName
|
|
317236
|
+
otlpEndpoint,
|
|
317237
|
+
serviceName
|
|
316963
317238
|
});
|
|
316964
317239
|
}
|
|
316965
|
-
if (
|
|
316966
|
-
return new OtelMetrics;
|
|
317240
|
+
if (options.allowLegacyBrokerConfig === false) {
|
|
317241
|
+
return new OtelMetrics({ serviceName });
|
|
316967
317242
|
}
|
|
317243
|
+
const host = getOtelHostFromEnv();
|
|
317244
|
+
if (!host)
|
|
317245
|
+
return new OtelMetrics({ serviceName });
|
|
316968
317246
|
const port = getOtelPortFromEnv();
|
|
316969
317247
|
const config = {
|
|
316970
317248
|
host,
|
|
316971
317249
|
port: port ?? DEFAULT_OTLP_PORT,
|
|
316972
317250
|
protocol: getOtelProtocolFromEnv(),
|
|
316973
|
-
serviceName
|
|
317251
|
+
serviceName
|
|
316974
317252
|
};
|
|
316975
317253
|
return new OtelMetrics(config);
|
|
316976
317254
|
}
|
|
317255
|
+
function stableAttributeKey(attributes) {
|
|
317256
|
+
return JSON.stringify(Object.entries(attributes).sort(([left], [right]) => left.localeCompare(right)));
|
|
317257
|
+
}
|
|
316977
317258
|
function createOtelLogsFromEnv() {
|
|
316978
317259
|
const logsEndpoint = process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT;
|
|
316979
317260
|
const genericEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
@@ -330786,6 +331067,12 @@ function parseActionPayload(schema, rawPayload, callback) {
|
|
|
330786
331067
|
// src/helpers/grpc/status.ts
|
|
330787
331068
|
var grpc2 = __toESM(require_src3(), 1);
|
|
330788
331069
|
function stableGrpcErrorCode(message) {
|
|
331070
|
+
if (message.startsWith("AuthenticationError:")) {
|
|
331071
|
+
return grpc2.status.UNAUTHENTICATED;
|
|
331072
|
+
}
|
|
331073
|
+
if (message.startsWith("InsufficientFunds:")) {
|
|
331074
|
+
return grpc2.status.FAILED_PRECONDITION;
|
|
331075
|
+
}
|
|
330789
331076
|
if (message.startsWith("venue_discovery_unavailable:")) {
|
|
330790
331077
|
return grpc2.status.UNIMPLEMENTED;
|
|
330791
331078
|
}
|
|
@@ -330936,7 +331223,8 @@ async function handleDeposit(ctx) {
|
|
|
330936
331223
|
message: `deposit_address_mismatch: expected address ${value.recipientAddress}, observed ${String(observedAddress)}`
|
|
330937
331224
|
}, null);
|
|
330938
331225
|
}
|
|
330939
|
-
const
|
|
331226
|
+
const responseStatus = normalizeDepositStatus(depositField(deposit, ["status", "state"]));
|
|
331227
|
+
const archiveStatus = normalizeCcxtTransactionForArchive(deposit).status;
|
|
330940
331228
|
const depositTxid = String(depositField(deposit, ["txid", "txId", "tx_hash", "txHash"]) ?? value.transactionHash);
|
|
330941
331229
|
const creditedAt = depositField(deposit, [
|
|
330942
331230
|
"creditedAt",
|
|
@@ -330953,7 +331241,7 @@ async function handleDeposit(ctx) {
|
|
|
330953
331241
|
transfer: {
|
|
330954
331242
|
eventKind: "deposit",
|
|
330955
331243
|
lifecycleAction: "observe_deposit",
|
|
330956
|
-
status:
|
|
331244
|
+
status: archiveStatus,
|
|
330957
331245
|
amount: observedAmount !== undefined ? String(observedAmount) : undefined,
|
|
330958
331246
|
address: String(observedAddress ?? value.recipientAddress),
|
|
330959
331247
|
network: depositNetwork?.exchangeNetworkId,
|
|
@@ -330967,7 +331255,7 @@ async function handleDeposit(ctx) {
|
|
|
330967
331255
|
return ctx.wrappedCallback(null, {
|
|
330968
331256
|
proof: ctx.verity.proof,
|
|
330969
331257
|
result: JSON.stringify({
|
|
330970
|
-
status:
|
|
331258
|
+
status: responseStatus,
|
|
330971
331259
|
exchange: normalizedCex,
|
|
330972
331260
|
accountSelector: selectedBrokerAccount?.label,
|
|
330973
331261
|
asset: symbol2,
|
|
@@ -331486,6 +331774,12 @@ function identifiesUnsupported(message) {
|
|
|
331486
331774
|
return /post[\s-]?only\b.*\b(?:not supported|unsupported|does not support)\b/.test(normalized) || /\b(?:not supported|unsupported|does not support)\b.*\bpost[\s-]?only\b/.test(normalized);
|
|
331487
331775
|
}
|
|
331488
331776
|
function classifyPassiveOrderError(error48) {
|
|
331777
|
+
if (error48 instanceof ccxt_default.InsufficientFunds) {
|
|
331778
|
+
return "InsufficientFunds";
|
|
331779
|
+
}
|
|
331780
|
+
if (error48 instanceof ccxt_default.AuthenticationError) {
|
|
331781
|
+
return "AuthenticationError";
|
|
331782
|
+
}
|
|
331489
331783
|
const message = getErrorMessage(error48);
|
|
331490
331784
|
if (error48 instanceof ccxt_default.OrderImmediatelyFillable || identifiesWouldCross(message)) {
|
|
331491
331785
|
return PASSIVE_ORDER_ERROR_CODES.wouldCross;
|
|
@@ -331614,9 +331908,9 @@ async function handleCreateOrder(ctx) {
|
|
|
331614
331908
|
emitOrderExecutionTelemetryInBackground(otelMetrics, failedCreateContext, undefined, error48);
|
|
331615
331909
|
archiveOrderExecutionInBackground(brokerArchiver, failedCreateContext, undefined, error48, { marketMetadataHash });
|
|
331616
331910
|
if (isPassiveOrder && submission === "in_flight") {
|
|
331617
|
-
const
|
|
331911
|
+
const stableErrorCode = classifyPassiveOrderError(error48);
|
|
331618
331912
|
return rejectWithGrpcError(ctx, error48, {
|
|
331619
|
-
message: `${
|
|
331913
|
+
message: `${stableErrorCode}: ${sanitizeErrorDetail(error48)}`,
|
|
331620
331914
|
preferStableMessageOnly: true
|
|
331621
331915
|
});
|
|
331622
331916
|
}
|
|
@@ -334516,6 +334810,7 @@ class CEXBroker {
|
|
|
334516
334810
|
orderActivityTracker = new OrderActivityTracker;
|
|
334517
334811
|
withdrawalObservationTracker = new WithdrawalObservationTracker;
|
|
334518
334812
|
fillArchivePoller;
|
|
334813
|
+
depositArchivePoller;
|
|
334519
334814
|
accountBalanceArchivePoller;
|
|
334520
334815
|
loadEnvConfig() {
|
|
334521
334816
|
log.info("\uD83D\uDD27 Loading CEX_BROKER_ environment variables:");
|
|
@@ -334661,6 +334956,10 @@ class CEXBroker {
|
|
|
334661
334956
|
this.fillArchivePoller.stop();
|
|
334662
334957
|
this.fillArchivePoller = undefined;
|
|
334663
334958
|
}
|
|
334959
|
+
if (this.depositArchivePoller) {
|
|
334960
|
+
await this.depositArchivePoller.stop();
|
|
334961
|
+
this.depositArchivePoller = undefined;
|
|
334962
|
+
}
|
|
334664
334963
|
if (this.accountBalanceArchivePoller) {
|
|
334665
334964
|
await this.accountBalanceArchivePoller.stop();
|
|
334666
334965
|
this.accountBalanceArchivePoller = undefined;
|
|
@@ -334690,6 +334989,10 @@ class CEXBroker {
|
|
|
334690
334989
|
this.fillArchivePoller.stop();
|
|
334691
334990
|
this.fillArchivePoller = undefined;
|
|
334692
334991
|
}
|
|
334992
|
+
if (this.depositArchivePoller) {
|
|
334993
|
+
await this.depositArchivePoller.stop();
|
|
334994
|
+
this.depositArchivePoller = undefined;
|
|
334995
|
+
}
|
|
334693
334996
|
if (this.accountBalanceArchivePoller) {
|
|
334694
334997
|
await this.accountBalanceArchivePoller.stop();
|
|
334695
334998
|
this.accountBalanceArchivePoller = undefined;
|
|
@@ -334721,6 +335024,12 @@ class CEXBroker {
|
|
|
334721
335024
|
metrics: this.otelMetrics
|
|
334722
335025
|
});
|
|
334723
335026
|
this.fillArchivePoller.start();
|
|
335027
|
+
this.depositArchivePoller = new DepositArchivePoller({
|
|
335028
|
+
brokers: this.brokers,
|
|
335029
|
+
archiver: this.brokerArchiver,
|
|
335030
|
+
metrics: this.otelMetrics
|
|
335031
|
+
});
|
|
335032
|
+
this.depositArchivePoller.start();
|
|
334724
335033
|
}
|
|
334725
335034
|
if (this.brokerArchiver?.canPersistAccountBalanceSnapshots()) {
|
|
334726
335035
|
this.accountBalanceArchivePoller = new AccountBalanceArchivePoller({
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { BrokerPoolEntry } from "./broker";
|
|
2
|
+
import { type BrokerExecutionArchiver } from "./broker-execution-archive";
|
|
3
|
+
import type { OtelMetrics } from "./otel";
|
|
4
|
+
export type DepositArchivePollerConfig = {
|
|
5
|
+
pollIntervalMs: number;
|
|
6
|
+
lookbackMs: number;
|
|
7
|
+
depositsLimit: number;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Advances the inclusive since-watermark only across a fully observed,
|
|
11
|
+
* terminal prefix of deposit history.
|
|
12
|
+
*/
|
|
13
|
+
export declare function nextDepositCursor(deposits: unknown[], currentSince: number, depositsLimit: number, exchangeId?: string): number;
|
|
14
|
+
/**
|
|
15
|
+
* Broker-internal periodic capture of venue deposit history. It polls every
|
|
16
|
+
* configured account sequentially, using ccxt's unfiltered deposit-history
|
|
17
|
+
* surface so newly funded currencies do not depend on balance or market
|
|
18
|
+
* discovery.
|
|
19
|
+
*/
|
|
20
|
+
export declare class DepositArchivePoller {
|
|
21
|
+
#private;
|
|
22
|
+
private readonly params;
|
|
23
|
+
constructor(params: {
|
|
24
|
+
brokers: Record<string, BrokerPoolEntry>;
|
|
25
|
+
archiver: BrokerExecutionArchiver;
|
|
26
|
+
metrics?: OtelMetrics;
|
|
27
|
+
config?: Partial<DepositArchivePollerConfig>;
|
|
28
|
+
});
|
|
29
|
+
start(): void;
|
|
30
|
+
stop(): Promise<void>;
|
|
31
|
+
pollAllOnce(): Promise<boolean>;
|
|
32
|
+
}
|
package/dist/helpers/otel.d.ts
CHANGED
|
@@ -27,6 +27,12 @@ export interface MetricData {
|
|
|
27
27
|
labels: string;
|
|
28
28
|
service: string;
|
|
29
29
|
}
|
|
30
|
+
export interface OtelMetricsEnvOptions {
|
|
31
|
+
/** Service name used when OTEL_SERVICE_NAME is not configured. */
|
|
32
|
+
defaultServiceName?: string;
|
|
33
|
+
/** Whether broker-specific legacy collector host variables are accepted. */
|
|
34
|
+
allowLegacyBrokerConfig?: boolean;
|
|
35
|
+
}
|
|
30
36
|
declare abstract class BaseOtelSignal<TProvider> {
|
|
31
37
|
private readonly signal;
|
|
32
38
|
private provider;
|
|
@@ -45,6 +51,7 @@ declare abstract class BaseOtelSignal<TProvider> {
|
|
|
45
51
|
export declare class OtelMetrics extends BaseOtelSignal<MeterProviderType> {
|
|
46
52
|
private readonly counters;
|
|
47
53
|
private readonly histograms;
|
|
54
|
+
private readonly observableGauges;
|
|
48
55
|
constructor(config?: OtelConfig);
|
|
49
56
|
protected createProvider(endpoint: string, serviceName: string, appendSignalPath: boolean): MeterProviderType;
|
|
50
57
|
protected onProviderCreated(provider: MeterProviderType): void;
|
|
@@ -54,6 +61,11 @@ export declare class OtelMetrics extends BaseOtelSignal<MeterProviderType> {
|
|
|
54
61
|
insertMetrics(metricsList: MetricData[]): Promise<void>;
|
|
55
62
|
recordCounter(metricName: string, value: number, labels: Record<string, string | number>, service?: string): Promise<void>;
|
|
56
63
|
recordGauge(metricName: string, value: number, labels: Record<string, string | number>, service?: string): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Set a gauge value that is observed on every export. This is suitable for
|
|
66
|
+
* staleness signals where a quiet live process must keep exporting its last value.
|
|
67
|
+
*/
|
|
68
|
+
setObservableGauge(metricName: string, value: number, labels: Record<string, string | number>, service?: string): Promise<void>;
|
|
57
69
|
recordHistogram(metricName: string, value: number, labels: Record<string, string | number>, service?: string): Promise<void>;
|
|
58
70
|
}
|
|
59
71
|
export declare class OtelLogs extends BaseOtelSignal<LoggerProvider> {
|
|
@@ -65,6 +77,6 @@ export declare class OtelLogs extends BaseOtelSignal<LoggerProvider> {
|
|
|
65
77
|
protected onProviderClosed(): void;
|
|
66
78
|
emit(record: LogRecord): void;
|
|
67
79
|
}
|
|
68
|
-
export declare function createOtelMetricsFromEnv(): OtelMetrics;
|
|
80
|
+
export declare function createOtelMetricsFromEnv(options?: OtelMetricsEnvOptions): OtelMetrics;
|
|
69
81
|
export declare function createOtelLogsFromEnv(): OtelLogs;
|
|
70
82
|
export {};
|
|
@@ -4,4 +4,5 @@ export declare const PASSIVE_ORDER_ERROR_CODES: {
|
|
|
4
4
|
readonly wouldCross: "passive_order_would_cross";
|
|
5
5
|
};
|
|
6
6
|
export type PassiveOrderErrorCode = (typeof PASSIVE_ORDER_ERROR_CODES)[keyof typeof PASSIVE_ORDER_ERROR_CODES];
|
|
7
|
-
export
|
|
7
|
+
export type PassiveOrderSubmissionErrorCode = PassiveOrderErrorCode | "AuthenticationError" | "InsufficientFunds";
|
|
8
|
+
export declare function classifyPassiveOrderError(error: unknown): PassiveOrderSubmissionErrorCode;
|
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export default class CEXBroker {
|
|
|
16
16
|
private readonly orderActivityTracker;
|
|
17
17
|
private readonly withdrawalObservationTracker;
|
|
18
18
|
private fillArchivePoller?;
|
|
19
|
+
private depositArchivePoller?;
|
|
19
20
|
private accountBalanceArchivePoller?;
|
|
20
21
|
/**
|
|
21
22
|
* Loads environment variables prefixed with CEX_BROKER_
|