@usherlabs/cex-broker 0.2.34 → 0.2.35
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 +242 -7
- package/dist/helpers/deposit-archive-poller.d.ts +32 -0
- package/dist/helpers/passive-order.d.ts +2 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +243 -8
- package/dist/index.js.map +9 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -292048,8 +292048,215 @@ class AccountBalanceArchivePoller {
|
|
|
292048
292048
|
}
|
|
292049
292049
|
}
|
|
292050
292050
|
|
|
292051
|
-
// src/helpers/
|
|
292051
|
+
// src/helpers/deposit-archive-poller.ts
|
|
292052
292052
|
var DEFAULT_CONFIG2 = {
|
|
292053
|
+
pollIntervalMs: 60000,
|
|
292054
|
+
lookbackMs: 24 * 60 * 60 * 1000,
|
|
292055
|
+
depositsLimit: 50
|
|
292056
|
+
};
|
|
292057
|
+
var ALL_CURRENCIES_CODE = "*";
|
|
292058
|
+
function nextDepositCursor(deposits, currentSince, depositsLimit) {
|
|
292059
|
+
if (deposits.length >= depositsLimit) {
|
|
292060
|
+
return currentSince;
|
|
292061
|
+
}
|
|
292062
|
+
let next = currentSince;
|
|
292063
|
+
let oldestPending = Number.POSITIVE_INFINITY;
|
|
292064
|
+
for (const deposit of deposits) {
|
|
292065
|
+
const record = asRecord(deposit);
|
|
292066
|
+
if (!record) {
|
|
292067
|
+
return currentSince;
|
|
292068
|
+
}
|
|
292069
|
+
const observedAt = depositField(record, [
|
|
292070
|
+
"timestamp",
|
|
292071
|
+
"creditedAt",
|
|
292072
|
+
"credited_at",
|
|
292073
|
+
"updated",
|
|
292074
|
+
"updatedAt",
|
|
292075
|
+
"datetime"
|
|
292076
|
+
]);
|
|
292077
|
+
const timestamp = typeof observedAt === "number" ? observedAt : typeof observedAt === "string" ? Date.parse(observedAt) : Number.NaN;
|
|
292078
|
+
const status = normalizeDepositStatus(depositField(record, ["status", "state"]));
|
|
292079
|
+
if (!Number.isFinite(timestamp)) {
|
|
292080
|
+
if (status === "pending") {
|
|
292081
|
+
return currentSince;
|
|
292082
|
+
}
|
|
292083
|
+
continue;
|
|
292084
|
+
}
|
|
292085
|
+
if (status === "pending") {
|
|
292086
|
+
oldestPending = Math.min(oldestPending, timestamp);
|
|
292087
|
+
} else {
|
|
292088
|
+
next = Math.max(next, timestamp + 1);
|
|
292089
|
+
}
|
|
292090
|
+
}
|
|
292091
|
+
return Number.isFinite(oldestPending) ? Math.max(currentSince, oldestPending) : next;
|
|
292092
|
+
}
|
|
292093
|
+
|
|
292094
|
+
class DepositArchivePoller {
|
|
292095
|
+
params;
|
|
292096
|
+
#timer = null;
|
|
292097
|
+
#stopped = false;
|
|
292098
|
+
#running = null;
|
|
292099
|
+
#cursors = new Map;
|
|
292100
|
+
#unsupportedLogged = new Set;
|
|
292101
|
+
#config;
|
|
292102
|
+
constructor(params) {
|
|
292103
|
+
this.params = params;
|
|
292104
|
+
this.#config = { ...DEFAULT_CONFIG2, ...params.config };
|
|
292105
|
+
}
|
|
292106
|
+
start() {
|
|
292107
|
+
if (this.#timer || this.#stopped || !this.params.archiver.isEnabled()) {
|
|
292108
|
+
return;
|
|
292109
|
+
}
|
|
292110
|
+
log.info("\uD83D\uDCE5 Deposit archive poller started");
|
|
292111
|
+
this.#schedule(0);
|
|
292112
|
+
}
|
|
292113
|
+
async stop() {
|
|
292114
|
+
this.#stopped = true;
|
|
292115
|
+
if (this.#timer) {
|
|
292116
|
+
clearTimeout(this.#timer);
|
|
292117
|
+
this.#timer = null;
|
|
292118
|
+
}
|
|
292119
|
+
await this.#running;
|
|
292120
|
+
}
|
|
292121
|
+
async pollAllOnce() {
|
|
292122
|
+
if (this.#stopped || this.#running || !this.params.archiver.isEnabled()) {
|
|
292123
|
+
return false;
|
|
292124
|
+
}
|
|
292125
|
+
this.#running = this.#pollAllSequentially();
|
|
292126
|
+
try {
|
|
292127
|
+
return await this.#running;
|
|
292128
|
+
} finally {
|
|
292129
|
+
this.#running = null;
|
|
292130
|
+
}
|
|
292131
|
+
}
|
|
292132
|
+
#targets() {
|
|
292133
|
+
const targets = [];
|
|
292134
|
+
for (const [exchangeId, pool] of Object.entries(this.params.brokers)) {
|
|
292135
|
+
for (const account of [pool.primary, ...pool.secondaryBrokers]) {
|
|
292136
|
+
targets.push({
|
|
292137
|
+
exchangeId,
|
|
292138
|
+
account,
|
|
292139
|
+
code: ALL_CURRENCIES_CODE
|
|
292140
|
+
});
|
|
292141
|
+
}
|
|
292142
|
+
}
|
|
292143
|
+
return targets;
|
|
292144
|
+
}
|
|
292145
|
+
async#pollAllSequentially() {
|
|
292146
|
+
for (const target of this.#targets()) {
|
|
292147
|
+
if (this.#stopped) {
|
|
292148
|
+
break;
|
|
292149
|
+
}
|
|
292150
|
+
await this.#pollOne(target);
|
|
292151
|
+
}
|
|
292152
|
+
return true;
|
|
292153
|
+
}
|
|
292154
|
+
async#pollOne(target) {
|
|
292155
|
+
const exchange = target.account.exchange;
|
|
292156
|
+
const key = this.#targetKey(target);
|
|
292157
|
+
if (typeof exchange.fetchDeposits !== "function" || exchange.has?.fetchDeposits === false) {
|
|
292158
|
+
if (!this.#unsupportedLogged.has(key)) {
|
|
292159
|
+
this.#unsupportedLogged.add(key);
|
|
292160
|
+
log.info("Deposit archive poll skipped: fetchDeposits unsupported", {
|
|
292161
|
+
exchange: target.exchangeId,
|
|
292162
|
+
account: target.account.label
|
|
292163
|
+
});
|
|
292164
|
+
}
|
|
292165
|
+
return;
|
|
292166
|
+
}
|
|
292167
|
+
const since = this.#cursors.get(key) ?? Date.now() - this.#config.lookbackMs;
|
|
292168
|
+
let deposits;
|
|
292169
|
+
try {
|
|
292170
|
+
deposits = await exchange.fetchDeposits(undefined, since, this.#config.depositsLimit);
|
|
292171
|
+
} catch (error) {
|
|
292172
|
+
this.params.metrics?.recordCounter("cex_deposit_poller_errors_total", 1, { exchange: target.exchangeId });
|
|
292173
|
+
log.warn("Deposit archive poll failed", {
|
|
292174
|
+
exchange: target.exchangeId,
|
|
292175
|
+
account: target.account.label,
|
|
292176
|
+
error
|
|
292177
|
+
});
|
|
292178
|
+
return;
|
|
292179
|
+
}
|
|
292180
|
+
if (!Array.isArray(deposits) || deposits.length === 0) {
|
|
292181
|
+
return;
|
|
292182
|
+
}
|
|
292183
|
+
let archived = 0;
|
|
292184
|
+
for (const deposit of deposits) {
|
|
292185
|
+
const record = asRecord(deposit);
|
|
292186
|
+
if (!record) {
|
|
292187
|
+
continue;
|
|
292188
|
+
}
|
|
292189
|
+
const assetSymbol = depositField(record, ["currency", "code", "asset"]);
|
|
292190
|
+
const amount = depositField(record, ["amount"]);
|
|
292191
|
+
const address = depositField(record, [
|
|
292192
|
+
"address",
|
|
292193
|
+
"recipientAddress",
|
|
292194
|
+
"to",
|
|
292195
|
+
"destination"
|
|
292196
|
+
]);
|
|
292197
|
+
const txid = depositField(record, ["txid", "txId", "tx_hash", "txHash"]);
|
|
292198
|
+
const network = depositField(record, ["network", "chain"]);
|
|
292199
|
+
const archiveStatus = normalizeCcxtTransactionForArchive(record).status;
|
|
292200
|
+
const creditedAt = depositField(record, [
|
|
292201
|
+
"creditedAt",
|
|
292202
|
+
"credited_at",
|
|
292203
|
+
"updated",
|
|
292204
|
+
"updatedAt",
|
|
292205
|
+
"timestamp",
|
|
292206
|
+
"datetime"
|
|
292207
|
+
]);
|
|
292208
|
+
const depositTxid = txid === undefined ? undefined : String(txid);
|
|
292209
|
+
this.params.archiver.enqueue(buildTransferEventArchiveRow({
|
|
292210
|
+
tags: buildCommonArchiveTags({
|
|
292211
|
+
deploymentId: this.params.archiver.getDeploymentId(),
|
|
292212
|
+
accountSelector: target.account.label,
|
|
292213
|
+
exchange: target.exchangeId,
|
|
292214
|
+
symbol: assetSymbol === undefined ? undefined : String(assetSymbol)
|
|
292215
|
+
}),
|
|
292216
|
+
transfer: {
|
|
292217
|
+
eventKind: "deposit",
|
|
292218
|
+
lifecycleAction: "observe_deposit",
|
|
292219
|
+
status: archiveStatus,
|
|
292220
|
+
amount: amount === undefined ? undefined : String(amount),
|
|
292221
|
+
address: address === undefined ? undefined : String(address),
|
|
292222
|
+
network: network === undefined ? undefined : String(network),
|
|
292223
|
+
externalId: depositTxid,
|
|
292224
|
+
txid: depositTxid,
|
|
292225
|
+
exchangeTimestamp: typeof creditedAt === "string" ? creditedAt : undefined,
|
|
292226
|
+
payload: record
|
|
292227
|
+
}
|
|
292228
|
+
}));
|
|
292229
|
+
archived += 1;
|
|
292230
|
+
}
|
|
292231
|
+
if (archived > 0) {
|
|
292232
|
+
this.params.metrics?.recordCounter("cex_deposit_poller_deposits_archived_total", archived, { exchange: target.exchangeId });
|
|
292233
|
+
}
|
|
292234
|
+
this.#cursors.set(key, nextDepositCursor(deposits, since, this.#config.depositsLimit));
|
|
292235
|
+
}
|
|
292236
|
+
#targetKey(target) {
|
|
292237
|
+
return `${target.exchangeId}|${target.account.label}|${target.code}`;
|
|
292238
|
+
}
|
|
292239
|
+
#schedule(delayMs) {
|
|
292240
|
+
this.#timer = setTimeout(() => void this.#tick(), delayMs);
|
|
292241
|
+
this.#timer.unref?.();
|
|
292242
|
+
}
|
|
292243
|
+
async#tick() {
|
|
292244
|
+
this.#timer = null;
|
|
292245
|
+
try {
|
|
292246
|
+
await this.pollAllOnce();
|
|
292247
|
+
} catch (error) {
|
|
292248
|
+
rethrowArchiveDurabilityError(error);
|
|
292249
|
+
log.error("Deposit archive poller tick failed", error);
|
|
292250
|
+
} finally {
|
|
292251
|
+
if (!this.#stopped) {
|
|
292252
|
+
this.#schedule(this.#config.pollIntervalMs);
|
|
292253
|
+
}
|
|
292254
|
+
}
|
|
292255
|
+
}
|
|
292256
|
+
}
|
|
292257
|
+
|
|
292258
|
+
// src/helpers/fill-archive-poller.ts
|
|
292259
|
+
var DEFAULT_CONFIG3 = {
|
|
292053
292260
|
pollIntervalMs: 60000,
|
|
292054
292261
|
lookbackMs: 24 * 60 * 60 * 1000,
|
|
292055
292262
|
tradesLimit: 500
|
|
@@ -292074,7 +292281,7 @@ class FillArchivePoller {
|
|
|
292074
292281
|
#config;
|
|
292075
292282
|
constructor(params) {
|
|
292076
292283
|
this.params = params;
|
|
292077
|
-
this.#config = { ...
|
|
292284
|
+
this.#config = { ...DEFAULT_CONFIG3, ...params.config };
|
|
292078
292285
|
}
|
|
292079
292286
|
start() {
|
|
292080
292287
|
if (this.#timer || this.#stopped) {
|
|
@@ -306300,6 +306507,12 @@ function parseActionPayload(schema, rawPayload, callback) {
|
|
|
306300
306507
|
// src/helpers/grpc/status.ts
|
|
306301
306508
|
import * as grpc2 from "@grpc/grpc-js";
|
|
306302
306509
|
function stableGrpcErrorCode(message) {
|
|
306510
|
+
if (message.startsWith("AuthenticationError:")) {
|
|
306511
|
+
return grpc2.status.UNAUTHENTICATED;
|
|
306512
|
+
}
|
|
306513
|
+
if (message.startsWith("InsufficientFunds:")) {
|
|
306514
|
+
return grpc2.status.FAILED_PRECONDITION;
|
|
306515
|
+
}
|
|
306303
306516
|
if (message.startsWith("venue_discovery_unavailable:")) {
|
|
306304
306517
|
return grpc2.status.UNIMPLEMENTED;
|
|
306305
306518
|
}
|
|
@@ -306450,7 +306663,8 @@ async function handleDeposit(ctx) {
|
|
|
306450
306663
|
message: `deposit_address_mismatch: expected address ${value.recipientAddress}, observed ${String(observedAddress)}`
|
|
306451
306664
|
}, null);
|
|
306452
306665
|
}
|
|
306453
|
-
const
|
|
306666
|
+
const responseStatus = normalizeDepositStatus(depositField(deposit, ["status", "state"]));
|
|
306667
|
+
const archiveStatus = normalizeCcxtTransactionForArchive(deposit).status;
|
|
306454
306668
|
const depositTxid = String(depositField(deposit, ["txid", "txId", "tx_hash", "txHash"]) ?? value.transactionHash);
|
|
306455
306669
|
const creditedAt = depositField(deposit, [
|
|
306456
306670
|
"creditedAt",
|
|
@@ -306467,7 +306681,7 @@ async function handleDeposit(ctx) {
|
|
|
306467
306681
|
transfer: {
|
|
306468
306682
|
eventKind: "deposit",
|
|
306469
306683
|
lifecycleAction: "observe_deposit",
|
|
306470
|
-
status:
|
|
306684
|
+
status: archiveStatus,
|
|
306471
306685
|
amount: observedAmount !== undefined ? String(observedAmount) : undefined,
|
|
306472
306686
|
address: String(observedAddress ?? value.recipientAddress),
|
|
306473
306687
|
network: depositNetwork?.exchangeNetworkId,
|
|
@@ -306481,7 +306695,7 @@ async function handleDeposit(ctx) {
|
|
|
306481
306695
|
return ctx.wrappedCallback(null, {
|
|
306482
306696
|
proof: ctx.verity.proof,
|
|
306483
306697
|
result: JSON.stringify({
|
|
306484
|
-
status:
|
|
306698
|
+
status: responseStatus,
|
|
306485
306699
|
exchange: normalizedCex,
|
|
306486
306700
|
accountSelector: selectedBrokerAccount?.label,
|
|
306487
306701
|
asset: symbol2,
|
|
@@ -307000,6 +307214,12 @@ function identifiesUnsupported(message) {
|
|
|
307000
307214
|
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);
|
|
307001
307215
|
}
|
|
307002
307216
|
function classifyPassiveOrderError(error48) {
|
|
307217
|
+
if (error48 instanceof ccxt_default.InsufficientFunds) {
|
|
307218
|
+
return "InsufficientFunds";
|
|
307219
|
+
}
|
|
307220
|
+
if (error48 instanceof ccxt_default.AuthenticationError) {
|
|
307221
|
+
return "AuthenticationError";
|
|
307222
|
+
}
|
|
307003
307223
|
const message = getErrorMessage(error48);
|
|
307004
307224
|
if (error48 instanceof ccxt_default.OrderImmediatelyFillable || identifiesWouldCross(message)) {
|
|
307005
307225
|
return PASSIVE_ORDER_ERROR_CODES.wouldCross;
|
|
@@ -307128,9 +307348,9 @@ async function handleCreateOrder(ctx) {
|
|
|
307128
307348
|
emitOrderExecutionTelemetryInBackground(otelMetrics, failedCreateContext, undefined, error48);
|
|
307129
307349
|
archiveOrderExecutionInBackground(brokerArchiver, failedCreateContext, undefined, error48, { marketMetadataHash });
|
|
307130
307350
|
if (isPassiveOrder && submission === "in_flight") {
|
|
307131
|
-
const
|
|
307351
|
+
const stableErrorCode = classifyPassiveOrderError(error48);
|
|
307132
307352
|
return rejectWithGrpcError(ctx, error48, {
|
|
307133
|
-
message: `${
|
|
307353
|
+
message: `${stableErrorCode}: ${sanitizeErrorDetail(error48)}`,
|
|
307134
307354
|
preferStableMessageOnly: true
|
|
307135
307355
|
});
|
|
307136
307356
|
}
|
|
@@ -310030,6 +310250,7 @@ class CEXBroker {
|
|
|
310030
310250
|
orderActivityTracker = new OrderActivityTracker;
|
|
310031
310251
|
withdrawalObservationTracker = new WithdrawalObservationTracker;
|
|
310032
310252
|
fillArchivePoller;
|
|
310253
|
+
depositArchivePoller;
|
|
310033
310254
|
accountBalanceArchivePoller;
|
|
310034
310255
|
loadEnvConfig() {
|
|
310035
310256
|
log.info("\uD83D\uDD27 Loading CEX_BROKER_ environment variables:");
|
|
@@ -310175,6 +310396,10 @@ class CEXBroker {
|
|
|
310175
310396
|
this.fillArchivePoller.stop();
|
|
310176
310397
|
this.fillArchivePoller = undefined;
|
|
310177
310398
|
}
|
|
310399
|
+
if (this.depositArchivePoller) {
|
|
310400
|
+
await this.depositArchivePoller.stop();
|
|
310401
|
+
this.depositArchivePoller = undefined;
|
|
310402
|
+
}
|
|
310178
310403
|
if (this.accountBalanceArchivePoller) {
|
|
310179
310404
|
await this.accountBalanceArchivePoller.stop();
|
|
310180
310405
|
this.accountBalanceArchivePoller = undefined;
|
|
@@ -310204,6 +310429,10 @@ class CEXBroker {
|
|
|
310204
310429
|
this.fillArchivePoller.stop();
|
|
310205
310430
|
this.fillArchivePoller = undefined;
|
|
310206
310431
|
}
|
|
310432
|
+
if (this.depositArchivePoller) {
|
|
310433
|
+
await this.depositArchivePoller.stop();
|
|
310434
|
+
this.depositArchivePoller = undefined;
|
|
310435
|
+
}
|
|
310207
310436
|
if (this.accountBalanceArchivePoller) {
|
|
310208
310437
|
await this.accountBalanceArchivePoller.stop();
|
|
310209
310438
|
this.accountBalanceArchivePoller = undefined;
|
|
@@ -310235,6 +310464,12 @@ class CEXBroker {
|
|
|
310235
310464
|
metrics: this.otelMetrics
|
|
310236
310465
|
});
|
|
310237
310466
|
this.fillArchivePoller.start();
|
|
310467
|
+
this.depositArchivePoller = new DepositArchivePoller({
|
|
310468
|
+
brokers: this.brokers,
|
|
310469
|
+
archiver: this.brokerArchiver,
|
|
310470
|
+
metrics: this.otelMetrics
|
|
310471
|
+
});
|
|
310472
|
+
this.depositArchivePoller.start();
|
|
310238
310473
|
}
|
|
310239
310474
|
if (this.brokerArchiver?.canPersistAccountBalanceSnapshots()) {
|
|
310240
310475
|
this.accountBalanceArchivePoller = new AccountBalanceArchivePoller({
|
|
@@ -310251,4 +310486,4 @@ export {
|
|
|
310251
310486
|
CEXBroker as default
|
|
310252
310487
|
};
|
|
310253
310488
|
|
|
310254
|
-
//# debugId=
|
|
310489
|
+
//# debugId=A02D63306D4E3E3864756E2164756E21
|