@blackcube/xgate-sdk 0.21.0 → 0.22.0
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 +95 -5
- package/dist/index.cjs +183 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +117 -2
- package/dist/index.d.ts +117 -2
- package/dist/index.js +181 -5
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -23,10 +23,10 @@ une capacité n'est comptée que si un test réel l'a exercée.
|
|
|
23
23
|
| **hyperliquid** | ✔ | ✔ | ✔ | ✔ **prouvée** |
|
|
24
24
|
| **pacifica** | ✔ | ✔ | ✔ | ✔ **prouvée** |
|
|
25
25
|
| **aster** | ✔ | ✔ | ✔ | ⚠ **à valider** |
|
|
26
|
-
| lighter | ✔ |
|
|
26
|
+
| lighter | ✔ | ✔ *(sans clé)* | — | — |
|
|
27
27
|
| extended | ✔ | — | — | — |
|
|
28
28
|
| paradex | ✔ | — | — | — |
|
|
29
|
-
| bullet | ✔ | — | — | — |
|
|
29
|
+
| bullet | ✔ *(bougies en WS seulement)* | — | — | — |
|
|
30
30
|
| blofin | ✔ | ✔ | ✔ | — |
|
|
31
31
|
| binance | ✔ *(+ comptant)* | — | — | — |
|
|
32
32
|
| bybit | ✔ *(+ comptant)* | — | — | — |
|
|
@@ -41,9 +41,19 @@ sur une ouverture protégée, c'est la position nue que ce paquet existe pour re
|
|
|
41
41
|
`openWithProtection` y **lève** tant que la preuve manque. Tout le reste (positions, ordres,
|
|
42
42
|
historique, clôture) fonctionne. Détail complet dans le backlog.
|
|
43
43
|
|
|
44
|
+
**lighter se lit sans aucune clé.** Sa documentation classe `/account`, `accountsByL1Address` et
|
|
45
|
+
`positions` parmi les routes **sans authentification**, et `/account` accepte « an account's index,
|
|
46
|
+
**or L1 address** » : une adresse suffit, comme sur les autres DEX. Son trading, lui, demanderait
|
|
47
|
+
une clé d'API — aucune n'existe aujourd'hui.
|
|
48
|
+
|
|
49
|
+
**bullet ne sert aucune bougie en REST.** Sa documentation officielle ne comporte aucune section de
|
|
50
|
+
données historiques, et six noms d'endpoint testés répondent tous 404 — son REST existe pourtant
|
|
51
|
+
(`/fapi/v1/depth`, `trades`, `premiumIndex`, `fundingRate`, `ticker/24hr`, `openInterest`). Ses
|
|
52
|
+
bougies passent donc par `WsCandlesService` ; `CandlesService` y lève.
|
|
53
|
+
|
|
44
54
|
**Les autres sont en lecture.** Elles servent le catalogue, les bougies et les prix ; il leur manque
|
|
45
|
-
un accès de compte (clé d'API pour bullet, adresse active pour
|
|
46
|
-
|
|
55
|
+
un accès de compte (clé d'API pour bullet, adresse active pour extended et paradex) ou la partie
|
|
56
|
+
signée n'est pas câblée. Binance et bybit sont volontairement limitées au **public** : leurs
|
|
47
57
|
SDK ne couvrent que catalogue, bougies et prix, et toute route signée y lève.
|
|
48
58
|
|
|
49
59
|
## Les services
|
|
@@ -54,7 +64,8 @@ Chacun est un `@Injectable()` NestJS, utilisable seul ou via `XgateModule`.
|
|
|
54
64
|
|---|---|
|
|
55
65
|
| `CatalogService` | les paires perpétuelles de toutes les venues, normalisées |
|
|
56
66
|
| `SpotCatalogService` | les paires **comptant** — binance et bybit uniquement |
|
|
57
|
-
| `CandlesService` | les bougies REST
|
|
67
|
+
| `CandlesService` | les bougies REST **perpétuelles**, avec agrégation quand la venue ne sert pas l'intervalle |
|
|
68
|
+
| `SpotCandlesService` | les bougies REST **comptant** — binance et bybit |
|
|
58
69
|
| `WsCandlesService` | les bougies en temps réel, un handler pour N venues |
|
|
59
70
|
| `PricesService` | mark, oracle, mid, bid/ask, funding, open interest |
|
|
60
71
|
| `WalletService` | soldes, mouvements, historique d'équité |
|
|
@@ -70,6 +81,29 @@ position n'est jamais sans stop.
|
|
|
70
81
|
| fermer sans reliquat | `closePosition` | toutes celles qui tradent |
|
|
71
82
|
| relire le prix de sortie réel | `exitPrice` | toutes celles qui tradent |
|
|
72
83
|
|
|
84
|
+
## Le portefeuille
|
|
85
|
+
|
|
86
|
+
`balances()` rend **le comptant et le collatéral**, chaque ligne portant sa nature :
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
const soldes = await wallet.balances(access);
|
|
90
|
+
soldes.filter((solde) => solde.xtras?.scope === 'perp'); // la marge
|
|
91
|
+
soldes.filter((solde) => solde.xtras?.scope !== 'perp'); // le comptant
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Les SDK de venue, eux, ne rendent que le comptant — c'est leur convention, et la documentation de
|
|
95
|
+
chacune le confirme. **Sans le collatéral, un portefeuille peut paraître vide** : sur un compte
|
|
96
|
+
pacifica dont tout le solde est en marge, les soldes comptant valent `[]`, ce qui se lit « pas
|
|
97
|
+
d'argent » alors qu'il y a 499 USDC. XGate lit donc aussi la marge et l'ajoute, marquée.
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
pacifica USDC 499.204355 (perp)
|
|
101
|
+
hyperliquid USDC 1137.565924 (spot) + USDC 7.320954 (perp)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Les deux lignes s'additionnent sans se recouvrir : le collatéral est hors comptant chez les deux
|
|
105
|
+
venues.
|
|
106
|
+
|
|
73
107
|
## Le trading
|
|
74
108
|
|
|
75
109
|
**Jamais un ordre sans stop.** `openWithProtection` est la seule façon d'ouvrir une position, et
|
|
@@ -181,6 +215,58 @@ open · partiallyFilled · filled · canceled · rejected · expired · other
|
|
|
181
215
|
`canceled` prend **un seul L**. Un statut inconnu retombe sur `other` plutôt que de traverser la
|
|
182
216
|
passerelle tel quel : `ORDER_STATUSES` est exporté pour valider plutôt que caster.
|
|
183
217
|
|
|
218
|
+
## Comptant et perpétuel ne se mélangent pas
|
|
219
|
+
|
|
220
|
+
Deux services, jamais un drapeau : `CandlesService` sert le perpétuel, `SpotCandlesService` le
|
|
221
|
+
comptant.
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
await spotCandles.candles(XgateEx.Binance, { symbol: 'BTCUSDT', interval: '1h', limit: 100 });
|
|
225
|
+
await spotCandles.candlesOf([XgateEx.Binance, XgateEx.Bybit], { symbol: 'BTCUSDT', interval: '1h' });
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Ce n'est pas de la coquetterie d'API : **les échelles de cotation diffèrent entre les deux
|
|
229
|
+
marchés**. `SATSUSDT` au comptant chez bybit cote ×1 quand son perpétuel cote ×10000 — mélanger les
|
|
230
|
+
deux séries fabrique un graphe qui saute d'un facteur 10 000. Une venue sans comptant lève.
|
|
231
|
+
|
|
232
|
+
## La semaine s'ouvre le lundi, partout
|
|
233
|
+
|
|
234
|
+
`hyperliquid` sert des bougies `1w` **alignées sur jeudi** (elle aligne ses seaux sur l'epoch Unix,
|
|
235
|
+
qui tombe un jeudi), là où binance et pacifica ouvrent le lundi. XGate ne les prend donc pas : il
|
|
236
|
+
**reconstruit sa semaine depuis le journalier**, et les bougies rendues portent `derived: true`.
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
hyperliquid lun 2026-06-01* lun 2026-06-08* lun 2026-06-15* ← reconstruites
|
|
240
|
+
binance lun 2026-06-01 lun 2026-06-08 lun 2026-06-15 ← natives
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Sans cela, deux séries portant le même intervalle `1w` couvriraient des jours différents et se
|
|
244
|
+
compareraient silencieusement.
|
|
245
|
+
|
|
246
|
+
## Le catalogue : deux pièges que les venues ne signalent pas
|
|
247
|
+
|
|
248
|
+
**Le multiplicateur de cotation.** Aucune venue ne le publie — il n'existe que dans le préfixe du
|
|
249
|
+
nom, que le canonique fait disparaître. `IPair.multiplier` le conserve :
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
SATS binance:1000SATSUSDT = ×1000 bybit:10000SATSUSDT = ×10000 bybit-spot:SATSUSDT = ×1
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Trois échelles pour le même actif sur trois sources. **Une médiane calculée sur ces prix bruts ne
|
|
256
|
+
veut rien dire** ; divisés par `multiplier`, ils redeviennent comparables. 98 lignes du catalogue
|
|
257
|
+
sont concernées.
|
|
258
|
+
|
|
259
|
+
**Les contrats à échéance.** 44 lignes portent `kind: 'perp'` alors qu'elles expirent, dont neuf
|
|
260
|
+
sur le seul BTC chez bybit. `IPair.expiresAt` les distingue — `undefined` pour un perpétuel.
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
pairs.filter((pair) => pair.expiresAt === undefined); // les vrais perpétuels
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
L'échéance est dérivée du **type de contrat**, jamais de la date de livraison : binance publie
|
|
267
|
+
`deliveryDate: 4133404800000` — le 1ᵉʳ janvier 2100 — sur **tous** ses perpétuels. S'y fier
|
|
268
|
+
marquerait le catalogue entier comme daté.
|
|
269
|
+
|
|
184
270
|
## Les conventions qui traversent tout
|
|
185
271
|
|
|
186
272
|
- **Une bougie inclut sa dernière milliseconde** : 10:00:00.000 → 10:59:59.999. `closedAt` se
|
|
@@ -191,6 +277,10 @@ passerelle tel quel : `ORDER_STATUSES` est exporté pour valider plutôt que cas
|
|
|
191
277
|
compte.
|
|
192
278
|
- **Une taille de position est toujours positive** : le sens est porté par `side`.
|
|
193
279
|
- **Pas de mensuel.** Les intervalles s'arrêtent à `1w`.
|
|
280
|
+
- **Le suffixe d'un symbole n'est pas sa cotation.** bybit nomme ses perpétuels USDC avec `PERP`
|
|
281
|
+
(`1000BONKPERP` → `BONK`/`USDC`), binance suffixe ses trimestriels par leur date
|
|
282
|
+
(`BTCUSDT_260925`). XGate lit ce que la venue **déclare** (`baseAsset`, `baseCoin`) plutôt que de
|
|
283
|
+
re-parser le nom.
|
|
194
284
|
|
|
195
285
|
## Versions
|
|
196
286
|
|
package/dist/index.cjs
CHANGED
|
@@ -150,6 +150,8 @@ function sum(left, right) {
|
|
|
150
150
|
var CONCAT_QUOTE_XEXS = /* @__PURE__ */ new Set(["aster", "binance", "bybit"]);
|
|
151
151
|
var K_MULTIPLIER = /^k(?=[A-Z])/u;
|
|
152
152
|
var NUMERIC_MULTIPLIER = /^10{3,}(?=[A-Z0-9])/u;
|
|
153
|
+
var ABBREV_MULTIPLIER = /^1([MK])(?=[A-Z]{2,})/u;
|
|
154
|
+
var ABBREV_FACTOR = { M: 1e6, K: 1e3 };
|
|
153
155
|
var STOCK_24_5 = /_24_5$/u;
|
|
154
156
|
var TICKER_ALIASES = {
|
|
155
157
|
GOOG: "GOOGL",
|
|
@@ -174,13 +176,26 @@ function canonicalFromXex(rawSymbol, xexId) {
|
|
|
174
176
|
s = s.replace(/-USD-PERP$/u, "").replace(/-PERP$/u, "").replace(/-USDT$/u, "").replace(/-USDC$/u, "").replace(/-USD$/u, "");
|
|
175
177
|
}
|
|
176
178
|
s = s.replace(NUMERIC_MULTIPLIER, "");
|
|
179
|
+
s = s.replace(ABBREV_MULTIPLIER, "");
|
|
177
180
|
s = s.replace(STOCK_24_5, "");
|
|
178
181
|
return TICKER_ALIASES[s] ?? s;
|
|
179
182
|
}
|
|
180
183
|
function canonicalFromBase(base) {
|
|
181
|
-
const s = base.replace(K_MULTIPLIER, "").toUpperCase().replace(NUMERIC_MULTIPLIER, "").replace(STOCK_24_5, "");
|
|
184
|
+
const s = base.replace(K_MULTIPLIER, "").toUpperCase().replace(NUMERIC_MULTIPLIER, "").replace(ABBREV_MULTIPLIER, "").replace(STOCK_24_5, "");
|
|
182
185
|
return TICKER_ALIASES[s] ?? s;
|
|
183
186
|
}
|
|
187
|
+
function multiplierFromXex(rawSymbol) {
|
|
188
|
+
if (K_MULTIPLIER.test(rawSymbol)) {
|
|
189
|
+
return 1e3;
|
|
190
|
+
}
|
|
191
|
+
const upper = rawSymbol.toUpperCase();
|
|
192
|
+
const abbrev = ABBREV_MULTIPLIER.exec(upper);
|
|
193
|
+
if (abbrev !== null) {
|
|
194
|
+
return ABBREV_FACTOR[abbrev[1]] ?? 1;
|
|
195
|
+
}
|
|
196
|
+
const zeros = NUMERIC_MULTIPLIER.exec(upper);
|
|
197
|
+
return zeros === null ? 1 : Number(zeros[0]);
|
|
198
|
+
}
|
|
184
199
|
|
|
185
200
|
// src/helpers/candle-mapper.ts
|
|
186
201
|
function toCandle(wire, xex, interval, quote) {
|
|
@@ -265,6 +280,19 @@ function createAccountXex(xex, access) {
|
|
|
265
280
|
},
|
|
266
281
|
{ default: "a" }
|
|
267
282
|
);
|
|
283
|
+
case "lighter" /* Lighter */:
|
|
284
|
+
return new lighterSdk.Lighter(
|
|
285
|
+
{
|
|
286
|
+
a: {
|
|
287
|
+
apiPrivateKey: access.privateKey ?? NULL_KEY,
|
|
288
|
+
apiKeyIndex: access.apiKeyIndex ?? 0,
|
|
289
|
+
accountIndex: access.accountIndex ?? 0,
|
|
290
|
+
l1Address: adresse(),
|
|
291
|
+
network: access.network ?? "mainnet"
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
{ default: "a" }
|
|
295
|
+
);
|
|
268
296
|
case "aster" /* Aster */:
|
|
269
297
|
if (access.privateKey === void 0 || access.privateKey === "") {
|
|
270
298
|
throw new Error(
|
|
@@ -305,6 +333,7 @@ var WALLET_XEXES = [
|
|
|
305
333
|
"hyperliquid" /* Hyperliquid */,
|
|
306
334
|
"pacifica" /* Pacifica */,
|
|
307
335
|
"aster" /* Aster */,
|
|
336
|
+
"lighter" /* Lighter */,
|
|
308
337
|
"blofin" /* Blofin */
|
|
309
338
|
];
|
|
310
339
|
function createPublicXex(xex) {
|
|
@@ -349,8 +378,11 @@ function createPublicSpotXex(xex) {
|
|
|
349
378
|
}
|
|
350
379
|
|
|
351
380
|
// src/helpers/xex-timeframes.ts
|
|
381
|
+
var HYPERLIQUID_NATIVE = Object.values(Timeframe).filter(
|
|
382
|
+
(interval) => interval !== "1w" /* W */
|
|
383
|
+
);
|
|
352
384
|
var NATIVE = {
|
|
353
|
-
["hyperliquid" /* Hyperliquid */]:
|
|
385
|
+
["hyperliquid" /* Hyperliquid */]: HYPERLIQUID_NATIVE,
|
|
354
386
|
["pacifica" /* Pacifica */]: Object.values(Timeframe),
|
|
355
387
|
["aster" /* Aster */]: Object.values(Timeframe),
|
|
356
388
|
["blofin" /* Blofin */]: Object.values(Timeframe),
|
|
@@ -478,6 +510,43 @@ exports.CandlesService = class CandlesService {
|
|
|
478
510
|
exports.CandlesService = __decorateClass([
|
|
479
511
|
common.Injectable()
|
|
480
512
|
], exports.CandlesService);
|
|
513
|
+
|
|
514
|
+
// src/helpers/contract-expiry.ts
|
|
515
|
+
var DATED_CONTRACT_TYPES = /* @__PURE__ */ new Set([
|
|
516
|
+
// binance : les trimestriels (`BTCUSDT_260925`, `ETHUSDT_261225`).
|
|
517
|
+
"CURRENT_QUARTER",
|
|
518
|
+
"NEXT_QUARTER",
|
|
519
|
+
"CURRENT_QUARTER_DELIVERING",
|
|
520
|
+
"NEXT_QUARTER_DELIVERING",
|
|
521
|
+
"PERPETUAL_DELIVERING",
|
|
522
|
+
// bybit : les datés (`BTCUSDT-25DEC26`), face à `LinearPerpetual`.
|
|
523
|
+
"LinearFutures",
|
|
524
|
+
"InverseFutures"
|
|
525
|
+
]);
|
|
526
|
+
var EXPIRY_FIELDS = ["deliveryTime", "deliveryDate", "expiryTime", "settleTime"];
|
|
527
|
+
function expiryOf(xtras) {
|
|
528
|
+
if (xtras === void 0) {
|
|
529
|
+
return void 0;
|
|
530
|
+
}
|
|
531
|
+
const type = xtras.contractType;
|
|
532
|
+
if (typeof type !== "string" || DATED_CONTRACT_TYPES.has(type) === false) {
|
|
533
|
+
return void 0;
|
|
534
|
+
}
|
|
535
|
+
for (const field of EXPIRY_FIELDS) {
|
|
536
|
+
const brut = xtras[field];
|
|
537
|
+
const ms = typeof brut === "string" ? Number(brut) : brut;
|
|
538
|
+
if (typeof ms === "number" && Number.isFinite(ms) === true && ms > 0) {
|
|
539
|
+
return new Date(ms);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return void 0;
|
|
543
|
+
}
|
|
544
|
+
function isDated(xtras) {
|
|
545
|
+
const type = xtras?.contractType;
|
|
546
|
+
return typeof type === "string" && DATED_CONTRACT_TYPES.has(type);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// src/services/catalog.service.ts
|
|
481
550
|
var NAMES_ITS_BASE = /* @__PURE__ */ new Set(["binance" /* Binance */, "bybit" /* Bybit */]);
|
|
482
551
|
exports.CatalogService = class CatalogService {
|
|
483
552
|
logger = new common.Logger(exports.CatalogService.name);
|
|
@@ -523,6 +592,8 @@ exports.CatalogService = class CatalogService {
|
|
|
523
592
|
symbolXex: pair.name,
|
|
524
593
|
xex,
|
|
525
594
|
kind: pair.kind,
|
|
595
|
+
multiplier: multiplierFromXex(pair.name),
|
|
596
|
+
expiresAt: expiryOf(pair.xtras),
|
|
526
597
|
ref: pair.ref ?? null,
|
|
527
598
|
szDecimals: pair.szDecimals,
|
|
528
599
|
tickSize: pair.tickSize,
|
|
@@ -597,6 +668,65 @@ exports.PricesService = class PricesService {
|
|
|
597
668
|
exports.PricesService = __decorateClass([
|
|
598
669
|
common.Injectable()
|
|
599
670
|
], exports.PricesService);
|
|
671
|
+
exports.SpotCandlesService = class SpotCandlesService {
|
|
672
|
+
logger = new common.Logger(exports.SpotCandlesService.name);
|
|
673
|
+
/** Les venues dont XGate sert le comptant. */
|
|
674
|
+
venues() {
|
|
675
|
+
return SPOT_XEXES;
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Les bougies comptant d'une venue, sur une plage de **dates**.
|
|
679
|
+
*
|
|
680
|
+
* **Lève** si la venue ne sert pas de comptant, plutôt que de rendre une liste vide : « cette
|
|
681
|
+
* venue n'a pas de marché comptant » et « ce marché n'a pas coté » appellent des suites très
|
|
682
|
+
* différentes.
|
|
683
|
+
*/
|
|
684
|
+
async candles(xex, query) {
|
|
685
|
+
if (servesSpot(xex) === false) {
|
|
686
|
+
throw new Error(
|
|
687
|
+
`spotCandles(${xex}) : cette venue ne sert pas de comptant. Disponibles : ${SPOT_XEXES.join(", ")}.`
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
const spot = createPublicSpotXex(xex).spot();
|
|
691
|
+
if (spot.getCandles === void 0) {
|
|
692
|
+
throw new Error(`spotCandles(${xex}) : cette venue ne sert pas de bougies comptant en REST.`);
|
|
693
|
+
}
|
|
694
|
+
const wires = await spot.getCandles(toXexQuery(query));
|
|
695
|
+
return wires.map((wire) => toCandle(wire, xex, query.interval, ""));
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Les bougies comptant d'un même marché chez PLUSIEURS venues, en une liste.
|
|
699
|
+
*
|
|
700
|
+
* Même politique d'échec que partout ailleurs : seule, une venue en échec fait échouer l'appel ;
|
|
701
|
+
* parmi d'autres, elle est journalisée et ignorée. Chaque bougie porte son `xex`.
|
|
702
|
+
*/
|
|
703
|
+
async candlesOf(xexes, query) {
|
|
704
|
+
if (xexes.length === 0) {
|
|
705
|
+
throw new Error("spotCandlesOf() : aucune venue demand\xE9e \u2014 pr\xE9cise au moins un XgateEx.");
|
|
706
|
+
}
|
|
707
|
+
const solo = xexes.length === 1;
|
|
708
|
+
const collected = await Promise.all(
|
|
709
|
+
xexes.map(async (xex) => {
|
|
710
|
+
try {
|
|
711
|
+
return await this.candles(xex, query);
|
|
712
|
+
} catch (error) {
|
|
713
|
+
if (solo === true) {
|
|
714
|
+
throw error;
|
|
715
|
+
}
|
|
716
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
717
|
+
this.logger.error(
|
|
718
|
+
`spotCandles(${xex}, ${query.interval}) a \xE9chou\xE9, venue ignor\xE9e : ${message}`
|
|
719
|
+
);
|
|
720
|
+
return [];
|
|
721
|
+
}
|
|
722
|
+
})
|
|
723
|
+
);
|
|
724
|
+
return collected.flat();
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
exports.SpotCandlesService = __decorateClass([
|
|
728
|
+
common.Injectable()
|
|
729
|
+
], exports.SpotCandlesService);
|
|
600
730
|
exports.SpotCatalogService = class SpotCatalogService {
|
|
601
731
|
logger = new common.Logger(exports.SpotCatalogService.name);
|
|
602
732
|
/** Les venues dont XGate sert le comptant. */
|
|
@@ -648,6 +778,9 @@ exports.SpotCatalogService = class SpotCatalogService {
|
|
|
648
778
|
symbolXex: pair.name,
|
|
649
779
|
xex,
|
|
650
780
|
kind: pair.kind,
|
|
781
|
+
// Le comptant porte les mêmes échelles que le perp : `SATSUSDT` (×1) et `1000SATSUSDT`
|
|
782
|
+
// coexistent, et c'est justement entre eux que la comparaison serait fausse.
|
|
783
|
+
multiplier: multiplierFromXex(pair.name),
|
|
651
784
|
ref: pair.ref ?? null,
|
|
652
785
|
szDecimals: pair.szDecimals,
|
|
653
786
|
tickSize: pair.tickSize,
|
|
@@ -1013,8 +1146,9 @@ exports.WalletService = class WalletService {
|
|
|
1013
1146
|
*/
|
|
1014
1147
|
async balances(...accesses) {
|
|
1015
1148
|
return this.collect(accesses, "balances", async (access) => {
|
|
1016
|
-
const
|
|
1017
|
-
|
|
1149
|
+
const source = createAccountXex(access.xex, access);
|
|
1150
|
+
const balances = await source.account().getBalances();
|
|
1151
|
+
const lignes = balances.map((balance) => ({
|
|
1018
1152
|
xex: access.xex,
|
|
1019
1153
|
asset: balance.asset,
|
|
1020
1154
|
total: balance.total,
|
|
@@ -1022,8 +1156,48 @@ exports.WalletService = class WalletService {
|
|
|
1022
1156
|
usdValue: balance.usdValue,
|
|
1023
1157
|
xtras: balance.xtras
|
|
1024
1158
|
}));
|
|
1159
|
+
const collateral = await this.perpCollateral(source, access.xex);
|
|
1160
|
+
return collateral === null ? lignes : [...lignes, collateral];
|
|
1025
1161
|
});
|
|
1026
1162
|
}
|
|
1163
|
+
/**
|
|
1164
|
+
* LE COLLATÉRAL PERPÉTUEL, ajouté aux soldes comptant — sans lui, un portefeuille peut paraître VIDE.
|
|
1165
|
+
*
|
|
1166
|
+
* `getBalances` ne rend que le **comptant**, chez les huit venues : c'est leur convention, et la
|
|
1167
|
+
* documentation de chacune le dit. Or l'argent qui sert à trader n'y est pas. Sur un compte
|
|
1168
|
+
* pacifica dont tout le solde est en marge, `getBalances` rend `[]` — ce qui se lit « pas
|
|
1169
|
+
* d'argent » alors qu'il y a 499 USDC. Sur hyperliquid, le comptant s'affiche mais la marge
|
|
1170
|
+
* manque.
|
|
1171
|
+
*
|
|
1172
|
+
* La ligne est **marquée `scope: 'perp'`** dans `xtras` : elle s'additionne au comptant sans le
|
|
1173
|
+
* doubler, et un appelant qui ne veut que l'un des deux peut trancher.
|
|
1174
|
+
*
|
|
1175
|
+
* Le champ diffère par venue et n'est pas devinable — d'où la lecture explicite, venue par venue,
|
|
1176
|
+
* plutôt qu'un parcours à l'aveugle d'un objet `unknown`.
|
|
1177
|
+
*/
|
|
1178
|
+
async perpCollateral(source, xex) {
|
|
1179
|
+
const facade = source;
|
|
1180
|
+
if (typeof facade.perp !== "function") {
|
|
1181
|
+
return null;
|
|
1182
|
+
}
|
|
1183
|
+
const scope = facade.perp();
|
|
1184
|
+
if (typeof scope.getAccountInfo !== "function") {
|
|
1185
|
+
return null;
|
|
1186
|
+
}
|
|
1187
|
+
const info = await scope.getAccountInfo();
|
|
1188
|
+
const total = info.balance ?? info.marginSummary?.accountValue;
|
|
1189
|
+
if (total === void 0 || Number(total) === 0) {
|
|
1190
|
+
return null;
|
|
1191
|
+
}
|
|
1192
|
+
return {
|
|
1193
|
+
xex,
|
|
1194
|
+
asset: "USDC",
|
|
1195
|
+
total,
|
|
1196
|
+
available: info.availableToSpend ?? info.withdrawable ?? null,
|
|
1197
|
+
usdValue: total,
|
|
1198
|
+
xtras: { scope: "perp" }
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1027
1201
|
/**
|
|
1028
1202
|
* Les mouvements d'argent, triés du plus ancien au plus récent, montants **signés**.
|
|
1029
1203
|
*
|
|
@@ -1171,6 +1345,7 @@ exports.XgateModule = __decorateClass([
|
|
|
1171
1345
|
exports.WsCandlesService,
|
|
1172
1346
|
exports.PricesService,
|
|
1173
1347
|
exports.SpotCatalogService,
|
|
1348
|
+
exports.SpotCandlesService,
|
|
1174
1349
|
exports.WalletService
|
|
1175
1350
|
],
|
|
1176
1351
|
exports: [
|
|
@@ -1179,6 +1354,7 @@ exports.XgateModule = __decorateClass([
|
|
|
1179
1354
|
exports.WsCandlesService,
|
|
1180
1355
|
exports.PricesService,
|
|
1181
1356
|
exports.SpotCatalogService,
|
|
1357
|
+
exports.SpotCandlesService,
|
|
1182
1358
|
exports.WalletService
|
|
1183
1359
|
]
|
|
1184
1360
|
})
|
|
@@ -1195,6 +1371,9 @@ exports.buildLevels = buildLevels;
|
|
|
1195
1371
|
exports.buildProtection = buildProtection;
|
|
1196
1372
|
exports.canonicalFromBase = canonicalFromBase;
|
|
1197
1373
|
exports.canonicalFromXex = canonicalFromXex;
|
|
1374
|
+
exports.expiryOf = expiryOf;
|
|
1375
|
+
exports.isDated = isDated;
|
|
1376
|
+
exports.multiplierFromXex = multiplierFromXex;
|
|
1198
1377
|
exports.roundPrice = roundPrice;
|
|
1199
1378
|
exports.roundToStep = roundToStep;
|
|
1200
1379
|
exports.servesNatively = servesNatively;
|