@usherlabs/cex-broker 0.3.0 → 0.3.1
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 +116 -8
- package/dist/commands/cli.js +1151 -396
- package/dist/index.js +1152 -397
- package/dist/index.js.map +20 -16
- package/dist/proto/node.descriptor.ts +3 -1
- package/dist/proto/node.proto +2 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -50010,7 +50010,7 @@ var require_src13 = __commonJS((exports) => {
|
|
|
50010
50010
|
});
|
|
50011
50011
|
|
|
50012
50012
|
// src/index.ts
|
|
50013
|
-
import * as
|
|
50013
|
+
import * as grpc17 from "@grpc/grpc-js";
|
|
50014
50014
|
|
|
50015
50015
|
// node_modules/@usherlabs/ccxt/js/src/base/functions.js
|
|
50016
50016
|
var exports_functions = {};
|
|
@@ -290094,7 +290094,9 @@ var Action = {
|
|
|
290094
290094
|
FetchFees: 12,
|
|
290095
290095
|
InternalTransfer: 13,
|
|
290096
290096
|
GetPerpConfigState: 14,
|
|
290097
|
-
SetPerpConfigState: 15
|
|
290097
|
+
SetPerpConfigState: 15,
|
|
290098
|
+
FetchMarketRules: 16,
|
|
290099
|
+
Batch: 17
|
|
290098
290100
|
};
|
|
290099
290101
|
var SubscriptionType = {
|
|
290100
290102
|
NO_ACTION: 0,
|
|
@@ -290642,6 +290644,30 @@ function redactUnknownValue(value, secretLiterals) {
|
|
|
290642
290644
|
}
|
|
290643
290645
|
return String(value);
|
|
290644
290646
|
}
|
|
290647
|
+
function removeSecretMaterial(value, secretLiterals = []) {
|
|
290648
|
+
if (value === null || value === undefined) {
|
|
290649
|
+
return value;
|
|
290650
|
+
}
|
|
290651
|
+
if (typeof value === "string") {
|
|
290652
|
+
return redactSecretLiterals(value, secretLiterals);
|
|
290653
|
+
}
|
|
290654
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
290655
|
+
return value;
|
|
290656
|
+
}
|
|
290657
|
+
if (Array.isArray(value)) {
|
|
290658
|
+
return value.map((entry) => removeSecretMaterial(entry, secretLiterals));
|
|
290659
|
+
}
|
|
290660
|
+
if (typeof value === "object") {
|
|
290661
|
+
const clean = {};
|
|
290662
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
290663
|
+
if (!SECRET_KEY_PATTERN.test(key)) {
|
|
290664
|
+
clean[key] = removeSecretMaterial(entry, secretLiterals);
|
|
290665
|
+
}
|
|
290666
|
+
}
|
|
290667
|
+
return clean;
|
|
290668
|
+
}
|
|
290669
|
+
return String(value);
|
|
290670
|
+
}
|
|
290645
290671
|
function redactStreamPayload(payload, secretLiterals = []) {
|
|
290646
290672
|
if (Array.isArray(payload)) {
|
|
290647
290673
|
return {
|
|
@@ -292744,6 +292770,17 @@ function withTimeout(promise, timeoutMs, label) {
|
|
|
292744
292770
|
return Promise.race([promise, expiry]).finally(() => clearTimeout(timer));
|
|
292745
292771
|
}
|
|
292746
292772
|
var ALL_CURRENCIES_CODE = "*";
|
|
292773
|
+
var MAX_FAILURE_REASON_CHARS = 256;
|
|
292774
|
+
var DEFAULT_FAILURE_REASON = "fetchDeposits failed";
|
|
292775
|
+
function decimal(value) {
|
|
292776
|
+
return value === null || value === undefined ? null : String(value);
|
|
292777
|
+
}
|
|
292778
|
+
function failureReason(error, exchange) {
|
|
292779
|
+
const text = error instanceof Error ? error.message : typeof error === "string" ? error : "";
|
|
292780
|
+
const secrets = [exchange.apiKey, exchange.secret].filter((value) => typeof value === "string" && value.length > 0);
|
|
292781
|
+
const trimmed = redactSecretLiterals(text, secrets).replace(/\s+/g, " ").trim().slice(0, MAX_FAILURE_REASON_CHARS);
|
|
292782
|
+
return trimmed.length > 0 ? trimmed : DEFAULT_FAILURE_REASON;
|
|
292783
|
+
}
|
|
292747
292784
|
var BINANCE_UNLOCK_PROGRESS_SOURCE = {
|
|
292748
292785
|
venue: "binance",
|
|
292749
292786
|
endpoint: "GET /sapi/v1/capital/deposit/hisrec",
|
|
@@ -292754,6 +292791,20 @@ var BINANCE_UNLOCK_PROGRESS_SOURCE = {
|
|
|
292754
292791
|
completeTime: "info.completeTime"
|
|
292755
292792
|
}
|
|
292756
292793
|
};
|
|
292794
|
+
function observedProgress(progress) {
|
|
292795
|
+
if (progress === undefined)
|
|
292796
|
+
return null;
|
|
292797
|
+
return {
|
|
292798
|
+
state: progress.state,
|
|
292799
|
+
progress_state: progress.progress_state,
|
|
292800
|
+
reason: progress.reason,
|
|
292801
|
+
native_status: decimal(progress.native_status),
|
|
292802
|
+
current: decimal(progress.current),
|
|
292803
|
+
credit_required: decimal(progress.credit_required),
|
|
292804
|
+
unlock_required: decimal(progress.unlock_required),
|
|
292805
|
+
complete_time: decimal(progress.complete_time)
|
|
292806
|
+
};
|
|
292807
|
+
}
|
|
292757
292808
|
function depositTimestamp(record) {
|
|
292758
292809
|
const observedAt = depositField(record, [
|
|
292759
292810
|
"timestamp",
|
|
@@ -292983,6 +293034,7 @@ class DepositArchivePoller {
|
|
|
292983
293034
|
#cursors = new Map;
|
|
292984
293035
|
#lastArchivedByTarget = new Map;
|
|
292985
293036
|
#unsupportedLogged = new Set;
|
|
293037
|
+
#coverage = new Map;
|
|
292986
293038
|
#config;
|
|
292987
293039
|
constructor(params) {
|
|
292988
293040
|
this.params = params;
|
|
@@ -292993,6 +293045,7 @@ class DepositArchivePoller {
|
|
|
292993
293045
|
return;
|
|
292994
293046
|
}
|
|
292995
293047
|
log.info("\uD83D\uDCE5 Deposit archive poller started");
|
|
293048
|
+
this.params.coveragePublisher?.start();
|
|
292996
293049
|
this.#schedule(0);
|
|
292997
293050
|
}
|
|
292998
293051
|
async stop() {
|
|
@@ -293002,6 +293055,7 @@ class DepositArchivePoller {
|
|
|
293002
293055
|
this.#timer = null;
|
|
293003
293056
|
}
|
|
293004
293057
|
await this.#running;
|
|
293058
|
+
await this.params.coveragePublisher?.close(this.#coverageSnapshots());
|
|
293005
293059
|
}
|
|
293006
293060
|
async pollAllOnce() {
|
|
293007
293061
|
if (this.#stopped || this.#running || !this.params.archiver.isEnabled()) {
|
|
@@ -293037,14 +293091,90 @@ class DepositArchivePoller {
|
|
|
293037
293091
|
return true;
|
|
293038
293092
|
}
|
|
293039
293093
|
async#pollOne(target) {
|
|
293094
|
+
const attemptedAt = new Date().toISOString();
|
|
293040
293095
|
let outcome = "error";
|
|
293096
|
+
let observation;
|
|
293041
293097
|
try {
|
|
293042
|
-
|
|
293098
|
+
const result = await this.#pollTarget(target, attemptedAt);
|
|
293099
|
+
outcome = result.outcome;
|
|
293100
|
+
observation = result.observation;
|
|
293101
|
+
} catch (error) {
|
|
293102
|
+
observation = this.#observation("error", attemptedAt, {
|
|
293103
|
+
errorReason: failureReason(error, target.account.exchange)
|
|
293104
|
+
});
|
|
293105
|
+
throw error;
|
|
293043
293106
|
} finally {
|
|
293044
293107
|
this.params.metrics?.recordCounter("cex_deposit_poller_polls_total", 1, { exchange: target.exchangeId, outcome });
|
|
293108
|
+
this.#recordCoverage(target, outcome, observation ?? this.#observation("error", attemptedAt, {
|
|
293109
|
+
errorReason: "poll aborted before completion"
|
|
293110
|
+
}));
|
|
293045
293111
|
}
|
|
293046
293112
|
}
|
|
293047
|
-
|
|
293113
|
+
#observation(disposition, attemptedAt, detail = {}) {
|
|
293114
|
+
if (disposition === "success" && detail.completedAt !== undefined && detail.completedAt < attemptedAt) {
|
|
293115
|
+
return this.#observation("error", attemptedAt, {
|
|
293116
|
+
requestSinceMs: detail.requestSinceMs,
|
|
293117
|
+
errorReason: `source clock invalid: completed ${detail.completedAt} before attempted ${attemptedAt}`
|
|
293118
|
+
});
|
|
293119
|
+
}
|
|
293120
|
+
return {
|
|
293121
|
+
version: "1",
|
|
293122
|
+
disposition,
|
|
293123
|
+
attempted_at: attemptedAt,
|
|
293124
|
+
completed_at: disposition === "success" ? detail.completedAt ?? null : null,
|
|
293125
|
+
poll_interval_ms: String(this.#config.pollIntervalMs),
|
|
293126
|
+
fetch_timeout_ms: String(this.#config.fetchTimeoutMs),
|
|
293127
|
+
deposits_limit: String(this.#config.depositsLimit),
|
|
293128
|
+
request_since_ms: decimal(detail.requestSinceMs),
|
|
293129
|
+
next_cursor_ms: disposition === "success" ? decimal(detail.nextCursorMs) : null,
|
|
293130
|
+
response_truncated: disposition === "success" ? detail.responseTruncated ?? null : null,
|
|
293131
|
+
malformed_count: String(detail.malformedCount ?? 0),
|
|
293132
|
+
error_reason: disposition === "error" ? detail.errorReason?.trim() || DEFAULT_FAILURE_REASON : "",
|
|
293133
|
+
observed_deposits: disposition === "success" ? detail.observedDeposits ?? [] : []
|
|
293134
|
+
};
|
|
293135
|
+
}
|
|
293136
|
+
#recordCoverage(target, outcome, observation) {
|
|
293137
|
+
const publisher = this.params.coveragePublisher;
|
|
293138
|
+
if (!publisher)
|
|
293139
|
+
return;
|
|
293140
|
+
const key = this.#targetKey(target);
|
|
293141
|
+
const previous = this.#coverage.get(key);
|
|
293142
|
+
const now3 = observation.completed_at ?? new Date().toISOString();
|
|
293143
|
+
const state = outcome === "ok" ? "connected" : "error";
|
|
293144
|
+
const base2 = previous?.snapshot;
|
|
293145
|
+
const stateChangedAt = base2 && base2.state === state ? base2.stateChangedAt : now3;
|
|
293146
|
+
const attempts = BigInt(base2?.connectAttemptCount ?? "0") + 1n;
|
|
293147
|
+
const errors = BigInt(base2?.errorCount ?? "0") + (outcome === "error" ? 1n : 0n);
|
|
293148
|
+
const reconnects = BigInt(base2?.reconnectCount ?? "0") + (outcome === "ok" && previous?.lastOutcome === "error" ? 1n : 0n);
|
|
293149
|
+
const snapshot = {
|
|
293150
|
+
exchange: target.exchangeId,
|
|
293151
|
+
accountSelector: target.account.label,
|
|
293152
|
+
accountRole: target.account.role,
|
|
293153
|
+
streamKind: "deposit_poller",
|
|
293154
|
+
accountScope: "spot",
|
|
293155
|
+
registryStatus: "active",
|
|
293156
|
+
retiredAt: null,
|
|
293157
|
+
state,
|
|
293158
|
+
stateChangedAt,
|
|
293159
|
+
lastConnectedAt: outcome === "ok" ? observation.completed_at : base2?.lastConnectedAt ?? null,
|
|
293160
|
+
lastAuthenticatedAt: null,
|
|
293161
|
+
lastReceivedAt: outcome === "ok" ? observation.completed_at : base2?.lastReceivedAt ?? null,
|
|
293162
|
+
connectAttemptCount: attempts.toString(),
|
|
293163
|
+
reconnectCount: reconnects.toString(),
|
|
293164
|
+
errorCount: errors.toString(),
|
|
293165
|
+
lastFailureKind: outcome === "ok" ? "none" : outcome === "unsupported" ? "unsupported_connector" : "transport_error",
|
|
293166
|
+
lastFailureReason: outcome === "ok" ? "" : outcome === "unsupported" ? "fetchDeposits unsupported" : observation.error_reason,
|
|
293167
|
+
trafficMode: "continuous",
|
|
293168
|
+
sourceWatermark: null,
|
|
293169
|
+
pollObservation: observation
|
|
293170
|
+
};
|
|
293171
|
+
this.#coverage.set(key, { snapshot, lastOutcome: outcome });
|
|
293172
|
+
publisher.publish(this.#coverageSnapshots());
|
|
293173
|
+
}
|
|
293174
|
+
#coverageSnapshots() {
|
|
293175
|
+
return [...this.#coverage.values()].map((entry) => entry.snapshot);
|
|
293176
|
+
}
|
|
293177
|
+
async#pollTarget(target, attemptedAt) {
|
|
293048
293178
|
const exchange = target.account.exchange;
|
|
293049
293179
|
const key = this.#targetKey(target);
|
|
293050
293180
|
if (typeof exchange.fetchDeposits !== "function" || exchange.has?.fetchDeposits === false) {
|
|
@@ -293055,7 +293185,10 @@ class DepositArchivePoller {
|
|
|
293055
293185
|
account: target.account.label
|
|
293056
293186
|
});
|
|
293057
293187
|
}
|
|
293058
|
-
return
|
|
293188
|
+
return {
|
|
293189
|
+
outcome: "unsupported",
|
|
293190
|
+
observation: this.#observation("unsupported", attemptedAt)
|
|
293191
|
+
};
|
|
293059
293192
|
}
|
|
293060
293193
|
const since = this.#cursors.get(key) ?? Date.now() - this.#config.lookbackMs;
|
|
293061
293194
|
let deposits;
|
|
@@ -293068,15 +293201,48 @@ class DepositArchivePoller {
|
|
|
293068
293201
|
account: target.account.label,
|
|
293069
293202
|
error
|
|
293070
293203
|
});
|
|
293071
|
-
return
|
|
293204
|
+
return {
|
|
293205
|
+
outcome: "error",
|
|
293206
|
+
observation: this.#observation("error", attemptedAt, {
|
|
293207
|
+
requestSinceMs: since,
|
|
293208
|
+
errorReason: failureReason(error, exchange)
|
|
293209
|
+
})
|
|
293210
|
+
};
|
|
293072
293211
|
}
|
|
293073
|
-
if (!Array.isArray(deposits)
|
|
293074
|
-
return
|
|
293212
|
+
if (!Array.isArray(deposits)) {
|
|
293213
|
+
return {
|
|
293214
|
+
outcome: "error",
|
|
293215
|
+
observation: this.#observation("error", attemptedAt, {
|
|
293216
|
+
requestSinceMs: since,
|
|
293217
|
+
errorReason: "fetchDeposits returned a non-array response"
|
|
293218
|
+
})
|
|
293219
|
+
};
|
|
293220
|
+
}
|
|
293221
|
+
if (deposits.length > this.#config.depositsLimit) {
|
|
293222
|
+
return {
|
|
293223
|
+
outcome: "error",
|
|
293224
|
+
observation: this.#observation("error", attemptedAt, {
|
|
293225
|
+
requestSinceMs: since,
|
|
293226
|
+
errorReason: `fetchDeposits returned ${deposits.length} rows for a limit of ${this.#config.depositsLimit}`
|
|
293227
|
+
})
|
|
293228
|
+
};
|
|
293229
|
+
}
|
|
293230
|
+
if (deposits.length === 0) {
|
|
293231
|
+
return this.#completed(this.#observation("success", attemptedAt, {
|
|
293232
|
+
completedAt: new Date().toISOString(),
|
|
293233
|
+
requestSinceMs: since,
|
|
293234
|
+
nextCursorMs: this.#cursors.get(key),
|
|
293235
|
+
responseTruncated: false,
|
|
293236
|
+
observedDeposits: []
|
|
293237
|
+
}));
|
|
293075
293238
|
}
|
|
293076
293239
|
let archived = 0;
|
|
293240
|
+
let malformed = 0;
|
|
293241
|
+
const observed = [];
|
|
293077
293242
|
for (const deposit of deposits) {
|
|
293078
293243
|
const record = asRecord(deposit);
|
|
293079
293244
|
if (!record) {
|
|
293245
|
+
malformed += 1;
|
|
293080
293246
|
continue;
|
|
293081
293247
|
}
|
|
293082
293248
|
const info = asRecord(record.info);
|
|
@@ -293101,6 +293267,16 @@ class DepositArchivePoller {
|
|
|
293101
293267
|
});
|
|
293102
293268
|
const lastArchived = identity === undefined ? undefined : this.#lastArchivedByTarget.get(key)?.get(identity);
|
|
293103
293269
|
const classification = classifyDeposit(target.exchangeId, record, lastArchived);
|
|
293270
|
+
const depositTimestampMs = depositTimestamp(record);
|
|
293271
|
+
observed.push({
|
|
293272
|
+
coin: assetSymbol === undefined ? "" : String(assetSymbol),
|
|
293273
|
+
network: network === undefined ? "" : String(network),
|
|
293274
|
+
external_id: depositTxid ?? "",
|
|
293275
|
+
txid: depositTxid ?? "",
|
|
293276
|
+
deposit_timestamp_ms: decimal(depositTimestampMs),
|
|
293277
|
+
status: classification.archiveStatus,
|
|
293278
|
+
progress: observedProgress(classification.unlockProgress)
|
|
293279
|
+
});
|
|
293104
293280
|
if (lastArchived && lastArchived.status === classification.archiveStatus && lastArchived.progressKey === classification.progressKey) {
|
|
293105
293281
|
continue;
|
|
293106
293282
|
}
|
|
@@ -293141,7 +293317,7 @@ class DepositArchivePoller {
|
|
|
293141
293317
|
}
|
|
293142
293318
|
targetDeposits2.set(identity, {
|
|
293143
293319
|
status: classification.archiveStatus,
|
|
293144
|
-
timestamp:
|
|
293320
|
+
timestamp: depositTimestampMs,
|
|
293145
293321
|
progressKey: classification.progressKey,
|
|
293146
293322
|
highWatermark: classification.highWatermark
|
|
293147
293323
|
});
|
|
@@ -293164,7 +293340,20 @@ class DepositArchivePoller {
|
|
|
293164
293340
|
this.#lastArchivedByTarget.delete(key);
|
|
293165
293341
|
}
|
|
293166
293342
|
}
|
|
293167
|
-
return "
|
|
293343
|
+
return this.#completed(this.#observation("success", attemptedAt, {
|
|
293344
|
+
completedAt: new Date().toISOString(),
|
|
293345
|
+
requestSinceMs: since,
|
|
293346
|
+
nextCursorMs: nextCursor,
|
|
293347
|
+
responseTruncated: deposits.length >= this.#config.depositsLimit,
|
|
293348
|
+
malformedCount: malformed,
|
|
293349
|
+
observedDeposits: observed
|
|
293350
|
+
}));
|
|
293351
|
+
}
|
|
293352
|
+
#completed(observation) {
|
|
293353
|
+
return {
|
|
293354
|
+
outcome: observation.disposition === "success" ? "ok" : "error",
|
|
293355
|
+
observation
|
|
293356
|
+
};
|
|
293168
293357
|
}
|
|
293169
293358
|
#targetKey(target) {
|
|
293170
293359
|
return `${target.exchangeId}|${target.account.label}|${target.code}`;
|
|
@@ -296042,7 +296231,8 @@ import { request as httpsRequest3 } from "node:https";
|
|
|
296042
296231
|
import { dirname as dirname2 } from "node:path";
|
|
296043
296232
|
var SOURCE = "broker_write";
|
|
296044
296233
|
var TABLE = "broker_stream_health.snapshots";
|
|
296045
|
-
var
|
|
296234
|
+
var USER_DATA_STREAM_HEALTH_PRODUCER_ID = "cex-broker-user-data";
|
|
296235
|
+
var DEPOSIT_POLLER_STREAM_HEALTH_PRODUCER_ID = "cex-broker-deposit-poller";
|
|
296046
296236
|
var STATE_VERSION = 1;
|
|
296047
296237
|
var HEARTBEAT_MS = 30000;
|
|
296048
296238
|
var FORWARDER_TIMEOUT_MS = 3000;
|
|
@@ -296077,11 +296267,11 @@ function registryRevision(snapshots) {
|
|
|
296077
296267
|
})).sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
|
|
296078
296268
|
return createHash5("sha256").update(JSON.stringify(rows)).digest("hex");
|
|
296079
296269
|
}
|
|
296080
|
-
function validState(value) {
|
|
296270
|
+
function validState(value, producerId) {
|
|
296081
296271
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
296082
296272
|
return false;
|
|
296083
296273
|
const state = value;
|
|
296084
|
-
return state.version === STATE_VERSION && state.producerId ===
|
|
296274
|
+
return state.version === STATE_VERSION && state.producerId === producerId && typeof state.producerEpoch === "string" && typeof state.runId === "string" && typeof state.nextBatchSequence === "string" && state.nextStreamSequences !== null && typeof state.nextStreamSequences === "object" && (state.pendingBody === undefined || typeof state.pendingBody === "string");
|
|
296085
296275
|
}
|
|
296086
296276
|
function forwarderPost(url2, body, authToken, timeoutMs) {
|
|
296087
296277
|
const request = url2.protocol === "http:" ? httpRequest3 : httpsRequest3;
|
|
@@ -296111,6 +296301,7 @@ function forwarderPost(url2, body, authToken, timeoutMs) {
|
|
|
296111
296301
|
}
|
|
296112
296302
|
|
|
296113
296303
|
class StreamHealthPublisher {
|
|
296304
|
+
#producerId;
|
|
296114
296305
|
#deploymentId;
|
|
296115
296306
|
#statePath;
|
|
296116
296307
|
#heartbeatMs;
|
|
@@ -296125,6 +296316,7 @@ class StreamHealthPublisher {
|
|
|
296125
296316
|
#retry = null;
|
|
296126
296317
|
#retryAttempt = 0;
|
|
296127
296318
|
constructor(options) {
|
|
296319
|
+
this.#producerId = identifier(options.producerId, "producer_id");
|
|
296128
296320
|
this.#deploymentId = identifier(options.deploymentId, "deployment_id");
|
|
296129
296321
|
this.#statePath = options.statePath.trim();
|
|
296130
296322
|
if (!this.#statePath) {
|
|
@@ -296149,7 +296341,7 @@ class StreamHealthPublisher {
|
|
|
296149
296341
|
const loaded = this.#read();
|
|
296150
296342
|
this.#state = loaded ?? {
|
|
296151
296343
|
version: STATE_VERSION,
|
|
296152
|
-
producerId:
|
|
296344
|
+
producerId: this.#producerId,
|
|
296153
296345
|
producerEpoch: "1",
|
|
296154
296346
|
runId: randomUUID3(),
|
|
296155
296347
|
nextBatchSequence: "1",
|
|
@@ -296262,7 +296454,7 @@ class StreamHealthPublisher {
|
|
|
296262
296454
|
return {
|
|
296263
296455
|
table: TABLE,
|
|
296264
296456
|
row: {
|
|
296265
|
-
producer_id:
|
|
296457
|
+
producer_id: this.#producerId,
|
|
296266
296458
|
producer_epoch: this.#state.producerEpoch,
|
|
296267
296459
|
run_id: this.#state.runId,
|
|
296268
296460
|
batch_sequence: batchSequence,
|
|
@@ -296289,7 +296481,8 @@ class StreamHealthPublisher {
|
|
|
296289
296481
|
last_failure_kind: snapshot.lastFailureKind,
|
|
296290
296482
|
last_failure_reason: snapshot.lastFailureReason,
|
|
296291
296483
|
traffic_mode: snapshot.trafficMode,
|
|
296292
|
-
source_watermark: snapshot.sourceWatermark
|
|
296484
|
+
source_watermark: snapshot.sourceWatermark,
|
|
296485
|
+
...snapshot.streamKind === "deposit_poller" ? { poll_observation: snapshot.pollObservation } : {}
|
|
296293
296486
|
}
|
|
296294
296487
|
};
|
|
296295
296488
|
});
|
|
@@ -296317,7 +296510,7 @@ class StreamHealthPublisher {
|
|
|
296317
296510
|
#read() {
|
|
296318
296511
|
try {
|
|
296319
296512
|
const parsed = JSON.parse(readFileSync2(this.#statePath, "utf8"));
|
|
296320
|
-
if (!validState(parsed))
|
|
296513
|
+
if (!validState(parsed, this.#producerId))
|
|
296321
296514
|
throw new Error("invalid state shape");
|
|
296322
296515
|
return parsed;
|
|
296323
296516
|
} catch (error) {
|
|
@@ -296380,6 +296573,14 @@ function streamHealthPublisherConfigFromEnv(env = process.env) {
|
|
|
296380
296573
|
forwarderAuthToken: env.CEX_BROKER_ARCHIVE_FORWARDER_TOKEN?.trim() || undefined
|
|
296381
296574
|
};
|
|
296382
296575
|
}
|
|
296576
|
+
function depositPollerStreamHealthPublisherConfigFromEnv(env = process.env) {
|
|
296577
|
+
const base2 = streamHealthPublisherConfigFromEnv(env);
|
|
296578
|
+
return {
|
|
296579
|
+
...base2,
|
|
296580
|
+
producerId: DEPOSIT_POLLER_STREAM_HEALTH_PRODUCER_ID,
|
|
296581
|
+
statePath: `${base2.statePath}.deposit-poller`
|
|
296582
|
+
};
|
|
296583
|
+
}
|
|
296383
296584
|
|
|
296384
296585
|
// src/helpers/user-asset-archive-poller.ts
|
|
296385
296586
|
var DEFAULT_CONFIG4 = {
|
|
@@ -296950,7 +297151,7 @@ class AccountWorker {
|
|
|
296950
297151
|
for (const subscriber of [...this.#subscribers])
|
|
296951
297152
|
subscriber.close();
|
|
296952
297153
|
}
|
|
296953
|
-
#transition(state, failureKind,
|
|
297154
|
+
#transition(state, failureKind, failureReason2) {
|
|
296954
297155
|
const timestamp = now3();
|
|
296955
297156
|
if (this.#snapshot.state !== state) {
|
|
296956
297157
|
this.#snapshot.state = state;
|
|
@@ -296958,7 +297159,7 @@ class AccountWorker {
|
|
|
296958
297159
|
}
|
|
296959
297160
|
if (failureKind) {
|
|
296960
297161
|
this.#snapshot.lastFailureKind = failureKind;
|
|
296961
|
-
this.#snapshot.lastFailureReason =
|
|
297162
|
+
this.#snapshot.lastFailureReason = failureReason2 ?? "";
|
|
296962
297163
|
}
|
|
296963
297164
|
this.onChange();
|
|
296964
297165
|
}
|
|
@@ -297082,7 +297283,7 @@ class UserDataStreamSupervisor {
|
|
|
297082
297283
|
}
|
|
297083
297284
|
|
|
297084
297285
|
// src/server.ts
|
|
297085
|
-
import * as
|
|
297286
|
+
import * as grpc16 from "@grpc/grpc-js";
|
|
297086
297287
|
|
|
297087
297288
|
// src/handlers/execute-action/deposit.ts
|
|
297088
297289
|
import * as grpc3 from "@grpc/grpc-js";
|
|
@@ -310734,7 +310935,10 @@ config(en_default());
|
|
|
310734
310935
|
// src/schemas/action-payloads.ts
|
|
310735
310936
|
var parseJsonString = (value) => {
|
|
310736
310937
|
if (typeof value !== "string") {
|
|
310737
|
-
|
|
310938
|
+
if (value === null || value === undefined || typeof value === "number" || typeof value === "boolean" || typeof value === "object") {
|
|
310939
|
+
return value;
|
|
310940
|
+
}
|
|
310941
|
+
return String(value);
|
|
310738
310942
|
}
|
|
310739
310943
|
try {
|
|
310740
310944
|
return JSON.parse(value);
|
|
@@ -310743,19 +310947,6 @@ var parseJsonString = (value) => {
|
|
|
310743
310947
|
}
|
|
310744
310948
|
};
|
|
310745
310949
|
var stringNumberRecordSchema = exports_external.record(exports_external.string(), exports_external.union([exports_external.string(), exports_external.number()]));
|
|
310746
|
-
var booleanLikeSchema = exports_external.preprocess((value) => {
|
|
310747
|
-
if (typeof value !== "string") {
|
|
310748
|
-
return value;
|
|
310749
|
-
}
|
|
310750
|
-
const normalized = value.trim().toLowerCase();
|
|
310751
|
-
if (["true", "1", "yes"].includes(normalized)) {
|
|
310752
|
-
return true;
|
|
310753
|
-
}
|
|
310754
|
-
if (["false", "0", "no"].includes(normalized)) {
|
|
310755
|
-
return false;
|
|
310756
|
-
}
|
|
310757
|
-
return value;
|
|
310758
|
-
}, exports_external.boolean());
|
|
310759
310950
|
var DepositPayloadSchema = exports_external.object({
|
|
310760
310951
|
recipientAddress: exports_external.string().min(1),
|
|
310761
310952
|
amount: exports_external.coerce.number().positive(),
|
|
@@ -310816,10 +311007,22 @@ var CancelOrderPayloadSchema = exports_external.object({
|
|
|
310816
311007
|
orderId: exports_external.string().min(1),
|
|
310817
311008
|
params: exports_external.preprocess(parseJsonString, stringNumberRecordSchema).default({})
|
|
310818
311009
|
});
|
|
310819
|
-
var
|
|
310820
|
-
|
|
310821
|
-
|
|
310822
|
-
|
|
311010
|
+
var EmptyActionPayloadSchema = exports_external.object({}).strict();
|
|
311011
|
+
var FetchFeesPayloadSchema = EmptyActionPayloadSchema;
|
|
311012
|
+
var FetchCurrencyPayloadSchema = exports_external.object({
|
|
311013
|
+
network: exports_external.string().trim().min(1)
|
|
311014
|
+
}).strict();
|
|
311015
|
+
var MAX_BATCH_CHILDREN = 32;
|
|
311016
|
+
var MAX_BATCH_REQUEST_BYTES = 256 * 1024;
|
|
311017
|
+
var BatchChildRequestSchema = exports_external.object({
|
|
311018
|
+
id: exports_external.string().trim().min(1),
|
|
311019
|
+
action: exports_external.number().int().nonnegative(),
|
|
311020
|
+
symbol: exports_external.string(),
|
|
311021
|
+
payload: exports_external.record(exports_external.string(), exports_external.string())
|
|
311022
|
+
}).strict();
|
|
311023
|
+
var BatchPayloadSchema = exports_external.object({
|
|
311024
|
+
requests: exports_external.preprocess(parseJsonString, exports_external.array(BatchChildRequestSchema).min(1).max(MAX_BATCH_CHILDREN))
|
|
311025
|
+
}).strict();
|
|
310823
311026
|
|
|
310824
311027
|
// src/helpers/grpc/callbacks.ts
|
|
310825
311028
|
import * as grpc from "@grpc/grpc-js";
|
|
@@ -310868,7 +311071,7 @@ function stableGrpcErrorCode(message) {
|
|
|
310868
311071
|
if (message.startsWith("AuthenticationError:")) {
|
|
310869
311072
|
return grpc2.status.UNAUTHENTICATED;
|
|
310870
311073
|
}
|
|
310871
|
-
if (message.startsWith("InsufficientFunds:")) {
|
|
311074
|
+
if (message.startsWith("InsufficientFunds:") || message.startsWith("fee_unavailable:")) {
|
|
310872
311075
|
return grpc2.status.FAILED_PRECONDITION;
|
|
310873
311076
|
}
|
|
310874
311077
|
if (message.startsWith("venue_discovery_unavailable:")) {
|
|
@@ -310928,6 +311131,13 @@ function resolveGrpcError(error48, message) {
|
|
|
310928
311131
|
}
|
|
310929
311132
|
|
|
310930
311133
|
// src/handlers/execute-action/context.ts
|
|
311134
|
+
function requireSymbol(ctx, message = "ValidationError: Symbol required") {
|
|
311135
|
+
if (!ctx.symbol) {
|
|
311136
|
+
ctx.wrappedCallback(invalidArgumentError(message), null);
|
|
311137
|
+
return false;
|
|
311138
|
+
}
|
|
311139
|
+
return true;
|
|
311140
|
+
}
|
|
310931
311141
|
function parsePayloadForAction(ctx, schema) {
|
|
310932
311142
|
return parseActionPayload(schema, ctx.call.request.payload, ctx.wrappedCallback);
|
|
310933
311143
|
}
|
|
@@ -310950,6 +311160,12 @@ function rejectWithGrpcError(ctx, error48, options) {
|
|
|
310950
311160
|
}
|
|
310951
311161
|
ctx.wrappedCallback({ code, message: finalMessage }, null);
|
|
310952
311162
|
}
|
|
311163
|
+
function successWithProof(ctx, result) {
|
|
311164
|
+
ctx.wrappedCallback(null, {
|
|
311165
|
+
proof: ctx.verity.proof,
|
|
311166
|
+
result: JSON.stringify(result)
|
|
311167
|
+
});
|
|
311168
|
+
}
|
|
310953
311169
|
|
|
310954
311170
|
// src/handlers/execute-action/deposit.ts
|
|
310955
311171
|
async function handleDeposit(ctx) {
|
|
@@ -311127,7 +311343,7 @@ async function handleDeposit(ctx) {
|
|
|
311127
311343
|
}
|
|
311128
311344
|
}
|
|
311129
311345
|
// src/handlers/execute-action/handler.ts
|
|
311130
|
-
import * as
|
|
311346
|
+
import * as grpc14 from "@grpc/grpc-js";
|
|
311131
311347
|
|
|
311132
311348
|
// src/helpers/grpc/broker.ts
|
|
311133
311349
|
function selectBrokerAccountForCex(normalizedCex, brokers, metadata) {
|
|
@@ -311259,10 +311475,458 @@ async function handleOrderBookCall(ctx) {
|
|
|
311259
311475
|
}
|
|
311260
311476
|
|
|
311261
311477
|
// src/handlers/execute-action/registry.ts
|
|
311262
|
-
import * as
|
|
311478
|
+
import * as grpc13 from "@grpc/grpc-js";
|
|
311263
311479
|
|
|
311264
|
-
// src/handlers/execute-action/
|
|
311480
|
+
// src/handlers/execute-action/batch.ts
|
|
311265
311481
|
import * as grpc5 from "@grpc/grpc-js";
|
|
311482
|
+
|
|
311483
|
+
// src/helpers/venue-evidence.ts
|
|
311484
|
+
var DECIMAL_PATTERN = /^\+?(\d+)(?:\.(\d*))?(?:[eE]([+-]?\d+))?$/;
|
|
311485
|
+
function exchangeSecretLiterals(broker) {
|
|
311486
|
+
const record2 = broker;
|
|
311487
|
+
return [
|
|
311488
|
+
record2.apiKey,
|
|
311489
|
+
record2.secret,
|
|
311490
|
+
record2.password,
|
|
311491
|
+
record2.privateKey
|
|
311492
|
+
].filter((value) => typeof value === "string" && value.length > 0);
|
|
311493
|
+
}
|
|
311494
|
+
function sanitizeVenueError(error48, broker) {
|
|
311495
|
+
return redactSecretLiterals(sanitizeErrorDetail(error48), exchangeSecretLiterals(broker));
|
|
311496
|
+
}
|
|
311497
|
+
function resolveEvidenceAccountScope(selectedBrokerAccount, metadata) {
|
|
311498
|
+
return {
|
|
311499
|
+
accountSelector: selectedBrokerAccount?.label ?? getCurrentBrokerSelector(metadata),
|
|
311500
|
+
credentialSource: selectedBrokerAccount ? "configured_pool" : "request_metadata"
|
|
311501
|
+
};
|
|
311502
|
+
}
|
|
311503
|
+
function canonicalNonnegativeDecimal(value, field) {
|
|
311504
|
+
const text = typeof value === "number" ? Number.isFinite(value) ? String(value) : "" : typeof value === "string" ? value.trim() : "";
|
|
311505
|
+
const match = text.match(DECIMAL_PATTERN);
|
|
311506
|
+
if (!match) {
|
|
311507
|
+
throw new Error(`venue_discovery_unavailable: ${field} must be decimal`);
|
|
311508
|
+
}
|
|
311509
|
+
const integer2 = match[1] ?? "0";
|
|
311510
|
+
const fraction = match[2] ?? "";
|
|
311511
|
+
const exponent = Number.parseInt(match[3] ?? "0", 10);
|
|
311512
|
+
if (!Number.isSafeInteger(exponent)) {
|
|
311513
|
+
throw new Error(`venue_discovery_unavailable: ${field} exponent is invalid`);
|
|
311514
|
+
}
|
|
311515
|
+
const digits = `${integer2}${fraction}`;
|
|
311516
|
+
const decimalIndex = integer2.length + exponent;
|
|
311517
|
+
let rendered;
|
|
311518
|
+
if (decimalIndex <= 0) {
|
|
311519
|
+
rendered = `0.${"0".repeat(-decimalIndex)}${digits}`;
|
|
311520
|
+
} else if (decimalIndex >= digits.length) {
|
|
311521
|
+
rendered = `${digits}${"0".repeat(decimalIndex - digits.length)}`;
|
|
311522
|
+
} else {
|
|
311523
|
+
rendered = `${digits.slice(0, decimalIndex)}.${digits.slice(decimalIndex)}`;
|
|
311524
|
+
}
|
|
311525
|
+
const [whole = "0", decimals = ""] = rendered.split(".");
|
|
311526
|
+
const normalizedWhole = whole.replace(/^0+(?=\d)/, "") || "0";
|
|
311527
|
+
const normalizedDecimals = decimals.replace(/0+$/, "");
|
|
311528
|
+
return normalizedDecimals ? `${normalizedWhole}.${normalizedDecimals}` : normalizedWhole;
|
|
311529
|
+
}
|
|
311530
|
+
function decimalFractionToBasisPoints(value) {
|
|
311531
|
+
const canonical = canonicalNonnegativeDecimal(value, "fee rate");
|
|
311532
|
+
return canonicalNonnegativeDecimal(`${canonical}e4`, "fee basis points");
|
|
311533
|
+
}
|
|
311534
|
+
function canonicalOptionalDecimal(value, field) {
|
|
311535
|
+
return value === undefined || value === null ? undefined : canonicalNonnegativeDecimal(value, field);
|
|
311536
|
+
}
|
|
311537
|
+
function precisionIncrement(value, precisionMode, field) {
|
|
311538
|
+
if ((precisionMode === 2 || precisionMode === "DECIMAL_PLACES") && (typeof value === "number" || typeof value === "string")) {
|
|
311539
|
+
const decimalPlaces = Number(value);
|
|
311540
|
+
if (Number.isInteger(decimalPlaces) && decimalPlaces >= 0) {
|
|
311541
|
+
return canonicalNonnegativeDecimal(`1e-${decimalPlaces}`, field);
|
|
311542
|
+
}
|
|
311543
|
+
}
|
|
311544
|
+
return canonicalNonnegativeDecimal(value, field);
|
|
311545
|
+
}
|
|
311546
|
+
function evidenceSourceDigest(input) {
|
|
311547
|
+
const source = removeSecretMaterial(input.source, exchangeSecretLiterals(input.broker));
|
|
311548
|
+
return sha256Canonical({
|
|
311549
|
+
action: input.action,
|
|
311550
|
+
exchange: input.exchange,
|
|
311551
|
+
requestedKey: input.requestedKey,
|
|
311552
|
+
accountSelector: input.accountSelector,
|
|
311553
|
+
sourceMethod: input.sourceMethod,
|
|
311554
|
+
source
|
|
311555
|
+
});
|
|
311556
|
+
}
|
|
311557
|
+
async function resolveSpotMarketIdentity(broker, symbol2) {
|
|
311558
|
+
const requestedSymbol = symbol2.trim().toUpperCase();
|
|
311559
|
+
if (!/^[^/\s]+\/[^/\s]+$/.test(requestedSymbol)) {
|
|
311560
|
+
throw new Error("venue_discovery_unavailable: symbol must be a slash-delimited spot pair");
|
|
311561
|
+
}
|
|
311562
|
+
await broker.loadMarkets();
|
|
311563
|
+
const market = broker.market(requestedSymbol);
|
|
311564
|
+
if (!isRecord(market)) {
|
|
311565
|
+
throw new Error(`venue_discovery_unavailable: market not found for ${requestedSymbol}`);
|
|
311566
|
+
}
|
|
311567
|
+
const unifiedSymbol = typeof market.symbol === "string" ? market.symbol.trim().toUpperCase() : requestedSymbol;
|
|
311568
|
+
const baseAsset = typeof market.base === "string" ? market.base.trim().toUpperCase() : "";
|
|
311569
|
+
const quoteAsset = typeof market.quote === "string" ? market.quote.trim().toUpperCase() : "";
|
|
311570
|
+
const sourceSymbol = typeof market.id === "string" ? market.id.trim() : "";
|
|
311571
|
+
const isSpot = market.spot === true || market.type === "spot";
|
|
311572
|
+
if (unifiedSymbol !== requestedSymbol || !baseAsset || !quoteAsset || !sourceSymbol || !isSpot || market.active !== true) {
|
|
311573
|
+
throw new Error(`venue_discovery_unavailable: active spot market identity unavailable for ${requestedSymbol}`);
|
|
311574
|
+
}
|
|
311575
|
+
return {
|
|
311576
|
+
market,
|
|
311577
|
+
canonicalPair: `${baseAsset}-${quoteAsset}`,
|
|
311578
|
+
unifiedSymbol,
|
|
311579
|
+
sourceSymbol,
|
|
311580
|
+
baseAsset,
|
|
311581
|
+
quoteAsset
|
|
311582
|
+
};
|
|
311583
|
+
}
|
|
311584
|
+
function extractTradingFeeRates(response) {
|
|
311585
|
+
if (!isRecord(response)) {
|
|
311586
|
+
throw new Error("fee_unavailable: trading-fee response is not an object");
|
|
311587
|
+
}
|
|
311588
|
+
const info = isRecord(response.info) ? response.info : undefined;
|
|
311589
|
+
const nestedData = isRecord(info?.data) ? info.data : isRecord(response.data) ? response.data : undefined;
|
|
311590
|
+
const maker = [
|
|
311591
|
+
response.maker,
|
|
311592
|
+
response.makerCommission,
|
|
311593
|
+
nestedData?.maker,
|
|
311594
|
+
nestedData?.makerCommission
|
|
311595
|
+
].find((value) => value !== undefined && value !== null);
|
|
311596
|
+
const taker = [
|
|
311597
|
+
response.taker,
|
|
311598
|
+
response.takerCommission,
|
|
311599
|
+
nestedData?.taker,
|
|
311600
|
+
nestedData?.takerCommission
|
|
311601
|
+
].find((value) => value !== undefined && value !== null);
|
|
311602
|
+
try {
|
|
311603
|
+
return {
|
|
311604
|
+
makerRate: canonicalNonnegativeDecimal(maker, "maker commission"),
|
|
311605
|
+
takerRate: canonicalNonnegativeDecimal(taker, "taker commission")
|
|
311606
|
+
};
|
|
311607
|
+
} catch (error48) {
|
|
311608
|
+
throw new Error(`fee_unavailable: ${sanitizeErrorDetail(error48)}`, {
|
|
311609
|
+
cause: error48
|
|
311610
|
+
});
|
|
311611
|
+
}
|
|
311612
|
+
}
|
|
311613
|
+
function evidenceExchange(broker) {
|
|
311614
|
+
return broker;
|
|
311615
|
+
}
|
|
311616
|
+
|
|
311617
|
+
// src/schemas/action-evidence.ts
|
|
311618
|
+
var EVIDENCE_DIGEST_ALGORITHM = "sha256-canonical-json-v1";
|
|
311619
|
+
var CanonicalDecimalStringSchema = exports_external.string().regex(/^(?:0|[1-9]\d*)(?:\.\d*[1-9])?$/);
|
|
311620
|
+
var accountScopeShape = {
|
|
311621
|
+
accountSelector: exports_external.string().min(1),
|
|
311622
|
+
credentialSource: exports_external.enum(["configured_pool", "request_metadata"])
|
|
311623
|
+
};
|
|
311624
|
+
var evidenceSourceShape = {
|
|
311625
|
+
observedAt: exports_external.string().datetime({ offset: true }),
|
|
311626
|
+
digestAlgorithm: exports_external.literal(EVIDENCE_DIGEST_ALGORITHM),
|
|
311627
|
+
sourceDigest: exports_external.string().regex(/^[a-f0-9]{64}$/)
|
|
311628
|
+
};
|
|
311629
|
+
var TradingFeeEvidenceSchema = exports_external.object({
|
|
311630
|
+
schemaVersion: exports_external.literal("cex-trading-fee-evidence/v1"),
|
|
311631
|
+
exchange: exports_external.string().min(1),
|
|
311632
|
+
marketType: exports_external.literal("spot"),
|
|
311633
|
+
canonicalPair: exports_external.string().regex(/^[^-\s]+-[^-\s]+$/),
|
|
311634
|
+
unifiedSymbol: exports_external.string().regex(/^[^/\s]+\/[^/\s]+$/),
|
|
311635
|
+
sourceSymbol: exports_external.string().min(1),
|
|
311636
|
+
...accountScopeShape,
|
|
311637
|
+
...evidenceSourceShape,
|
|
311638
|
+
sourceMethod: exports_external.literal("ccxt.fetchTradingFee"),
|
|
311639
|
+
makerRate: CanonicalDecimalStringSchema,
|
|
311640
|
+
takerRate: CanonicalDecimalStringSchema,
|
|
311641
|
+
rateUnit: exports_external.literal("decimal_fraction"),
|
|
311642
|
+
makerBasisPoints: CanonicalDecimalStringSchema,
|
|
311643
|
+
takerBasisPoints: CanonicalDecimalStringSchema,
|
|
311644
|
+
basisPointsUnit: exports_external.literal("basis_points")
|
|
311645
|
+
}).strict();
|
|
311646
|
+
var MarketRuleEvidenceSchema = exports_external.object({
|
|
311647
|
+
schemaVersion: exports_external.literal("cex-market-rule-evidence/v1"),
|
|
311648
|
+
exchange: exports_external.string().min(1),
|
|
311649
|
+
marketType: exports_external.literal("spot"),
|
|
311650
|
+
canonicalPair: exports_external.string().regex(/^[^-\s]+-[^-\s]+$/),
|
|
311651
|
+
unifiedSymbol: exports_external.string().regex(/^[^/\s]+\/[^/\s]+$/),
|
|
311652
|
+
sourceSymbol: exports_external.string().min(1),
|
|
311653
|
+
baseAsset: exports_external.string().min(1),
|
|
311654
|
+
quoteAsset: exports_external.string().min(1),
|
|
311655
|
+
active: exports_external.literal(true),
|
|
311656
|
+
precisionMode: exports_external.union([exports_external.string().min(1), exports_external.number().int()]),
|
|
311657
|
+
priceIncrement: CanonicalDecimalStringSchema,
|
|
311658
|
+
amountIncrement: CanonicalDecimalStringSchema,
|
|
311659
|
+
minimumAmount: CanonicalDecimalStringSchema,
|
|
311660
|
+
minimumNotional: CanonicalDecimalStringSchema,
|
|
311661
|
+
maximumAmount: CanonicalDecimalStringSchema.optional(),
|
|
311662
|
+
maximumPrice: CanonicalDecimalStringSchema.optional(),
|
|
311663
|
+
maximumNotional: CanonicalDecimalStringSchema.optional(),
|
|
311664
|
+
...accountScopeShape,
|
|
311665
|
+
...evidenceSourceShape,
|
|
311666
|
+
sourceMethod: exports_external.literal("ccxt.loadMarkets")
|
|
311667
|
+
}).strict();
|
|
311668
|
+
var TransferNetworkEvidenceSchema = exports_external.object({
|
|
311669
|
+
schemaVersion: exports_external.literal("cex-transfer-network-evidence/v1"),
|
|
311670
|
+
exchange: exports_external.string().min(1),
|
|
311671
|
+
asset: exports_external.string().min(1),
|
|
311672
|
+
operatorNetworkAlias: exports_external.string().min(1),
|
|
311673
|
+
brokerNetworkId: exports_external.string().min(1),
|
|
311674
|
+
exchangeNetworkId: exports_external.string().min(1),
|
|
311675
|
+
depositAvailable: exports_external.boolean(),
|
|
311676
|
+
withdrawalAvailable: exports_external.boolean(),
|
|
311677
|
+
withdrawalFee: CanonicalDecimalStringSchema.nullable(),
|
|
311678
|
+
withdrawalLimits: exports_external.object({
|
|
311679
|
+
minimum: CanonicalDecimalStringSchema.nullable(),
|
|
311680
|
+
maximum: CanonicalDecimalStringSchema.nullable()
|
|
311681
|
+
}).strict(),
|
|
311682
|
+
...accountScopeShape,
|
|
311683
|
+
...evidenceSourceShape,
|
|
311684
|
+
sourceMethod: exports_external.literal("ccxt.fetchCurrencies")
|
|
311685
|
+
}).strict();
|
|
311686
|
+
var BatchSuccessEntrySchema = exports_external.object({
|
|
311687
|
+
id: exports_external.string().min(1),
|
|
311688
|
+
action: exports_external.number().int().nonnegative(),
|
|
311689
|
+
symbol: exports_external.string(),
|
|
311690
|
+
response: exports_external.object({
|
|
311691
|
+
result: exports_external.string(),
|
|
311692
|
+
proof: exports_external.string()
|
|
311693
|
+
}).strict(),
|
|
311694
|
+
error: exports_external.null()
|
|
311695
|
+
}).strict();
|
|
311696
|
+
var BatchErrorEntrySchema = exports_external.object({
|
|
311697
|
+
id: exports_external.string().min(1),
|
|
311698
|
+
action: exports_external.number().int().nonnegative(),
|
|
311699
|
+
symbol: exports_external.string(),
|
|
311700
|
+
response: exports_external.null(),
|
|
311701
|
+
error: exports_external.object({
|
|
311702
|
+
code: exports_external.string().min(1),
|
|
311703
|
+
grpcStatus: exports_external.number().int().nonnegative(),
|
|
311704
|
+
message: exports_external.string()
|
|
311705
|
+
}).strict()
|
|
311706
|
+
}).strict();
|
|
311707
|
+
var BatchResponseEntrySchema = exports_external.union([
|
|
311708
|
+
BatchSuccessEntrySchema,
|
|
311709
|
+
BatchErrorEntrySchema
|
|
311710
|
+
]);
|
|
311711
|
+
var BatchResponseEnvelopeSchema = exports_external.object({
|
|
311712
|
+
schemaVersion: exports_external.literal("cex-broker-action-batch/v1"),
|
|
311713
|
+
responses: exports_external.array(BatchResponseEntrySchema).max(32)
|
|
311714
|
+
}).strict();
|
|
311715
|
+
|
|
311716
|
+
// src/handlers/execute-action/batch.ts
|
|
311717
|
+
var FORBIDDEN_ROUTING_KEYS = new Set([
|
|
311718
|
+
"account",
|
|
311719
|
+
"accountid",
|
|
311720
|
+
"accountselector",
|
|
311721
|
+
"apikey",
|
|
311722
|
+
"apisecret",
|
|
311723
|
+
"auth",
|
|
311724
|
+
"authorization",
|
|
311725
|
+
"cex",
|
|
311726
|
+
"credential",
|
|
311727
|
+
"credentials",
|
|
311728
|
+
"exchange",
|
|
311729
|
+
"metadata",
|
|
311730
|
+
"password",
|
|
311731
|
+
"secret",
|
|
311732
|
+
"signature",
|
|
311733
|
+
"usesecondarykey"
|
|
311734
|
+
]);
|
|
311735
|
+
function normalizeRoutingKey(key2) {
|
|
311736
|
+
return key2.replace(/[-_]/g, "").toLowerCase();
|
|
311737
|
+
}
|
|
311738
|
+
function hasForbiddenRoutingKey(value) {
|
|
311739
|
+
if (Array.isArray(value)) {
|
|
311740
|
+
return value.some(hasForbiddenRoutingKey);
|
|
311741
|
+
}
|
|
311742
|
+
if (value === null || typeof value !== "object") {
|
|
311743
|
+
return false;
|
|
311744
|
+
}
|
|
311745
|
+
for (const [key2, entry] of Object.entries(value)) {
|
|
311746
|
+
if (FORBIDDEN_ROUTING_KEYS.has(normalizeRoutingKey(key2))) {
|
|
311747
|
+
return true;
|
|
311748
|
+
}
|
|
311749
|
+
if (hasForbiddenRoutingKey(entry)) {
|
|
311750
|
+
return true;
|
|
311751
|
+
}
|
|
311752
|
+
}
|
|
311753
|
+
return false;
|
|
311754
|
+
}
|
|
311755
|
+
function childContainsRoutingOverride(child) {
|
|
311756
|
+
for (const [key2, value] of Object.entries(child.payload)) {
|
|
311757
|
+
if (FORBIDDEN_ROUTING_KEYS.has(normalizeRoutingKey(key2))) {
|
|
311758
|
+
return true;
|
|
311759
|
+
}
|
|
311760
|
+
try {
|
|
311761
|
+
if (hasForbiddenRoutingKey(JSON.parse(value))) {
|
|
311762
|
+
return true;
|
|
311763
|
+
}
|
|
311764
|
+
} catch {}
|
|
311765
|
+
}
|
|
311766
|
+
return false;
|
|
311767
|
+
}
|
|
311768
|
+
function stableBatchErrorCode(message, grpcStatus) {
|
|
311769
|
+
const prefix = message.match(/^([A-Za-z][A-Za-z0-9_]*):/)?.[1];
|
|
311770
|
+
if (prefix) {
|
|
311771
|
+
return prefix;
|
|
311772
|
+
}
|
|
311773
|
+
return grpc5.status[grpcStatus] ?? "UNKNOWN";
|
|
311774
|
+
}
|
|
311775
|
+
function batchErrorEntry(child, error48, broker) {
|
|
311776
|
+
const resolved = resolveGrpcError(error48);
|
|
311777
|
+
const errorRecord = error48 !== null && typeof error48 === "object" ? error48 : undefined;
|
|
311778
|
+
const grpcStatus = typeof errorRecord?.code === "number" ? errorRecord.code : resolved.code;
|
|
311779
|
+
const rawMessage = typeof errorRecord?.message === "string" ? errorRecord.message : resolved.message;
|
|
311780
|
+
const sanitizedMessage = redactSecretLiterals(sanitizeErrorDetail(rawMessage), exchangeSecretLiterals(broker));
|
|
311781
|
+
return {
|
|
311782
|
+
id: child.id,
|
|
311783
|
+
action: child.action,
|
|
311784
|
+
symbol: child.symbol,
|
|
311785
|
+
response: null,
|
|
311786
|
+
error: {
|
|
311787
|
+
code: stableBatchErrorCode(rawMessage, grpcStatus),
|
|
311788
|
+
grpcStatus,
|
|
311789
|
+
message: sanitizedMessage
|
|
311790
|
+
}
|
|
311791
|
+
};
|
|
311792
|
+
}
|
|
311793
|
+
function childCall(ctx, request) {
|
|
311794
|
+
return {
|
|
311795
|
+
...ctx.call,
|
|
311796
|
+
request
|
|
311797
|
+
};
|
|
311798
|
+
}
|
|
311799
|
+
async function executeChild(ctx, child, descriptor) {
|
|
311800
|
+
const action = resolveAction(child.action);
|
|
311801
|
+
if (action === undefined) {
|
|
311802
|
+
return batchErrorEntry(child, new Error(`ValidationError: invalid action ${child.action}`), ctx.broker);
|
|
311803
|
+
}
|
|
311804
|
+
const proofState = { proof: "" };
|
|
311805
|
+
let completion;
|
|
311806
|
+
const localCallback = (error48, response) => {
|
|
311807
|
+
if (completion === undefined) {
|
|
311808
|
+
completion = { error: error48, response: response ?? null };
|
|
311809
|
+
}
|
|
311810
|
+
};
|
|
311811
|
+
const request = {
|
|
311812
|
+
action,
|
|
311813
|
+
cex: ctx.cex,
|
|
311814
|
+
symbol: child.symbol,
|
|
311815
|
+
payload: child.payload
|
|
311816
|
+
};
|
|
311817
|
+
const childContext = {
|
|
311818
|
+
...ctx,
|
|
311819
|
+
call: childCall(ctx, request),
|
|
311820
|
+
wrappedCallback: localCallback,
|
|
311821
|
+
action,
|
|
311822
|
+
symbol: child.symbol,
|
|
311823
|
+
verity: proofState,
|
|
311824
|
+
applyVerityToBroker: (target) => ctx.applyVerityToBroker(target, proofState)
|
|
311825
|
+
};
|
|
311826
|
+
try {
|
|
311827
|
+
childContext.applyVerityToBroker(ctx.broker);
|
|
311828
|
+
await descriptor.handler(childContext);
|
|
311829
|
+
} catch (error48) {
|
|
311830
|
+
completion ??= {
|
|
311831
|
+
error: resolveGrpcError(error48),
|
|
311832
|
+
response: null
|
|
311833
|
+
};
|
|
311834
|
+
}
|
|
311835
|
+
if (completion === undefined) {
|
|
311836
|
+
return batchErrorEntry(child, new Error(`INTERNAL: ${getActionName(action)} completed without a callback`), ctx.broker);
|
|
311837
|
+
}
|
|
311838
|
+
if (completion.error || !completion.response) {
|
|
311839
|
+
return batchErrorEntry(child, completion.error ?? new Error("INTERNAL: child returned no response"), ctx.broker);
|
|
311840
|
+
}
|
|
311841
|
+
return {
|
|
311842
|
+
id: child.id,
|
|
311843
|
+
action: child.action,
|
|
311844
|
+
symbol: child.symbol,
|
|
311845
|
+
response: {
|
|
311846
|
+
result: completion.response.result,
|
|
311847
|
+
proof: completion.response.proof ?? proofState.proof
|
|
311848
|
+
},
|
|
311849
|
+
error: null
|
|
311850
|
+
};
|
|
311851
|
+
}
|
|
311852
|
+
async function handleBatch(ctx, lookupDescriptor) {
|
|
311853
|
+
if (ctx.symbol?.trim()) {
|
|
311854
|
+
return ctx.wrappedCallback({
|
|
311855
|
+
code: grpc5.status.INVALID_ARGUMENT,
|
|
311856
|
+
message: "ValidationError: Batch symbol must be empty"
|
|
311857
|
+
}, null);
|
|
311858
|
+
}
|
|
311859
|
+
const encodedRequests = ctx.call.request.payload?.requests;
|
|
311860
|
+
if (typeof encodedRequests === "string" && Buffer.byteLength(encodedRequests, "utf8") > MAX_BATCH_REQUEST_BYTES) {
|
|
311861
|
+
return ctx.wrappedCallback({
|
|
311862
|
+
code: grpc5.status.INVALID_ARGUMENT,
|
|
311863
|
+
message: `ValidationError: Batch requests exceed ${MAX_BATCH_REQUEST_BYTES} bytes`
|
|
311864
|
+
}, null);
|
|
311865
|
+
}
|
|
311866
|
+
const payload = parsePayloadForAction(ctx, BatchPayloadSchema);
|
|
311867
|
+
if (payload === null) {
|
|
311868
|
+
return;
|
|
311869
|
+
}
|
|
311870
|
+
const seenIds = new Set;
|
|
311871
|
+
const prepared = [];
|
|
311872
|
+
for (const child of payload.requests) {
|
|
311873
|
+
if (seenIds.has(child.id)) {
|
|
311874
|
+
return ctx.wrappedCallback({
|
|
311875
|
+
code: grpc5.status.INVALID_ARGUMENT,
|
|
311876
|
+
message: `ValidationError: duplicate batch child id '${child.id}'`
|
|
311877
|
+
}, null);
|
|
311878
|
+
}
|
|
311879
|
+
seenIds.add(child.id);
|
|
311880
|
+
if (childContainsRoutingOverride(child)) {
|
|
311881
|
+
return ctx.wrappedCallback({
|
|
311882
|
+
code: grpc5.status.INVALID_ARGUMENT,
|
|
311883
|
+
message: `ValidationError: batch child '${child.id}' contains a routing override`
|
|
311884
|
+
}, null);
|
|
311885
|
+
}
|
|
311886
|
+
const action = resolveAction(child.action);
|
|
311887
|
+
const descriptor = action === undefined ? undefined : lookupDescriptor(action);
|
|
311888
|
+
if (!descriptor || descriptor.access !== "read" || !descriptor.batchable) {
|
|
311889
|
+
return ctx.wrappedCallback({
|
|
311890
|
+
code: grpc5.status.INVALID_ARGUMENT,
|
|
311891
|
+
message: `ValidationError: batch child '${child.id}' action ${child.action} is not batchable`
|
|
311892
|
+
}, null);
|
|
311893
|
+
}
|
|
311894
|
+
const validation = descriptor.validateBatchRequest?.({
|
|
311895
|
+
action,
|
|
311896
|
+
cex: ctx.cex,
|
|
311897
|
+
symbol: child.symbol,
|
|
311898
|
+
payload: child.payload
|
|
311899
|
+
});
|
|
311900
|
+
if (validation && !validation.valid) {
|
|
311901
|
+
return ctx.wrappedCallback({
|
|
311902
|
+
code: grpc5.status.INVALID_ARGUMENT,
|
|
311903
|
+
message: `ValidationError: batch child '${child.id}': ${validation.message}`
|
|
311904
|
+
}, null);
|
|
311905
|
+
}
|
|
311906
|
+
prepared.push({ child, descriptor });
|
|
311907
|
+
}
|
|
311908
|
+
const responses = [];
|
|
311909
|
+
for (const { child, descriptor } of prepared) {
|
|
311910
|
+
const response = await executeChild(ctx, child, descriptor);
|
|
311911
|
+
responses.push(response);
|
|
311912
|
+
ctx.otelMetrics?.recordCounter("execute_action_batch_items_total", 1, {
|
|
311913
|
+
action: getActionName(child.action),
|
|
311914
|
+
cex: ctx.normalizedCex,
|
|
311915
|
+
outcome: response.error ? "error" : "success"
|
|
311916
|
+
});
|
|
311917
|
+
}
|
|
311918
|
+
const envelope = BatchResponseEnvelopeSchema.parse({
|
|
311919
|
+
schemaVersion: "cex-broker-action-batch/v1",
|
|
311920
|
+
responses
|
|
311921
|
+
});
|
|
311922
|
+
ctx.wrappedCallback(null, {
|
|
311923
|
+
result: JSON.stringify(envelope),
|
|
311924
|
+
proof: ""
|
|
311925
|
+
});
|
|
311926
|
+
}
|
|
311927
|
+
|
|
311928
|
+
// src/handlers/execute-action/internal-transfer.ts
|
|
311929
|
+
import * as grpc6 from "@grpc/grpc-js";
|
|
311266
311930
|
async function handleInternalTransfer(ctx) {
|
|
311267
311931
|
const {
|
|
311268
311932
|
brokers,
|
|
@@ -311276,7 +311940,7 @@ async function handleInternalTransfer(ctx) {
|
|
|
311276
311940
|
} = ctx;
|
|
311277
311941
|
if (!symbol2) {
|
|
311278
311942
|
return ctx.wrappedCallback({
|
|
311279
|
-
code:
|
|
311943
|
+
code: grpc6.status.INVALID_ARGUMENT,
|
|
311280
311944
|
message: `ValidationError: Symbol required`
|
|
311281
311945
|
}, null);
|
|
311282
311946
|
}
|
|
@@ -311285,14 +311949,14 @@ async function handleInternalTransfer(ctx) {
|
|
|
311285
311949
|
return;
|
|
311286
311950
|
if (normalizedCex !== "binance") {
|
|
311287
311951
|
return ctx.wrappedCallback({
|
|
311288
|
-
code:
|
|
311952
|
+
code: grpc6.status.UNIMPLEMENTED,
|
|
311289
311953
|
message: `InternalTransfer is only supported for Binance`
|
|
311290
311954
|
}, null);
|
|
311291
311955
|
}
|
|
311292
311956
|
const pool = brokers[normalizedCex];
|
|
311293
311957
|
if (!pool) {
|
|
311294
311958
|
return ctx.wrappedCallback({
|
|
311295
|
-
code:
|
|
311959
|
+
code: grpc6.status.FAILED_PRECONDITION,
|
|
311296
311960
|
message: `No broker accounts configured for ${normalizedCex}`
|
|
311297
311961
|
}, null);
|
|
311298
311962
|
}
|
|
@@ -311301,14 +311965,14 @@ async function handleInternalTransfer(ctx) {
|
|
|
311301
311965
|
const sourceAccount = resolveBrokerAccount(pool, fromSelector);
|
|
311302
311966
|
if (!sourceAccount) {
|
|
311303
311967
|
return ctx.wrappedCallback({
|
|
311304
|
-
code:
|
|
311968
|
+
code: grpc6.status.INVALID_ARGUMENT,
|
|
311305
311969
|
message: `Source account "${fromSelector}" is not configured`
|
|
311306
311970
|
}, null);
|
|
311307
311971
|
}
|
|
311308
311972
|
const destAccount = resolveBrokerAccount(pool, toSelector);
|
|
311309
311973
|
if (!destAccount) {
|
|
311310
311974
|
return ctx.wrappedCallback({
|
|
311311
|
-
code:
|
|
311975
|
+
code: grpc6.status.INVALID_ARGUMENT,
|
|
311312
311976
|
message: `Destination account "${toSelector}" is not configured`
|
|
311313
311977
|
}, null);
|
|
311314
311978
|
}
|
|
@@ -311342,18 +312006,18 @@ async function handleInternalTransfer(ctx) {
|
|
|
311342
312006
|
safeLogError("InternalTransfer failed", error48);
|
|
311343
312007
|
if (error48 instanceof BrokerAccountPreconditionError) {
|
|
311344
312008
|
return ctx.wrappedCallback({
|
|
311345
|
-
code:
|
|
312009
|
+
code: grpc6.status.FAILED_PRECONDITION,
|
|
311346
312010
|
message: getErrorMessage(error48)
|
|
311347
312011
|
}, null);
|
|
311348
312012
|
}
|
|
311349
312013
|
const msg = getErrorMessage(error48);
|
|
311350
312014
|
let code;
|
|
311351
312015
|
if (msg.includes("Unsupported transfer direction")) {
|
|
311352
|
-
code =
|
|
312016
|
+
code = grpc6.status.INVALID_ARGUMENT;
|
|
311353
312017
|
} else if (msg.includes("unavailable in this CCXT build")) {
|
|
311354
|
-
code =
|
|
312018
|
+
code = grpc6.status.UNIMPLEMENTED;
|
|
311355
312019
|
} else {
|
|
311356
|
-
code = mapCcxtErrorToGrpcStatus(error48) ??
|
|
312020
|
+
code = mapCcxtErrorToGrpcStatus(error48) ?? grpc6.status.INTERNAL;
|
|
311357
312021
|
}
|
|
311358
312022
|
ctx.wrappedCallback({
|
|
311359
312023
|
code,
|
|
@@ -311363,7 +312027,7 @@ async function handleInternalTransfer(ctx) {
|
|
|
311363
312027
|
}
|
|
311364
312028
|
|
|
311365
312029
|
// src/handlers/execute-action/orders.ts
|
|
311366
|
-
import * as
|
|
312030
|
+
import * as grpc7 from "@grpc/grpc-js";
|
|
311367
312031
|
|
|
311368
312032
|
// src/helpers/passive-order.ts
|
|
311369
312033
|
var PASSIVE_ORDER_ERROR_CODES = {
|
|
@@ -311424,7 +312088,7 @@ async function handleCreateOrder(ctx) {
|
|
|
311424
312088
|
const isPassiveOrder = orderValue.orderIntent === "passive_only";
|
|
311425
312089
|
if (isPassiveOrder && orderValue.orderType !== "limit") {
|
|
311426
312090
|
return ctx.wrappedCallback({
|
|
311427
|
-
code:
|
|
312091
|
+
code: grpc7.status.INVALID_ARGUMENT,
|
|
311428
312092
|
message: "ValidationError: passive_only order intent requires a limit order"
|
|
311429
312093
|
}, null);
|
|
311430
312094
|
}
|
|
@@ -311441,14 +312105,14 @@ async function handleCreateOrder(ctx) {
|
|
|
311441
312105
|
try {
|
|
311442
312106
|
if (!broker) {
|
|
311443
312107
|
return ctx.wrappedCallback({
|
|
311444
|
-
code:
|
|
312108
|
+
code: grpc7.status.INVALID_ARGUMENT,
|
|
311445
312109
|
message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
|
|
311446
312110
|
}, null);
|
|
311447
312111
|
}
|
|
311448
312112
|
const resolution = await resolveOrderExecution(policy, broker, cex3, orderValue.fromToken, orderValue.toToken, orderValue.amount, orderValue.price, orderValue.marketType);
|
|
311449
312113
|
if (!resolution.valid || !resolution.symbol || !resolution.side) {
|
|
311450
312114
|
return ctx.wrappedCallback({
|
|
311451
|
-
code:
|
|
312115
|
+
code: grpc7.status.INVALID_ARGUMENT,
|
|
311452
312116
|
message: resolution.error ?? "Order rejected by policy: market or limits not satisfied"
|
|
311453
312117
|
}, null);
|
|
311454
312118
|
}
|
|
@@ -311521,7 +312185,7 @@ async function handleCreateOrder(ctx) {
|
|
|
311521
312185
|
});
|
|
311522
312186
|
}
|
|
311523
312187
|
ctx.wrappedCallback({
|
|
311524
|
-
code:
|
|
312188
|
+
code: grpc7.status.INTERNAL,
|
|
311525
312189
|
message: `Order Creation failed: ${sanitizeErrorDetail(error48)}`
|
|
311526
312190
|
}, null);
|
|
311527
312191
|
}
|
|
@@ -311553,7 +312217,7 @@ async function handleGetOrderDetails(ctx) {
|
|
|
311553
312217
|
try {
|
|
311554
312218
|
if (!broker) {
|
|
311555
312219
|
return ctx.wrappedCallback({
|
|
311556
|
-
code:
|
|
312220
|
+
code: grpc7.status.INVALID_ARGUMENT,
|
|
311557
312221
|
message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
|
|
311558
312222
|
}, null);
|
|
311559
312223
|
}
|
|
@@ -311594,7 +312258,7 @@ async function handleGetOrderDetails(ctx) {
|
|
|
311594
312258
|
emitOrderExecutionTelemetryInBackground(otelMetrics, failedGetOrderContext, undefined, error48);
|
|
311595
312259
|
archiveOrderExecutionInBackground(brokerArchiver, failedGetOrderContext, undefined, error48);
|
|
311596
312260
|
ctx.wrappedCallback({
|
|
311597
|
-
code:
|
|
312261
|
+
code: grpc7.status.INTERNAL,
|
|
311598
312262
|
message: `Failed to fetch order details from ${cex3}: ${sanitizeErrorDetail(error48)}`
|
|
311599
312263
|
}, null);
|
|
311600
312264
|
}
|
|
@@ -311626,7 +312290,7 @@ async function handleCancelOrder(ctx) {
|
|
|
311626
312290
|
try {
|
|
311627
312291
|
if (!broker) {
|
|
311628
312292
|
return ctx.wrappedCallback({
|
|
311629
|
-
code:
|
|
312293
|
+
code: grpc7.status.INVALID_ARGUMENT,
|
|
311630
312294
|
message: `Invalid CEX key: ${cex3}. Supported keys: ${Object.keys(brokers).join(", ")}`
|
|
311631
312295
|
}, null);
|
|
311632
312296
|
}
|
|
@@ -311658,7 +312322,7 @@ async function handleCancelOrder(ctx) {
|
|
|
311658
312322
|
emitOrderExecutionTelemetryInBackground(otelMetrics, failedCancelContext, undefined, error48);
|
|
311659
312323
|
archiveOrderExecutionInBackground(brokerArchiver, failedCancelContext, undefined, error48);
|
|
311660
312324
|
ctx.wrappedCallback({
|
|
311661
|
-
code:
|
|
312325
|
+
code: grpc7.status.INTERNAL,
|
|
311662
312326
|
message: `Failed to cancel order from ${cex3}: ${sanitizeErrorDetail(error48)}`
|
|
311663
312327
|
}, null);
|
|
311664
312328
|
}
|
|
@@ -311673,261 +312337,254 @@ async function handleOrders(ctx) {
|
|
|
311673
312337
|
}
|
|
311674
312338
|
|
|
311675
312339
|
// src/handlers/execute-action/pass-through.ts
|
|
311676
|
-
import * as
|
|
311677
|
-
|
|
311678
|
-
|
|
311679
|
-
|
|
311680
|
-
|
|
311681
|
-
|
|
311682
|
-
|
|
311683
|
-
|
|
311684
|
-
|
|
311685
|
-
|
|
311686
|
-
|
|
311687
|
-
|
|
311688
|
-
|
|
311689
|
-
|
|
311690
|
-
|
|
311691
|
-
|
|
311692
|
-
|
|
311693
|
-
|
|
311694
|
-
|
|
311695
|
-
|
|
311696
|
-
|
|
312340
|
+
import * as grpc9 from "@grpc/grpc-js";
|
|
312341
|
+
|
|
312342
|
+
// src/handlers/execute-action/venue-evidence.ts
|
|
312343
|
+
import * as grpc8 from "@grpc/grpc-js";
|
|
312344
|
+
function failVenueDiscovery(ctx, error48, operation) {
|
|
312345
|
+
safeLogRedactedError(`${operation} failed`, error48);
|
|
312346
|
+
const sanitized = sanitizeVenueError(error48, ctx.broker);
|
|
312347
|
+
const message = sanitized.startsWith("venue_discovery_unavailable:") ? sanitized : `venue_discovery_unavailable: ${sanitized}`;
|
|
312348
|
+
ctx.wrappedCallback({
|
|
312349
|
+
code: stableGrpcErrorCode(message) ?? mapCcxtErrorToGrpcStatus(error48) ?? grpc8.status.UNIMPLEMENTED,
|
|
312350
|
+
message
|
|
312351
|
+
}, null);
|
|
312352
|
+
}
|
|
312353
|
+
function optionalDecimalFields(values2) {
|
|
312354
|
+
const result = {};
|
|
312355
|
+
for (const [key2, value, field] of values2) {
|
|
312356
|
+
const normalized = canonicalOptionalDecimal(value, field);
|
|
312357
|
+
if (normalized !== undefined) {
|
|
312358
|
+
result[key2] = normalized;
|
|
312359
|
+
}
|
|
312360
|
+
}
|
|
312361
|
+
return result;
|
|
312362
|
+
}
|
|
312363
|
+
async function handleFetchFeesEvidence(ctx) {
|
|
312364
|
+
if (!requireSymbol(ctx, "ValidationError: symbol must be a slash-delimited spot pair")) {
|
|
312365
|
+
return;
|
|
312366
|
+
}
|
|
312367
|
+
if (!/^[^/\s]+\/[^/\s]+$/.test(ctx.symbol.trim())) {
|
|
311697
312368
|
return ctx.wrappedCallback({
|
|
311698
|
-
code:
|
|
311699
|
-
message:
|
|
312369
|
+
code: grpc8.status.INVALID_ARGUMENT,
|
|
312370
|
+
message: "ValidationError: symbol must be a slash-delimited spot pair"
|
|
311700
312371
|
}, null);
|
|
311701
312372
|
}
|
|
312373
|
+
if (parsePayloadForAction(ctx, FetchFeesPayloadSchema) === null) {
|
|
312374
|
+
return;
|
|
312375
|
+
}
|
|
311702
312376
|
try {
|
|
311703
|
-
const
|
|
311704
|
-
const
|
|
311705
|
-
if (
|
|
311706
|
-
|
|
311707
|
-
|
|
311708
|
-
|
|
311709
|
-
|
|
311710
|
-
|
|
311711
|
-
|
|
311712
|
-
|
|
311713
|
-
|
|
311714
|
-
|
|
311715
|
-
|
|
311716
|
-
|
|
311717
|
-
|
|
311718
|
-
|
|
311719
|
-
|
|
311720
|
-
|
|
311721
|
-
|
|
311722
|
-
|
|
312377
|
+
const identity2 = await resolveSpotMarketIdentity(ctx.broker, ctx.symbol);
|
|
312378
|
+
const exchange = evidenceExchange(ctx.broker);
|
|
312379
|
+
if (exchange.has?.fetchTradingFee === false || typeof exchange.fetchTradingFee !== "function") {
|
|
312380
|
+
throw new Error(`fee_unavailable: ${ctx.normalizedCex} does not support fetchTradingFee`);
|
|
312381
|
+
}
|
|
312382
|
+
const sourceResponse = await exchange.fetchTradingFee(identity2.unifiedSymbol);
|
|
312383
|
+
if (isRecord(sourceResponse) && typeof sourceResponse.symbol === "string" && sourceResponse.symbol.trim().toUpperCase() !== identity2.unifiedSymbol) {
|
|
312384
|
+
throw new Error(`fee_unavailable: trading-fee response symbol does not match ${identity2.unifiedSymbol}`);
|
|
312385
|
+
}
|
|
312386
|
+
const { makerRate, takerRate } = extractTradingFeeRates(sourceResponse);
|
|
312387
|
+
const accountScope = resolveEvidenceAccountScope(ctx.selectedBrokerAccount, ctx.metadata);
|
|
312388
|
+
const evidence = TradingFeeEvidenceSchema.parse({
|
|
312389
|
+
schemaVersion: "cex-trading-fee-evidence/v1",
|
|
312390
|
+
exchange: ctx.normalizedCex,
|
|
312391
|
+
marketType: "spot",
|
|
312392
|
+
canonicalPair: identity2.canonicalPair,
|
|
312393
|
+
unifiedSymbol: identity2.unifiedSymbol,
|
|
312394
|
+
sourceSymbol: identity2.sourceSymbol,
|
|
312395
|
+
...accountScope,
|
|
312396
|
+
observedAt: new Date().toISOString(),
|
|
312397
|
+
sourceMethod: "ccxt.fetchTradingFee",
|
|
312398
|
+
makerRate,
|
|
312399
|
+
takerRate,
|
|
312400
|
+
rateUnit: "decimal_fraction",
|
|
312401
|
+
makerBasisPoints: decimalFractionToBasisPoints(makerRate),
|
|
312402
|
+
takerBasisPoints: decimalFractionToBasisPoints(takerRate),
|
|
312403
|
+
basisPointsUnit: "basis_points",
|
|
312404
|
+
digestAlgorithm: "sha256-canonical-json-v1",
|
|
312405
|
+
sourceDigest: evidenceSourceDigest({
|
|
312406
|
+
action: "FetchFees",
|
|
312407
|
+
exchange: ctx.normalizedCex,
|
|
312408
|
+
requestedKey: identity2.canonicalPair,
|
|
312409
|
+
accountSelector: accountScope.accountSelector,
|
|
312410
|
+
sourceMethod: "ccxt.fetchTradingFee",
|
|
312411
|
+
source: sourceResponse,
|
|
312412
|
+
broker: ctx.broker
|
|
311723
312413
|
})
|
|
311724
312414
|
});
|
|
312415
|
+
successWithProof(ctx, evidence);
|
|
311725
312416
|
} catch (error48) {
|
|
311726
|
-
|
|
311727
|
-
const
|
|
312417
|
+
safeLogRedactedError(`FetchFees failed for ${ctx.normalizedCex}/${ctx.symbol}`, error48);
|
|
312418
|
+
const sanitized = sanitizeVenueError(error48, ctx.broker);
|
|
312419
|
+
const message = sanitized.startsWith("fee_unavailable:") ? sanitized : `fee_unavailable: ${sanitized}`;
|
|
311728
312420
|
ctx.wrappedCallback({
|
|
311729
|
-
code:
|
|
311730
|
-
message
|
|
312421
|
+
code: grpc8.status.FAILED_PRECONDITION,
|
|
312422
|
+
message
|
|
311731
312423
|
}, null);
|
|
311732
312424
|
}
|
|
311733
312425
|
}
|
|
311734
|
-
async function
|
|
311735
|
-
|
|
311736
|
-
|
|
311737
|
-
|
|
311738
|
-
|
|
311739
|
-
|
|
311740
|
-
|
|
311741
|
-
|
|
311742
|
-
|
|
311743
|
-
|
|
311744
|
-
|
|
311745
|
-
|
|
311746
|
-
|
|
311747
|
-
applyVerityToBroker,
|
|
311748
|
-
useVerity,
|
|
311749
|
-
verityProverUrl,
|
|
311750
|
-
otelMetrics
|
|
311751
|
-
} = ctx;
|
|
311752
|
-
const verityProof = verity.proof;
|
|
312426
|
+
async function handleFetchMarketRulesEvidence(ctx) {
|
|
312427
|
+
if (!requireSymbol(ctx, "ValidationError: symbol must be a slash-delimited spot pair")) {
|
|
312428
|
+
return;
|
|
312429
|
+
}
|
|
312430
|
+
if (!/^[^/\s]+\/[^/\s]+$/.test(ctx.symbol.trim())) {
|
|
312431
|
+
return ctx.wrappedCallback({
|
|
312432
|
+
code: grpc8.status.INVALID_ARGUMENT,
|
|
312433
|
+
message: "ValidationError: symbol must be a slash-delimited spot pair"
|
|
312434
|
+
}, null);
|
|
312435
|
+
}
|
|
312436
|
+
if (parsePayloadForAction(ctx, EmptyActionPayloadSchema) === null) {
|
|
312437
|
+
return;
|
|
312438
|
+
}
|
|
311753
312439
|
try {
|
|
311754
|
-
const
|
|
311755
|
-
|
|
311756
|
-
|
|
311757
|
-
|
|
312440
|
+
const identity2 = await resolveSpotMarketIdentity(ctx.broker, ctx.symbol);
|
|
312441
|
+
const precision = isRecord(identity2.market.precision) ? identity2.market.precision : {};
|
|
312442
|
+
const limits = isRecord(identity2.market.limits) ? identity2.market.limits : {};
|
|
312443
|
+
const amountLimits = isRecord(limits.amount) ? limits.amount : {};
|
|
312444
|
+
const priceLimits = isRecord(limits.price) ? limits.price : {};
|
|
312445
|
+
const costLimits = isRecord(limits.cost) ? limits.cost : {};
|
|
312446
|
+
const precisionMode = evidenceExchange(ctx.broker).precisionMode;
|
|
312447
|
+
if (precisionMode === undefined || precisionMode === null) {
|
|
312448
|
+
throw new Error("venue_discovery_unavailable: precision mode is unavailable");
|
|
312449
|
+
}
|
|
312450
|
+
const accountScope = resolveEvidenceAccountScope(ctx.selectedBrokerAccount, ctx.metadata);
|
|
312451
|
+
const evidence = MarketRuleEvidenceSchema.parse({
|
|
312452
|
+
schemaVersion: "cex-market-rule-evidence/v1",
|
|
312453
|
+
exchange: ctx.normalizedCex,
|
|
312454
|
+
marketType: "spot",
|
|
312455
|
+
canonicalPair: identity2.canonicalPair,
|
|
312456
|
+
unifiedSymbol: identity2.unifiedSymbol,
|
|
312457
|
+
sourceSymbol: identity2.sourceSymbol,
|
|
312458
|
+
baseAsset: identity2.baseAsset,
|
|
312459
|
+
quoteAsset: identity2.quoteAsset,
|
|
312460
|
+
active: true,
|
|
312461
|
+
precisionMode,
|
|
312462
|
+
priceIncrement: precisionIncrement(precision.price, precisionMode, "price increment"),
|
|
312463
|
+
amountIncrement: precisionIncrement(precision.amount, precisionMode, "amount increment"),
|
|
312464
|
+
minimumAmount: canonicalNonnegativeDecimal(amountLimits.min, "minimum amount"),
|
|
312465
|
+
minimumNotional: canonicalNonnegativeDecimal(costLimits.min, "minimum notional"),
|
|
312466
|
+
...optionalDecimalFields([
|
|
312467
|
+
["maximumAmount", amountLimits.max, "maximum amount"],
|
|
312468
|
+
["maximumPrice", priceLimits.max, "maximum price"],
|
|
312469
|
+
["maximumNotional", costLimits.max, "maximum notional"]
|
|
312470
|
+
]),
|
|
312471
|
+
...accountScope,
|
|
312472
|
+
observedAt: new Date().toISOString(),
|
|
312473
|
+
sourceMethod: "ccxt.loadMarkets",
|
|
312474
|
+
digestAlgorithm: "sha256-canonical-json-v1",
|
|
312475
|
+
sourceDigest: evidenceSourceDigest({
|
|
312476
|
+
action: "FetchMarketRules",
|
|
312477
|
+
exchange: ctx.normalizedCex,
|
|
312478
|
+
requestedKey: identity2.canonicalPair,
|
|
312479
|
+
accountSelector: accountScope.accountSelector,
|
|
312480
|
+
sourceMethod: "ccxt.loadMarkets",
|
|
312481
|
+
source: identity2.market,
|
|
312482
|
+
broker: ctx.broker
|
|
312483
|
+
})
|
|
311758
312484
|
});
|
|
312485
|
+
successWithProof(ctx, evidence);
|
|
311759
312486
|
} catch (error48) {
|
|
311760
|
-
|
|
311761
|
-
ctx.wrappedCallback({
|
|
311762
|
-
code: grpc7.status.INTERNAL,
|
|
311763
|
-
message: `Error fetching account ID from ${cex3}`
|
|
311764
|
-
}, null);
|
|
312487
|
+
failVenueDiscovery(ctx, error48, `FetchMarketRules ${ctx.normalizedCex}/${ctx.symbol}`);
|
|
311765
312488
|
}
|
|
311766
312489
|
}
|
|
311767
|
-
async function
|
|
311768
|
-
|
|
311769
|
-
|
|
311770
|
-
wrappedCallback,
|
|
311771
|
-
policy,
|
|
311772
|
-
brokers,
|
|
311773
|
-
metadata,
|
|
311774
|
-
normalizedCex,
|
|
311775
|
-
cex: cex3,
|
|
311776
|
-
symbol: symbol2,
|
|
311777
|
-
selectedBrokerAccount,
|
|
311778
|
-
broker,
|
|
311779
|
-
verity,
|
|
311780
|
-
applyVerityToBroker,
|
|
311781
|
-
useVerity,
|
|
311782
|
-
verityProverUrl,
|
|
311783
|
-
otelMetrics
|
|
311784
|
-
} = ctx;
|
|
311785
|
-
const verityProof = verity.proof;
|
|
311786
|
-
if (!symbol2) {
|
|
311787
|
-
return ctx.wrappedCallback({
|
|
311788
|
-
code: grpc7.status.INVALID_ARGUMENT,
|
|
311789
|
-
message: `ValidationError: Symbol required`
|
|
311790
|
-
}, null);
|
|
312490
|
+
async function handleFetchCurrencyEvidence(ctx) {
|
|
312491
|
+
if (!requireSymbol(ctx)) {
|
|
312492
|
+
return;
|
|
311791
312493
|
}
|
|
311792
|
-
const
|
|
311793
|
-
if (
|
|
312494
|
+
const payload = parsePayloadForAction(ctx, FetchCurrencyPayloadSchema);
|
|
312495
|
+
if (payload === null) {
|
|
311794
312496
|
return;
|
|
311795
|
-
|
|
312497
|
+
}
|
|
312498
|
+
const asset = ctx.symbol.trim().toUpperCase();
|
|
312499
|
+
const requestedAlias = payload.network.trim().toUpperCase();
|
|
311796
312500
|
try {
|
|
311797
|
-
|
|
311798
|
-
|
|
311799
|
-
|
|
311800
|
-
|
|
311801
|
-
|
|
311802
|
-
|
|
311803
|
-
|
|
311804
|
-
|
|
311805
|
-
|
|
311806
|
-
|
|
311807
|
-
|
|
311808
|
-
|
|
311809
|
-
|
|
311810
|
-
|
|
311811
|
-
|
|
311812
|
-
|
|
311813
|
-
|
|
311814
|
-
|
|
311815
|
-
|
|
311816
|
-
|
|
311817
|
-
|
|
311818
|
-
|
|
311819
|
-
|
|
311820
|
-
|
|
311821
|
-
|
|
311822
|
-
|
|
311823
|
-
|
|
311824
|
-
|
|
311825
|
-
|
|
311826
|
-
|
|
311827
|
-
|
|
311828
|
-
|
|
311829
|
-
|
|
311830
|
-
|
|
311831
|
-
|
|
311832
|
-
|
|
311833
|
-
|
|
311834
|
-
|
|
311835
|
-
|
|
311836
|
-
|
|
311837
|
-
|
|
311838
|
-
|
|
311839
|
-
|
|
311840
|
-
|
|
311841
|
-
|
|
311842
|
-
|
|
311843
|
-
|
|
311844
|
-
|
|
311845
|
-
|
|
311846
|
-
|
|
311847
|
-
|
|
311848
|
-
|
|
311849
|
-
|
|
311850
|
-
|
|
311851
|
-
|
|
311852
|
-
|
|
311853
|
-
|
|
311854
|
-
}
|
|
311855
|
-
|
|
311856
|
-
|
|
311857
|
-
|
|
311858
|
-
|
|
311859
|
-
|
|
311860
|
-
|
|
311861
|
-
|
|
311862
|
-
|
|
311863
|
-
|
|
311864
|
-
|
|
311865
|
-
|
|
311866
|
-
|
|
311867
|
-
feeScope: "market",
|
|
311868
|
-
generalFee,
|
|
311869
|
-
feeStatus,
|
|
311870
|
-
market
|
|
311871
|
-
})
|
|
311872
|
-
});
|
|
311873
|
-
}
|
|
311874
|
-
const currencyCodes = Array.from(new Set([market.base, market.quote]));
|
|
311875
|
-
const { fundingFeeSource: fundingFeeSource2, fundingFeesByCurrency: fundingFeesByCurrency2 } = await fetchFundingFees(currencyCodes);
|
|
311876
|
-
return ctx.wrappedCallback(null, {
|
|
311877
|
-
proof: ctx.verity.proof,
|
|
311878
|
-
result: JSON.stringify({
|
|
311879
|
-
feeScope: "market+funding",
|
|
311880
|
-
generalFee,
|
|
311881
|
-
feeStatus,
|
|
311882
|
-
market,
|
|
311883
|
-
fundingFeeSource: fundingFeeSource2,
|
|
311884
|
-
fundingFeesByCurrency: fundingFeesByCurrency2
|
|
311885
|
-
})
|
|
311886
|
-
});
|
|
311887
|
-
}
|
|
311888
|
-
const tokenCode = symbol2.toUpperCase();
|
|
311889
|
-
const { fundingFeeSource, fundingFeesByCurrency } = await fetchFundingFees([
|
|
311890
|
-
tokenCode
|
|
311891
|
-
]);
|
|
312501
|
+
const exchange = evidenceExchange(ctx.broker);
|
|
312502
|
+
if (exchange.has?.fetchCurrencies === false || typeof exchange.fetchCurrencies !== "function") {
|
|
312503
|
+
throw new Error(`venue_discovery_unavailable: fetchCurrencies unavailable for ${asset}`);
|
|
312504
|
+
}
|
|
312505
|
+
const currencies = await exchange.fetchCurrencies();
|
|
312506
|
+
const currency = currencies[asset];
|
|
312507
|
+
if (!isRecord(currency)) {
|
|
312508
|
+
throw new Error(`venue_discovery_unavailable: currency not found for ${asset}`);
|
|
312509
|
+
}
|
|
312510
|
+
const networkEvidence = buildTransferNetworkEvidence(currency);
|
|
312511
|
+
const brokerNetworkId = normalizeBrokerNetworkId(requestedAlias);
|
|
312512
|
+
const resolution = networkEvidence.aliases[requestedAlias] ?? networkEvidence.aliases[brokerNetworkId];
|
|
312513
|
+
if (!resolution?.networkKey) {
|
|
312514
|
+
throw new Error(`network_alias_unresolved: ${asset}/${requestedAlias} is not available in discovered transfer networks`);
|
|
312515
|
+
}
|
|
312516
|
+
const networks = isRecord(currency.networks) ? currency.networks : {};
|
|
312517
|
+
const networkCandidate = networks[resolution.networkKey];
|
|
312518
|
+
if (!isRecord(networkCandidate) || typeof networkCandidate.deposit !== "boolean" || typeof networkCandidate.withdraw !== "boolean") {
|
|
312519
|
+
throw new Error(`venue_discovery_unavailable: transfer availability is incomplete for ${asset}/${requestedAlias}`);
|
|
312520
|
+
}
|
|
312521
|
+
const network = networkCandidate;
|
|
312522
|
+
const limits = isRecord(network.limits) ? network.limits : {};
|
|
312523
|
+
const withdrawalLimits = isRecord(limits.withdraw) ? limits.withdraw : {};
|
|
312524
|
+
const accountScope = resolveEvidenceAccountScope(ctx.selectedBrokerAccount, ctx.metadata);
|
|
312525
|
+
const evidence = TransferNetworkEvidenceSchema.parse({
|
|
312526
|
+
schemaVersion: "cex-transfer-network-evidence/v1",
|
|
312527
|
+
exchange: ctx.normalizedCex,
|
|
312528
|
+
asset,
|
|
312529
|
+
operatorNetworkAlias: requestedAlias,
|
|
312530
|
+
brokerNetworkId,
|
|
312531
|
+
exchangeNetworkId: resolution.exchangeNetworkId,
|
|
312532
|
+
depositAvailable: network.deposit,
|
|
312533
|
+
withdrawalAvailable: network.withdraw,
|
|
312534
|
+
withdrawalFee: canonicalOptionalDecimal(network.fee, "withdrawal fee") ?? null,
|
|
312535
|
+
withdrawalLimits: {
|
|
312536
|
+
minimum: canonicalOptionalDecimal(withdrawalLimits.min, "minimum withdrawal") ?? null,
|
|
312537
|
+
maximum: canonicalOptionalDecimal(withdrawalLimits.max, "maximum withdrawal") ?? null
|
|
312538
|
+
},
|
|
312539
|
+
...accountScope,
|
|
312540
|
+
observedAt: new Date().toISOString(),
|
|
312541
|
+
sourceMethod: "ccxt.fetchCurrencies",
|
|
312542
|
+
digestAlgorithm: "sha256-canonical-json-v1",
|
|
312543
|
+
sourceDigest: evidenceSourceDigest({
|
|
312544
|
+
action: "FetchCurrency",
|
|
312545
|
+
exchange: ctx.normalizedCex,
|
|
312546
|
+
requestedKey: `${asset}/${requestedAlias}`,
|
|
312547
|
+
accountSelector: accountScope.accountSelector,
|
|
312548
|
+
sourceMethod: "ccxt.fetchCurrencies",
|
|
312549
|
+
source: network,
|
|
312550
|
+
broker: ctx.broker
|
|
312551
|
+
})
|
|
312552
|
+
});
|
|
312553
|
+
successWithProof(ctx, evidence);
|
|
312554
|
+
} catch (error48) {
|
|
312555
|
+
safeLogRedactedError(`FetchCurrency failed for ${ctx.normalizedCex}/${asset}/${requestedAlias}`, error48);
|
|
312556
|
+
const sanitized = sanitizeVenueError(error48, ctx.broker);
|
|
312557
|
+
const isAliasError = sanitized.startsWith("network_alias_unresolved:");
|
|
312558
|
+
const message = isAliasError || sanitized.startsWith("venue_discovery_unavailable:") ? sanitized : `venue_discovery_unavailable: ${sanitized}`;
|
|
312559
|
+
ctx.wrappedCallback({
|
|
312560
|
+
code: stableGrpcErrorCode(message) ?? mapCcxtErrorToGrpcStatus(error48) ?? grpc8.status.UNIMPLEMENTED,
|
|
312561
|
+
message
|
|
312562
|
+
}, null);
|
|
312563
|
+
}
|
|
312564
|
+
}
|
|
312565
|
+
|
|
312566
|
+
// src/handlers/execute-action/pass-through.ts
|
|
312567
|
+
async function handleFetchAccountId(ctx) {
|
|
312568
|
+
const { cex: cex3, broker } = ctx;
|
|
312569
|
+
try {
|
|
312570
|
+
const accountId = await broker.fetchAccountId();
|
|
311892
312571
|
return ctx.wrappedCallback(null, {
|
|
311893
312572
|
proof: ctx.verity.proof,
|
|
311894
|
-
result: JSON.stringify({
|
|
311895
|
-
feeScope: "token",
|
|
311896
|
-
symbol: tokenCode,
|
|
311897
|
-
fundingFeeSource,
|
|
311898
|
-
fundingFeesByCurrency
|
|
311899
|
-
})
|
|
312573
|
+
result: JSON.stringify({ accountId })
|
|
311900
312574
|
});
|
|
311901
312575
|
} catch (error48) {
|
|
311902
|
-
safeLogError(`Error fetching
|
|
312576
|
+
safeLogError(`Error fetching account ID ${cex3}`, error48);
|
|
311903
312577
|
ctx.wrappedCallback({
|
|
311904
|
-
code:
|
|
311905
|
-
message: `Error fetching
|
|
312578
|
+
code: grpc9.status.INTERNAL,
|
|
312579
|
+
message: `Error fetching account ID from ${cex3}`
|
|
311906
312580
|
}, null);
|
|
311907
312581
|
}
|
|
311908
312582
|
}
|
|
311909
312583
|
async function handleFetchDepositAddresses(ctx) {
|
|
311910
|
-
const {
|
|
311911
|
-
call,
|
|
311912
|
-
wrappedCallback,
|
|
311913
|
-
policy,
|
|
311914
|
-
brokers,
|
|
311915
|
-
metadata,
|
|
311916
|
-
normalizedCex,
|
|
311917
|
-
cex: cex3,
|
|
311918
|
-
symbol: symbol2,
|
|
311919
|
-
selectedBrokerAccount,
|
|
311920
|
-
broker,
|
|
311921
|
-
verity,
|
|
311922
|
-
applyVerityToBroker,
|
|
311923
|
-
useVerity,
|
|
311924
|
-
verityProverUrl,
|
|
311925
|
-
otelMetrics
|
|
311926
|
-
} = ctx;
|
|
311927
|
-
const verityProof = verity.proof;
|
|
312584
|
+
const { policy, cex: cex3, symbol: symbol2, broker } = ctx;
|
|
311928
312585
|
if (!symbol2) {
|
|
311929
312586
|
return ctx.wrappedCallback({
|
|
311930
|
-
code:
|
|
312587
|
+
code: grpc9.status.INVALID_ARGUMENT,
|
|
311931
312588
|
message: `ValidationError: Symbol required`
|
|
311932
312589
|
}, null);
|
|
311933
312590
|
}
|
|
@@ -311940,14 +312597,14 @@ async function handleFetchDepositAddresses(ctx) {
|
|
|
311940
312597
|
} catch (error48) {
|
|
311941
312598
|
const message = getErrorMessage(error48);
|
|
311942
312599
|
return ctx.wrappedCallback({
|
|
311943
|
-
code: stableGrpcErrorCode(message) ??
|
|
312600
|
+
code: stableGrpcErrorCode(message) ?? grpc9.status.INVALID_ARGUMENT,
|
|
311944
312601
|
message
|
|
311945
312602
|
}, null);
|
|
311946
312603
|
}
|
|
311947
312604
|
const depositValidation = validateDeposit(policy, cex3, depositNetwork.brokerNetworkId, symbol2);
|
|
311948
312605
|
if (!depositValidation.valid) {
|
|
311949
312606
|
return ctx.wrappedCallback({
|
|
311950
|
-
code:
|
|
312607
|
+
code: grpc9.status.PERMISSION_DENIED,
|
|
311951
312608
|
message: `policy_deposit_denied: ${depositValidation.error}`
|
|
311952
312609
|
}, null);
|
|
311953
312610
|
}
|
|
@@ -311973,45 +312630,30 @@ async function handleFetchDepositAddresses(ctx) {
|
|
|
311973
312630
|
});
|
|
311974
312631
|
}
|
|
311975
312632
|
ctx.wrappedCallback({
|
|
311976
|
-
code:
|
|
312633
|
+
code: grpc9.status.INTERNAL,
|
|
311977
312634
|
message: "Deposit confirmation failed"
|
|
311978
312635
|
}, null);
|
|
311979
312636
|
} catch (error48) {
|
|
311980
312637
|
safeLogError("Fetch Deposit Addresses confirmation failed", error48);
|
|
311981
312638
|
const message = getErrorMessage(error48);
|
|
311982
312639
|
ctx.wrappedCallback({
|
|
311983
|
-
code:
|
|
312640
|
+
code: grpc9.status.INTERNAL,
|
|
311984
312641
|
message: "Fetch Deposit Addresses confirmation failed: " + message
|
|
311985
312642
|
}, null);
|
|
311986
312643
|
}
|
|
311987
312644
|
}
|
|
311988
312645
|
async function handleFetchBalances(ctx) {
|
|
311989
|
-
const {
|
|
311990
|
-
call,
|
|
311991
|
-
wrappedCallback,
|
|
311992
|
-
policy,
|
|
311993
|
-
brokers,
|
|
311994
|
-
metadata,
|
|
311995
|
-
normalizedCex,
|
|
311996
|
-
cex: cex3,
|
|
311997
|
-
symbol: symbol2,
|
|
311998
|
-
selectedBrokerAccount,
|
|
311999
|
-
broker,
|
|
312000
|
-
verity,
|
|
312001
|
-
applyVerityToBroker,
|
|
312002
|
-
useVerity,
|
|
312003
|
-
verityProverUrl,
|
|
312004
|
-
otelMetrics
|
|
312005
|
-
} = ctx;
|
|
312006
|
-
const verityProof = verity.proof;
|
|
312646
|
+
const { call, cex: cex3, symbol: symbol2, broker } = ctx;
|
|
312007
312647
|
try {
|
|
312008
|
-
const payload =
|
|
312009
|
-
|
|
312648
|
+
const payload = {
|
|
312649
|
+
...call.request.payload ?? {}
|
|
312650
|
+
};
|
|
312651
|
+
const providedBalanceType = typeof payload.balanceType === "string" ? payload.balanceType : undefined;
|
|
312010
312652
|
const balanceType = (providedBalanceType ?? "total").toString();
|
|
312011
312653
|
const validBalanceTypes = new Set(["free", "used", "total"]);
|
|
312012
312654
|
if (!validBalanceTypes.has(balanceType)) {
|
|
312013
312655
|
return ctx.wrappedCallback({
|
|
312014
|
-
code:
|
|
312656
|
+
code: grpc9.status.INVALID_ARGUMENT,
|
|
312015
312657
|
message: `ValidationError: invalid balanceType '${providedBalanceType}'. Expected one of: free | used | total`
|
|
312016
312658
|
}, null);
|
|
312017
312659
|
}
|
|
@@ -312052,33 +312694,16 @@ async function handleFetchBalances(ctx) {
|
|
|
312052
312694
|
} catch (error48) {
|
|
312053
312695
|
safeLogError(`Error fetching balance from ${cex3}`, error48);
|
|
312054
312696
|
ctx.wrappedCallback({
|
|
312055
|
-
code:
|
|
312697
|
+
code: grpc9.status.INTERNAL,
|
|
312056
312698
|
message: `Failed to fetch balance from ${cex3}`
|
|
312057
312699
|
}, null);
|
|
312058
312700
|
}
|
|
312059
312701
|
}
|
|
312060
312702
|
async function handleFetchTicker(ctx) {
|
|
312061
|
-
const {
|
|
312062
|
-
call,
|
|
312063
|
-
wrappedCallback,
|
|
312064
|
-
policy,
|
|
312065
|
-
brokers,
|
|
312066
|
-
metadata,
|
|
312067
|
-
normalizedCex,
|
|
312068
|
-
cex: cex3,
|
|
312069
|
-
symbol: symbol2,
|
|
312070
|
-
selectedBrokerAccount,
|
|
312071
|
-
broker,
|
|
312072
|
-
verity,
|
|
312073
|
-
applyVerityToBroker,
|
|
312074
|
-
useVerity,
|
|
312075
|
-
verityProverUrl,
|
|
312076
|
-
otelMetrics
|
|
312077
|
-
} = ctx;
|
|
312078
|
-
const verityProof = verity.proof;
|
|
312703
|
+
const { cex: cex3, symbol: symbol2, broker } = ctx;
|
|
312079
312704
|
if (!symbol2) {
|
|
312080
312705
|
return ctx.wrappedCallback({
|
|
312081
|
-
code:
|
|
312706
|
+
code: grpc9.status.INVALID_ARGUMENT,
|
|
312082
312707
|
message: `ValidationError: Symbol required`
|
|
312083
312708
|
}, null);
|
|
312084
312709
|
}
|
|
@@ -312091,18 +312716,20 @@ async function handleFetchTicker(ctx) {
|
|
|
312091
312716
|
} catch (error48) {
|
|
312092
312717
|
safeLogError(`Error fetching ticker from ${cex3}`, error48);
|
|
312093
312718
|
ctx.wrappedCallback({
|
|
312094
|
-
code:
|
|
312719
|
+
code: grpc9.status.INTERNAL,
|
|
312095
312720
|
message: `Failed to fetch ticker from ${cex3}`
|
|
312096
312721
|
}, null);
|
|
312097
312722
|
}
|
|
312098
312723
|
}
|
|
312099
312724
|
async function handlePassThrough(ctx) {
|
|
312100
312725
|
if (ctx.action === Action.FetchCurrency)
|
|
312101
|
-
return
|
|
312726
|
+
return handleFetchCurrencyEvidence(ctx);
|
|
312102
312727
|
if (ctx.action === Action.FetchAccountId)
|
|
312103
312728
|
return handleFetchAccountId(ctx);
|
|
312104
312729
|
if (ctx.action === Action.FetchFees)
|
|
312105
|
-
return
|
|
312730
|
+
return handleFetchFeesEvidence(ctx);
|
|
312731
|
+
if (ctx.action === Action.FetchMarketRules)
|
|
312732
|
+
return handleFetchMarketRulesEvidence(ctx);
|
|
312106
312733
|
if (ctx.action === Action.FetchDepositAddresses)
|
|
312107
312734
|
return handleFetchDepositAddresses(ctx);
|
|
312108
312735
|
if (ctx.action === Action.FetchBalances)
|
|
@@ -312112,7 +312739,7 @@ async function handlePassThrough(ctx) {
|
|
|
312112
312739
|
}
|
|
312113
312740
|
|
|
312114
312741
|
// src/handlers/execute-action/perp-config.ts
|
|
312115
|
-
import * as
|
|
312742
|
+
import * as grpc10 from "@grpc/grpc-js";
|
|
312116
312743
|
function exchangeSupports(broker, capability) {
|
|
312117
312744
|
return broker.has?.[capability] === true;
|
|
312118
312745
|
}
|
|
@@ -312131,14 +312758,14 @@ async function handleGetPerpConfigState(ctx) {
|
|
|
312131
312758
|
}
|
|
312132
312759
|
if (!broker) {
|
|
312133
312760
|
return wrappedCallback({
|
|
312134
|
-
code:
|
|
312761
|
+
code: grpc10.status.INVALID_ARGUMENT,
|
|
312135
312762
|
message: `Invalid CEX key: ${cex3}`
|
|
312136
312763
|
}, null);
|
|
312137
312764
|
}
|
|
312138
312765
|
const exchange = broker;
|
|
312139
312766
|
if (!exchangeSupports(exchange, "fetchPositions")) {
|
|
312140
312767
|
return wrappedCallback({
|
|
312141
|
-
code:
|
|
312768
|
+
code: grpc10.status.UNIMPLEMENTED,
|
|
312142
312769
|
message: `${normalizedCex} does not support fetchPositions`
|
|
312143
312770
|
}, null);
|
|
312144
312771
|
}
|
|
@@ -312155,7 +312782,7 @@ async function handleGetPerpConfigState(ctx) {
|
|
|
312155
312782
|
} catch (error48) {
|
|
312156
312783
|
safeLogError(`GetPerpConfigState failed for ${cex3}`, error48);
|
|
312157
312784
|
ctx.wrappedCallback({
|
|
312158
|
-
code:
|
|
312785
|
+
code: grpc10.status.INTERNAL,
|
|
312159
312786
|
message: `GetPerpConfigState failed: ${sanitizeErrorDetail(error48)}`
|
|
312160
312787
|
}, null);
|
|
312161
312788
|
}
|
|
@@ -312168,14 +312795,14 @@ async function handleSetPerpConfigState(ctx) {
|
|
|
312168
312795
|
}
|
|
312169
312796
|
if (!broker) {
|
|
312170
312797
|
return wrappedCallback({
|
|
312171
|
-
code:
|
|
312798
|
+
code: grpc10.status.INVALID_ARGUMENT,
|
|
312172
312799
|
message: `Invalid CEX key: ${cex3}`
|
|
312173
312800
|
}, null);
|
|
312174
312801
|
}
|
|
312175
312802
|
const exchange = broker;
|
|
312176
312803
|
if (!exchangeSupports(exchange, "setLeverage")) {
|
|
312177
312804
|
return wrappedCallback({
|
|
312178
|
-
code:
|
|
312805
|
+
code: grpc10.status.UNIMPLEMENTED,
|
|
312179
312806
|
message: `${normalizedCex} does not support setLeverage`
|
|
312180
312807
|
}, null);
|
|
312181
312808
|
}
|
|
@@ -312196,7 +312823,7 @@ async function handleSetPerpConfigState(ctx) {
|
|
|
312196
312823
|
} catch (error48) {
|
|
312197
312824
|
safeLogError(`SetPerpConfigState failed for ${cex3}`, error48);
|
|
312198
312825
|
ctx.wrappedCallback({
|
|
312199
|
-
code:
|
|
312826
|
+
code: grpc10.status.INTERNAL,
|
|
312200
312827
|
message: `SetPerpConfigState failed: ${sanitizeErrorDetail(error48)}`
|
|
312201
312828
|
}, null);
|
|
312202
312829
|
}
|
|
@@ -312211,7 +312838,7 @@ async function handlePerpConfig(ctx) {
|
|
|
312211
312838
|
}
|
|
312212
312839
|
|
|
312213
312840
|
// src/handlers/execute-action/treasury-call.ts
|
|
312214
|
-
import * as
|
|
312841
|
+
import * as grpc11 from "@grpc/grpc-js";
|
|
312215
312842
|
async function handleTreasuryCall(ctx) {
|
|
312216
312843
|
const { broker } = ctx;
|
|
312217
312844
|
const callValue = parsePayloadForAction(ctx, CallPayloadSchema);
|
|
@@ -312222,7 +312849,7 @@ async function handleTreasuryCall(ctx) {
|
|
|
312222
312849
|
try {
|
|
312223
312850
|
if (callValue.functionName.startsWith("_") || callValue.functionName.includes("constructor") || callValue.functionName.includes("prototype")) {
|
|
312224
312851
|
return ctx.wrappedCallback({
|
|
312225
|
-
code:
|
|
312852
|
+
code: grpc11.status.PERMISSION_DENIED,
|
|
312226
312853
|
message: "Access to the requested function is denied"
|
|
312227
312854
|
}, null);
|
|
312228
312855
|
}
|
|
@@ -312237,7 +312864,7 @@ async function handleTreasuryCall(ctx) {
|
|
|
312237
312864
|
const fn = broker[callValue.functionName];
|
|
312238
312865
|
if (typeof fn !== "function" || broker.has?.[callValue.functionName] === false) {
|
|
312239
312866
|
return ctx.wrappedCallback({
|
|
312240
|
-
code:
|
|
312867
|
+
code: grpc11.status.INVALID_ARGUMENT,
|
|
312241
312868
|
message: `Function not found on broker: ${callValue.functionName}`
|
|
312242
312869
|
}, null);
|
|
312243
312870
|
}
|
|
@@ -312316,7 +312943,7 @@ function asFiniteNumber(value) {
|
|
|
312316
312943
|
}
|
|
312317
312944
|
|
|
312318
312945
|
// src/handlers/execute-action/withdraw.ts
|
|
312319
|
-
import * as
|
|
312946
|
+
import * as grpc12 from "@grpc/grpc-js";
|
|
312320
312947
|
async function handleWithdraw(ctx) {
|
|
312321
312948
|
const {
|
|
312322
312949
|
call,
|
|
@@ -312339,7 +312966,7 @@ async function handleWithdraw(ctx) {
|
|
|
312339
312966
|
const verityProof = verity.proof;
|
|
312340
312967
|
if (!symbol2) {
|
|
312341
312968
|
return ctx.wrappedCallback({
|
|
312342
|
-
code:
|
|
312969
|
+
code: grpc12.status.INVALID_ARGUMENT,
|
|
312343
312970
|
message: `ValidationError: Symbol required`
|
|
312344
312971
|
}, null);
|
|
312345
312972
|
}
|
|
@@ -312352,21 +312979,21 @@ async function handleWithdraw(ctx) {
|
|
|
312352
312979
|
} catch (error48) {
|
|
312353
312980
|
const message = getErrorMessage(error48);
|
|
312354
312981
|
return ctx.wrappedCallback({
|
|
312355
|
-
code: stableGrpcErrorCode(message) ??
|
|
312982
|
+
code: stableGrpcErrorCode(message) ?? grpc12.status.INVALID_ARGUMENT,
|
|
312356
312983
|
message
|
|
312357
312984
|
}, null);
|
|
312358
312985
|
}
|
|
312359
312986
|
const transferValidation = validateWithdraw(policy, cex3, withdrawNetwork.brokerNetworkId, transferValue.recipientAddress, transferValue.amount, symbol2);
|
|
312360
312987
|
if (!transferValidation.valid) {
|
|
312361
312988
|
return ctx.wrappedCallback({
|
|
312362
|
-
code:
|
|
312989
|
+
code: grpc12.status.PERMISSION_DENIED,
|
|
312363
312990
|
message: `policy_withdrawal_denied: ${transferValidation.error}`
|
|
312364
312991
|
}, null);
|
|
312365
312992
|
}
|
|
312366
312993
|
const travelRule = resolveTravelRuleDecision(policy, cex3, transferValue.recipientAddress);
|
|
312367
312994
|
if (travelRule.mode === "denied") {
|
|
312368
312995
|
return ctx.wrappedCallback({
|
|
312369
|
-
code:
|
|
312996
|
+
code: grpc12.status.FAILED_PRECONDITION,
|
|
312370
312997
|
message: `travel_rule_denied: ${travelRule.error}`
|
|
312371
312998
|
}, null);
|
|
312372
312999
|
}
|
|
@@ -312433,7 +313060,7 @@ async function handleWithdraw(ctx) {
|
|
|
312433
313060
|
payload: { recipientAddress: transferValue.recipientAddress }
|
|
312434
313061
|
}
|
|
312435
313062
|
});
|
|
312436
|
-
const code = mapCcxtErrorToGrpcStatus(error48) ??
|
|
313063
|
+
const code = mapCcxtErrorToGrpcStatus(error48) ?? grpc12.status.INTERNAL;
|
|
312437
313064
|
ctx.wrappedCallback({
|
|
312438
313065
|
code,
|
|
312439
313066
|
message: `Withdraw failed: ${sanitizeErrorDetail(error48)}`
|
|
@@ -312442,33 +313069,152 @@ async function handleWithdraw(ctx) {
|
|
|
312442
313069
|
}
|
|
312443
313070
|
|
|
312444
313071
|
// src/handlers/execute-action/registry.ts
|
|
312445
|
-
|
|
312446
|
-
|
|
312447
|
-
|
|
312448
|
-
|
|
312449
|
-
|
|
312450
|
-
|
|
312451
|
-
|
|
312452
|
-
|
|
312453
|
-
[
|
|
312454
|
-
|
|
312455
|
-
|
|
312456
|
-
|
|
312457
|
-
|
|
312458
|
-
|
|
312459
|
-
|
|
312460
|
-
|
|
313072
|
+
function valid() {
|
|
313073
|
+
return { valid: true };
|
|
313074
|
+
}
|
|
313075
|
+
function requireSymbol2(request) {
|
|
313076
|
+
return request.symbol?.trim() ? valid() : { valid: false, message: "symbol is required" };
|
|
313077
|
+
}
|
|
313078
|
+
function requireSpotSymbol(request) {
|
|
313079
|
+
const symbol2 = request.symbol?.trim() ?? "";
|
|
313080
|
+
return /^[^/\s]+\/[^/\s]+$/.test(symbol2) ? valid() : {
|
|
313081
|
+
valid: false,
|
|
313082
|
+
message: "symbol must be a slash-delimited spot pair"
|
|
313083
|
+
};
|
|
313084
|
+
}
|
|
313085
|
+
function validatePayload(schema, request) {
|
|
313086
|
+
const parsed = parsePayload(schema, request.payload);
|
|
313087
|
+
return parsed.success ? valid() : { valid: false, message: parsed.message };
|
|
313088
|
+
}
|
|
313089
|
+
function validateSymbolAndPayload(schema) {
|
|
313090
|
+
return (request) => {
|
|
313091
|
+
const symbolValidation = requireSymbol2(request);
|
|
313092
|
+
return symbolValidation.valid ? validatePayload(schema, request) : symbolValidation;
|
|
313093
|
+
};
|
|
313094
|
+
}
|
|
313095
|
+
function validateSpotSymbolAndPayload(schema) {
|
|
313096
|
+
return (request) => {
|
|
313097
|
+
const symbolValidation = requireSpotSymbol(request);
|
|
313098
|
+
return symbolValidation.valid ? validatePayload(schema, request) : symbolValidation;
|
|
313099
|
+
};
|
|
313100
|
+
}
|
|
313101
|
+
function validateFetchBalances(request) {
|
|
313102
|
+
const balanceType = request.payload?.balanceType;
|
|
313103
|
+
if (balanceType !== undefined && !new Set(["free", "used", "total"]).has(balanceType)) {
|
|
313104
|
+
return {
|
|
313105
|
+
valid: false,
|
|
313106
|
+
message: "balanceType must be free, used, or total"
|
|
313107
|
+
};
|
|
313108
|
+
}
|
|
313109
|
+
return valid();
|
|
313110
|
+
}
|
|
313111
|
+
var ACTION_DESCRIPTORS = {
|
|
313112
|
+
[Action.Deposit]: {
|
|
313113
|
+
handler: handleDeposit,
|
|
313114
|
+
access: "write",
|
|
313115
|
+
batchable: false
|
|
313116
|
+
},
|
|
313117
|
+
[Action.Withdraw]: {
|
|
313118
|
+
handler: handleWithdraw,
|
|
313119
|
+
access: "write",
|
|
313120
|
+
batchable: false
|
|
313121
|
+
},
|
|
313122
|
+
[Action.Call]: {
|
|
313123
|
+
handler: handleTreasuryCall,
|
|
313124
|
+
access: "write",
|
|
313125
|
+
batchable: false
|
|
313126
|
+
},
|
|
313127
|
+
[Action.InternalTransfer]: {
|
|
313128
|
+
handler: handleInternalTransfer,
|
|
313129
|
+
access: "write",
|
|
313130
|
+
batchable: false
|
|
313131
|
+
},
|
|
313132
|
+
[Action.CreateOrder]: {
|
|
313133
|
+
handler: handleOrders,
|
|
313134
|
+
access: "write",
|
|
313135
|
+
batchable: false
|
|
313136
|
+
},
|
|
313137
|
+
[Action.GetOrderDetails]: {
|
|
313138
|
+
handler: handleOrders,
|
|
313139
|
+
access: "read",
|
|
313140
|
+
batchable: false
|
|
313141
|
+
},
|
|
313142
|
+
[Action.CancelOrder]: {
|
|
313143
|
+
handler: handleOrders,
|
|
313144
|
+
access: "write",
|
|
313145
|
+
batchable: false
|
|
313146
|
+
},
|
|
313147
|
+
[Action.FetchCurrency]: {
|
|
313148
|
+
handler: handlePassThrough,
|
|
313149
|
+
access: "read",
|
|
313150
|
+
batchable: true,
|
|
313151
|
+
validateBatchRequest: validateSymbolAndPayload(FetchCurrencyPayloadSchema)
|
|
313152
|
+
},
|
|
313153
|
+
[Action.FetchAccountId]: {
|
|
313154
|
+
handler: handlePassThrough,
|
|
313155
|
+
access: "read",
|
|
313156
|
+
batchable: true,
|
|
313157
|
+
validateBatchRequest: (request) => validatePayload(EmptyActionPayloadSchema, request)
|
|
313158
|
+
},
|
|
313159
|
+
[Action.FetchFees]: {
|
|
313160
|
+
handler: handlePassThrough,
|
|
313161
|
+
access: "read",
|
|
313162
|
+
batchable: true,
|
|
313163
|
+
validateBatchRequest: validateSpotSymbolAndPayload(FetchFeesPayloadSchema)
|
|
313164
|
+
},
|
|
313165
|
+
[Action.FetchDepositAddresses]: {
|
|
313166
|
+
handler: handlePassThrough,
|
|
313167
|
+
access: "read",
|
|
313168
|
+
batchable: false
|
|
313169
|
+
},
|
|
313170
|
+
[Action.FetchBalances]: {
|
|
313171
|
+
handler: handlePassThrough,
|
|
313172
|
+
access: "read",
|
|
313173
|
+
batchable: true,
|
|
313174
|
+
validateBatchRequest: validateFetchBalances
|
|
313175
|
+
},
|
|
313176
|
+
[Action.FetchTicker]: {
|
|
313177
|
+
handler: handlePassThrough,
|
|
313178
|
+
access: "read",
|
|
313179
|
+
batchable: true,
|
|
313180
|
+
validateBatchRequest: validateSymbolAndPayload(EmptyActionPayloadSchema)
|
|
313181
|
+
},
|
|
313182
|
+
[Action.GetPerpConfigState]: {
|
|
313183
|
+
handler: handlePerpConfig,
|
|
313184
|
+
access: "read",
|
|
313185
|
+
batchable: true,
|
|
313186
|
+
validateBatchRequest: (request) => validatePayload(GetPerpConfigStatePayloadSchema, request)
|
|
313187
|
+
},
|
|
313188
|
+
[Action.SetPerpConfigState]: {
|
|
313189
|
+
handler: handlePerpConfig,
|
|
313190
|
+
access: "write",
|
|
313191
|
+
batchable: false
|
|
313192
|
+
},
|
|
313193
|
+
[Action.FetchMarketRules]: {
|
|
313194
|
+
handler: handlePassThrough,
|
|
313195
|
+
access: "read",
|
|
313196
|
+
batchable: true,
|
|
313197
|
+
validateBatchRequest: validateSpotSymbolAndPayload(EmptyActionPayloadSchema)
|
|
313198
|
+
},
|
|
313199
|
+
[Action.Batch]: {
|
|
313200
|
+
handler: (ctx) => handleBatch(ctx, getActionDescriptor),
|
|
313201
|
+
access: "read",
|
|
313202
|
+
batchable: false
|
|
313203
|
+
}
|
|
312461
313204
|
};
|
|
313205
|
+
function getActionDescriptor(action) {
|
|
313206
|
+
return ACTION_DESCRIPTORS[action];
|
|
313207
|
+
}
|
|
312462
313208
|
async function dispatchExecuteAction(ctx) {
|
|
312463
|
-
const
|
|
312464
|
-
if (!
|
|
313209
|
+
const descriptor = getActionDescriptor(ctx.action);
|
|
313210
|
+
if (!descriptor) {
|
|
312465
313211
|
ctx.wrappedCallback({
|
|
312466
|
-
code:
|
|
313212
|
+
code: grpc13.status.INVALID_ARGUMENT,
|
|
312467
313213
|
message: "Invalid Action"
|
|
312468
313214
|
}, null);
|
|
312469
313215
|
return;
|
|
312470
313216
|
}
|
|
312471
|
-
await handler(ctx);
|
|
313217
|
+
await descriptor.handler(ctx);
|
|
312472
313218
|
}
|
|
312473
313219
|
|
|
312474
313220
|
// src/handlers/execute-action/handler.ts
|
|
@@ -312484,7 +313230,7 @@ function grpcStatusName(error48) {
|
|
|
312484
313230
|
if (typeof error48.code !== "number") {
|
|
312485
313231
|
return "UNKNOWN";
|
|
312486
313232
|
}
|
|
312487
|
-
return
|
|
313233
|
+
return grpc14.status[error48.code] ?? "UNKNOWN";
|
|
312488
313234
|
}
|
|
312489
313235
|
function createExecuteActionHandler(deps) {
|
|
312490
313236
|
const {
|
|
@@ -312532,7 +313278,7 @@ function createExecuteActionHandler(deps) {
|
|
|
312532
313278
|
otelMetrics?.recordCounter("execute_action_errors_total", 1, {
|
|
312533
313279
|
action: actionName,
|
|
312534
313280
|
cex: cex3 || "unknown",
|
|
312535
|
-
error_type: error48.code ?
|
|
313281
|
+
error_type: error48.code ? grpc14.status[error48.code] || "unknown" : "unknown"
|
|
312536
313282
|
});
|
|
312537
313283
|
} else {
|
|
312538
313284
|
otelMetrics?.recordCounter("execute_action_success_total", 1, {
|
|
@@ -312555,13 +313301,13 @@ function createExecuteActionHandler(deps) {
|
|
|
312555
313301
|
});
|
|
312556
313302
|
if (!authenticateRequest(call, whitelistIps)) {
|
|
312557
313303
|
return wrappedCallback({
|
|
312558
|
-
code:
|
|
313304
|
+
code: grpc14.status.PERMISSION_DENIED,
|
|
312559
313305
|
message: "Access denied: Unauthorized IP"
|
|
312560
313306
|
}, null);
|
|
312561
313307
|
}
|
|
312562
313308
|
if (!action || !cex3) {
|
|
312563
313309
|
return wrappedCallback({
|
|
312564
|
-
code:
|
|
313310
|
+
code: grpc14.status.INVALID_ARGUMENT,
|
|
312565
313311
|
message: "`action` AND `cex` fields are required"
|
|
312566
313312
|
}, null);
|
|
312567
313313
|
}
|
|
@@ -312571,17 +313317,20 @@ function createExecuteActionHandler(deps) {
|
|
|
312571
313317
|
const broker = selectedBrokerAccount?.exchange ?? createBroker(normalizedCex, call.metadata) ?? (isPublicMarketDataAction(action, call.request.payload) ? createPublicBroker(normalizedCex) : null);
|
|
312572
313318
|
if (!broker) {
|
|
312573
313319
|
return wrappedCallback({
|
|
312574
|
-
code:
|
|
313320
|
+
code: grpc14.status.UNAUTHENTICATED,
|
|
312575
313321
|
message: `This Exchange is not registered and No API metadata was found`
|
|
312576
313322
|
}, null);
|
|
312577
313323
|
}
|
|
312578
313324
|
const verity = { proof: "" };
|
|
312579
|
-
const applyVerityToBroker = (targetBroker) => {
|
|
313325
|
+
const applyVerityToBroker = (targetBroker, proofState = verity) => {
|
|
312580
313326
|
if (!useVerity)
|
|
312581
313327
|
return;
|
|
312582
313328
|
const override = buildHttpClientOverrideFromMetadata(metadata, verityProverUrl, (proof, notaryPubKey) => {
|
|
312583
|
-
|
|
312584
|
-
log.debug(`Verity proof
|
|
313329
|
+
proofState.proof = proof;
|
|
313330
|
+
log.debug(`Verity proof received`, {
|
|
313331
|
+
has_proof: proof.length > 0,
|
|
313332
|
+
has_notary_public_key: Boolean(notaryPubKey)
|
|
313333
|
+
});
|
|
312585
313334
|
});
|
|
312586
313335
|
targetBroker.setHttpClientOverride(override, verityHttpClientOverridePredicate);
|
|
312587
313336
|
};
|
|
@@ -312617,7 +313366,7 @@ function createExecuteActionHandler(deps) {
|
|
|
312617
313366
|
} catch (error48) {
|
|
312618
313367
|
safeLogError("ExecuteAction unhandled error", error48);
|
|
312619
313368
|
return wrappedCallback({
|
|
312620
|
-
code:
|
|
313369
|
+
code: grpc14.status.INTERNAL,
|
|
312621
313370
|
message: "ExecuteAction failed unexpectedly"
|
|
312622
313371
|
}, null);
|
|
312623
313372
|
}
|
|
@@ -312677,7 +313426,7 @@ class SubscribeBrokerLifecycle {
|
|
|
312677
313426
|
}
|
|
312678
313427
|
}
|
|
312679
313428
|
// src/handlers/subscribe/handler.ts
|
|
312680
|
-
import * as
|
|
313429
|
+
import * as grpc15 from "@grpc/grpc-js";
|
|
312681
313430
|
|
|
312682
313431
|
// src/helpers/binance-user-data-normalization.ts
|
|
312683
313432
|
function requireQuantity(entry, key2) {
|
|
@@ -312788,8 +313537,8 @@ async function writeSubscribeError(call, isClosed, frame) {
|
|
|
312788
313537
|
call.end();
|
|
312789
313538
|
}
|
|
312790
313539
|
}
|
|
312791
|
-
function grpcStatusName2(
|
|
312792
|
-
return typeof
|
|
313540
|
+
function grpcStatusName2(status16) {
|
|
313541
|
+
return typeof status16 === "number" ? grpc15.status[status16] ?? "UNKNOWN" : "UNKNOWN";
|
|
312793
313542
|
}
|
|
312794
313543
|
function getBinanceEventMarketId(event) {
|
|
312795
313544
|
const value = event.s;
|
|
@@ -312982,9 +313731,9 @@ function createSubscribeHandler(deps) {
|
|
|
312982
313731
|
log.withMetadata(fields).info("Subscribe ended");
|
|
312983
313732
|
}
|
|
312984
313733
|
};
|
|
312985
|
-
const writeTerminalError = async (frame,
|
|
313734
|
+
const writeTerminalError = async (frame, status16 = grpc15.status.UNKNOWN) => {
|
|
312986
313735
|
terminalOutcome = "error";
|
|
312987
|
-
terminalGrpcStatus = grpcStatusName2(
|
|
313736
|
+
terminalGrpcStatus = grpcStatusName2(status16);
|
|
312988
313737
|
await writeSubscribeError(call, isStreamClosed, frame);
|
|
312989
313738
|
};
|
|
312990
313739
|
log.withMetadata(operationalFields).info("Subscribe started");
|
|
@@ -313020,7 +313769,7 @@ function createSubscribeHandler(deps) {
|
|
|
313020
313769
|
error_type: "permission_denied"
|
|
313021
313770
|
});
|
|
313022
313771
|
call.emit("error", {
|
|
313023
|
-
code:
|
|
313772
|
+
code: grpc15.status.PERMISSION_DENIED,
|
|
313024
313773
|
message: "Access denied: Unauthorized IP"
|
|
313025
313774
|
}, null);
|
|
313026
313775
|
call.destroy(new Error("Access denied: Unauthorized IP"));
|
|
@@ -313041,7 +313790,7 @@ function createSubscribeHandler(deps) {
|
|
|
313041
313790
|
timestamp: Date.now(),
|
|
313042
313791
|
symbol: symbol2 || "",
|
|
313043
313792
|
type: subscriptionType2
|
|
313044
|
-
},
|
|
313793
|
+
}, grpc15.status.INVALID_ARGUMENT);
|
|
313045
313794
|
return;
|
|
313046
313795
|
}
|
|
313047
313796
|
if (isPublicMarketDataSubscription(subscriptionType2)) {
|
|
@@ -313098,7 +313847,7 @@ function createSubscribeHandler(deps) {
|
|
|
313098
313847
|
timestamp: Date.now(),
|
|
313099
313848
|
symbol: symbol2,
|
|
313100
313849
|
type: subscriptionType2
|
|
313101
|
-
},
|
|
313850
|
+
}, grpc15.status.NOT_FOUND);
|
|
313102
313851
|
return;
|
|
313103
313852
|
}
|
|
313104
313853
|
if (!selectedBrokerAccount) {
|
|
@@ -313133,7 +313882,7 @@ function createSubscribeHandler(deps) {
|
|
|
313133
313882
|
timestamp: Date.now(),
|
|
313134
313883
|
symbol: resolvedSymbol,
|
|
313135
313884
|
type: subscriptionType2
|
|
313136
|
-
},
|
|
313885
|
+
}, grpc15.status.FAILED_PRECONDITION);
|
|
313137
313886
|
return;
|
|
313138
313887
|
}
|
|
313139
313888
|
const marketId = subscriptionType2 === SubscriptionType.ORDERS ? await getBinanceMarketId(accountBroker, resolvedSymbol) : undefined;
|
|
@@ -313147,7 +313896,7 @@ function createSubscribeHandler(deps) {
|
|
|
313147
313896
|
timestamp: Date.now(),
|
|
313148
313897
|
symbol: resolvedSymbol,
|
|
313149
313898
|
type: subscriptionType2
|
|
313150
|
-
},
|
|
313899
|
+
}, grpc15.status.FAILED_PRECONDITION);
|
|
313151
313900
|
return;
|
|
313152
313901
|
}
|
|
313153
313902
|
userDataSource = userDataStreamSupervisor.subscribe({
|
|
@@ -313205,7 +313954,7 @@ function createSubscribeHandler(deps) {
|
|
|
313205
313954
|
timestamp: Date.now(),
|
|
313206
313955
|
symbol: symbol2,
|
|
313207
313956
|
type: subscriptionType2
|
|
313208
|
-
},
|
|
313957
|
+
}, grpc15.status.INVALID_ARGUMENT);
|
|
313209
313958
|
}
|
|
313210
313959
|
} catch (error48) {
|
|
313211
313960
|
log.error("Error in Subscribe stream:", error48);
|
|
@@ -313215,7 +313964,7 @@ function createSubscribeHandler(deps) {
|
|
|
313215
313964
|
timestamp: Date.now(),
|
|
313216
313965
|
symbol: "",
|
|
313217
313966
|
type: subscriptionType2
|
|
313218
|
-
},
|
|
313967
|
+
}, grpc15.status.INTERNAL);
|
|
313219
313968
|
} finally {
|
|
313220
313969
|
call.off("cancelled", closeOwnedBrokerOnCallEnd);
|
|
313221
313970
|
call.off("error", closeOwnedBrokerOnCallEnd);
|
|
@@ -313347,7 +314096,9 @@ var descriptor = {
|
|
|
313347
314096
|
FetchFees: 12,
|
|
313348
314097
|
InternalTransfer: 13,
|
|
313349
314098
|
GetPerpConfigState: 14,
|
|
313350
|
-
SetPerpConfigState: 15
|
|
314099
|
+
SetPerpConfigState: 15,
|
|
314100
|
+
FetchMarketRules: 16,
|
|
314101
|
+
Batch: 17
|
|
313351
314102
|
}
|
|
313352
314103
|
}
|
|
313353
314104
|
}
|
|
@@ -313369,10 +314120,10 @@ var PROTO_LOADER_OPTIONS = {
|
|
|
313369
314120
|
var CEX_BROKER_PACKAGE_DEFINITION = protoLoader.fromJSON(node_descriptor_default, PROTO_LOADER_OPTIONS);
|
|
313370
314121
|
|
|
313371
314122
|
// src/server.ts
|
|
313372
|
-
var grpcObj =
|
|
314123
|
+
var grpcObj = grpc16.loadPackageDefinition(CEX_BROKER_PACKAGE_DEFINITION);
|
|
313373
314124
|
var cexNode = grpcObj.cex_broker;
|
|
313374
314125
|
function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, otelMetrics, brokerArchiver, orderActivityTracker, withdrawalObservationTracker, subscribeBrokerLifecycle, userDataStreamSupervisor, publicMarketDataFeedSupervisor) {
|
|
313375
|
-
const server = new
|
|
314126
|
+
const server = new grpc16.Server;
|
|
313376
314127
|
server.addService(cexNode.cex_service.service, {
|
|
313377
314128
|
ExecuteAction: createExecuteActionHandler({
|
|
313378
314129
|
policy,
|
|
@@ -313761,7 +314512,10 @@ class CEXBroker {
|
|
|
313761
314512
|
await this.otelMetrics.initialize();
|
|
313762
314513
|
}
|
|
313763
314514
|
if (!this.userDataStreamSupervisor && Object.keys(this.brokers).length > 0) {
|
|
313764
|
-
const publisher = new StreamHealthPublisher(
|
|
314515
|
+
const publisher = new StreamHealthPublisher({
|
|
314516
|
+
...streamHealthPublisherConfigFromEnv(),
|
|
314517
|
+
producerId: USER_DATA_STREAM_HEALTH_PRODUCER_ID
|
|
314518
|
+
});
|
|
313765
314519
|
this.userDataStreamSupervisor = new UserDataStreamSupervisor({
|
|
313766
314520
|
brokers: this.brokers,
|
|
313767
314521
|
publisher
|
|
@@ -313774,7 +314528,7 @@ class CEXBroker {
|
|
|
313774
314528
|
otelMetrics: this.otelMetrics
|
|
313775
314529
|
});
|
|
313776
314530
|
this.server = getServer(this.policy, this.brokers, this.whitelistIps, this.useVerity, this.#verityProverUrl, this.otelMetrics, this.brokerArchiver, this.orderActivityTracker, this.withdrawalObservationTracker, undefined, this.userDataStreamSupervisor, this.publicMarketDataFeedSupervisor);
|
|
313777
|
-
this.server.bindAsync(`0.0.0.0:${this.port}`,
|
|
314531
|
+
this.server.bindAsync(`0.0.0.0:${this.port}`, grpc17.ServerCredentials.createInsecure(), (err2, port) => {
|
|
313778
314532
|
if (err2) {
|
|
313779
314533
|
log.error(err2);
|
|
313780
314534
|
return;
|
|
@@ -313799,7 +314553,8 @@ class CEXBroker {
|
|
|
313799
314553
|
this.depositArchivePoller = new DepositArchivePoller({
|
|
313800
314554
|
brokers: this.brokers,
|
|
313801
314555
|
archiver: this.brokerArchiver,
|
|
313802
|
-
metrics: this.otelMetrics
|
|
314556
|
+
metrics: this.otelMetrics,
|
|
314557
|
+
coveragePublisher: Object.keys(this.brokers).length > 0 ? new StreamHealthPublisher(depositPollerStreamHealthPublisherConfigFromEnv()) : undefined
|
|
313803
314558
|
});
|
|
313804
314559
|
this.depositArchivePoller.start();
|
|
313805
314560
|
if (this.userDataStreamSupervisor) {
|
|
@@ -313832,4 +314587,4 @@ export {
|
|
|
313832
314587
|
CEXBroker as default
|
|
313833
314588
|
};
|
|
313834
314589
|
|
|
313835
|
-
//# debugId=
|
|
314590
|
+
//# debugId=9C0805B96506F17064756E2164756E21
|