@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 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 lighter, extended et paradex) ou la
46
- partie signée n'est pas câblée. Binance et bybit sont volontairement limitées au **public** : leurs
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, avec agrégation quand la venue ne sert pas l'intervalle |
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 */]: Object.values(Timeframe),
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 balances = await createAccountXex(access.xex, access).account().getBalances();
1017
- return balances.map((balance) => ({
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;