@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
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Exchange } from "@usherlabs/ccxt";
|
|
2
|
+
import Joi from "joi";
|
|
3
|
+
import type { PolicyConfig, TravelRuleQuestionnaire } from "../types";
|
|
4
|
+
export declare const australiaQuestionnaireSchema: Joi.ObjectSchema<any>;
|
|
5
|
+
export type TravelRuleDecision = {
|
|
6
|
+
mode: "standard";
|
|
7
|
+
} | {
|
|
8
|
+
mode: "localentity";
|
|
9
|
+
questionnaire: TravelRuleQuestionnaire;
|
|
10
|
+
} | {
|
|
11
|
+
mode: "denied";
|
|
12
|
+
error: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Decides whether a withdrawal must use Binance's travel-rule endpoint.
|
|
16
|
+
*
|
|
17
|
+
* Travel rule is opt-in per exchange via the `enabled` flag, so a non-AU account
|
|
18
|
+
* keeps using the standard endpoint. When enabled, the destination address must
|
|
19
|
+
* have a configured questionnaire; if it does not we fail closed rather than fall
|
|
20
|
+
* back to the standard endpoint (which would just reproduce the -4104 rejection).
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveTravelRuleDecision(policy: PolicyConfig, exchange: string, recipientAddress: string): TravelRuleDecision;
|
|
23
|
+
/**
|
|
24
|
+
* Registers Binance's `localentity/withdraw/apply` endpoint on the exchange
|
|
25
|
+
* instance. No-op for non-Binance exchanges. Idempotent — ccxt just reassigns
|
|
26
|
+
* the generated implicit method if called again.
|
|
27
|
+
*/
|
|
28
|
+
export declare function registerBinanceTravelRuleWithdrawEndpoint(exchange: Exchange): void;
|
|
29
|
+
type LocalEntityWithdrawArgs = {
|
|
30
|
+
code: string;
|
|
31
|
+
amount: number;
|
|
32
|
+
address: string;
|
|
33
|
+
network: string;
|
|
34
|
+
questionnaire: TravelRuleQuestionnaire;
|
|
35
|
+
params?: Record<string, string | number>;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Mirrors ccxt's `binance.withdraw` currency/network/precision handling, but
|
|
39
|
+
* targets the travel-rule endpoint and attaches the questionnaire. ccxt's
|
|
40
|
+
* `urlencode` applies `encodeURIComponent` to the value, satisfying Binance's
|
|
41
|
+
* requirement that the questionnaire JSON be URL-encoded in the request body.
|
|
42
|
+
*/
|
|
43
|
+
export declare function withdrawViaLocalEntity(broker: Exchange, args: LocalEntityWithdrawArgs): Promise<import("@usherlabs/ccxt").Transaction>;
|
|
44
|
+
export {};
|
package/dist/index.js
CHANGED
|
@@ -270096,11 +270096,11 @@ var ccxt = Object.assign({ version: version2, Exchange, Precise, exchanges: Obje
|
|
|
270096
270096
|
var ccxt_default = ccxt;
|
|
270097
270097
|
|
|
270098
270098
|
// src/index.ts
|
|
270099
|
-
var
|
|
270099
|
+
var import_joi3 = __toESM(require_lib4(), 1);
|
|
270100
270100
|
import { unwatchFile, watchFile } from "fs";
|
|
270101
270101
|
|
|
270102
270102
|
// src/helpers/index.ts
|
|
270103
|
-
var
|
|
270103
|
+
var import_joi2 = __toESM(require_lib4(), 1);
|
|
270104
270104
|
import fs from "fs";
|
|
270105
270105
|
|
|
270106
270106
|
// src/helpers/exchange-credentials.ts
|
|
@@ -272784,6 +272784,81 @@ if (process.env.LOG_LEVEL !== "debug") {
|
|
|
272784
272784
|
}
|
|
272785
272785
|
var log = baseLogger;
|
|
272786
272786
|
|
|
272787
|
+
// src/helpers/travel-rule.ts
|
|
272788
|
+
var import_joi = __toESM(require_lib4(), 1);
|
|
272789
|
+
var isAnotherBeneficiary = import_joi.default.number().valid(2).required();
|
|
272790
|
+
var isIndividual = import_joi.default.number().valid(0).required();
|
|
272791
|
+
var isCorporate = import_joi.default.number().valid(1).required();
|
|
272792
|
+
var isSendToVasp = import_joi.default.number().valid(2).required();
|
|
272793
|
+
var isVaspOthers = import_joi.default.string().valid("others").required();
|
|
272794
|
+
function requiredWhen(base2, ref, is) {
|
|
272795
|
+
return base2.when(ref, {
|
|
272796
|
+
is,
|
|
272797
|
+
then: import_joi.default.required(),
|
|
272798
|
+
otherwise: import_joi.default.forbidden()
|
|
272799
|
+
});
|
|
272800
|
+
}
|
|
272801
|
+
var australiaQuestionnaireSchema = import_joi.default.object({
|
|
272802
|
+
isAddressOwner: import_joi.default.number().valid(1, 2).required(),
|
|
272803
|
+
sendTo: import_joi.default.number().valid(1, 2).required(),
|
|
272804
|
+
declaration: import_joi.default.boolean().valid(true).required(),
|
|
272805
|
+
bnfType: requiredWhen(import_joi.default.number().valid(0, 1), "isAddressOwner", isAnotherBeneficiary),
|
|
272806
|
+
bnfFirstName: requiredWhen(import_joi.default.string(), "bnfType", isIndividual),
|
|
272807
|
+
bnfLastName: requiredWhen(import_joi.default.string(), "bnfType", isIndividual),
|
|
272808
|
+
country: requiredWhen(import_joi.default.string(), "bnfType", isIndividual),
|
|
272809
|
+
city: requiredWhen(import_joi.default.string(), "bnfType", isIndividual),
|
|
272810
|
+
bnfCorpName: requiredWhen(import_joi.default.string(), "bnfType", isCorporate),
|
|
272811
|
+
bnfCorpCountry: requiredWhen(import_joi.default.string(), "bnfType", isCorporate),
|
|
272812
|
+
bnfCorpCity: requiredWhen(import_joi.default.string(), "bnfType", isCorporate),
|
|
272813
|
+
vasp: requiredWhen(import_joi.default.string(), "sendTo", isSendToVasp),
|
|
272814
|
+
vaspName: requiredWhen(import_joi.default.string(), "vasp", isVaspOthers)
|
|
272815
|
+
});
|
|
272816
|
+
function resolveTravelRuleDecision(policy, exchange, recipientAddress) {
|
|
272817
|
+
const rules = policy.travelRule?.rule ?? [];
|
|
272818
|
+
const exchangeNorm = exchange.trim().toUpperCase();
|
|
272819
|
+
const entry = rules.find((rule) => rule.exchange.trim().toUpperCase() === exchangeNorm);
|
|
272820
|
+
if (!entry || !entry.enabled) {
|
|
272821
|
+
return { mode: "standard" };
|
|
272822
|
+
}
|
|
272823
|
+
const addressNorm = recipientAddress.trim().toLowerCase();
|
|
272824
|
+
const match = Object.entries(entry.addresses).find(([address]) => address.trim().toLowerCase() === addressNorm);
|
|
272825
|
+
if (!match) {
|
|
272826
|
+
return {
|
|
272827
|
+
mode: "denied",
|
|
272828
|
+
error: `no travel-rule questionnaire configured for ${exchangeNorm} address ${recipientAddress}`
|
|
272829
|
+
};
|
|
272830
|
+
}
|
|
272831
|
+
return { mode: "localentity", questionnaire: match[1].questionnaire };
|
|
272832
|
+
}
|
|
272833
|
+
function registerBinanceTravelRuleWithdrawEndpoint(exchange) {
|
|
272834
|
+
if (exchange.id !== "binance") {
|
|
272835
|
+
return;
|
|
272836
|
+
}
|
|
272837
|
+
exchange.defineRestApi({ sapi: { post: { "localentity/withdraw/apply": 4.0002 } } }, "request");
|
|
272838
|
+
}
|
|
272839
|
+
async function withdrawViaLocalEntity(broker, args) {
|
|
272840
|
+
const exchange = broker;
|
|
272841
|
+
if (typeof exchange.sapiPostLocalentityWithdrawApply !== "function") {
|
|
272842
|
+
throw new Error("binance_localentity_withdraw_unavailable: travel-rule withdraw endpoint is not registered on this exchange instance");
|
|
272843
|
+
}
|
|
272844
|
+
broker.checkAddress(args.address);
|
|
272845
|
+
await broker.loadMarkets();
|
|
272846
|
+
const currency = broker.currency(args.code);
|
|
272847
|
+
const networks = broker.safeDict(broker.options, "networks", {});
|
|
272848
|
+
const networkUpper = args.network.trim().toUpperCase();
|
|
272849
|
+
const mappedNetwork = broker.safeString(networks, networkUpper, networkUpper);
|
|
272850
|
+
const request = {
|
|
272851
|
+
...args.params,
|
|
272852
|
+
coin: currency.id,
|
|
272853
|
+
address: args.address,
|
|
272854
|
+
amount: broker.currencyToPrecision(args.code, args.amount),
|
|
272855
|
+
network: mappedNetwork,
|
|
272856
|
+
questionnaire: JSON.stringify(args.questionnaire)
|
|
272857
|
+
};
|
|
272858
|
+
const response = await exchange.sapiPostLocalentityWithdrawApply(request);
|
|
272859
|
+
return broker.parseTransaction(response, currency);
|
|
272860
|
+
}
|
|
272861
|
+
|
|
272787
272862
|
// src/helpers/broker.ts
|
|
272788
272863
|
class BrokerAccountPreconditionError extends Error {
|
|
272789
272864
|
constructor(message) {
|
|
@@ -272808,6 +272883,7 @@ function applyCommonExchangeConfig(exchange) {
|
|
|
272808
272883
|
recvWindow: 60000,
|
|
272809
272884
|
adjustForTimeDifference: true
|
|
272810
272885
|
});
|
|
272886
|
+
registerBinanceTravelRuleWithdrawEndpoint(exchange);
|
|
272811
272887
|
}
|
|
272812
272888
|
function createBroker(cex3, credsOrMetadata) {
|
|
272813
272889
|
let apiKey;
|
|
@@ -273224,36 +273300,47 @@ var verityHttpClientOverridePredicate = ({
|
|
|
273224
273300
|
function loadPolicy(policyPath) {
|
|
273225
273301
|
try {
|
|
273226
273302
|
const policyData = fs.readFileSync(policyPath, "utf8");
|
|
273227
|
-
const withdrawRuleEntrySchema =
|
|
273228
|
-
exchange:
|
|
273229
|
-
network:
|
|
273230
|
-
whitelist:
|
|
273231
|
-
coins:
|
|
273303
|
+
const withdrawRuleEntrySchema = import_joi2.default.object({
|
|
273304
|
+
exchange: import_joi2.default.string().required(),
|
|
273305
|
+
network: import_joi2.default.string().required(),
|
|
273306
|
+
whitelist: import_joi2.default.array().items(import_joi2.default.string()).required(),
|
|
273307
|
+
coins: import_joi2.default.array().items(import_joi2.default.string()).optional()
|
|
273232
273308
|
});
|
|
273233
|
-
const depositRuleEntrySchema =
|
|
273234
|
-
exchange:
|
|
273235
|
-
network:
|
|
273236
|
-
coins:
|
|
273309
|
+
const depositRuleEntrySchema = import_joi2.default.object({
|
|
273310
|
+
exchange: import_joi2.default.string().required(),
|
|
273311
|
+
network: import_joi2.default.string().required(),
|
|
273312
|
+
coins: import_joi2.default.array().items(import_joi2.default.string()).optional()
|
|
273237
273313
|
});
|
|
273238
|
-
const orderRuleSchema =
|
|
273239
|
-
markets:
|
|
273240
|
-
limits:
|
|
273241
|
-
from:
|
|
273242
|
-
to:
|
|
273243
|
-
min:
|
|
273244
|
-
max:
|
|
273314
|
+
const orderRuleSchema = import_joi2.default.object({
|
|
273315
|
+
markets: import_joi2.default.array().items(import_joi2.default.string()).required(),
|
|
273316
|
+
limits: import_joi2.default.array().items(import_joi2.default.object({
|
|
273317
|
+
from: import_joi2.default.string().required(),
|
|
273318
|
+
to: import_joi2.default.string().required(),
|
|
273319
|
+
min: import_joi2.default.number().required(),
|
|
273320
|
+
max: import_joi2.default.number().required()
|
|
273245
273321
|
})).default([])
|
|
273246
273322
|
});
|
|
273247
|
-
const
|
|
273248
|
-
|
|
273249
|
-
|
|
273323
|
+
const travelRuleEntrySchema = import_joi2.default.object({
|
|
273324
|
+
exchange: import_joi2.default.string().uppercase().valid("BINANCE").required(),
|
|
273325
|
+
enabled: import_joi2.default.boolean().required(),
|
|
273326
|
+
description: import_joi2.default.string().optional(),
|
|
273327
|
+
addresses: import_joi2.default.object().pattern(import_joi2.default.string(), import_joi2.default.object({
|
|
273328
|
+
questionnaire: australiaQuestionnaireSchema.required()
|
|
273329
|
+
})).required()
|
|
273330
|
+
});
|
|
273331
|
+
const policyConfigSchema = import_joi2.default.object({
|
|
273332
|
+
withdraw: import_joi2.default.object({
|
|
273333
|
+
rule: import_joi2.default.array().items(withdrawRuleEntrySchema).min(1).required()
|
|
273250
273334
|
}).required(),
|
|
273251
|
-
deposit:
|
|
273252
|
-
rule:
|
|
273335
|
+
deposit: import_joi2.default.object({
|
|
273336
|
+
rule: import_joi2.default.array().items(depositRuleEntrySchema).optional()
|
|
273253
273337
|
}).required(),
|
|
273254
|
-
order:
|
|
273338
|
+
order: import_joi2.default.object({
|
|
273255
273339
|
rule: orderRuleSchema.required()
|
|
273256
|
-
}).required()
|
|
273340
|
+
}).required(),
|
|
273341
|
+
travelRule: import_joi2.default.object({
|
|
273342
|
+
rule: import_joi2.default.array().items(travelRuleEntrySchema).required()
|
|
273343
|
+
}).optional()
|
|
273257
273344
|
});
|
|
273258
273345
|
const { error, value } = policyConfigSchema.validate(JSON.parse(policyData));
|
|
273259
273346
|
if (error) {
|
|
@@ -293067,8 +293154,22 @@ async function handleWithdraw(ctx) {
|
|
|
293067
293154
|
message: `policy_withdrawal_denied: ${transferValidation.error}`
|
|
293068
293155
|
}, null);
|
|
293069
293156
|
}
|
|
293157
|
+
const travelRule = resolveTravelRuleDecision(policy, cex3, transferValue.recipientAddress);
|
|
293158
|
+
if (travelRule.mode === "denied") {
|
|
293159
|
+
return ctx.wrappedCallback({
|
|
293160
|
+
code: grpc10.status.FAILED_PRECONDITION,
|
|
293161
|
+
message: `travel_rule_denied: ${travelRule.error}`
|
|
293162
|
+
}, null);
|
|
293163
|
+
}
|
|
293070
293164
|
try {
|
|
293071
|
-
const transaction =
|
|
293165
|
+
const transaction = travelRule.mode === "localentity" ? await withdrawViaLocalEntity(broker, {
|
|
293166
|
+
code: symbol2,
|
|
293167
|
+
amount: transferValue.amount,
|
|
293168
|
+
address: transferValue.recipientAddress,
|
|
293169
|
+
network: withdrawNetwork.exchangeNetworkId,
|
|
293170
|
+
questionnaire: travelRule.questionnaire,
|
|
293171
|
+
params: transferValue.params
|
|
293172
|
+
}) : await broker.withdraw(symbol2, transferValue.amount, transferValue.recipientAddress, undefined, {
|
|
293072
293173
|
...transferValue.params ?? {},
|
|
293073
293174
|
network: withdrawNetwork.exchangeNetworkId
|
|
293074
293175
|
});
|
|
@@ -293238,15 +293339,12 @@ function createExecuteActionHandler(deps) {
|
|
|
293238
293339
|
import * as grpc13 from "@grpc/grpc-js";
|
|
293239
293340
|
|
|
293240
293341
|
// src/helpers/binance-user-data-stream.ts
|
|
293342
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
293241
293343
|
import { createHmac } from "crypto";
|
|
293344
|
+
import WebSocket2 from "ws";
|
|
293242
293345
|
var BINANCE_SPOT_WS_API_URL = "wss://ws-api.binance.com:443/ws-api/v3";
|
|
293243
|
-
|
|
293244
|
-
|
|
293245
|
-
if (!WebSocketCtor) {
|
|
293246
|
-
throw new Error("WebSocket is not available in this runtime");
|
|
293247
|
-
}
|
|
293248
|
-
return WebSocketCtor;
|
|
293249
|
-
}
|
|
293346
|
+
var DEFAULT_BINANCE_USER_DATA_MAX_BUFFERED_EVENTS = 16;
|
|
293347
|
+
var createWebSocket = (url3) => new WebSocket2(url3);
|
|
293250
293348
|
function getExchangeString(exchange, key) {
|
|
293251
293349
|
const value = exchange[key];
|
|
293252
293350
|
if (typeof value !== "string" || value.length === 0) {
|
|
@@ -293272,24 +293370,106 @@ function getBinanceSpotWsApiUrl(exchange) {
|
|
|
293272
293370
|
const urls = exchange.urls;
|
|
293273
293371
|
return urls?.api?.ws?.["ws-api"]?.spot ?? BINANCE_SPOT_WS_API_URL;
|
|
293274
293372
|
}
|
|
293373
|
+
function getRecord(value) {
|
|
293374
|
+
return typeof value === "object" && value !== null ? value : null;
|
|
293375
|
+
}
|
|
293376
|
+
function getMessage(value) {
|
|
293377
|
+
if (value instanceof Error) {
|
|
293378
|
+
return value.message;
|
|
293379
|
+
}
|
|
293380
|
+
if (typeof value === "string" && value.length > 0) {
|
|
293381
|
+
return value;
|
|
293382
|
+
}
|
|
293383
|
+
const record2 = getRecord(value);
|
|
293384
|
+
const message = record2?.message;
|
|
293385
|
+
return typeof message === "string" && message.length > 0 ? message : null;
|
|
293386
|
+
}
|
|
293387
|
+
function getOptionalExchangeString(exchange, key) {
|
|
293388
|
+
const value = exchange[key];
|
|
293389
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
293390
|
+
}
|
|
293391
|
+
function redactDiagnosticMessage(message, secretValues) {
|
|
293392
|
+
let redacted = message;
|
|
293393
|
+
for (const value of secretValues) {
|
|
293394
|
+
if (value.length > 0) {
|
|
293395
|
+
redacted = redacted.split(value).join("[redacted]");
|
|
293396
|
+
}
|
|
293397
|
+
}
|
|
293398
|
+
return redacted.replace(/(\b(?:apiKey|secret|signature)\b\s*=\s*)[^\s&,;)]+/gi, "$1[redacted]").replace(/("(?:apiKey|secret|signature)"\s*:\s*")[^"]*(")/gi, "$1[redacted]$2");
|
|
293399
|
+
}
|
|
293400
|
+
function formatBinanceUserDataWebSocketError(event, secretValues) {
|
|
293401
|
+
const record2 = getRecord(event);
|
|
293402
|
+
const message = getMessage(record2?.error) ?? getMessage(record2?.message) ?? getMessage(event);
|
|
293403
|
+
const safeMessage = message === null ? null : redactDiagnosticMessage(message, secretValues);
|
|
293404
|
+
return new Error(safeMessage ? `Binance user-data WebSocket error: ${safeMessage}` : "Binance user-data WebSocket error");
|
|
293405
|
+
}
|
|
293406
|
+
function getCloseReason(value) {
|
|
293407
|
+
if (typeof value === "string") {
|
|
293408
|
+
return value.length > 0 ? value : null;
|
|
293409
|
+
}
|
|
293410
|
+
if (Buffer2.isBuffer(value)) {
|
|
293411
|
+
const reason = value.toString("utf8");
|
|
293412
|
+
return reason.length > 0 ? reason : null;
|
|
293413
|
+
}
|
|
293414
|
+
if (value instanceof Uint8Array) {
|
|
293415
|
+
const reason = Buffer2.from(value).toString("utf8");
|
|
293416
|
+
return reason.length > 0 ? reason : null;
|
|
293417
|
+
}
|
|
293418
|
+
return null;
|
|
293419
|
+
}
|
|
293420
|
+
function formatBinanceUserDataWebSocketClose(codeOrEvent, reasonOrUndefined, secretValues) {
|
|
293421
|
+
const record2 = getRecord(codeOrEvent);
|
|
293422
|
+
const code = record2 ? record2.code : codeOrEvent;
|
|
293423
|
+
const reason = getCloseReason(record2 ? record2.reason : reasonOrUndefined);
|
|
293424
|
+
const safeReason = reason === null ? null : redactDiagnosticMessage(reason, secretValues);
|
|
293425
|
+
const details = [
|
|
293426
|
+
typeof code === "number" || typeof code === "string" ? `code=${code}` : null,
|
|
293427
|
+
safeReason ? `reason=${safeReason}` : null
|
|
293428
|
+
].filter((detail) => detail !== null);
|
|
293429
|
+
return new Error(details.length > 0 ? `Binance user-data WebSocket closed unexpectedly (${details.join(", ")})` : "Binance user-data WebSocket closed unexpectedly");
|
|
293430
|
+
}
|
|
293431
|
+
function decodeMessageData(data) {
|
|
293432
|
+
if (typeof data === "string") {
|
|
293433
|
+
return data;
|
|
293434
|
+
}
|
|
293435
|
+
if (Buffer2.isBuffer(data)) {
|
|
293436
|
+
return data.toString("utf8");
|
|
293437
|
+
}
|
|
293438
|
+
if (data instanceof ArrayBuffer) {
|
|
293439
|
+
return Buffer2.from(data).toString("utf8");
|
|
293440
|
+
}
|
|
293441
|
+
if (ArrayBuffer.isView(data)) {
|
|
293442
|
+
return Buffer2.from(data.buffer, data.byteOffset, data.byteLength).toString("utf8");
|
|
293443
|
+
}
|
|
293444
|
+
if (Array.isArray(data) && data.every((item) => Buffer2.isBuffer(item))) {
|
|
293445
|
+
return Buffer2.concat(data).toString("utf8");
|
|
293446
|
+
}
|
|
293447
|
+
return data;
|
|
293448
|
+
}
|
|
293275
293449
|
|
|
293276
293450
|
class BinanceSpotUserDataStream {
|
|
293277
293451
|
exchange;
|
|
293278
293452
|
ws;
|
|
293453
|
+
secretValues;
|
|
293279
293454
|
requestId = `user-data-${Date.now()}-${Math.random()}`;
|
|
293455
|
+
maxBufferedEvents;
|
|
293280
293456
|
queue = [];
|
|
293281
293457
|
waiters = [];
|
|
293282
293458
|
closed = false;
|
|
293283
293459
|
closeError = null;
|
|
293284
293460
|
subscriptionId = null;
|
|
293285
|
-
constructor(exchange) {
|
|
293461
|
+
constructor(exchange, options = {}) {
|
|
293286
293462
|
this.exchange = exchange;
|
|
293287
|
-
|
|
293288
|
-
this.
|
|
293289
|
-
|
|
293290
|
-
|
|
293291
|
-
|
|
293292
|
-
this.ws
|
|
293463
|
+
this.maxBufferedEvents = options.maxBufferedEvents ?? DEFAULT_BINANCE_USER_DATA_MAX_BUFFERED_EVENTS;
|
|
293464
|
+
this.secretValues = [
|
|
293465
|
+
getOptionalExchangeString(exchange, "apiKey"),
|
|
293466
|
+
getOptionalExchangeString(exchange, "secret")
|
|
293467
|
+
].filter((value) => value !== null);
|
|
293468
|
+
this.ws = createWebSocket(getBinanceSpotWsApiUrl(exchange));
|
|
293469
|
+
this.ws.on("open", () => this.subscribe());
|
|
293470
|
+
this.ws.on("message", (data) => this.handleMessage(data));
|
|
293471
|
+
this.ws.on("error", (error48) => this.fail(formatBinanceUserDataWebSocketError(error48, this.secretValues)));
|
|
293472
|
+
this.ws.on("close", (code, reason) => this.handleClose(code, reason));
|
|
293293
293473
|
}
|
|
293294
293474
|
async* [Symbol.asyncIterator]() {
|
|
293295
293475
|
while (true) {
|
|
@@ -293305,11 +293485,18 @@ class BinanceSpotUserDataStream {
|
|
|
293305
293485
|
return;
|
|
293306
293486
|
}
|
|
293307
293487
|
this.closed = true;
|
|
293488
|
+
this.queue.length = 0;
|
|
293308
293489
|
try {
|
|
293309
293490
|
this.ws.close();
|
|
293310
293491
|
} catch {}
|
|
293311
293492
|
this.flushWaiters();
|
|
293312
293493
|
}
|
|
293494
|
+
handleClose(code, reason) {
|
|
293495
|
+
if (this.closed) {
|
|
293496
|
+
return;
|
|
293497
|
+
}
|
|
293498
|
+
this.fail(formatBinanceUserDataWebSocketClose(code, reason, this.secretValues));
|
|
293499
|
+
}
|
|
293313
293500
|
subscribe() {
|
|
293314
293501
|
const apiKey = getExchangeString(this.exchange, "apiKey");
|
|
293315
293502
|
const signedParams = signUserDataStreamParams(this.exchange, {
|
|
@@ -293323,9 +293510,13 @@ class BinanceSpotUserDataStream {
|
|
|
293323
293510
|
}));
|
|
293324
293511
|
}
|
|
293325
293512
|
handleMessage(data) {
|
|
293513
|
+
if (this.closed) {
|
|
293514
|
+
return;
|
|
293515
|
+
}
|
|
293326
293516
|
let message;
|
|
293327
293517
|
try {
|
|
293328
|
-
|
|
293518
|
+
const decodedData = decodeMessageData(data);
|
|
293519
|
+
message = typeof decodedData === "string" ? JSON.parse(decodedData) : decodedData;
|
|
293329
293520
|
} catch (error48) {
|
|
293330
293521
|
this.fail(error48 instanceof Error ? error48 : new Error("Invalid Binance user-data message"));
|
|
293331
293522
|
return;
|
|
@@ -293348,11 +293539,18 @@ class BinanceSpotUserDataStream {
|
|
|
293348
293539
|
this.push({ subscriptionId, event: message.event });
|
|
293349
293540
|
}
|
|
293350
293541
|
push(event) {
|
|
293542
|
+
if (this.closed) {
|
|
293543
|
+
return;
|
|
293544
|
+
}
|
|
293351
293545
|
const waiter = this.waiters.shift();
|
|
293352
293546
|
if (waiter) {
|
|
293353
293547
|
waiter.resolve(event);
|
|
293354
293548
|
return;
|
|
293355
293549
|
}
|
|
293550
|
+
if (this.queue.length >= this.maxBufferedEvents) {
|
|
293551
|
+
this.fail(new Error(`Binance user-data stream buffered event limit exceeded (${this.maxBufferedEvents}); downstream consumer is not keeping up`));
|
|
293552
|
+
return;
|
|
293553
|
+
}
|
|
293356
293554
|
this.queue.push(event);
|
|
293357
293555
|
}
|
|
293358
293556
|
nextEvent() {
|
|
@@ -293376,6 +293574,7 @@ class BinanceSpotUserDataStream {
|
|
|
293376
293574
|
}
|
|
293377
293575
|
this.closeError = error48;
|
|
293378
293576
|
this.closed = true;
|
|
293577
|
+
this.queue.length = 0;
|
|
293379
293578
|
this.flushWaiters();
|
|
293380
293579
|
try {
|
|
293381
293580
|
this.ws.close();
|
|
@@ -293403,16 +293602,49 @@ function isBinanceOrderUserDataEvent(event) {
|
|
|
293403
293602
|
function isBinanceSpotAccountSubscription(cex3, subscriptionType, marketTypeInput) {
|
|
293404
293603
|
return cex3 === "binance" && parseMarketType(marketTypeInput) === "spot" && (subscriptionType === SubscriptionType.BALANCE || subscriptionType === SubscriptionType.ORDERS);
|
|
293405
293604
|
}
|
|
293406
|
-
function
|
|
293605
|
+
function waitForSubscribeDrain(call, isClosed) {
|
|
293606
|
+
if (isClosed() || call.destroyed) {
|
|
293607
|
+
return Promise.resolve(false);
|
|
293608
|
+
}
|
|
293609
|
+
return new Promise((resolve) => {
|
|
293610
|
+
let settled = false;
|
|
293611
|
+
const cleanup = () => {
|
|
293612
|
+
call.off("drain", onDrain);
|
|
293613
|
+
call.off("close", onClosed);
|
|
293614
|
+
call.off("cancelled", onClosed);
|
|
293615
|
+
call.off("end", onClosed);
|
|
293616
|
+
call.off("error", onClosed);
|
|
293617
|
+
};
|
|
293618
|
+
const settle2 = (drained) => {
|
|
293619
|
+
if (settled) {
|
|
293620
|
+
return;
|
|
293621
|
+
}
|
|
293622
|
+
settled = true;
|
|
293623
|
+
cleanup();
|
|
293624
|
+
resolve(drained && !isClosed() && !call.destroyed);
|
|
293625
|
+
};
|
|
293626
|
+
const onDrain = () => settle2(true);
|
|
293627
|
+
const onClosed = () => settle2(false);
|
|
293628
|
+
call.once("drain", onDrain);
|
|
293629
|
+
call.once("close", onClosed);
|
|
293630
|
+
call.once("cancelled", onClosed);
|
|
293631
|
+
call.once("end", onClosed);
|
|
293632
|
+
call.once("error", onClosed);
|
|
293633
|
+
});
|
|
293634
|
+
}
|
|
293635
|
+
async function writeSubscribeFrame(call, isClosed, frame) {
|
|
293407
293636
|
if (isClosed() || call.destroyed) {
|
|
293408
293637
|
return false;
|
|
293409
293638
|
}
|
|
293410
|
-
call.write(frame);
|
|
293411
|
-
|
|
293639
|
+
const canContinue = call.write(frame);
|
|
293640
|
+
if (canContinue) {
|
|
293641
|
+
return true;
|
|
293642
|
+
}
|
|
293643
|
+
return waitForSubscribeDrain(call, isClosed);
|
|
293412
293644
|
}
|
|
293413
|
-
function writeSubscribeError(call, isClosed, frame) {
|
|
293414
|
-
writeSubscribeFrame(call, isClosed, frame);
|
|
293415
|
-
if (!isClosed() && !call.destroyed) {
|
|
293645
|
+
async function writeSubscribeError(call, isClosed, frame) {
|
|
293646
|
+
const frameWritten = await writeSubscribeFrame(call, isClosed, frame);
|
|
293647
|
+
if (frameWritten && !isClosed() && !call.destroyed) {
|
|
293416
293648
|
call.end();
|
|
293417
293649
|
}
|
|
293418
293650
|
}
|
|
@@ -293459,7 +293691,7 @@ async function streamBinanceUserData(call, broker, symbol2, subscriptionType, is
|
|
|
293459
293691
|
continue;
|
|
293460
293692
|
}
|
|
293461
293693
|
}
|
|
293462
|
-
if (!writeSubscribeFrame(call, isClosed, {
|
|
293694
|
+
if (!await writeSubscribeFrame(call, isClosed, {
|
|
293463
293695
|
data: JSON.stringify({
|
|
293464
293696
|
subscriptionId: message.subscriptionId,
|
|
293465
293697
|
event
|
|
@@ -293478,7 +293710,7 @@ async function streamBinanceUserData(call, broker, symbol2, subscriptionType, is
|
|
|
293478
293710
|
async function runCcxtSubscribeLoop(call, isClosed, symbol2, subscriptionType, watch) {
|
|
293479
293711
|
while (!isClosed()) {
|
|
293480
293712
|
const data = await watch();
|
|
293481
|
-
if (!writeSubscribeFrame(call, isClosed, {
|
|
293713
|
+
if (!await writeSubscribeFrame(call, isClosed, {
|
|
293482
293714
|
data: JSON.stringify(data),
|
|
293483
293715
|
timestamp: Date.now(),
|
|
293484
293716
|
symbol: symbol2,
|
|
@@ -293544,7 +293776,7 @@ function createSubscribeHandler(deps) {
|
|
|
293544
293776
|
type: subscriptionTypeName
|
|
293545
293777
|
});
|
|
293546
293778
|
if (!cex3 || !symbol2) {
|
|
293547
|
-
writeSubscribeError(call, isStreamClosed, {
|
|
293779
|
+
await writeSubscribeError(call, isStreamClosed, {
|
|
293548
293780
|
data: JSON.stringify({
|
|
293549
293781
|
error: "cex, symbol, and type are required"
|
|
293550
293782
|
}),
|
|
@@ -293558,7 +293790,7 @@ function createSubscribeHandler(deps) {
|
|
|
293558
293790
|
const selectedBroker = selectBroker(brokers[normalizedCex], metadata) ?? createBroker(normalizedCex, metadata);
|
|
293559
293791
|
const broker = selectedBroker ?? createPublicBroker(normalizedCex);
|
|
293560
293792
|
if (!broker) {
|
|
293561
|
-
writeSubscribeError(call, isStreamClosed, {
|
|
293793
|
+
await writeSubscribeError(call, isStreamClosed, {
|
|
293562
293794
|
data: JSON.stringify({
|
|
293563
293795
|
error: "Exchange not registered and no API metadata found"
|
|
293564
293796
|
}),
|
|
@@ -293571,7 +293803,7 @@ function createSubscribeHandler(deps) {
|
|
|
293571
293803
|
const resolvedSymbol = await resolveSubscriptionSymbol(broker, symbol2, options?.marketType);
|
|
293572
293804
|
if (isBinanceSpotAccountSubscription(normalizedCex, subscriptionType, options?.marketType)) {
|
|
293573
293805
|
if (!selectedBroker) {
|
|
293574
|
-
writeSubscribeError(call, isStreamClosed, {
|
|
293806
|
+
await writeSubscribeError(call, isStreamClosed, {
|
|
293575
293807
|
data: JSON.stringify({
|
|
293576
293808
|
error: "Binance account subscriptions require API credentials"
|
|
293577
293809
|
}),
|
|
@@ -293591,7 +293823,7 @@ function createSubscribeHandler(deps) {
|
|
|
293591
293823
|
const depthLimit = parseOptionalDepthLimit(options?.depthLimit ?? options?.limit);
|
|
293592
293824
|
const orderbook = depthLimit === undefined ? await broker.watchOrderBook(resolvedSymbol) : await broker.watchOrderBook(resolvedSymbol, depthLimit);
|
|
293593
293825
|
const receivedTimestamp = Date.now();
|
|
293594
|
-
if (!writeSubscribeFrame(call, isStreamClosed, {
|
|
293826
|
+
if (!await writeSubscribeFrame(call, isStreamClosed, {
|
|
293595
293827
|
data: JSON.stringify(normalizeOrderBookSnapshot(orderbook, {
|
|
293596
293828
|
exchange: normalizedCex,
|
|
293597
293829
|
symbol: resolvedSymbol,
|
|
@@ -293608,7 +293840,7 @@ function createSubscribeHandler(deps) {
|
|
|
293608
293840
|
} catch (error48) {
|
|
293609
293841
|
log.error(`Error fetching orderbook for ${resolvedSymbol} on ${cex3}:`, error48);
|
|
293610
293842
|
const message = getErrorMessage(error48);
|
|
293611
|
-
writeSubscribeError(call, isStreamClosed, {
|
|
293843
|
+
await writeSubscribeError(call, isStreamClosed, {
|
|
293612
293844
|
data: JSON.stringify({
|
|
293613
293845
|
error: `Failed to fetch orderbook: ${message}`
|
|
293614
293846
|
}),
|
|
@@ -293624,7 +293856,7 @@ function createSubscribeHandler(deps) {
|
|
|
293624
293856
|
} catch (error48) {
|
|
293625
293857
|
const message = getErrorMessage(error48);
|
|
293626
293858
|
log.error(`Error fetching trades for ${resolvedSymbol} on ${cex3}:`, error48);
|
|
293627
|
-
writeSubscribeError(call, isStreamClosed, {
|
|
293859
|
+
await writeSubscribeError(call, isStreamClosed, {
|
|
293628
293860
|
data: JSON.stringify({
|
|
293629
293861
|
error: `Failed to fetch trades: ${message}`
|
|
293630
293862
|
}),
|
|
@@ -293640,7 +293872,7 @@ function createSubscribeHandler(deps) {
|
|
|
293640
293872
|
} catch (error48) {
|
|
293641
293873
|
const message = getErrorMessage(error48);
|
|
293642
293874
|
log.error(`Error fetching ticker for ${resolvedSymbol} on ${cex3}:`, error48);
|
|
293643
|
-
writeSubscribeError(call, isStreamClosed, {
|
|
293875
|
+
await writeSubscribeError(call, isStreamClosed, {
|
|
293644
293876
|
data: JSON.stringify({
|
|
293645
293877
|
error: `Failed to fetch ticker: ${message}`
|
|
293646
293878
|
}),
|
|
@@ -293657,7 +293889,7 @@ function createSubscribeHandler(deps) {
|
|
|
293657
293889
|
} catch (error48) {
|
|
293658
293890
|
log.error(`Error fetching OHLCV for ${resolvedSymbol} on ${cex3}:`, error48);
|
|
293659
293891
|
const message = getErrorMessage(error48);
|
|
293660
|
-
writeSubscribeError(call, isStreamClosed, {
|
|
293892
|
+
await writeSubscribeError(call, isStreamClosed, {
|
|
293661
293893
|
data: JSON.stringify({
|
|
293662
293894
|
error: `Failed to fetch OHLCV: ${message}`
|
|
293663
293895
|
}),
|
|
@@ -293673,7 +293905,7 @@ function createSubscribeHandler(deps) {
|
|
|
293673
293905
|
} catch (error48) {
|
|
293674
293906
|
const message = getErrorMessage(error48);
|
|
293675
293907
|
log.error(`Error fetching balance for ${cex3}:`, error48);
|
|
293676
|
-
writeSubscribeError(call, isStreamClosed, {
|
|
293908
|
+
await writeSubscribeError(call, isStreamClosed, {
|
|
293677
293909
|
data: JSON.stringify({
|
|
293678
293910
|
error: `Failed to fetch balance: ${message}`
|
|
293679
293911
|
}),
|
|
@@ -293689,7 +293921,7 @@ function createSubscribeHandler(deps) {
|
|
|
293689
293921
|
} catch (error48) {
|
|
293690
293922
|
log.error(`Error fetching orders for ${resolvedSymbol} on ${cex3}:`, error48);
|
|
293691
293923
|
const message = getErrorMessage(error48);
|
|
293692
|
-
writeSubscribeError(call, isStreamClosed, {
|
|
293924
|
+
await writeSubscribeError(call, isStreamClosed, {
|
|
293693
293925
|
data: JSON.stringify({
|
|
293694
293926
|
error: `Failed to fetch orders: ${message}`
|
|
293695
293927
|
}),
|
|
@@ -293700,7 +293932,7 @@ function createSubscribeHandler(deps) {
|
|
|
293700
293932
|
}
|
|
293701
293933
|
break;
|
|
293702
293934
|
default:
|
|
293703
|
-
writeSubscribeError(call, isStreamClosed, {
|
|
293935
|
+
await writeSubscribeError(call, isStreamClosed, {
|
|
293704
293936
|
data: JSON.stringify({ error: "Invalid subscription type" }),
|
|
293705
293937
|
timestamp: Date.now(),
|
|
293706
293938
|
symbol: symbol2,
|
|
@@ -293710,7 +293942,7 @@ function createSubscribeHandler(deps) {
|
|
|
293710
293942
|
} catch (error48) {
|
|
293711
293943
|
log.error("Error in Subscribe stream:", error48);
|
|
293712
293944
|
const message = getErrorMessage(error48);
|
|
293713
|
-
writeSubscribeError(call, isStreamClosed, {
|
|
293945
|
+
await writeSubscribeError(call, isStreamClosed, {
|
|
293714
293946
|
data: JSON.stringify({ error: `Internal server error: ${message}` }),
|
|
293715
293947
|
timestamp: Date.now(),
|
|
293716
293948
|
symbol: "",
|
|
@@ -294085,20 +294317,20 @@ class CEXBroker {
|
|
|
294085
294317
|
this.brokers = createBrokerPool(configMap);
|
|
294086
294318
|
}
|
|
294087
294319
|
loadExchangeCredentials(creds) {
|
|
294088
|
-
const schema =
|
|
294089
|
-
apiKey:
|
|
294090
|
-
apiSecret:
|
|
294091
|
-
role:
|
|
294092
|
-
email:
|
|
294093
|
-
subAccountId:
|
|
294094
|
-
uid:
|
|
294095
|
-
secondaryKeys:
|
|
294096
|
-
apiKey:
|
|
294097
|
-
apiSecret:
|
|
294098
|
-
role:
|
|
294099
|
-
email:
|
|
294100
|
-
subAccountId:
|
|
294101
|
-
uid:
|
|
294320
|
+
const schema = import_joi3.default.object().pattern(import_joi3.default.string().allow(...BrokerList).required(), import_joi3.default.object({
|
|
294321
|
+
apiKey: import_joi3.default.string().required(),
|
|
294322
|
+
apiSecret: import_joi3.default.string().required(),
|
|
294323
|
+
role: import_joi3.default.string().valid("master", "subaccount").optional(),
|
|
294324
|
+
email: import_joi3.default.string().optional(),
|
|
294325
|
+
subAccountId: import_joi3.default.string().optional(),
|
|
294326
|
+
uid: import_joi3.default.string().optional(),
|
|
294327
|
+
secondaryKeys: import_joi3.default.array().items(import_joi3.default.object({
|
|
294328
|
+
apiKey: import_joi3.default.string().required(),
|
|
294329
|
+
apiSecret: import_joi3.default.string().required(),
|
|
294330
|
+
role: import_joi3.default.string().valid("master", "subaccount").optional(),
|
|
294331
|
+
email: import_joi3.default.string().optional(),
|
|
294332
|
+
subAccountId: import_joi3.default.string().optional(),
|
|
294333
|
+
uid: import_joi3.default.string().optional()
|
|
294102
294334
|
})).default([])
|
|
294103
294335
|
})).required();
|
|
294104
294336
|
const { value, error: error48 } = schema.validate(creds);
|
|
@@ -294185,4 +294417,4 @@ export {
|
|
|
294185
294417
|
CEXBroker as default
|
|
294186
294418
|
};
|
|
294187
294419
|
|
|
294188
|
-
//# debugId=
|
|
294420
|
+
//# debugId=8F76D79C41F9E70764756E2164756E21
|