@usherlabs/cex-broker 0.2.13 → 0.2.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/cli.js +306 -75
- package/dist/helpers/binance-user-data-stream.d.ts +19 -1
- package/dist/helpers/index.d.ts +1 -0
- package/dist/helpers/travel-rule.d.ts +44 -0
- package/dist/index.js +308 -76
- package/dist/index.js.map +10 -9
- package/dist/types.d.ts +27 -0
- package/package.json +3 -1
package/dist/commands/cli.js
CHANGED
|
@@ -310973,11 +310973,11 @@ var ccxt = Object.assign({ version: version2, Exchange, Precise, exchanges: Obje
|
|
|
310973
310973
|
var ccxt_default = ccxt;
|
|
310974
310974
|
|
|
310975
310975
|
// src/index.ts
|
|
310976
|
-
var
|
|
310976
|
+
var import_joi3 = __toESM(require_lib4(), 1);
|
|
310977
310977
|
import { unwatchFile, watchFile } from "fs";
|
|
310978
310978
|
|
|
310979
310979
|
// src/helpers/index.ts
|
|
310980
|
-
var
|
|
310980
|
+
var import_joi2 = __toESM(require_lib4(), 1);
|
|
310981
310981
|
import fs from "fs";
|
|
310982
310982
|
|
|
310983
310983
|
// src/helpers/exchange-credentials.ts
|
|
@@ -313531,6 +313531,81 @@ if (process.env.LOG_LEVEL !== "debug") {
|
|
|
313531
313531
|
}
|
|
313532
313532
|
var log = baseLogger;
|
|
313533
313533
|
|
|
313534
|
+
// src/helpers/travel-rule.ts
|
|
313535
|
+
var import_joi = __toESM(require_lib4(), 1);
|
|
313536
|
+
var isAnotherBeneficiary = import_joi.default.number().valid(2).required();
|
|
313537
|
+
var isIndividual = import_joi.default.number().valid(0).required();
|
|
313538
|
+
var isCorporate = import_joi.default.number().valid(1).required();
|
|
313539
|
+
var isSendToVasp = import_joi.default.number().valid(2).required();
|
|
313540
|
+
var isVaspOthers = import_joi.default.string().valid("others").required();
|
|
313541
|
+
function requiredWhen(base2, ref, is) {
|
|
313542
|
+
return base2.when(ref, {
|
|
313543
|
+
is,
|
|
313544
|
+
then: import_joi.default.required(),
|
|
313545
|
+
otherwise: import_joi.default.forbidden()
|
|
313546
|
+
});
|
|
313547
|
+
}
|
|
313548
|
+
var australiaQuestionnaireSchema = import_joi.default.object({
|
|
313549
|
+
isAddressOwner: import_joi.default.number().valid(1, 2).required(),
|
|
313550
|
+
sendTo: import_joi.default.number().valid(1, 2).required(),
|
|
313551
|
+
declaration: import_joi.default.boolean().valid(true).required(),
|
|
313552
|
+
bnfType: requiredWhen(import_joi.default.number().valid(0, 1), "isAddressOwner", isAnotherBeneficiary),
|
|
313553
|
+
bnfFirstName: requiredWhen(import_joi.default.string(), "bnfType", isIndividual),
|
|
313554
|
+
bnfLastName: requiredWhen(import_joi.default.string(), "bnfType", isIndividual),
|
|
313555
|
+
country: requiredWhen(import_joi.default.string(), "bnfType", isIndividual),
|
|
313556
|
+
city: requiredWhen(import_joi.default.string(), "bnfType", isIndividual),
|
|
313557
|
+
bnfCorpName: requiredWhen(import_joi.default.string(), "bnfType", isCorporate),
|
|
313558
|
+
bnfCorpCountry: requiredWhen(import_joi.default.string(), "bnfType", isCorporate),
|
|
313559
|
+
bnfCorpCity: requiredWhen(import_joi.default.string(), "bnfType", isCorporate),
|
|
313560
|
+
vasp: requiredWhen(import_joi.default.string(), "sendTo", isSendToVasp),
|
|
313561
|
+
vaspName: requiredWhen(import_joi.default.string(), "vasp", isVaspOthers)
|
|
313562
|
+
});
|
|
313563
|
+
function resolveTravelRuleDecision(policy, exchange, recipientAddress) {
|
|
313564
|
+
const rules = policy.travelRule?.rule ?? [];
|
|
313565
|
+
const exchangeNorm = exchange.trim().toUpperCase();
|
|
313566
|
+
const entry = rules.find((rule) => rule.exchange.trim().toUpperCase() === exchangeNorm);
|
|
313567
|
+
if (!entry || !entry.enabled) {
|
|
313568
|
+
return { mode: "standard" };
|
|
313569
|
+
}
|
|
313570
|
+
const addressNorm = recipientAddress.trim().toLowerCase();
|
|
313571
|
+
const match = Object.entries(entry.addresses).find(([address]) => address.trim().toLowerCase() === addressNorm);
|
|
313572
|
+
if (!match) {
|
|
313573
|
+
return {
|
|
313574
|
+
mode: "denied",
|
|
313575
|
+
error: `no travel-rule questionnaire configured for ${exchangeNorm} address ${recipientAddress}`
|
|
313576
|
+
};
|
|
313577
|
+
}
|
|
313578
|
+
return { mode: "localentity", questionnaire: match[1].questionnaire };
|
|
313579
|
+
}
|
|
313580
|
+
function registerBinanceTravelRuleWithdrawEndpoint(exchange) {
|
|
313581
|
+
if (exchange.id !== "binance") {
|
|
313582
|
+
return;
|
|
313583
|
+
}
|
|
313584
|
+
exchange.defineRestApi({ sapi: { post: { "localentity/withdraw/apply": 4.0002 } } }, "request");
|
|
313585
|
+
}
|
|
313586
|
+
async function withdrawViaLocalEntity(broker, args) {
|
|
313587
|
+
const exchange = broker;
|
|
313588
|
+
if (typeof exchange.sapiPostLocalentityWithdrawApply !== "function") {
|
|
313589
|
+
throw new Error("binance_localentity_withdraw_unavailable: travel-rule withdraw endpoint is not registered on this exchange instance");
|
|
313590
|
+
}
|
|
313591
|
+
broker.checkAddress(args.address);
|
|
313592
|
+
await broker.loadMarkets();
|
|
313593
|
+
const currency = broker.currency(args.code);
|
|
313594
|
+
const networks = broker.safeDict(broker.options, "networks", {});
|
|
313595
|
+
const networkUpper = args.network.trim().toUpperCase();
|
|
313596
|
+
const mappedNetwork = broker.safeString(networks, networkUpper, networkUpper);
|
|
313597
|
+
const request = {
|
|
313598
|
+
...args.params,
|
|
313599
|
+
coin: currency.id,
|
|
313600
|
+
address: args.address,
|
|
313601
|
+
amount: broker.currencyToPrecision(args.code, args.amount),
|
|
313602
|
+
network: mappedNetwork,
|
|
313603
|
+
questionnaire: JSON.stringify(args.questionnaire)
|
|
313604
|
+
};
|
|
313605
|
+
const response = await exchange.sapiPostLocalentityWithdrawApply(request);
|
|
313606
|
+
return broker.parseTransaction(response, currency);
|
|
313607
|
+
}
|
|
313608
|
+
|
|
313534
313609
|
// src/helpers/broker.ts
|
|
313535
313610
|
class BrokerAccountPreconditionError extends Error {
|
|
313536
313611
|
constructor(message) {
|
|
@@ -313555,6 +313630,7 @@ function applyCommonExchangeConfig(exchange) {
|
|
|
313555
313630
|
recvWindow: 60000,
|
|
313556
313631
|
adjustForTimeDifference: true
|
|
313557
313632
|
});
|
|
313633
|
+
registerBinanceTravelRuleWithdrawEndpoint(exchange);
|
|
313558
313634
|
}
|
|
313559
313635
|
function createBroker(cex3, credsOrMetadata) {
|
|
313560
313636
|
let apiKey;
|
|
@@ -313971,36 +314047,47 @@ var verityHttpClientOverridePredicate = ({
|
|
|
313971
314047
|
function loadPolicy(policyPath) {
|
|
313972
314048
|
try {
|
|
313973
314049
|
const policyData = fs.readFileSync(policyPath, "utf8");
|
|
313974
|
-
const withdrawRuleEntrySchema =
|
|
313975
|
-
exchange:
|
|
313976
|
-
network:
|
|
313977
|
-
whitelist:
|
|
313978
|
-
coins:
|
|
314050
|
+
const withdrawRuleEntrySchema = import_joi2.default.object({
|
|
314051
|
+
exchange: import_joi2.default.string().required(),
|
|
314052
|
+
network: import_joi2.default.string().required(),
|
|
314053
|
+
whitelist: import_joi2.default.array().items(import_joi2.default.string()).required(),
|
|
314054
|
+
coins: import_joi2.default.array().items(import_joi2.default.string()).optional()
|
|
313979
314055
|
});
|
|
313980
|
-
const depositRuleEntrySchema =
|
|
313981
|
-
exchange:
|
|
313982
|
-
network:
|
|
313983
|
-
coins:
|
|
314056
|
+
const depositRuleEntrySchema = import_joi2.default.object({
|
|
314057
|
+
exchange: import_joi2.default.string().required(),
|
|
314058
|
+
network: import_joi2.default.string().required(),
|
|
314059
|
+
coins: import_joi2.default.array().items(import_joi2.default.string()).optional()
|
|
313984
314060
|
});
|
|
313985
|
-
const orderRuleSchema =
|
|
313986
|
-
markets:
|
|
313987
|
-
limits:
|
|
313988
|
-
from:
|
|
313989
|
-
to:
|
|
313990
|
-
min:
|
|
313991
|
-
max:
|
|
314061
|
+
const orderRuleSchema = import_joi2.default.object({
|
|
314062
|
+
markets: import_joi2.default.array().items(import_joi2.default.string()).required(),
|
|
314063
|
+
limits: import_joi2.default.array().items(import_joi2.default.object({
|
|
314064
|
+
from: import_joi2.default.string().required(),
|
|
314065
|
+
to: import_joi2.default.string().required(),
|
|
314066
|
+
min: import_joi2.default.number().required(),
|
|
314067
|
+
max: import_joi2.default.number().required()
|
|
313992
314068
|
})).default([])
|
|
313993
314069
|
});
|
|
313994
|
-
const
|
|
313995
|
-
|
|
313996
|
-
|
|
314070
|
+
const travelRuleEntrySchema = import_joi2.default.object({
|
|
314071
|
+
exchange: import_joi2.default.string().uppercase().valid("BINANCE").required(),
|
|
314072
|
+
enabled: import_joi2.default.boolean().required(),
|
|
314073
|
+
description: import_joi2.default.string().optional(),
|
|
314074
|
+
addresses: import_joi2.default.object().pattern(import_joi2.default.string(), import_joi2.default.object({
|
|
314075
|
+
questionnaire: australiaQuestionnaireSchema.required()
|
|
314076
|
+
})).required()
|
|
314077
|
+
});
|
|
314078
|
+
const policyConfigSchema = import_joi2.default.object({
|
|
314079
|
+
withdraw: import_joi2.default.object({
|
|
314080
|
+
rule: import_joi2.default.array().items(withdrawRuleEntrySchema).min(1).required()
|
|
313997
314081
|
}).required(),
|
|
313998
|
-
deposit:
|
|
313999
|
-
rule:
|
|
314082
|
+
deposit: import_joi2.default.object({
|
|
314083
|
+
rule: import_joi2.default.array().items(depositRuleEntrySchema).optional()
|
|
314000
314084
|
}).required(),
|
|
314001
|
-
order:
|
|
314085
|
+
order: import_joi2.default.object({
|
|
314002
314086
|
rule: orderRuleSchema.required()
|
|
314003
|
-
}).required()
|
|
314087
|
+
}).required(),
|
|
314088
|
+
travelRule: import_joi2.default.object({
|
|
314089
|
+
rule: import_joi2.default.array().items(travelRuleEntrySchema).required()
|
|
314090
|
+
}).optional()
|
|
314004
314091
|
});
|
|
314005
314092
|
const { error, value } = policyConfigSchema.validate(JSON.parse(policyData));
|
|
314006
314093
|
if (error) {
|
|
@@ -330114,8 +330201,22 @@ async function handleWithdraw(ctx) {
|
|
|
330114
330201
|
message: `policy_withdrawal_denied: ${transferValidation.error}`
|
|
330115
330202
|
}, null);
|
|
330116
330203
|
}
|
|
330204
|
+
const travelRule = resolveTravelRuleDecision(policy, cex3, transferValue.recipientAddress);
|
|
330205
|
+
if (travelRule.mode === "denied") {
|
|
330206
|
+
return ctx.wrappedCallback({
|
|
330207
|
+
code: grpc10.status.FAILED_PRECONDITION,
|
|
330208
|
+
message: `travel_rule_denied: ${travelRule.error}`
|
|
330209
|
+
}, null);
|
|
330210
|
+
}
|
|
330117
330211
|
try {
|
|
330118
|
-
const transaction =
|
|
330212
|
+
const transaction = travelRule.mode === "localentity" ? await withdrawViaLocalEntity(broker, {
|
|
330213
|
+
code: symbol2,
|
|
330214
|
+
amount: transferValue.amount,
|
|
330215
|
+
address: transferValue.recipientAddress,
|
|
330216
|
+
network: withdrawNetwork.exchangeNetworkId,
|
|
330217
|
+
questionnaire: travelRule.questionnaire,
|
|
330218
|
+
params: transferValue.params
|
|
330219
|
+
}) : await broker.withdraw(symbol2, transferValue.amount, transferValue.recipientAddress, undefined, {
|
|
330119
330220
|
...transferValue.params ?? {},
|
|
330120
330221
|
network: withdrawNetwork.exchangeNetworkId
|
|
330121
330222
|
});
|
|
@@ -330285,15 +330386,11 @@ function createExecuteActionHandler(deps) {
|
|
|
330285
330386
|
var grpc13 = __toESM(require_src3(), 1);
|
|
330286
330387
|
|
|
330287
330388
|
// src/helpers/binance-user-data-stream.ts
|
|
330389
|
+
import { Buffer as Buffer2 } from "node:buffer";
|
|
330288
330390
|
import { createHmac } from "node:crypto";
|
|
330289
330391
|
var BINANCE_SPOT_WS_API_URL = "wss://ws-api.binance.com:443/ws-api/v3";
|
|
330290
|
-
|
|
330291
|
-
|
|
330292
|
-
if (!WebSocketCtor) {
|
|
330293
|
-
throw new Error("WebSocket is not available in this runtime");
|
|
330294
|
-
}
|
|
330295
|
-
return WebSocketCtor;
|
|
330296
|
-
}
|
|
330392
|
+
var DEFAULT_BINANCE_USER_DATA_MAX_BUFFERED_EVENTS = 16;
|
|
330393
|
+
var createWebSocket = (url3) => new wrapper_default(url3);
|
|
330297
330394
|
function getExchangeString(exchange, key) {
|
|
330298
330395
|
const value = exchange[key];
|
|
330299
330396
|
if (typeof value !== "string" || value.length === 0) {
|
|
@@ -330319,24 +330416,106 @@ function getBinanceSpotWsApiUrl(exchange) {
|
|
|
330319
330416
|
const urls = exchange.urls;
|
|
330320
330417
|
return urls?.api?.ws?.["ws-api"]?.spot ?? BINANCE_SPOT_WS_API_URL;
|
|
330321
330418
|
}
|
|
330419
|
+
function getRecord(value) {
|
|
330420
|
+
return typeof value === "object" && value !== null ? value : null;
|
|
330421
|
+
}
|
|
330422
|
+
function getMessage(value) {
|
|
330423
|
+
if (value instanceof Error) {
|
|
330424
|
+
return value.message;
|
|
330425
|
+
}
|
|
330426
|
+
if (typeof value === "string" && value.length > 0) {
|
|
330427
|
+
return value;
|
|
330428
|
+
}
|
|
330429
|
+
const record2 = getRecord(value);
|
|
330430
|
+
const message = record2?.message;
|
|
330431
|
+
return typeof message === "string" && message.length > 0 ? message : null;
|
|
330432
|
+
}
|
|
330433
|
+
function getOptionalExchangeString(exchange, key) {
|
|
330434
|
+
const value = exchange[key];
|
|
330435
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
330436
|
+
}
|
|
330437
|
+
function redactDiagnosticMessage(message, secretValues) {
|
|
330438
|
+
let redacted = message;
|
|
330439
|
+
for (const value of secretValues) {
|
|
330440
|
+
if (value.length > 0) {
|
|
330441
|
+
redacted = redacted.split(value).join("[redacted]");
|
|
330442
|
+
}
|
|
330443
|
+
}
|
|
330444
|
+
return redacted.replace(/(\b(?:apiKey|secret|signature)\b\s*=\s*)[^\s&,;)]+/gi, "$1[redacted]").replace(/("(?:apiKey|secret|signature)"\s*:\s*")[^"]*(")/gi, "$1[redacted]$2");
|
|
330445
|
+
}
|
|
330446
|
+
function formatBinanceUserDataWebSocketError(event, secretValues) {
|
|
330447
|
+
const record2 = getRecord(event);
|
|
330448
|
+
const message = getMessage(record2?.error) ?? getMessage(record2?.message) ?? getMessage(event);
|
|
330449
|
+
const safeMessage = message === null ? null : redactDiagnosticMessage(message, secretValues);
|
|
330450
|
+
return new Error(safeMessage ? `Binance user-data WebSocket error: ${safeMessage}` : "Binance user-data WebSocket error");
|
|
330451
|
+
}
|
|
330452
|
+
function getCloseReason(value) {
|
|
330453
|
+
if (typeof value === "string") {
|
|
330454
|
+
return value.length > 0 ? value : null;
|
|
330455
|
+
}
|
|
330456
|
+
if (Buffer2.isBuffer(value)) {
|
|
330457
|
+
const reason = value.toString("utf8");
|
|
330458
|
+
return reason.length > 0 ? reason : null;
|
|
330459
|
+
}
|
|
330460
|
+
if (value instanceof Uint8Array) {
|
|
330461
|
+
const reason = Buffer2.from(value).toString("utf8");
|
|
330462
|
+
return reason.length > 0 ? reason : null;
|
|
330463
|
+
}
|
|
330464
|
+
return null;
|
|
330465
|
+
}
|
|
330466
|
+
function formatBinanceUserDataWebSocketClose(codeOrEvent, reasonOrUndefined, secretValues) {
|
|
330467
|
+
const record2 = getRecord(codeOrEvent);
|
|
330468
|
+
const code = record2 ? record2.code : codeOrEvent;
|
|
330469
|
+
const reason = getCloseReason(record2 ? record2.reason : reasonOrUndefined);
|
|
330470
|
+
const safeReason = reason === null ? null : redactDiagnosticMessage(reason, secretValues);
|
|
330471
|
+
const details = [
|
|
330472
|
+
typeof code === "number" || typeof code === "string" ? `code=${code}` : null,
|
|
330473
|
+
safeReason ? `reason=${safeReason}` : null
|
|
330474
|
+
].filter((detail) => detail !== null);
|
|
330475
|
+
return new Error(details.length > 0 ? `Binance user-data WebSocket closed unexpectedly (${details.join(", ")})` : "Binance user-data WebSocket closed unexpectedly");
|
|
330476
|
+
}
|
|
330477
|
+
function decodeMessageData(data) {
|
|
330478
|
+
if (typeof data === "string") {
|
|
330479
|
+
return data;
|
|
330480
|
+
}
|
|
330481
|
+
if (Buffer2.isBuffer(data)) {
|
|
330482
|
+
return data.toString("utf8");
|
|
330483
|
+
}
|
|
330484
|
+
if (data instanceof ArrayBuffer) {
|
|
330485
|
+
return Buffer2.from(data).toString("utf8");
|
|
330486
|
+
}
|
|
330487
|
+
if (ArrayBuffer.isView(data)) {
|
|
330488
|
+
return Buffer2.from(data.buffer, data.byteOffset, data.byteLength).toString("utf8");
|
|
330489
|
+
}
|
|
330490
|
+
if (Array.isArray(data) && data.every((item) => Buffer2.isBuffer(item))) {
|
|
330491
|
+
return Buffer2.concat(data).toString("utf8");
|
|
330492
|
+
}
|
|
330493
|
+
return data;
|
|
330494
|
+
}
|
|
330322
330495
|
|
|
330323
330496
|
class BinanceSpotUserDataStream {
|
|
330324
330497
|
exchange;
|
|
330325
330498
|
ws;
|
|
330499
|
+
secretValues;
|
|
330326
330500
|
requestId = `user-data-${Date.now()}-${Math.random()}`;
|
|
330501
|
+
maxBufferedEvents;
|
|
330327
330502
|
queue = [];
|
|
330328
330503
|
waiters = [];
|
|
330329
330504
|
closed = false;
|
|
330330
330505
|
closeError = null;
|
|
330331
330506
|
subscriptionId = null;
|
|
330332
|
-
constructor(exchange) {
|
|
330507
|
+
constructor(exchange, options = {}) {
|
|
330333
330508
|
this.exchange = exchange;
|
|
330334
|
-
|
|
330335
|
-
this.
|
|
330336
|
-
|
|
330337
|
-
|
|
330338
|
-
|
|
330339
|
-
this.ws
|
|
330509
|
+
this.maxBufferedEvents = options.maxBufferedEvents ?? DEFAULT_BINANCE_USER_DATA_MAX_BUFFERED_EVENTS;
|
|
330510
|
+
this.secretValues = [
|
|
330511
|
+
getOptionalExchangeString(exchange, "apiKey"),
|
|
330512
|
+
getOptionalExchangeString(exchange, "secret")
|
|
330513
|
+
].filter((value) => value !== null);
|
|
330514
|
+
this.ws = createWebSocket(getBinanceSpotWsApiUrl(exchange));
|
|
330515
|
+
this.ws.on("open", () => this.subscribe());
|
|
330516
|
+
this.ws.on("message", (data) => this.handleMessage(data));
|
|
330517
|
+
this.ws.on("error", (error48) => this.fail(formatBinanceUserDataWebSocketError(error48, this.secretValues)));
|
|
330518
|
+
this.ws.on("close", (code, reason) => this.handleClose(code, reason));
|
|
330340
330519
|
}
|
|
330341
330520
|
async* [Symbol.asyncIterator]() {
|
|
330342
330521
|
while (true) {
|
|
@@ -330352,11 +330531,18 @@ class BinanceSpotUserDataStream {
|
|
|
330352
330531
|
return;
|
|
330353
330532
|
}
|
|
330354
330533
|
this.closed = true;
|
|
330534
|
+
this.queue.length = 0;
|
|
330355
330535
|
try {
|
|
330356
330536
|
this.ws.close();
|
|
330357
330537
|
} catch {}
|
|
330358
330538
|
this.flushWaiters();
|
|
330359
330539
|
}
|
|
330540
|
+
handleClose(code, reason) {
|
|
330541
|
+
if (this.closed) {
|
|
330542
|
+
return;
|
|
330543
|
+
}
|
|
330544
|
+
this.fail(formatBinanceUserDataWebSocketClose(code, reason, this.secretValues));
|
|
330545
|
+
}
|
|
330360
330546
|
subscribe() {
|
|
330361
330547
|
const apiKey = getExchangeString(this.exchange, "apiKey");
|
|
330362
330548
|
const signedParams = signUserDataStreamParams(this.exchange, {
|
|
@@ -330370,9 +330556,13 @@ class BinanceSpotUserDataStream {
|
|
|
330370
330556
|
}));
|
|
330371
330557
|
}
|
|
330372
330558
|
handleMessage(data) {
|
|
330559
|
+
if (this.closed) {
|
|
330560
|
+
return;
|
|
330561
|
+
}
|
|
330373
330562
|
let message;
|
|
330374
330563
|
try {
|
|
330375
|
-
|
|
330564
|
+
const decodedData = decodeMessageData(data);
|
|
330565
|
+
message = typeof decodedData === "string" ? JSON.parse(decodedData) : decodedData;
|
|
330376
330566
|
} catch (error48) {
|
|
330377
330567
|
this.fail(error48 instanceof Error ? error48 : new Error("Invalid Binance user-data message"));
|
|
330378
330568
|
return;
|
|
@@ -330395,11 +330585,18 @@ class BinanceSpotUserDataStream {
|
|
|
330395
330585
|
this.push({ subscriptionId, event: message.event });
|
|
330396
330586
|
}
|
|
330397
330587
|
push(event) {
|
|
330588
|
+
if (this.closed) {
|
|
330589
|
+
return;
|
|
330590
|
+
}
|
|
330398
330591
|
const waiter = this.waiters.shift();
|
|
330399
330592
|
if (waiter) {
|
|
330400
330593
|
waiter.resolve(event);
|
|
330401
330594
|
return;
|
|
330402
330595
|
}
|
|
330596
|
+
if (this.queue.length >= this.maxBufferedEvents) {
|
|
330597
|
+
this.fail(new Error(`Binance user-data stream buffered event limit exceeded (${this.maxBufferedEvents}); downstream consumer is not keeping up`));
|
|
330598
|
+
return;
|
|
330599
|
+
}
|
|
330403
330600
|
this.queue.push(event);
|
|
330404
330601
|
}
|
|
330405
330602
|
nextEvent() {
|
|
@@ -330423,6 +330620,7 @@ class BinanceSpotUserDataStream {
|
|
|
330423
330620
|
}
|
|
330424
330621
|
this.closeError = error48;
|
|
330425
330622
|
this.closed = true;
|
|
330623
|
+
this.queue.length = 0;
|
|
330426
330624
|
this.flushWaiters();
|
|
330427
330625
|
try {
|
|
330428
330626
|
this.ws.close();
|
|
@@ -330450,16 +330648,49 @@ function isBinanceOrderUserDataEvent(event) {
|
|
|
330450
330648
|
function isBinanceSpotAccountSubscription(cex3, subscriptionType, marketTypeInput) {
|
|
330451
330649
|
return cex3 === "binance" && parseMarketType(marketTypeInput) === "spot" && (subscriptionType === SubscriptionType.BALANCE || subscriptionType === SubscriptionType.ORDERS);
|
|
330452
330650
|
}
|
|
330453
|
-
function
|
|
330651
|
+
function waitForSubscribeDrain(call, isClosed) {
|
|
330652
|
+
if (isClosed() || call.destroyed) {
|
|
330653
|
+
return Promise.resolve(false);
|
|
330654
|
+
}
|
|
330655
|
+
return new Promise((resolve) => {
|
|
330656
|
+
let settled = false;
|
|
330657
|
+
const cleanup = () => {
|
|
330658
|
+
call.off("drain", onDrain);
|
|
330659
|
+
call.off("close", onClosed);
|
|
330660
|
+
call.off("cancelled", onClosed);
|
|
330661
|
+
call.off("end", onClosed);
|
|
330662
|
+
call.off("error", onClosed);
|
|
330663
|
+
};
|
|
330664
|
+
const settle2 = (drained) => {
|
|
330665
|
+
if (settled) {
|
|
330666
|
+
return;
|
|
330667
|
+
}
|
|
330668
|
+
settled = true;
|
|
330669
|
+
cleanup();
|
|
330670
|
+
resolve(drained && !isClosed() && !call.destroyed);
|
|
330671
|
+
};
|
|
330672
|
+
const onDrain = () => settle2(true);
|
|
330673
|
+
const onClosed = () => settle2(false);
|
|
330674
|
+
call.once("drain", onDrain);
|
|
330675
|
+
call.once("close", onClosed);
|
|
330676
|
+
call.once("cancelled", onClosed);
|
|
330677
|
+
call.once("end", onClosed);
|
|
330678
|
+
call.once("error", onClosed);
|
|
330679
|
+
});
|
|
330680
|
+
}
|
|
330681
|
+
async function writeSubscribeFrame(call, isClosed, frame) {
|
|
330454
330682
|
if (isClosed() || call.destroyed) {
|
|
330455
330683
|
return false;
|
|
330456
330684
|
}
|
|
330457
|
-
call.write(frame);
|
|
330458
|
-
|
|
330685
|
+
const canContinue = call.write(frame);
|
|
330686
|
+
if (canContinue) {
|
|
330687
|
+
return true;
|
|
330688
|
+
}
|
|
330689
|
+
return waitForSubscribeDrain(call, isClosed);
|
|
330459
330690
|
}
|
|
330460
|
-
function writeSubscribeError(call, isClosed, frame) {
|
|
330461
|
-
writeSubscribeFrame(call, isClosed, frame);
|
|
330462
|
-
if (!isClosed() && !call.destroyed) {
|
|
330691
|
+
async function writeSubscribeError(call, isClosed, frame) {
|
|
330692
|
+
const frameWritten = await writeSubscribeFrame(call, isClosed, frame);
|
|
330693
|
+
if (frameWritten && !isClosed() && !call.destroyed) {
|
|
330463
330694
|
call.end();
|
|
330464
330695
|
}
|
|
330465
330696
|
}
|
|
@@ -330506,7 +330737,7 @@ async function streamBinanceUserData(call, broker, symbol2, subscriptionType, is
|
|
|
330506
330737
|
continue;
|
|
330507
330738
|
}
|
|
330508
330739
|
}
|
|
330509
|
-
if (!writeSubscribeFrame(call, isClosed, {
|
|
330740
|
+
if (!await writeSubscribeFrame(call, isClosed, {
|
|
330510
330741
|
data: JSON.stringify({
|
|
330511
330742
|
subscriptionId: message.subscriptionId,
|
|
330512
330743
|
event
|
|
@@ -330525,7 +330756,7 @@ async function streamBinanceUserData(call, broker, symbol2, subscriptionType, is
|
|
|
330525
330756
|
async function runCcxtSubscribeLoop(call, isClosed, symbol2, subscriptionType, watch) {
|
|
330526
330757
|
while (!isClosed()) {
|
|
330527
330758
|
const data = await watch();
|
|
330528
|
-
if (!writeSubscribeFrame(call, isClosed, {
|
|
330759
|
+
if (!await writeSubscribeFrame(call, isClosed, {
|
|
330529
330760
|
data: JSON.stringify(data),
|
|
330530
330761
|
timestamp: Date.now(),
|
|
330531
330762
|
symbol: symbol2,
|
|
@@ -330591,7 +330822,7 @@ function createSubscribeHandler(deps) {
|
|
|
330591
330822
|
type: subscriptionTypeName
|
|
330592
330823
|
});
|
|
330593
330824
|
if (!cex3 || !symbol2) {
|
|
330594
|
-
writeSubscribeError(call, isStreamClosed, {
|
|
330825
|
+
await writeSubscribeError(call, isStreamClosed, {
|
|
330595
330826
|
data: JSON.stringify({
|
|
330596
330827
|
error: "cex, symbol, and type are required"
|
|
330597
330828
|
}),
|
|
@@ -330605,7 +330836,7 @@ function createSubscribeHandler(deps) {
|
|
|
330605
330836
|
const selectedBroker = selectBroker(brokers[normalizedCex], metadata) ?? createBroker(normalizedCex, metadata);
|
|
330606
330837
|
const broker = selectedBroker ?? createPublicBroker(normalizedCex);
|
|
330607
330838
|
if (!broker) {
|
|
330608
|
-
writeSubscribeError(call, isStreamClosed, {
|
|
330839
|
+
await writeSubscribeError(call, isStreamClosed, {
|
|
330609
330840
|
data: JSON.stringify({
|
|
330610
330841
|
error: "Exchange not registered and no API metadata found"
|
|
330611
330842
|
}),
|
|
@@ -330618,7 +330849,7 @@ function createSubscribeHandler(deps) {
|
|
|
330618
330849
|
const resolvedSymbol = await resolveSubscriptionSymbol(broker, symbol2, options?.marketType);
|
|
330619
330850
|
if (isBinanceSpotAccountSubscription(normalizedCex, subscriptionType, options?.marketType)) {
|
|
330620
330851
|
if (!selectedBroker) {
|
|
330621
|
-
writeSubscribeError(call, isStreamClosed, {
|
|
330852
|
+
await writeSubscribeError(call, isStreamClosed, {
|
|
330622
330853
|
data: JSON.stringify({
|
|
330623
330854
|
error: "Binance account subscriptions require API credentials"
|
|
330624
330855
|
}),
|
|
@@ -330638,7 +330869,7 @@ function createSubscribeHandler(deps) {
|
|
|
330638
330869
|
const depthLimit = parseOptionalDepthLimit(options?.depthLimit ?? options?.limit);
|
|
330639
330870
|
const orderbook = depthLimit === undefined ? await broker.watchOrderBook(resolvedSymbol) : await broker.watchOrderBook(resolvedSymbol, depthLimit);
|
|
330640
330871
|
const receivedTimestamp = Date.now();
|
|
330641
|
-
if (!writeSubscribeFrame(call, isStreamClosed, {
|
|
330872
|
+
if (!await writeSubscribeFrame(call, isStreamClosed, {
|
|
330642
330873
|
data: JSON.stringify(normalizeOrderBookSnapshot(orderbook, {
|
|
330643
330874
|
exchange: normalizedCex,
|
|
330644
330875
|
symbol: resolvedSymbol,
|
|
@@ -330655,7 +330886,7 @@ function createSubscribeHandler(deps) {
|
|
|
330655
330886
|
} catch (error48) {
|
|
330656
330887
|
log.error(`Error fetching orderbook for ${resolvedSymbol} on ${cex3}:`, error48);
|
|
330657
330888
|
const message = getErrorMessage(error48);
|
|
330658
|
-
writeSubscribeError(call, isStreamClosed, {
|
|
330889
|
+
await writeSubscribeError(call, isStreamClosed, {
|
|
330659
330890
|
data: JSON.stringify({
|
|
330660
330891
|
error: `Failed to fetch orderbook: ${message}`
|
|
330661
330892
|
}),
|
|
@@ -330671,7 +330902,7 @@ function createSubscribeHandler(deps) {
|
|
|
330671
330902
|
} catch (error48) {
|
|
330672
330903
|
const message = getErrorMessage(error48);
|
|
330673
330904
|
log.error(`Error fetching trades for ${resolvedSymbol} on ${cex3}:`, error48);
|
|
330674
|
-
writeSubscribeError(call, isStreamClosed, {
|
|
330905
|
+
await writeSubscribeError(call, isStreamClosed, {
|
|
330675
330906
|
data: JSON.stringify({
|
|
330676
330907
|
error: `Failed to fetch trades: ${message}`
|
|
330677
330908
|
}),
|
|
@@ -330687,7 +330918,7 @@ function createSubscribeHandler(deps) {
|
|
|
330687
330918
|
} catch (error48) {
|
|
330688
330919
|
const message = getErrorMessage(error48);
|
|
330689
330920
|
log.error(`Error fetching ticker for ${resolvedSymbol} on ${cex3}:`, error48);
|
|
330690
|
-
writeSubscribeError(call, isStreamClosed, {
|
|
330921
|
+
await writeSubscribeError(call, isStreamClosed, {
|
|
330691
330922
|
data: JSON.stringify({
|
|
330692
330923
|
error: `Failed to fetch ticker: ${message}`
|
|
330693
330924
|
}),
|
|
@@ -330704,7 +330935,7 @@ function createSubscribeHandler(deps) {
|
|
|
330704
330935
|
} catch (error48) {
|
|
330705
330936
|
log.error(`Error fetching OHLCV for ${resolvedSymbol} on ${cex3}:`, error48);
|
|
330706
330937
|
const message = getErrorMessage(error48);
|
|
330707
|
-
writeSubscribeError(call, isStreamClosed, {
|
|
330938
|
+
await writeSubscribeError(call, isStreamClosed, {
|
|
330708
330939
|
data: JSON.stringify({
|
|
330709
330940
|
error: `Failed to fetch OHLCV: ${message}`
|
|
330710
330941
|
}),
|
|
@@ -330720,7 +330951,7 @@ function createSubscribeHandler(deps) {
|
|
|
330720
330951
|
} catch (error48) {
|
|
330721
330952
|
const message = getErrorMessage(error48);
|
|
330722
330953
|
log.error(`Error fetching balance for ${cex3}:`, error48);
|
|
330723
|
-
writeSubscribeError(call, isStreamClosed, {
|
|
330954
|
+
await writeSubscribeError(call, isStreamClosed, {
|
|
330724
330955
|
data: JSON.stringify({
|
|
330725
330956
|
error: `Failed to fetch balance: ${message}`
|
|
330726
330957
|
}),
|
|
@@ -330736,7 +330967,7 @@ function createSubscribeHandler(deps) {
|
|
|
330736
330967
|
} catch (error48) {
|
|
330737
330968
|
log.error(`Error fetching orders for ${resolvedSymbol} on ${cex3}:`, error48);
|
|
330738
330969
|
const message = getErrorMessage(error48);
|
|
330739
|
-
writeSubscribeError(call, isStreamClosed, {
|
|
330970
|
+
await writeSubscribeError(call, isStreamClosed, {
|
|
330740
330971
|
data: JSON.stringify({
|
|
330741
330972
|
error: `Failed to fetch orders: ${message}`
|
|
330742
330973
|
}),
|
|
@@ -330747,7 +330978,7 @@ function createSubscribeHandler(deps) {
|
|
|
330747
330978
|
}
|
|
330748
330979
|
break;
|
|
330749
330980
|
default:
|
|
330750
|
-
writeSubscribeError(call, isStreamClosed, {
|
|
330981
|
+
await writeSubscribeError(call, isStreamClosed, {
|
|
330751
330982
|
data: JSON.stringify({ error: "Invalid subscription type" }),
|
|
330752
330983
|
timestamp: Date.now(),
|
|
330753
330984
|
symbol: symbol2,
|
|
@@ -330757,7 +330988,7 @@ function createSubscribeHandler(deps) {
|
|
|
330757
330988
|
} catch (error48) {
|
|
330758
330989
|
log.error("Error in Subscribe stream:", error48);
|
|
330759
330990
|
const message = getErrorMessage(error48);
|
|
330760
|
-
writeSubscribeError(call, isStreamClosed, {
|
|
330991
|
+
await writeSubscribeError(call, isStreamClosed, {
|
|
330761
330992
|
data: JSON.stringify({ error: `Internal server error: ${message}` }),
|
|
330762
330993
|
timestamp: Date.now(),
|
|
330763
330994
|
symbol: "",
|
|
@@ -331132,20 +331363,20 @@ class CEXBroker {
|
|
|
331132
331363
|
this.brokers = createBrokerPool(configMap);
|
|
331133
331364
|
}
|
|
331134
331365
|
loadExchangeCredentials(creds) {
|
|
331135
|
-
const schema =
|
|
331136
|
-
apiKey:
|
|
331137
|
-
apiSecret:
|
|
331138
|
-
role:
|
|
331139
|
-
email:
|
|
331140
|
-
subAccountId:
|
|
331141
|
-
uid:
|
|
331142
|
-
secondaryKeys:
|
|
331143
|
-
apiKey:
|
|
331144
|
-
apiSecret:
|
|
331145
|
-
role:
|
|
331146
|
-
email:
|
|
331147
|
-
subAccountId:
|
|
331148
|
-
uid:
|
|
331366
|
+
const schema = import_joi3.default.object().pattern(import_joi3.default.string().allow(...BrokerList).required(), import_joi3.default.object({
|
|
331367
|
+
apiKey: import_joi3.default.string().required(),
|
|
331368
|
+
apiSecret: import_joi3.default.string().required(),
|
|
331369
|
+
role: import_joi3.default.string().valid("master", "subaccount").optional(),
|
|
331370
|
+
email: import_joi3.default.string().optional(),
|
|
331371
|
+
subAccountId: import_joi3.default.string().optional(),
|
|
331372
|
+
uid: import_joi3.default.string().optional(),
|
|
331373
|
+
secondaryKeys: import_joi3.default.array().items(import_joi3.default.object({
|
|
331374
|
+
apiKey: import_joi3.default.string().required(),
|
|
331375
|
+
apiSecret: import_joi3.default.string().required(),
|
|
331376
|
+
role: import_joi3.default.string().valid("master", "subaccount").optional(),
|
|
331377
|
+
email: import_joi3.default.string().optional(),
|
|
331378
|
+
subAccountId: import_joi3.default.string().optional(),
|
|
331379
|
+
uid: import_joi3.default.string().optional()
|
|
331149
331380
|
})).default([])
|
|
331150
331381
|
})).required();
|
|
331151
331382
|
const { value, error: error48 } = schema.validate(creds);
|
|
@@ -1,22 +1,39 @@
|
|
|
1
1
|
import type { Exchange } from "@usherlabs/ccxt";
|
|
2
2
|
export declare const BINANCE_SPOT_WS_API_URL = "wss://ws-api.binance.com:443/ws-api/v3";
|
|
3
|
+
export declare const DEFAULT_BINANCE_USER_DATA_MAX_BUFFERED_EVENTS = 16;
|
|
3
4
|
export type BinanceUserDataEvent = {
|
|
4
5
|
subscriptionId: number;
|
|
5
6
|
event: Record<string, unknown>;
|
|
6
7
|
};
|
|
8
|
+
type WebSocketLike = {
|
|
9
|
+
on(event: "open", listener: () => void): unknown;
|
|
10
|
+
on(event: "message", listener: (data: unknown) => void): unknown;
|
|
11
|
+
on(event: "error", listener: (error: unknown) => void): unknown;
|
|
12
|
+
on(event: "close", listener: (code: unknown, reason: unknown) => void): unknown;
|
|
13
|
+
send(data: string): void;
|
|
14
|
+
close(code?: number, reason?: string): void;
|
|
15
|
+
};
|
|
16
|
+
type WebSocketFactory = (url: string) => WebSocketLike;
|
|
17
|
+
type BinanceSpotUserDataStreamOptions = {
|
|
18
|
+
maxBufferedEvents?: number;
|
|
19
|
+
};
|
|
20
|
+
export declare function setBinanceUserDataWebSocketFactoryForTests(factory: WebSocketFactory): () => void;
|
|
7
21
|
export declare function getBinanceSpotWsApiUrl(exchange: Exchange): string;
|
|
8
22
|
export declare class BinanceSpotUserDataStream implements AsyncIterable<BinanceUserDataEvent> {
|
|
9
23
|
private readonly exchange;
|
|
10
24
|
private readonly ws;
|
|
25
|
+
private readonly secretValues;
|
|
11
26
|
private readonly requestId;
|
|
27
|
+
private readonly maxBufferedEvents;
|
|
12
28
|
private readonly queue;
|
|
13
29
|
private readonly waiters;
|
|
14
30
|
private closed;
|
|
15
31
|
private closeError;
|
|
16
32
|
private subscriptionId;
|
|
17
|
-
constructor(exchange: Exchange);
|
|
33
|
+
constructor(exchange: Exchange, options?: BinanceSpotUserDataStreamOptions);
|
|
18
34
|
[Symbol.asyncIterator](): AsyncIterator<BinanceUserDataEvent>;
|
|
19
35
|
close(): void;
|
|
36
|
+
private handleClose;
|
|
20
37
|
private subscribe;
|
|
21
38
|
private handleMessage;
|
|
22
39
|
private push;
|
|
@@ -26,3 +43,4 @@ export declare class BinanceSpotUserDataStream implements AsyncIterable<BinanceU
|
|
|
26
43
|
}
|
|
27
44
|
export declare function isBinanceBalanceUserDataEvent(event: Record<string, unknown>): boolean;
|
|
28
45
|
export declare function isBinanceOrderUserDataEvent(event: Record<string, unknown>): boolean;
|
|
46
|
+
export {};
|
package/dist/helpers/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { PolicyConfig } from "../types";
|
|
|
3
3
|
import { type BrokerAccount } from "./broker";
|
|
4
4
|
export { authenticateRequest } from "./auth";
|
|
5
5
|
export { applyCommonExchangeConfig, type BrokerAccount, BrokerAccountPreconditionError, type BrokerPoolEntry, createBroker, createBrokerPool, createPublicBroker, getCurrentBrokerSelector, resolveBrokerAccount, selectBroker, selectBrokerAccount, } from "./broker";
|
|
6
|
+
export { australiaQuestionnaireSchema, registerBinanceTravelRuleWithdrawEndpoint, resolveTravelRuleDecision, type TravelRuleDecision, withdrawViaLocalEntity, } from "./travel-rule";
|
|
6
7
|
export { buildHttpClientOverrideFromMetadata, createVerityHttpClientOverride, verityHttpClientOverridePredicate, } from "./verity";
|
|
7
8
|
/**
|
|
8
9
|
* Loads and validates policy configuration
|