@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.
@@ -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 import_joi2 = __toESM(require_lib4(), 1);
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 import_joi = __toESM(require_lib4(), 1);
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 = import_joi.default.object({
313975
- exchange: import_joi.default.string().required(),
313976
- network: import_joi.default.string().required(),
313977
- whitelist: import_joi.default.array().items(import_joi.default.string()).required(),
313978
- coins: import_joi.default.array().items(import_joi.default.string()).optional()
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 = import_joi.default.object({
313981
- exchange: import_joi.default.string().required(),
313982
- network: import_joi.default.string().required(),
313983
- coins: import_joi.default.array().items(import_joi.default.string()).optional()
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 = import_joi.default.object({
313986
- markets: import_joi.default.array().items(import_joi.default.string()).required(),
313987
- limits: import_joi.default.array().items(import_joi.default.object({
313988
- from: import_joi.default.string().required(),
313989
- to: import_joi.default.string().required(),
313990
- min: import_joi.default.number().required(),
313991
- max: import_joi.default.number().required()
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 policyConfigSchema = import_joi.default.object({
313995
- withdraw: import_joi.default.object({
313996
- rule: import_joi.default.array().items(withdrawRuleEntrySchema).min(1).required()
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: import_joi.default.object({
313999
- rule: import_joi.default.array().items(depositRuleEntrySchema).optional()
314082
+ deposit: import_joi2.default.object({
314083
+ rule: import_joi2.default.array().items(depositRuleEntrySchema).optional()
314000
314084
  }).required(),
314001
- order: import_joi.default.object({
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 = await broker.withdraw(symbol2, transferValue.amount, transferValue.recipientAddress, undefined, {
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
- function getWebSocketConstructor() {
330291
- const WebSocketCtor = globalThis.WebSocket;
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
- const WebSocketCtor = getWebSocketConstructor();
330335
- this.ws = new WebSocketCtor(getBinanceSpotWsApiUrl(exchange));
330336
- this.ws.onopen = () => this.subscribe();
330337
- this.ws.onmessage = (message) => this.handleMessage(message.data);
330338
- this.ws.onerror = (error48) => this.fail(error48 instanceof Error ? error48 : new Error("Binance user-data WebSocket error"));
330339
- this.ws.onclose = () => this.close();
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
- message = typeof data === "string" ? JSON.parse(data) : data;
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 writeSubscribeFrame(call, isClosed, frame) {
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
- return true;
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 = import_joi2.default.object().pattern(import_joi2.default.string().allow(...BrokerList).required(), import_joi2.default.object({
331136
- apiKey: import_joi2.default.string().required(),
331137
- apiSecret: import_joi2.default.string().required(),
331138
- role: import_joi2.default.string().valid("master", "subaccount").optional(),
331139
- email: import_joi2.default.string().optional(),
331140
- subAccountId: import_joi2.default.string().optional(),
331141
- uid: import_joi2.default.string().optional(),
331142
- secondaryKeys: import_joi2.default.array().items(import_joi2.default.object({
331143
- apiKey: import_joi2.default.string().required(),
331144
- apiSecret: import_joi2.default.string().required(),
331145
- role: import_joi2.default.string().valid("master", "subaccount").optional(),
331146
- email: import_joi2.default.string().optional(),
331147
- subAccountId: import_joi2.default.string().optional(),
331148
- uid: import_joi2.default.string().optional()
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 {};
@@ -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