@blackcube/xgate-sdk 0.25.0 → 0.25.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -0
- package/dist/index.cjs +77 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +41 -6
- package/dist/index.d.ts +41 -6
- package/dist/index.js +77 -25
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -5,6 +5,46 @@ un catalogue, des bougies, des prix, un portefeuille et du trading — quelle qu
|
|
|
5
5
|
|
|
6
6
|
> **SDK communautaire / non officiel.** Non affilié aux exchanges couverts. Usage à vos risques.
|
|
7
7
|
|
|
8
|
+
## ⚠️ Si ça ne passe pas par XGate, arrête-toi
|
|
9
|
+
|
|
10
|
+
**XGate est la seule porte vers les venues.** Importer un `@blackcube/*-sdk` directement dans une
|
|
11
|
+
application, c'est contourner — et un contournement ne se voit pas : le code compile, les tests
|
|
12
|
+
passent, et la divergence n'apparaît qu'en production, sur la venue que personne n'a exercée.
|
|
13
|
+
|
|
14
|
+
Tu t'apprêtes à écrire quelque chose qui ne passe pas par XGate ? **Il n'y a que deux cas, et
|
|
15
|
+
aucun des deux ne consiste à contourner.**
|
|
16
|
+
|
|
17
|
+
**1. Tu n'as pas trouvé — c'est le cas le plus fréquent.** La méthode existe sous un autre nom. Le
|
|
18
|
+
vocabulaire d'XGate n'est celui d'aucune venue : il est unifié.
|
|
19
|
+
|
|
20
|
+
| tu cherches | c'est | et pas |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| passer un ordre | `openWithProtection` | `place` — qui n'existe pas ici |
|
|
23
|
+
| fermer | `closePosition` | un ordre marché `reduceOnly` à la main |
|
|
24
|
+
| modifier un stop | `moveStop` | annuler puis reposer |
|
|
25
|
+
| le levier | `setLeverage` | `updateLeverage` |
|
|
26
|
+
| l'état du compte | `accountInfo` | `getAccountInfo` |
|
|
27
|
+
| l'historique des ordres | `orderHistory` | `getHistory` |
|
|
28
|
+
| le funding payé | `fundingHistory` | `getFundingHistory` |
|
|
29
|
+
| les exécutions en direct | `WsTradesService.subscribe` | `subscribeUserTrades` |
|
|
30
|
+
| N marchés en temps réel | `CandlesStreamRegistry.of()` | N × `WsCandlesService` |
|
|
31
|
+
|
|
32
|
+
**Vérifie avant de conclure** : `grep -rn "public " node_modules/@blackcube/xgate-sdk/dist/index.d.ts`
|
|
33
|
+
ou la table des services plus bas. Ce README documente **35 méthodes publiques sur 35** — si tu ne
|
|
34
|
+
trouves pas, cherche encore.
|
|
35
|
+
|
|
36
|
+
**2. C'est un vrai trou.** Alors **dis-le, et discutes-en** — ne le comble pas dans ton coin.
|
|
37
|
+
Certaines absences sont des **décisions** : `place`, `placeProtection`, `cancelProtection`,
|
|
38
|
+
`subscribeAllCandles`, `subscribePrices` n'existent pas *exprès*, et les raisons sont écrites plus
|
|
39
|
+
bas. Les recréer côté application, c'est réintroduire ce qui a été retiré après incident.
|
|
40
|
+
|
|
41
|
+
D'autres absences sont réelles — `getOrderBook`, `edit`, `getExchangeInfo`, `withdraw`,
|
|
42
|
+
`subscribeOrderBook`, `subscribeBbo`. Elles se comblent **dans XGate**, où elles profitent à tous
|
|
43
|
+
les appelants et sont exercées contre les venues, jamais dans l'application.
|
|
44
|
+
|
|
45
|
+
> **Le réflexe qui trahit le contournement** : tu es sur le point d'écrire `import { Hyperliquid }
|
|
46
|
+
> from '@blackcube/hyperliquid-sdk'` dans du code applicatif. Arrête-toi là.
|
|
47
|
+
|
|
8
48
|
## Installation
|
|
9
49
|
|
|
10
50
|
```bash
|
package/dist/index.cjs
CHANGED
|
@@ -97,6 +97,24 @@ function toCandle(wire, xex, interval, quote) {
|
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
// src/helpers/socket-error.ts
|
|
101
|
+
function lisible(error) {
|
|
102
|
+
if (error instanceof Error) {
|
|
103
|
+
return error.message;
|
|
104
|
+
}
|
|
105
|
+
const event = error;
|
|
106
|
+
if (event !== null && typeof event === "object") {
|
|
107
|
+
const message = event.message ?? event.error?.message;
|
|
108
|
+
if (typeof message === "string" && message !== "") {
|
|
109
|
+
return message;
|
|
110
|
+
}
|
|
111
|
+
if (typeof event.type === "string" && event.type !== "") {
|
|
112
|
+
return `\xE9v\xE9nement \xAB ${event.type} \xBB`;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return String(error);
|
|
116
|
+
}
|
|
117
|
+
|
|
100
118
|
// src/helpers/ws-limits.ts
|
|
101
119
|
var WS_SUBSCRIPTION_LIMITS = {
|
|
102
120
|
aster: 200,
|
|
@@ -266,20 +284,36 @@ var CandlesStreamService = class {
|
|
|
266
284
|
unsubscribes = [];
|
|
267
285
|
/** Le plafond de souscriptions documenté par la venue, ou `null` si personne ne l'a publié. */
|
|
268
286
|
maxSubscriptions;
|
|
287
|
+
/**
|
|
288
|
+
* Notifié quand le flux meurt — après que les souscriptions ont été coupées.
|
|
289
|
+
*
|
|
290
|
+
* **Ce crochet a remplacé un `throw`.** Lever depuis un rappel de socket ne remonte à aucun
|
|
291
|
+
* appelant : l'exception sort en `unhandledRejection` et **tue le process entier**, ce qui est
|
|
292
|
+
* arrivé le 2026-08-13 sur Blips par un autre chemin. « Impossible à ignorer » et « fatal pour
|
|
293
|
+
* tout le monde » ne sont pas la même chose : la première est une garantie, la seconde une panne.
|
|
294
|
+
*
|
|
295
|
+
* Le contrat reste franc — le flux est coupé, il ne repartira pas tout seul, et le silence est
|
|
296
|
+
* journalisé en `.error` de toute façon. Mais c'est à l'appelant de décider ce qu'il en fait :
|
|
297
|
+
* rouvrir, basculer de venue, ou s'arrêter.
|
|
298
|
+
*/
|
|
299
|
+
onFailure = null;
|
|
269
300
|
/**
|
|
270
301
|
* Ouvre le flux sur une LISTE de marchés, sur une seule socket.
|
|
271
302
|
*
|
|
272
|
-
* **UNE ERREUR DE FLUX COUPE TOUT, PUIS
|
|
303
|
+
* **UNE ERREUR DE FLUX COUPE TOUT, PUIS NOTIFIE.** Elle n'arrive pas à la souscription : mesuré le
|
|
273
304
|
* 2026-08-07, la venue accepte un symbole inconnu sans broncher, puis ferme la connexion en le
|
|
274
305
|
* découvrant. Le SDK reconnecte, elle referme — cinq cycles en douze secondes, et sur une socket
|
|
275
306
|
* partagée **tous** les marchés valides cessent de recevoir, en silence.
|
|
276
307
|
*
|
|
277
308
|
* On refuse cette boucle : à la première erreur, les souscriptions sont coupées, l'incident est
|
|
278
|
-
* journalisé en `error`, et
|
|
279
|
-
* qu'un arrêt franc — on ne découvre le premier qu'en constatant l'absence de
|
|
309
|
+
* journalisé en `error`, et {@link onFailure} est notifié. Un flux à moitié mort qui se tait
|
|
310
|
+
* coûte plus cher qu'un arrêt franc — on ne découvre le premier qu'en constatant l'absence de
|
|
311
|
+
* données.
|
|
280
312
|
*
|
|
281
|
-
* ⚠️
|
|
282
|
-
*
|
|
313
|
+
* ⚠️ **On ne lève pas depuis le rappel de socket.** Une exception née là ne remonte à aucun
|
|
314
|
+
* appelant de `start()` : elle sort en `unhandledRejection` et emporte le process entier — pas
|
|
315
|
+
* seulement le flux fautif, mais les crons et tout ce qui tournait à côté. C'est {@link onFailure}
|
|
316
|
+
* qui porte l'information, et l'appelant qui décide.
|
|
283
317
|
*
|
|
284
318
|
* **binance et bybit ne remontent pas encore leurs erreurs** : leur client gère la fermeture en
|
|
285
319
|
* interne sans l'exposer. Sur ces deux venues, la boucle silencieuse reste possible.
|
|
@@ -291,7 +325,7 @@ var CandlesStreamService = class {
|
|
|
291
325
|
const raison = lisible(error);
|
|
292
326
|
this.logger.error(`${this.xex} : erreur de flux \u2014 ${raison}. Souscriptions coup\xE9es.`);
|
|
293
327
|
this.stop();
|
|
294
|
-
|
|
328
|
+
this.onFailure?.(new Error(`stream(${this.xex}) : flux interrompu \u2014 ${raison}`));
|
|
295
329
|
};
|
|
296
330
|
}
|
|
297
331
|
for (const symbolXex of symbolsXex) {
|
|
@@ -374,22 +408,6 @@ exports.CandlesStreamRegistry = class CandlesStreamRegistry {
|
|
|
374
408
|
exports.CandlesStreamRegistry = __decorateClass([
|
|
375
409
|
common.Injectable()
|
|
376
410
|
], exports.CandlesStreamRegistry);
|
|
377
|
-
function lisible(error) {
|
|
378
|
-
if (error instanceof Error) {
|
|
379
|
-
return error.message;
|
|
380
|
-
}
|
|
381
|
-
const event = error;
|
|
382
|
-
if (event !== null && typeof event === "object") {
|
|
383
|
-
const message = event.message ?? event.error?.message;
|
|
384
|
-
if (typeof message === "string" && message !== "") {
|
|
385
|
-
return message;
|
|
386
|
-
}
|
|
387
|
-
if (typeof event.type === "string" && event.type !== "") {
|
|
388
|
-
return `\xE9v\xE9nement \xAB ${event.type} \xBB`;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
return String(error);
|
|
392
|
-
}
|
|
393
411
|
|
|
394
412
|
// src/enums/timeframe.enums.ts
|
|
395
413
|
var Timeframe = /* @__PURE__ */ ((Timeframe2) => {
|
|
@@ -1821,6 +1839,8 @@ exports.WalletService = __decorateClass([
|
|
|
1821
1839
|
], exports.WalletService);
|
|
1822
1840
|
exports.WsCandlesService = class WsCandlesService {
|
|
1823
1841
|
logger = new common.Logger(exports.WsCandlesService.name);
|
|
1842
|
+
/** Un client temps réel par venue — voir {@link wireOf}. La clé EST la venue. */
|
|
1843
|
+
wires = /* @__PURE__ */ new Map();
|
|
1824
1844
|
/**
|
|
1825
1845
|
* Souscrit aux bougies d'un marché chez une venue. Rend la fonction de désabonnement.
|
|
1826
1846
|
*
|
|
@@ -1836,9 +1856,12 @@ exports.WsCandlesService = class WsCandlesService {
|
|
|
1836
1856
|
subscribe(xex, query, handler) {
|
|
1837
1857
|
const quote = this.quoteOf(query.symbol);
|
|
1838
1858
|
this.logger.log(`souscription bougies ${query.symbol} ${query.interval} sur ${xex}`);
|
|
1839
|
-
return
|
|
1840
|
-
|
|
1841
|
-
|
|
1859
|
+
return this.wireOf(xex).subscribeCandles(
|
|
1860
|
+
{ name: query.symbol, interval: query.interval },
|
|
1861
|
+
(wire) => {
|
|
1862
|
+
handler(toCandle(wire, xex, query.interval, quote));
|
|
1863
|
+
}
|
|
1864
|
+
);
|
|
1842
1865
|
}
|
|
1843
1866
|
/**
|
|
1844
1867
|
* Souscrit au MÊME marché chez plusieurs venues, avec un seul handler.
|
|
@@ -1865,6 +1888,35 @@ exports.WsCandlesService = class WsCandlesService {
|
|
|
1865
1888
|
}
|
|
1866
1889
|
};
|
|
1867
1890
|
}
|
|
1891
|
+
/**
|
|
1892
|
+
* Le client temps réel d'une venue — **créé une seule fois, donc UNE SOCKET PAR VENUE**.
|
|
1893
|
+
*
|
|
1894
|
+
* C'est la règle que `CandlesStreamService` énonce déjà et que ce service violait : chaque
|
|
1895
|
+
* `subscribe()` appelait `createPublicXex(xex)`, qui construit une façade NEUVE. Or la façade
|
|
1896
|
+
* mémoïse son client temps réel (`unifiedWs()`), et c'est lui qui porte la socket : une façade
|
|
1897
|
+
* par souscription, c'est une socket par souscription.
|
|
1898
|
+
*
|
|
1899
|
+
* Mesuré le 2026-08-13 sur Blips : 1 104 souscriptions ont ouvert **1 104 sockets** sur sept
|
|
1900
|
+
* venues, quand hyperliquid n'en accepte que 10 par IP. Les descripteurs s'épuisent, les venues
|
|
1901
|
+
* refusent, et chaque connexion refusée rejetait une promesse que personne n'attendait — le
|
|
1902
|
+
* process entier mourait deux minutes après l'ouverture des flux. Ici : **sept sockets**.
|
|
1903
|
+
*
|
|
1904
|
+
* Rien à fermer explicitement : le ref-count vit dans les SDK (`refs += 1` / `release()`), donc
|
|
1905
|
+
* la socket se ferme d'elle-même au dernier désabonnement de la venue, et se rouvre au suivant.
|
|
1906
|
+
*/
|
|
1907
|
+
wireOf(xex) {
|
|
1908
|
+
let wire = this.wires.get(xex);
|
|
1909
|
+
if (wire === void 0) {
|
|
1910
|
+
wire = createPublicXex(xex).ws();
|
|
1911
|
+
if ("onError" in wire) {
|
|
1912
|
+
wire.onError = (error) => {
|
|
1913
|
+
this.logger.error(`${xex} : erreur de flux \u2014 ${lisible(error)}`);
|
|
1914
|
+
};
|
|
1915
|
+
}
|
|
1916
|
+
this.wires.set(xex, wire);
|
|
1917
|
+
}
|
|
1918
|
+
return wire;
|
|
1919
|
+
}
|
|
1868
1920
|
/** Voir `CandlesService.quoteOf` : la cotation se déduit du symbole natif, faute d'être publiée. */
|
|
1869
1921
|
quoteOf(symbolXex) {
|
|
1870
1922
|
const parts = symbolXex.split("-");
|