@blackcube/xgate-sdk 0.20.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/enums/timeframe.enums.ts","../src/helpers/decimal.ts","../src/helpers/candle-aggregator.ts","../src/helpers/symbol-naming.ts","../src/helpers/candle-mapper.ts","../src/helpers/candle-query.ts","../src/enums/xgate-ex.enums.ts","../src/helpers/xex-registry.ts","../src/helpers/xex-timeframes.ts","../src/services/candles.service.ts","../src/services/catalog.service.ts","../src/services/prices.service.ts","../src/services/spot-catalog.service.ts","../src/helpers/protection.ts","../src/interfaces/trading.interfaces.ts","../src/services/trading.service.ts","../src/services/wallet.service.ts","../src/services/ws-candles.service.ts","../src/xgate.module.ts"],"names":["Timeframe","XgateEx","Logger","Injectable"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Les intervalles de bougies qu'XGate promet — **les mêmes que Blips**, mêmes noms, mêmes valeurs\n * (`Blips/server/src/shared/timeframe.enum.ts`). Le consommateur n'a pas à traduire pour parler à\n * la passerelle, ni la passerelle à inventer une échelle concurrente.\n *\n * Toutes les venues ne les servent pas : paradex s'arrête à l'heure (`resolution: must be one of\n * [1 3 5 15 30 60]`, dit la venue), lighter refuse `1w`, extended rend une liste vide pour ce même\n * intervalle. Ce qu'une venue ne sert pas se **fabrique par agrégation** depuis un intervalle plus\n * fin, jamais ne se réclame à l'appelant.\n */\nexport enum Timeframe {\n M1 = '1m',\n M5 = '5m',\n M15 = '15m',\n H1 = '1h',\n H4 = '4h',\n D = '1d',\n W = '1w',\n}\n\n/** Durée d'un intervalle, en minutes. */\nexport const TIMEFRAME_MINUTES: Record<Timeframe, number> = {\n [Timeframe.M1]: 1,\n [Timeframe.M5]: 5,\n [Timeframe.M15]: 15,\n [Timeframe.H1]: 60,\n [Timeframe.H4]: 240,\n [Timeframe.D]: 1440,\n [Timeframe.W]: 10080,\n};\n\n/** Durée d'un intervalle, en millisecondes. */\nexport function timeframeMs(timeframe: Timeframe): number {\n return TIMEFRAME_MINUTES[timeframe] * 60_000;\n}\n","/**\n * Addition décimale EXACTE, sur les chaînes que les venues rendent.\n *\n * XGate garde les prix et les volumes en `string` pour préserver leur précision — sommer en\n * `number` reviendrait à annuler ce choix au premier agrégat. `0.1 + 0.2` vaut\n * `0.30000000000000004` en JavaScript ; sur une semaine de volumes horaires, l'écart s'accumule.\n *\n * On lit donc la chaîne comme un entier et un nombre de décimales, on additionne en `bigint`, et\n * on reconstruit. Aucun `number` n'intervient.\n */\nfunction parse(value: string): { digits: bigint; scale: number } {\n const trimmed = value.trim();\n if (/^-?\\d+(\\.\\d+)?$/.test(trimmed) === false) {\n throw new Error(`Décimal illisible : \"${value}\".`);\n }\n const dot = trimmed.indexOf('.');\n if (dot < 0) {\n return { digits: BigInt(trimmed), scale: 0 };\n }\n return {\n digits: BigInt(trimmed.slice(0, dot) + trimmed.slice(dot + 1)),\n scale: trimmed.length - dot - 1,\n };\n}\n\nfunction format(digits: bigint, scale: number): string {\n const negative = digits < 0n;\n const plain = (negative === true ? -digits : digits).toString().padStart(scale + 1, '0');\n const sign = negative === true ? '-' : '';\n if (scale === 0) {\n return sign + plain;\n }\n const whole = plain.slice(0, plain.length - scale);\n const fraction = plain.slice(plain.length - scale).replace(/0+$/, '');\n return fraction === '' ? sign + whole : `${sign + whole}.${fraction}`;\n}\n\n/** Somme exacte de deux décimaux. Les échelles sont alignées sur la plus fine. */\nexport function addDecimals(left: string, right: string): string {\n const a = parse(left);\n const b = parse(right);\n const scale = Math.max(a.scale, b.scale);\n const digits =\n a.digits * 10n ** BigInt(scale - a.scale) + b.digits * 10n ** BigInt(scale - b.scale);\n return format(digits, scale);\n}\n\n/** Compare deux décimaux sans passer par `number` : `-1`, `0` ou `1`. */\nexport function compareDecimals(left: string, right: string): number {\n const a = parse(left);\n const b = parse(right);\n const scale = Math.max(a.scale, b.scale);\n const x = a.digits * 10n ** BigInt(scale - a.scale);\n const y = b.digits * 10n ** BigInt(scale - b.scale);\n if (x === y) {\n return 0;\n }\n return x > y ? 1 : -1;\n}\n","import { Timeframe, timeframeMs } from '../enums/timeframe.enums';\nimport type { ICandle } from '../interfaces/candle.interfaces';\nimport { addDecimals, compareDecimals } from './decimal';\n\n/**\n * L'epoch (1970-01-01) tombe un JEUDI. Aligner les semaines sur lundi 00:00 UTC impose donc de\n * décaler les seaux de quatre jours.\n */\nconst MONDAY_OFFSET_MS = 4 * 24 * 60 * 60 * 1000;\n\n/** Un seau en construction : la bougie, et de quoi juger si elle est complète. */\ninterface Bucket {\n candle: ICandle;\n sources: number;\n}\n\n/**\n * AGRÈGE des bougies fines en bougies d'un intervalle plus grand.\n *\n * Porté de `Blips/server/src/candles/helpers/internal/candle-aggregator.util.ts`, dont les règles\n * ont été payées en incidents. Deux sont contre-intuitives et se perdent si on les réécrit de\n * mémoire :\n *\n * **1. UN SEAU INCOMPLET EST REJETÉ, sauf s'il est encore ouvert.** Agréger 300 bougies horaires\n * en journalières donne un premier seau tronqué : il commence au milieu d'une journée, donc son\n * `open` n'est pas l'ouverture du jour et son volume en manque une part. Une telle bougie est\n * FAUSSE, pas approximative. Et « garder le dernier » ne suffit pas — sur paradex, 49 bougies 4h\n * sur 116 étaient closes ET incomplètes, parce que la venue ne rendait que quatre horaires\n * éparses. Être le dernier d'une série ne veut pas dire être en cours.\n *\n * **2. LE VOLUME PROPAGE L'INCONNU.** Une seule source à `null` et la somme vaut `null`. Compter\n * la source muette pour zéro donnerait un total plus petit que la réalité, sans que rien ne le\n * signale — la même faute que traiter un `null` comme un `0`.\n *\n * Les bougies rendues portent `derived: true` : personne ne les a servies telles quelles.\n */\nexport function aggregate(\n source: ICandle[],\n target: Timeframe,\n now: number = Date.now(),\n): ICandle[] {\n if (source.length === 0) {\n return [];\n }\n const sourceMs = timeframeMs(source[0]?.interval ?? Timeframe.M1);\n const targetMs = timeframeMs(target);\n if (targetMs <= sourceMs) {\n return [];\n }\n const offsetMs = target === Timeframe.W ? MONDAY_OFFSET_MS : 0;\n const expected = targetMs / sourceMs;\n const buckets = new Map<number, Bucket>();\n\n const sorted = [...source].sort(\n (left, right) => left.openedAt.getTime() - right.openedAt.getTime(),\n );\n for (const candle of sorted) {\n const start =\n Math.floor((candle.openedAt.getTime() - offsetMs) / targetMs) * targetMs + offsetMs;\n const bucket = buckets.get(start);\n if (bucket === undefined) {\n buckets.set(start, {\n sources: 1,\n candle: {\n ...candle,\n interval: target,\n openedAt: new Date(start),\n // Dernière milliseconde du créneau, comme les bougies servies par les venues.\n closedAt: new Date(start + targetMs - 1),\n derived: true,\n },\n });\n } else {\n bucket.sources += 1;\n const built = bucket.candle;\n built.high = compareDecimals(candle.high, built.high) > 0 ? candle.high : built.high;\n built.low = compareDecimals(candle.low, built.low) < 0 ? candle.low : built.low;\n // `open` reste celui de la première bougie du seau ; `close` suit la dernière.\n built.close = candle.close;\n built.volume = sum(built.volume, candle.volume);\n built.quoteVolume = sum(built.quoteVolume, candle.quoteVolume);\n built.takerBuyQuoteVolume = sum(built.takerBuyQuoteVolume, candle.takerBuyQuoteVolume);\n built.trades =\n built.trades === null || candle.trades === null ? null : built.trades + candle.trades;\n }\n }\n\n return [...buckets.keys()]\n .sort((left, right) => left - right)\n .map((start) => buckets.get(start))\n .filter((bucket): bucket is Bucket => bucket !== undefined)\n .filter((bucket) => {\n // Complet : le seau vaut ce qu'il annonce. Incomplet : acceptable seulement si la période\n // court toujours — il lui manque des bougies qui n'existent pas ENCORE.\n const stillOpen = bucket.candle.openedAt.getTime() + targetMs > now;\n return bucket.sources >= expected || stillOpen === true;\n })\n .map((bucket) => bucket.candle);\n}\n\n/** Somme qui propage l'inconnu : `null` domine. */\nfunction sum(left: string | null, right: string | null): string | null {\n if (left === null || right === null) {\n return null;\n }\n return addDecimals(left, right);\n}\n","// Repris tel quel de `Blips/server/src/shared/symbol-naming.ts` (2026-08-05). Ce code a été écrit\n// et éprouvé côté consommateur, mais sa place est ICI : à la frontière des venues, pas dans\n// l'application qui les consomme. Zéro dépendance — le « nom CoinMarketCap » est la convention\n// visée, jamais un appel réseau.\n//\n// Normalisation des symboles. Un même actif porte une forme différente sur chaque venue :\n// kPEPE (hyperliquid/pacifica/paradex) · 1000PEPEUSDT (aster) · 1000PEPE (lighter) · 1000PEPE-USD (extended)\n// Tous donnent le MÊME canonique : `PEPE` — le nom de l'actif chez CoinMarketCap, sans multiplicateur de cotation.\n// Le catalogue porte DEUX symboles par ligne :\n// - symbolXex : le BRUT de la venue (ce qu'on lui envoie : souscription, ordre, fetch REST) ;\n// - symbol : le CANONIQUE (clé interne : oracle, signaux, UI).\n// Ce fichier est l'unique frontière de conversion.\n\n// Venues « binance-like » : le quote est CONCATÉNÉ à l'actif (BTCUSDT, USDEUSDT). On strippe le quote final.\n// Ailleurs (hyperliquid/pacifica/lighter/extended/paradex) : soit actif nu (BTC, USDC), soit quote SÉPARÉ (BTC-USD).\nconst CONCAT_QUOTE_XEXS = new Set(['aster', 'binance', 'bybit']);\n\n// LE MULTIPLICATEUR DE COTATION DISPARAÎT DU CANONIQUE (règle Philippe, 2026-08-02).\n//\n// Les memecoins se cotent par paquets — `kPEPE` (hyperliquid/pacifica/paradex), `1000PEPE` (aster/bybit/lighter/\n// extended), `10000SATS`, `1000000BABYDOGE` — mais c'est une UNITÉ DE COTATION, pas un actif différent : même\n// token, même capitalisation. Le canonique doit être le nom de l'actif, c'est-à-dire celui de CoinMarketCap.\n//\n// MESURÉ le 2026-08-02 sur le top 1000 CMC : aucun `1000*` n'y existe. `PEPE` est au rang 48, `SATS` au 552\n// (« SATS (Ordinals) » — bien le token que binance cote `1000SATSUSDT`), `BONK`, `SHIB`, `FLOKI`, `BTT`, `LUNC`,\n// `XEC` tous sans préfixe. Sans ce strip, aucun de ces actifs ne se rattacherait à son classement.\n//\n// Deux formes, deux règles :\n// - `k` MINUSCULE en tête suivi d'une majuscule, retiré AVANT l'uppercase — sinon on amputerait les vrais actifs\n// en K majuscule (KAVA, KAS, KSM, KNC) ;\n// - `1` suivi d'AU MOINS TROIS zéros (1000, 10000, 1000000). Trois au minimum : `100X` ou `10X` resteraient\n// intacts, et aucun actif mesuré ne commence réellement par `1000`.\nconst K_MULTIPLIER = /^k(?=[A-Z])/u;\nconst NUMERIC_MULTIPLIER = /^10{3,}(?=[A-Z0-9])/u;\n\n// Marqueur « 24/5 » d'extended : contrats actions/ETF (cotés 24h/5j, vs crypto 24/7). Ex. `AAPL_24_5-USD`. On le\n// strippe à la normalisation (AAPL_24_5 → AAPL) pour obtenir le ticker canonique ; le type d'actif vient ensuite\n// du catalogue.\nconst STOCK_24_5 = /_24_5$/u;\n\n// Variantes d'un même actif → canonique unique, pour consolider entre venues (un actif listé sous des noms\n// différents par chacune : ex. S&P 500 = US500/SPX500M/SP500). Appliqué APRÈS strip du suffixe et uppercase.\nconst TICKER_ALIASES: Record<string, string> = {\n GOOG: 'GOOGL', // classe d'action (extended `GOOG` ≡ `GOOGL` ailleurs)\n // Indices\n US500: 'SP500',\n SPX500M: 'SP500',\n TECH100M: 'US100',\n // Matières premières\n BRENTOIL: 'BRENT',\n NATGAS: 'NG',\n // Actions (USD collé sur lighter, non strippé → aliasé explicitement)\n SAMSUNGUSD: 'SAMSUNG',\n SKHYNIXUSD: 'SKHYNIX',\n HYUNDAIUSD: 'HYUNDAI',\n};\n\n/**\n * SYMBOLE BRUT D'UNE VENUE → CANONIQUE.\n *\n * DÉPEND DE LA VENUE, pour ne JAMAIS amputer un stablecoin ni un actif finissant par USD(T/C) :\n * - aster/binance/bybit : quote concaténé → on strippe `USDT`/`USDC` FINAL (USDEUSDT→USDE, BTCUSDT→BTC) ;\n * - les autres : uniquement les suffixes SÉPARÉS (`-USD-PERP`, `-PERP`, `-USDT`, `-USDC`, `-USD`). Jamais de\n * strip nu → un actif nu (USDC, USDT, USDE, BUSD, BTC) reste intact ;\n * - dans tous les cas, le multiplicateur tombe d'abord (kPEPE → PEPE).\n *\n * `-USDT` A ÉTÉ AJOUTÉ le 2026-08-05, en agrégeant blofin pour la première fois : elle cote\n * `BTC-USDT` avec un tiret, forme qu'aucune venue de Blips n'utilisait. Sans lui, **477 des 487\n * symboles blofin restaient non canonisés** (`BTC-USDT` au lieu de `BTC`) et aucun ne se\n * rapprochait de son homologue chez une autre venue. Le tiret rend le strip sûr : l'actif nu\n * `USDT` n'y correspond pas.\n */\nexport function canonicalFromXex(rawSymbol: string, xexId: string): string {\n let s = rawSymbol.replace(K_MULTIPLIER, '').toUpperCase(); // kPEPE → PEPE (KAVA intact)\n if (CONCAT_QUOTE_XEXS.has(xexId)) {\n s = s.replace(/(USDT|USDC)$/u, ''); // quote concaténé final (le reste = actif, même si stablecoin)\n } else {\n s = s\n .replace(/-USD-PERP$/u, '')\n .replace(/-PERP$/u, '')\n .replace(/-USDT$/u, '') // blofin cote `BTC-USDT` (tiret + USDT) — cf. note ci-dessous\n .replace(/-USDC$/u, '')\n .replace(/-USD$/u, ''); // quotes SÉPARÉS uniquement → stablecoins nus préservés\n }\n s = s.replace(NUMERIC_MULTIPLIER, ''); // 1000PEPE → PEPE, 10000SATS → SATS, 1000000BABYDOGE → BABYDOGE\n s = s.replace(STOCK_24_5, ''); // contrat 24/5 extended (AAPL_24_5 → AAPL). Inoffensif ailleurs.\n return TICKER_ALIASES[s] ?? s; // variantes de classe (GOOG → GOOGL)\n}\n\n/**\n * ACTIF DE BASE NU → CANONIQUE, pour les venues qui NOMMENT leur actif au lieu de le concaténer : binance rend\n * `baseAsset`, bybit `baseCoin`. On prend ce qu'elles DISENT plutôt que de re-parser leur symbole — sans quoi\n * `ROSEBTC` (cotée en BTC, pas en USDT) donnerait le faux canonique `ROSEBTC`.\n *\n * Mêmes règles que `canonicalFromXex` MOINS le strip de cotation : l'appliquer à une base nue amputerait les\n * stablecoins (`USDC` → chaîne VIDE). C'est exactement le piège que la connaissance de la venue évite déjà côté\n * symbole.\n */\nexport function canonicalFromBase(base: string): string {\n const s = base\n .replace(K_MULTIPLIER, '') // kPEPE → PEPE, avant l'uppercase (KAVA intact)\n .toUpperCase()\n .replace(NUMERIC_MULTIPLIER, '') // 1000SATS → SATS (le nom CMC de l'actif)\n .replace(STOCK_24_5, '');\n return TICKER_ALIASES[s] ?? s;\n}\n","import type { Timeframe } from '../enums/timeframe.enums';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport type { ICandle } from '../interfaces/candle.interfaces';\nimport type { MarketKind } from '../interfaces/pair.interfaces';\nimport { canonicalFromXex } from './symbol-naming';\nimport type { IXexCandle } from './xex-registry';\n\n/**\n * Une bougie de venue → une bougie XGate.\n *\n * Deux traductions, et rien d'autre — le mapper ne calcule aucune valeur :\n *\n * - **les temps deviennent des `Date`.** Les SDK rendent `t`/`T` en millisecondes ; personne ne\n * devrait avoir à savoir dans quelle unité compte quelle venue.\n * - **`n: 0` devient `trades: null`.** Les venues qui ne comptent pas les transactions posent `0`\n * dans le cœur des SDK (convention de lighter, extended, paradex, drift, blofin). Un zéro se lit\n * « aucune transaction sur ce créneau », ce qui est une information — et elle serait fausse.\n * L'absence se dit `null`.\n */\nexport function toCandle(\n wire: IXexCandle,\n xex: XgateEx,\n interval: Timeframe,\n quote: string,\n): ICandle {\n return {\n symbol: canonicalFromXex(wire.symbol, xex),\n quote,\n symbolXex: wire.symbol,\n xex,\n kind: wire.kind as MarketKind,\n interval,\n openedAt: wire.openedAt,\n closedAt: wire.closedAt,\n open: wire.open,\n high: wire.high,\n low: wire.low,\n close: wire.close,\n volume: wire.volume,\n quoteVolume: wire.quoteVolume,\n takerBuyQuoteVolume: wire.takerBuyQuoteVolume,\n trades: wire.trades > 0 ? wire.trades : null,\n // Servie par la venue, pas fabriquée. L'agrégation posera `true`.\n derived: false,\n };\n}\n","import type { Timeframe } from '../enums/timeframe.enums';\n\n/** Ce qu'un SDK de venue attend pour une lecture de bougies. */\nexport interface IXexCandlesQuery {\n name: string;\n interval: string;\n startTime?: Date;\n endTime?: Date;\n limit?: number;\n}\n\n/**\n * Adapte une requête XGate à la forme des SDK.\n *\n * Il ne reste plus qu'un renommage (`symbol` → `name`) : **les dates passent telles quelles**,\n * parce que les SDK prennent désormais des `Date`. Cette fonction traduisait autrefois vers des\n * chaînes UTC — et vers des millisecondes pour blofin, avec des bornes inversées. Ces conversions\n * vivent maintenant dans chaque SDK, à la frontière de sa venue, là où elles appartiennent.\n */\nexport function toXexQuery(query: {\n symbol: string;\n interval: Timeframe;\n startTime?: Date;\n endTime?: Date;\n limit?: number;\n}): IXexCandlesQuery {\n return {\n name: query.symbol,\n interval: query.interval,\n startTime: query.startTime,\n endTime: query.endTime,\n limit: query.limit,\n };\n}\n","/**\n * Les venues qu'XGate sait interroger.\n *\n * Une seule énumération pour les CEX et les DEX : la distinction ne change rien à l'API — on\n * interroge un catalogue de la même façon des deux côtés. Elle deviendra une donnée portée par la\n * venue le jour où quelque chose en dépendra.\n *\n * **`drift` en est absent**, et ce n'est pas un oubli : son SDK ne porte aucun test (seul de la\n * famille), sa précision de taille est un placeholder assumé, et son catalogue est une constante\n * figée dans le paquet. On ne l'agrège pas tant qu'il n'est pas vérifiable.\n */\nexport enum XgateEx {\n Hyperliquid = 'hyperliquid',\n Pacifica = 'pacifica',\n Aster = 'aster',\n Lighter = 'lighter',\n Extended = 'extended',\n Paradex = 'paradex',\n Bullet = 'bullet',\n Blofin = 'blofin',\n /**\n * Les deux seules venues dont XGate sert aussi le **comptant** (cf. `IXexSpotSource`). Leurs SDK\n * ne couvrent que la lecture publique : catalogue, bougies et prix. Toute route signée y lève.\n */\n Binance = 'binance',\n Bybit = 'bybit',\n}\n\n/** Toutes les venues connues, dans l'ordre de déclaration. */\nexport const XGATE_EXCHANGES: readonly XgateEx[] = Object.values(XgateEx);\n","import { Aster } from '@blackcube/aster-sdk';\nimport { Binance } from '@blackcube/binance-sdk';\nimport { Blofin } from '@blackcube/blofin-sdk';\nimport { Bullet } from '@blackcube/bullet-sdk';\nimport { Bybit } from '@blackcube/bybit-sdk';\nimport { Extended } from '@blackcube/extended-sdk';\nimport { Hyperliquid } from '@blackcube/hyperliquid-sdk';\nimport { Lighter } from '@blackcube/lighter-sdk';\nimport { Pacifica } from '@blackcube/pacifica-sdk';\nimport { Paradex } from '@blackcube/paradex-sdk';\nimport { XgateEx } from '../enums/xgate-ex.enums';\n\n/** Une paire, telle qu'un SDK de venue la rend. */\nexport interface IXexPair {\n name: string;\n /**\n * L'actif de base, **tel que la venue le NOMME**. Décisif chez binance et bybit : leur symbole\n * concatène la cotation (`BTCUSDT`), et re-parser cette chaîne donnerait un faux canonique sur\n * tout ce qui n'est pas coté en USDT — `ROSEBTC` deviendrait `ROSEBTC` au lieu de `ROSE`.\n */\n base: string;\n quote: string;\n kind: string;\n ref?: string | null;\n szDecimals: number;\n tickSize?: string;\n stepSize?: string;\n minQty?: string;\n minNotional?: string;\n maxLeverage?: number;\n status?: string;\n delisted?: boolean;\n listedAt?: Date;\n xtras?: Record<string, unknown>;\n}\n\n/**\n * Une bougie, telle qu'un SDK de venue la rend : **clés longues et `Date`**.\n *\n * Les clés courtes (`t`, `T`, `o`, `qv`…) restent au niveau du wire, dans chaque SDK. Elles ne\n * remontent plus jusqu'ici — un `candle.T` ne se lit pas, et un `number` ne dit pas s'il compte des\n * secondes ou des millisecondes.\n */\nexport interface IXexCandle {\n openedAt: Date;\n closedAt: Date;\n symbol: string;\n interval: string;\n open: string;\n close: string;\n high: string;\n low: string;\n volume: string;\n trades: number;\n kind: string;\n quoteVolume: string | null;\n takerBuyBaseVolume: string | null;\n takerBuyQuoteVolume: string | null;\n xtras?: Record<string, unknown>;\n}\n\n/**\n * Une cotation, telle qu'un SDK de venue la rend : **clés longues et `Date`**, comme\n * {@link IXexCandle}. Le `time: number` d'origine est devenu `quotedAt: Date` — un nombre nu ne dit\n * pas s'il compte des secondes ou des millisecondes, et les deux existent selon les venues.\n */\nexport interface IXexPrice {\n name: string;\n kind: string;\n mark: string | null;\n oracle: string | null;\n mid: string | null;\n bid: string | null;\n ask: string | null;\n last: string | null;\n funding: string | null;\n openInterest: string | null;\n volume24h: string | null;\n prevDayPrice: string | null;\n quotedAt: Date | null;\n xtras?: Record<string, unknown>;\n}\n\n/**\n * Ce qu'XGate attend d'une façade de venue. Le plus petit dénominateur réellement commun aux huit\n * — mesuré, pas supposé.\n *\n * **`getCandles` est OPTIONNEL**, et le type doit le dire : bullet ne l'expose pas\n * (`perp().getCandles is not a function`, vérifié le 2026-08-05), il ne sert ses bougies qu'en\n * WebSocket. Le déclarer requis ferait échouer la compilation sur une venue pourtant valide pour\n * le catalogue.\n *\n * Les dates y sont des **chaînes** (`\"YYYY-MM-DD HH:MM:SS\"` UTC) chez huit venues et des\n * millisecondes chez blofin : c'est la frontière que les services traduisent.\n */\nexport interface IXexSource {\n perp(): {\n getPairs(): Promise<IXexPair[]>;\n getCandles?(query: {\n name: string;\n interval: string;\n /** Un `Date` : c'est au SDK de la venue de le traduire dans son format à elle. */\n startTime?: Date;\n endTime?: Date;\n limit?: number;\n }): Promise<IXexCandle[]>;\n getPrices(): Promise<IXexPrice[]>;\n };\n ws(): {\n subscribeCandles(\n query: { name: string; interval: string },\n handler: (candle: IXexCandle) => void,\n ): () => void;\n };\n}\n\n/**\n * Ce qu'XGate attend d'une façade de venue **côté COMPTANT**.\n *\n * **Volontairement séparé d'{@link IXexSource}, et non fondu dedans avec un paramètre.** Un\n * `perp()` qui servirait parfois du spot selon un drapeau serait une source d'erreurs silencieuses :\n * on croirait lire des perpétuels et on lirait du comptant, sans que rien ne le signale. Deux\n * contrats, deux registres, deux services — l'appelant choisit explicitement ce qu'il interroge.\n *\n * **Toutes les venues n'ont pas de spot** : c'est même minoritaire. Le registre lève pour celles qui\n * n'en servent pas, plutôt que de rendre une liste vide qui se lirait comme « ce marché ne cote\n * rien ».\n */\nexport interface IXexSpotSource {\n spot(): {\n getPairs(): Promise<IXexPair[]>;\n getCandles?(query: {\n name: string;\n interval: string;\n startTime?: Date;\n endTime?: Date;\n limit?: number;\n }): Promise<IXexCandle[]>;\n getPrices?(): Promise<IXexPrice[]>;\n };\n}\n\n/**\n * De quoi lire UN compte chez une venue.\n *\n * **Un sac d'accès volontairement lâche**, dont chaque venue prend ce qu'il lui faut : les huit\n * n'ont pas la même notion d'identité. Un DEX se lit à l'adresse ; lighter veut un index de compte,\n * extended une clé d'API plus un coffre, blofin un triplet clé/secret/passphrase. Un type strict par\n * venue obligerait l'appelant à connaître ces huit formes — c'est précisément ce qu'XGate existe\n * pour lui épargner.\n *\n * Le registre **lève** quand ce qu'il faut manque, avec le nom du champ attendu : mieux vaut un\n * message précis qu'un compte vide qui se lirait comme « pas d'argent ».\n */\nexport interface IXexAccess {\n /** L'adresse publique du compte. Suffit seule sur les DEX en lecture. */\n address?: string;\n privateKey?: string;\n /**\n * Le réseau de la venue. **Défaut `mainnet`** — c'est le cas courant, et un défaut `testnet`\n * ferait lire des soldes fantômes sans que personne ne s'en aperçoive.\n *\n * Il devient indispensable dès qu'on écrit : sans lui, `TradingService` ne pourrait ouvrir que sur\n * de l'argent réel, et le cycle ne serait exerçable nulle part.\n */\n network?: 'mainnet' | 'testnet';\n apiKey?: string;\n secret?: string;\n passphrase?: string;\n accountIndex?: number;\n apiKeyIndex?: number;\n vault?: number;\n}\n\n/** Un solde tel qu'un SDK de venue le rend. */\nexport interface IXexBalance {\n asset: string;\n total: string;\n available: string | null;\n usdValue: string | null;\n xtras?: Record<string, unknown>;\n}\n\n/** Un mouvement tel qu'un SDK de venue le rend : **clés longues et `Date`**. */\nexport interface IXexMovement {\n kind: string;\n amount: string;\n fee: string;\n asset: string;\n occurredAt: Date;\n status: string;\n externalId: string;\n xtras?: Record<string, unknown>;\n}\n\n/** Un point d'équité tel qu'un SDK de venue le rend. */\nexport interface IXexEquityPoint {\n measuredAt: Date;\n equity: number;\n}\n\n/**\n * Ce qu'XGate attend d'une façade de venue **côté COMPTE**.\n *\n * `getBalances` est le seul point commun aux huit — c'est donc le seul requis. `getMovements` manque\n * encore là où la venue ne l'expose pas, et `getEquityHistory` n'est publié que par hyperliquid et\n * pacifica : les déclarer requis ferait échouer la compilation sur des venues pourtant valides.\n */\nexport interface IXexAccountSource {\n account(): {\n getBalances(): Promise<IXexBalance[]>;\n getMovements?(query?: unknown): Promise<IXexMovement[]>;\n getEquityHistory?(range?: unknown): Promise<IXexEquityPoint[]>;\n };\n}\n\n/**\n * Instancie la façade d'une venue **avec un accès de compte**, en LECTURE.\n *\n * Sur les DEX, lire un portefeuille est **public** : l'adresse suffit et aucune signature n'est\n * produite — d'où la clé privée nulle passée à hyperliquid, aster, paradex et bullet, dont le\n * constructeur l'exige sans que la lecture s'en serve. Les CEX (blofin) réclament en revanche un\n * vrai triplet, même pour lire.\n */\nexport function createAccountXex(xex: XgateEx, access: IXexAccess): IXexAccountSource {\n const NULL_KEY = `0x${'0'.repeat(64)}`;\n const adresse = (): string => {\n if (access.address === undefined || access.address === '') {\n throw new Error(`wallet(${xex}) : il faut au moins une adresse (\\`access.address\\`).`);\n }\n return access.address;\n };\n switch (xex) {\n case XgateEx.Hyperliquid:\n return new Hyperliquid(\n {\n a: {\n privateKey: (access.privateKey ?? NULL_KEY) as `0x${string}`,\n publicKey: adresse() as `0x${string}`,\n network: access.network ?? 'mainnet',\n },\n },\n { default: 'a' },\n ) as unknown as IXexAccountSource;\n case XgateEx.Pacifica:\n // `secretKey` est exigée par le type du signer mais ne sert qu'à SIGNER : la chaîne vide\n // suffit pour lire, comme la clé nulle côté EVM.\n return new Pacifica(\n {\n a: {\n secretKey: access.privateKey ?? '',\n publicKey: adresse(),\n network: access.network ?? 'mainnet',\n },\n },\n { default: 'a' },\n ) as unknown as IXexAccountSource;\n case XgateEx.Aster:\n // Aster signe avec une clé d'API pour le compte d'une ADRESSE EVM : les deux sont exigées, et\n // l'adresse n'est pas déductible de la clé (modèle agent/master — l'agent signe, le master\n // détient). Sans `privateKey`, la façade se construirait et échouerait à la première écriture.\n if (access.privateKey === undefined || access.privateKey === '') {\n throw new Error(\n `wallet(${xex}) : Aster signe avec une clé d'API — il faut \\`privateKey\\` (la clé) et \\`address\\` (le compte EVM).`,\n );\n }\n return new Aster(\n {\n a: {\n privateKey: access.privateKey,\n user: adresse(),\n network: access.network ?? 'mainnet',\n },\n },\n { default: 'a' },\n ) as unknown as IXexAccountSource;\n case XgateEx.Blofin:\n if (access.apiKey === undefined || access.secret === undefined) {\n throw new Error(\n `wallet(${xex}) : BloFin signe même en lecture — il faut \\`apiKey\\`, \\`secret\\` et \\`passphrase\\`.`,\n );\n }\n // BloFin nomme son second réseau « demo trading » : seul l'hôte change, l'API est identique.\n // Sans ce réseau, une clé de démo tape le mainnet et la venue répond « Access key does not\n // exist » — un message qui accuse la clé alors que c'est l'adresse qui est fausse.\n return new Blofin({\n credentials: {\n apiKey: access.apiKey,\n secret: access.secret,\n passphrase: access.passphrase ?? '',\n },\n network: access.network ?? 'mainnet',\n }) as unknown as IXexAccountSource;\n default:\n throw new Error(\n `wallet(${xex}) : venue pas encore câblée côté compte. Disponibles : ${WALLET_XEXES.join(', ')}.`,\n );\n }\n}\n\n/**\n * Les venues dont XGate sait lire le portefeuille aujourd'hui.\n *\n * Les autres ne sont pas exclues par principe : il leur manque un accès (clé d'API pour bullet, une\n * adresse active pour lighter, extended et paradex). Voir le backlog.\n */\nexport const WALLET_XEXES: readonly XgateEx[] = [\n XgateEx.Hyperliquid,\n XgateEx.Pacifica,\n XgateEx.Aster,\n XgateEx.Blofin,\n];\n\n/** Conservé pour la lisibilité des appels de catalogue. */\nexport type IXexCatalogSource = IXexSource;\n\n/**\n * Instancie la façade d'une venue, en **lecture publique** : aucun credential.\n *\n * Un `switch` explicite plutôt qu'une table de constructeurs, parce que les signatures ne\n * coïncident pas — les DEX prennent `(signers, options)`, BloFin prend `(options)` seul. C'est\n * exactement l'écart que la normalisation doit résorber ; en attendant, il vit ici, à un seul\n * endroit, plutôt que chez chaque appelant.\n */\nexport function createPublicXex(xex: XgateEx): IXexSource {\n switch (xex) {\n case XgateEx.Hyperliquid:\n return new Hyperliquid();\n case XgateEx.Pacifica:\n return new Pacifica();\n case XgateEx.Aster:\n return new Aster();\n case XgateEx.Lighter:\n return new Lighter();\n case XgateEx.Extended:\n return new Extended();\n case XgateEx.Paradex:\n return new Paradex();\n case XgateEx.Bullet:\n return new Bullet();\n case XgateEx.Blofin:\n return new Blofin();\n case XgateEx.Binance:\n return new Binance();\n case XgateEx.Bybit:\n return new Bybit();\n }\n}\n\n/**\n * Les venues dont XGate sert le **comptant**. Les autres n'ont pas de marché spot exploitable, ou\n * ne l'exposent pas — et une venue absente d'ici n'est pas un oubli, c'est un fait mesuré.\n */\nexport const SPOT_XEXES: readonly XgateEx[] = [XgateEx.Binance, XgateEx.Bybit];\n\n/** Cette venue sert-elle du comptant via XGate ? */\nexport function servesSpot(xex: XgateEx): boolean {\n return SPOT_XEXES.includes(xex);\n}\n\n/**\n * Instancie la façade **comptant** d'une venue, en lecture publique.\n *\n * **Lève sur une venue sans spot**, plutôt que de rendre une source vide : un catalogue spot vide se\n * lirait comme « cette venue ne liste rien au comptant », alors que la vraie information est\n * « cette venue n'a pas de comptant ici ». Les deux ne se corrigent pas de la même façon.\n */\nexport function createPublicSpotXex(xex: XgateEx): IXexSpotSource {\n switch (xex) {\n case XgateEx.Binance:\n return new Binance();\n case XgateEx.Bybit:\n return new Bybit();\n default:\n throw new Error(\n `spot(${xex}) : cette venue ne sert pas de comptant via XGate. Venues avec spot : ${SPOT_XEXES.join(', ')}.`,\n );\n }\n}\n","import { TIMEFRAME_MINUTES, Timeframe } from '../enums/timeframe.enums';\nimport { XgateEx } from '../enums/xgate-ex.enums';\n\n/**\n * CE QUE CHAQUE VENUE SERT NATIVEMENT — mesuré le 2026-08-05 contre les API, pas lu dans une doc.\n *\n * Ce qui n'y figure pas n'est pas indisponible : c'est **fabriqué par agrégation** depuis un\n * intervalle plus fin. La table dit seulement qui sert quoi de première main.\n *\n * - **paradex** s'arrête à l'heure, et c'est la venue qui le dit :\n * `resolution: must be one of [1 3 5 15 30 60]`.\n * - **lighter** refuse `1w` (`invalid param`) ; **extended** rend une liste vide pour ce même\n * intervalle — deux façons de dire non, dont une muette.\n * - **bullet** ne sert aucune bougie en REST (`perp().getCandles is not a function`) : rien à\n * agréger, la source manque entièrement.\n */\nconst NATIVE: Record<XgateEx, readonly Timeframe[]> = {\n [XgateEx.Hyperliquid]: Object.values(Timeframe),\n [XgateEx.Pacifica]: Object.values(Timeframe),\n [XgateEx.Aster]: Object.values(Timeframe),\n [XgateEx.Blofin]: Object.values(Timeframe),\n [XgateEx.Lighter]: [\n Timeframe.M1,\n Timeframe.M5,\n Timeframe.M15,\n Timeframe.H1,\n Timeframe.H4,\n Timeframe.D,\n ],\n [XgateEx.Extended]: [\n Timeframe.M1,\n Timeframe.M5,\n Timeframe.M15,\n Timeframe.H1,\n Timeframe.H4,\n Timeframe.D,\n ],\n [XgateEx.Paradex]: [Timeframe.M1, Timeframe.M5, Timeframe.M15, Timeframe.H1],\n [XgateEx.Bullet]: [],\n // Les deux CEX servent les sept, et sous les mêmes noms côté binance. Bybit compte en minutes\n // (`60`, `240`) puis en lettres (`D`, `W`) : son SDK traduit, la table reste dans notre grille.\n [XgateEx.Binance]: Object.values(Timeframe),\n [XgateEx.Bybit]: Object.values(Timeframe),\n};\n\n/** La venue sert-elle cet intervalle de première main ? */\nexport function servesNatively(xex: XgateEx, interval: Timeframe): boolean {\n return NATIVE[xex].includes(interval);\n}\n\n/**\n * L'intervalle SOURCE à demander pour fabriquer `target` chez cette venue.\n *\n * On prend le **plus grand** intervalle servi nativement qui divise la cible : le plus grand,\n * parce qu'il minimise le nombre de bougies à tirer — reconstruire une semaine depuis des minutes\n * demanderait 10 080 points là où sept journalières suffisent.\n *\n * `undefined` = la venue ne sert rien d'exploitable, il n'y a pas de fabrication possible.\n */\nexport function sourceFor(xex: XgateEx, target: Timeframe): Timeframe | undefined {\n const targetMinutes = TIMEFRAME_MINUTES[target];\n const candidates = NATIVE[xex]\n .filter((interval) => {\n const minutes = TIMEFRAME_MINUTES[interval];\n return minutes < targetMinutes && targetMinutes % minutes === 0;\n })\n .sort((left, right) => TIMEFRAME_MINUTES[right] - TIMEFRAME_MINUTES[left]);\n return candidates[0];\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport type { Timeframe } from '../enums/timeframe.enums';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport { aggregate } from '../helpers/candle-aggregator';\nimport { toCandle } from '../helpers/candle-mapper';\nimport { toXexQuery } from '../helpers/candle-query';\nimport { createPublicXex } from '../helpers/xex-registry';\nimport { servesNatively, sourceFor } from '../helpers/xex-timeframes';\nimport type { ICandle, ICandlesQuery } from '../interfaces/candle.interfaces';\n\n/**\n * LES BOUGIES EN REST — l'historique.\n *\n * Le temps réel vit dans `WsCandlesService` : ce sont deux besoins distincts (rejouer le passé,\n * suivre le présent), et une application qui fait du backfill n'a pas à ouvrir de socket.\n */\n@Injectable()\nexport class CandlesService {\n private readonly logger = new Logger(CandlesService.name);\n\n /**\n * Les bougies d'une venue, sur une plage de **dates**.\n *\n * L'appelant donne des `Date` et un {@link Timeframe} ; le service traduit vers ce que la venue\n * attend — chaîne UTC pour huit d'entre elles, millisecondes pour blofin.\n *\n * **Un intervalle que la venue ne sert pas est FABRIQUÉ**, pas refusé : on tire l'intervalle\n * exploitable le plus grand et on agrège. Paradex s'arrête à l'heure, lighter et extended ne\n * font pas la semaine — l'appelant n'a pas à le savoir. Les bougies fabriquées portent\n * `derived: true`.\n *\n * **Lève si la venue ne sert rien du tout** (bullet, qui n'a pas de REST). Rendre une liste vide\n * ferait passer une incapacité pour une absence de données, et un backfill silencieusement troué\n * est pire qu'un backfill en erreur.\n */\n public async candles(xex: XgateEx, query: ICandlesQuery): Promise<ICandle[]> {\n if (servesNatively(xex, query.interval) === true) {\n return this.fetch(xex, query, query.interval);\n }\n const source = sourceFor(xex, query.interval);\n if (source === undefined) {\n throw new Error(\n `candles(${xex}) : ni ${query.interval} servi, ni intervalle plus fin pour le fabriquer.`,\n );\n }\n this.logger.log(\n `${xex} ne sert pas ${query.interval} : agrégation depuis ${source} (bougies dérivées).`,\n );\n const fine = await this.fetch(xex, query, source);\n return aggregate(fine, query.interval);\n }\n\n /** Un appel REST à la venue, dans l'intervalle qu'elle sait servir. */\n private async fetch(xex: XgateEx, query: ICandlesQuery, interval: Timeframe): Promise<ICandle[]> {\n const perp = createPublicXex(xex).perp();\n if (perp.getCandles === undefined) {\n throw new Error(\n `candles(${xex}) : cette venue ne sert pas de bougies en REST — utilise WsCandlesService.`,\n );\n }\n const wires = await perp.getCandles(toXexQuery({ ...query, interval }));\n return wires.map((wire) => toCandle(wire, xex, interval, this.quoteOf(wire.symbol)));\n }\n\n /**\n * Les bougies d'un même marché chez PLUSIEURS venues, en une liste.\n *\n * Comme pour le catalogue : une venue en échec parmi d'autres est journalisée et ignorée ; seule,\n * son échec est celui de l'appel. Chaque bougie porte son `xex`, donc la liste reste\n * exploitable telle quelle.\n */\n public async candlesOf(xexes: XgateEx[], query: ICandlesQuery): Promise<ICandle[]> {\n if (xexes.length === 0) {\n throw new Error('candlesOf() : aucune venue demandée — précise au moins un XgateEx.');\n }\n const solo = xexes.length === 1;\n const collected = await Promise.all(\n xexes.map(async (xex) => {\n try {\n return await this.candles(xex, query);\n } catch (error: unknown) {\n if (solo === true) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(\n `candles(${xex}, ${query.interval}) a échoué, venue ignorée : ${message}`,\n );\n return [];\n }\n }),\n );\n return collected.flat();\n }\n\n /**\n * La cotation, déduite du symbole natif. Les bougies ne la portent pas — seul le catalogue la\n * publie —, et refaire un appel de catalogue pour chaque série coûterait plus cher que ce\n * qu'elle apporte. `BTC-USDT` → `USDT` ; à défaut de séparateur, on ne prétend rien.\n */\n private quoteOf(symbolXex: string): string {\n const parts = symbolXex.split('-');\n const last = parts[parts.length - 1];\n return parts.length > 1 && last !== undefined ? last : '';\n }\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport { XgateEx } from '../enums/xgate-ex.enums';\nimport { canonicalFromBase, canonicalFromXex } from '../helpers/symbol-naming';\nimport { createPublicXex } from '../helpers/xex-registry';\nimport type { IPair, MarketKind } from '../interfaces/pair.interfaces';\n\n/**\n * Les venues qui NOMMENT leur actif de base au lieu de le laisser deviner : on prend ce qu'elles\n * disent (`baseAsset` chez binance, `baseCoin` chez bybit) plutôt que de re-parser leur symbole.\n *\n * Sans cela, le strip de cotation ne connaît que `USDT` et `USDC` : `ROSEBTC` — cotée en BTC —\n * donnerait le faux canonique `ROSEBTC`. Le cas est massif sur le spot binance, dont une grande\n * partie des 3 680 marchés se cote en BTC, ETH ou BNB.\n */\nconst NAMES_ITS_BASE: ReadonlySet<XgateEx> = new Set([XgateEx.Binance, XgateEx.Bybit]);\n\n/**\n * LE CATALOGUE, toutes venues confondues.\n *\n * L'appelant ne connaît qu'{@link XgateEx} et les types unifiés ; il n'importe aucun SDK, ne\n * construit aucune façade, et ne se soucie pas de ce que chaque venue nomme comment.\n *\n * **Un service par capacité, pas une façade unique** : une application qui ne veut que le\n * catalogue n'a aucune raison d'injecter de quoi trader. Les capacités suivantes vivront dans\n * leurs propres services, injectables séparément.\n */\n@Injectable()\nexport class CatalogService {\n private readonly logger = new Logger(CatalogService.name);\n\n /**\n * Le catalogue **perp** d'une ou plusieurs venues, en une seule liste.\n *\n * Chaque ligne porte sa venue d'origine (`xex`) et les deux symboles — le canonique pour\n * rapprocher un actif entre venues, le brut pour parler à celle-ci.\n *\n * **Le traitement d'une venue en échec dépend de ce qui a été demandé** : sur une seule venue,\n * un échec est l'échec de l'appel et il est relancé ; sur plusieurs, la liste partielle garde\n * sa valeur et l'incident est journalisé. Rendre `[]` en silence serait le pire des deux — un\n * catalogue vide se lit comme « cette venue ne liste rien », pas comme « je n'ai pas pu\n * demander ».\n */\n public async catalog(...xexes: XgateEx[]): Promise<IPair[]> {\n if (xexes.length === 0) {\n throw new Error('catalog() : aucune venue demandée — précise au moins un XgateEx.');\n }\n const solo = xexes.length === 1;\n const collected = await Promise.all(\n xexes.map(async (xex) => {\n try {\n return await this.catalogOf(xex);\n } catch (error: unknown) {\n if (solo === true) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`catalog(${xex}) a échoué, venue ignorée : ${message}`);\n return [];\n }\n }),\n );\n return collected.flat();\n }\n\n /** Le catalogue d'UNE venue, traduit vers {@link IPair}. */\n private async catalogOf(xex: XgateEx): Promise<IPair[]> {\n const pairs = await createPublicXex(xex).perp().getPairs();\n return pairs.map((pair) => ({\n symbol: NAMES_ITS_BASE.has(xex)\n ? canonicalFromBase(pair.base)\n : canonicalFromXex(pair.name, xex),\n quote: pair.quote,\n symbolXex: pair.name,\n xex,\n kind: pair.kind as MarketKind,\n ref: pair.ref ?? null,\n szDecimals: pair.szDecimals,\n tickSize: pair.tickSize,\n stepSize: pair.stepSize,\n minQty: pair.minQty,\n minNotional: pair.minNotional,\n maxLeverage: pair.maxLeverage,\n status: pair.status,\n delisted: pair.delisted,\n listedAt: pair.listedAt,\n xtras: pair.xtras,\n }));\n }\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport { canonicalFromXex } from '../helpers/symbol-naming';\nimport { createPublicXex } from '../helpers/xex-registry';\nimport type { MarketKind } from '../interfaces/pair.interfaces';\nimport type { IPrice } from '../interfaces/price.interfaces';\n\n/**\n * LES PRIX, toutes venues confondues.\n *\n * Même forme que `CatalogService`, et pour la même raison : l'appelant ne connaît qu'{@link XgateEx}\n * et les types unifiés. Chaque ligne porte sa venue d'origine, sans quoi un relevé multi-venues\n * perdrait ce qu'il a de plus utile — l'écart entre les cotations d'un même actif.\n *\n * **Ce service ne calcule rien.** Il ne dérive pas un `mid` d'un `bid`/`ask`, ne convertit pas un\n * volume, ne comble aucun trou : un champ à `null` dit que la venue ne le publie pas, et c'est une\n * information. Fabriquer la valeur manquante la rendrait indiscernable d'une valeur cotée.\n */\n@Injectable()\nexport class PricesService {\n private readonly logger = new Logger(PricesService.name);\n\n /**\n * Les prix **perp** d'une ou plusieurs venues, en une seule liste.\n *\n * **Le traitement d'une venue en échec dépend de ce qui a été demandé** : sur une seule venue, un\n * échec est l'échec de l'appel et il est relancé ; sur plusieurs, la liste partielle garde sa\n * valeur et l'incident est journalisé. Rendre `[]` en silence serait le pire des deux — un relevé\n * vide se lit comme « ce marché ne cote rien », pas comme « je n'ai pas pu demander ».\n */\n public async prices(...xexes: XgateEx[]): Promise<IPrice[]> {\n if (xexes.length === 0) {\n throw new Error('prices() : aucune venue demandée — précise au moins un XgateEx.');\n }\n const solo = xexes.length === 1;\n const collected = await Promise.all(\n xexes.map(async (xex) => {\n try {\n return await this.pricesOf(xex);\n } catch (error: unknown) {\n if (solo === true) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`prices(${xex}) a échoué, venue ignorée : ${message}`);\n return [];\n }\n }),\n );\n return collected.flat();\n }\n\n /** Les prix d'UNE venue, traduits vers {@link IPrice}. */\n private async pricesOf(xex: XgateEx): Promise<IPrice[]> {\n const prices = await createPublicXex(xex).perp().getPrices();\n return prices.map((price) => ({\n symbol: canonicalFromXex(price.name, xex),\n symbolXex: price.name,\n xex,\n kind: price.kind as MarketKind,\n mark: price.mark,\n oracle: price.oracle,\n mid: price.mid,\n bid: price.bid,\n ask: price.ask,\n last: price.last,\n funding: price.funding,\n openInterest: price.openInterest,\n volume24h: price.volume24h,\n prevDayPrice: price.prevDayPrice,\n quotedAt: price.quotedAt,\n xtras: price.xtras,\n }));\n }\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport { canonicalFromBase } from '../helpers/symbol-naming';\nimport { SPOT_XEXES, createPublicSpotXex, servesSpot } from '../helpers/xex-registry';\nimport type { IPair, MarketKind } from '../interfaces/pair.interfaces';\n\n/**\n * LE CATALOGUE COMPTANT, séparé du catalogue perpétuel.\n *\n * **Deux services, pas un service à drapeau.** Un `catalog(xex, { spot: true })` serait plus court\n * à écrire et plus dangereux à lire : on croirait interroger des perpétuels et on interrogerait du\n * comptant, sans que rien ne le signale. Ici, le nom de la classe dit ce qu'on lit.\n *\n * Le comptant est **minoritaire** dans la famille : deux venues sur dix le servent. Ce n'est pas un\n * manque à combler, c'est la réalité d'un ensemble de SDK bâti pour le perpétuel.\n */\n@Injectable()\nexport class SpotCatalogService {\n private readonly logger = new Logger(SpotCatalogService.name);\n\n /** Les venues dont XGate sert le comptant. */\n public venues(): readonly XgateEx[] {\n return SPOT_XEXES;\n }\n\n /**\n * Le catalogue **spot** d'une ou plusieurs venues, en une seule liste.\n *\n * Même politique d'échec que le catalogue perpétuel : sur une seule venue, l'échec est relancé ;\n * sur plusieurs, la liste partielle garde sa valeur et l'incident est journalisé.\n *\n * **Une venue sans comptant lève**, elle ne rend pas une liste vide : « cette venue n'a pas de\n * spot » et « cette venue ne cote rien au comptant » ne se corrigent pas de la même façon.\n */\n public async catalog(...xexes: XgateEx[]): Promise<IPair[]> {\n if (xexes.length === 0) {\n throw new Error('spot catalog() : aucune venue demandée — précise au moins un XgateEx.');\n }\n const solo = xexes.length === 1;\n const collected = await Promise.all(\n xexes.map(async (xex) => {\n try {\n return await this.catalogOf(xex);\n } catch (error: unknown) {\n if (solo === true) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`spot catalog(${xex}) a échoué, venue ignorée : ${message}`);\n return [];\n }\n }),\n );\n return collected.flat();\n }\n\n /** Le catalogue comptant d'UNE venue, traduit vers {@link IPair}. */\n private async catalogOf(xex: XgateEx): Promise<IPair[]> {\n if (servesSpot(xex) === false) {\n throw new Error(`spot catalog(${xex}) : cette venue ne sert pas de comptant via XGate.`);\n }\n const pairs = await createPublicSpotXex(xex).spot().getPairs();\n return pairs.map((pair) => ({\n // Les deux venues à comptant NOMMENT leur actif de base : on prend ce qu'elles disent. Re-parser\n // `ROSEBTC` donnerait `ROSEBTC`, le strip de cotation ne connaissant que USDT et USDC — et le\n // spot se cote massivement en BTC, ETH ou BNB.\n symbol: canonicalFromBase(pair.base),\n quote: pair.quote,\n symbolXex: pair.name,\n xex,\n kind: pair.kind as MarketKind,\n ref: pair.ref ?? null,\n szDecimals: pair.szDecimals,\n tickSize: pair.tickSize,\n stepSize: pair.stepSize,\n minQty: pair.minQty,\n minNotional: pair.minNotional,\n maxLeverage: pair.maxLeverage,\n status: pair.status,\n delisted: pair.delisted,\n listedAt: pair.listedAt,\n xtras: pair.xtras,\n }));\n }\n}\n","// Repris de `Blips/server/src/execution/components/{protection-math,paper-math}.component.ts` (2026-08-06).\n// Ce calcul a été écrit et éprouvé côté application, mais sa place est ICI : c'est une règle de\n// protection d'ordre, pas une règle de signal. Blips garde ce qui lui appartient — le CHOIX des\n// parts selon le moteur (`tpParts`) et la garde « borne d'entrée vs tp1 ».\n\n/** Sens de la position. Le SL et les TP se posent au sens OPPOSÉ. */\nexport type Direction = 'long' | 'short';\n\n/** Un ordre de protection, prêt à être posé : déclenchement, taille, borne d'exécution. */\nexport interface IProtectionLeg {\n /** Prix auquel l'ordre se déclenche. */\n triggerPrice: string;\n /** Taille **en unités de base**. */\n size: string;\n /**\n * Borne d'exécution du déclenché : sous le trigger en long, au-dessus en short. Hyperliquid\n * l'exige ; les autres venues l'ignorent sans que ce soit une erreur.\n */\n price: string;\n}\n\n/** Ce qu'xgate rend : un SL pleine taille, et N take-profits partiels. */\nexport interface IProtection {\n sl: IProtectionLeg;\n tps: IProtectionLeg[];\n}\n\n/**\n * Ce que l'appelant fournit. **Les pourcentages sont des écarts au prix d'entrée**, en fraction\n * (0.01 = 1 %) : `slPct: 0.02` place le stop 2 % sous l'entrée en long, 2 % au-dessus en short.\n */\nexport interface IProtectionInput {\n /** Prix d'entrée de référence. */\n entry: number;\n direction: Direction;\n /** Taille de la position, **en unités de base**. */\n size: number;\n /** Écart du stop au prix d'entrée, en fraction. Toujours du côté perdant. */\n slPct: number;\n /**\n * Les take-profits, dans l'ordre. `pct` est l'écart au prix d'entrée (côté gagnant) et `part` la\n * fraction de la position à sortir à ce niveau.\n *\n * **Une part à 0 ne produit AUCUN leg** — c'est un marqueur (un palier de breakeven, par exemple),\n * pas un ordre. Sauf s'il s'agit du dernier : celui-là clôture toujours.\n */\n tps: Array<{ pct: number; part: number }>;\n /** Pas de prix de la paire. */\n tickSize?: string | null;\n /** Pas de quantité de la paire. */\n lotSize?: string | null;\n /**\n * Écart entre le déclenchement et la borne d'exécution, en fraction (défaut 0,5 %). Le déclenché\n * sort SOUS son trigger en long, AU-DESSUS en short.\n */\n slippagePct?: number;\n}\n\nconst SLIPPAGE_DEFAUT = 0.005;\n\n/**\n * LE SEUIL DE CLÔTURE INTÉGRALE — 80 % de la position.\n *\n * Strictement au-dessus, les take-profits sont manifestement censés tout sortir : le dernier absorbe le\n * reliquat et la position ferme entièrement. Trois paliers à 33 % somment à 99 % — ce 1 % restant\n * n'est pas une intention, c'est un arrondi, et le laisser ouvert créerait une position résiduelle\n * que personne ne surveille.\n *\n * À 80 % ou en dessous, l'inverse : deux paliers à 33 % laissent délibérément courir 34 % de la position. Le\n * dernier take-profit prend alors sa part nominale, sans plus — c'est une sortie partielle voulue,\n * et la compléter d'office trahirait l'intention.\n *\n * Le stop, lui, couvre la taille pleine dans les deux cas : ce qui court reste protégé.\n */\nconst SEUIL_CLOTURE = 0.8;\n\n/** Arrondi à huit décimales — la précision de travail, avant tout arrondi à la grille de la paire. */\nfunction round8(value: number): number {\n return Number(value.toFixed(8));\n}\n\n/**\n * Arrondit à un pas. `floor` pour les tailles — jamais `nearest` : arrondir au plus proche ferait\n * dépasser la somme des legs au-delà de la position, et la venue rejetterait le dernier.\n */\nexport function roundToStep(\n value: number,\n step: string | null | undefined,\n mode: 'floor' | 'nearest',\n): number {\n if (step === null || step === undefined || step === '') {\n return Math.round(value * 1e4) / 1e4;\n }\n const stepNum = Number(step);\n if (Number.isFinite(stepNum) === false || stepNum <= 0) {\n return Math.round(value * 1e4) / 1e4;\n }\n // LE PLANCHER SE PREND SUR UN QUOTIENT NETTOYÉ, et c'est indispensable. `1.7 - 1.3` vaut\n // `0.39999999999999991` en flottant : divisé par un pas de `0.1`, on obtient `3.9999…`, dont le\n // plancher est `3` au lieu de `4`. Un pas entier de position se volatilise — précisément le\n // résidu que ce module existe pour empêcher. Neuf décimales suffisent à recoller le quotient sans\n // jamais franchir un pas réel (`0.39 / 0.1` reste `3.9`, donc `3`).\n const quotient = value / stepNum;\n const units = mode === 'floor' ? Math.floor(Number(quotient.toFixed(9))) : Math.round(quotient);\n const dot = step.indexOf('.');\n const decimals = dot === -1 ? 0 : step.length - dot - 1;\n return Number((units * stepNum).toFixed(decimals));\n}\n\n/**\n * Arrondit un PRIX à la grille de la paire : `tickSize` s'il est fourni, sinon la règle Hyperliquid\n * — cinq chiffres significatifs ET au plus `6 − szDecimals` décimales, `szDecimals` étant déduit du\n * pas de quantité. Sans cette seconde branche, une venue sans tick fixe reçoit un prix qu'elle\n * refuse.\n */\nexport function roundPrice(\n value: number,\n tickSize?: string | null,\n lotSize?: string | null,\n): number {\n if (tickSize !== null && tickSize !== undefined && tickSize !== '') {\n return roundToStep(value, tickSize, 'nearest');\n }\n const dot = (lotSize ?? '').indexOf('.');\n const szDecimals = dot === -1 ? 0 : (lotSize as string).length - dot - 1;\n const maxDecimals = 6 - szDecimals;\n const sig = Number(value.toPrecision(5));\n if (maxDecimals <= 0) {\n return Math.round(sig);\n }\n return Number(sig.toFixed(maxDecimals));\n}\n\n/**\n * LES NIVEAUX DE PROTECTION, calculés depuis des POURCENTAGES.\n *\n * L'appelant donne un prix d'entrée, un sens et des écarts ; il ne calcule aucun prix lui-même.\n * Le stop part du côté perdant, les take-profits du côté gagnant — le sens s'en occupe.\n */\nexport function buildLevels(\n entry: number,\n direction: Direction,\n slPct: number,\n tpPcts: readonly number[],\n): { sl: number; tps: number[] } {\n const dir = direction === 'long' ? 1 : -1;\n return {\n sl: round8(entry * (1 - dir * slPct)),\n tps: tpPcts.map((pct) => round8(entry * (1 + dir * pct))),\n };\n}\n\n/**\n * LA PROTECTION COMPLÈTE — un stop pleine taille et N take-profits partiels, prêts à poser.\n *\n * **GARANTIE ANTI-RÉSIDU, sous condition de seuil.** Quand les parts somment à **plus de 80 %**, le\n * DERNIER take-profit prend le reliquat EXACT (`size − Σ des précédents`) et la position ferme\n * entièrement : trois paliers à 33 % font 99 %, et ce 1 % restant est un arrondi, pas une intention.\n *\n * **À 80 % ou en dessous, on laisse courir.** Deux paliers à 33 % sortent 66 % et laissent 34 % de\n * position ouverte — c'est une sortie partielle voulue, et la compléter d'office la trahirait. Le\n * dernier palier prend alors sa part nominale, sans plus.\n *\n * Les paliers intermédiaires s'arrondissent au **plancher**, jamais au plus proche : arrondir au\n * plus proche ferait dépasser la somme, et c'est le dernier leg — celui qui clôture — que la venue\n * rejetterait.\n *\n * **LE STOP COUVRE TOUJOURS LA TAILLE PLEINE.** Un stop partiel laisserait une fraction de position\n * sans protection, ce qui est précisément ce qu'on veut rendre impossible : jamais une position\n * nue, même partiellement.\n *\n * Deux cas font disparaître un leg, et c'est voulu :\n * · une part à 0 sur un palier intermédiaire — c'est un marqueur, pas un ordre ;\n * · une taille qui tombe à 0 après arrondi — un ordre de taille nulle serait refusé.\n * Le dernier palier, lui, survit toujours : c'est lui qui ferme.\n */\nexport function buildProtection(input: IProtectionInput): IProtection {\n const slippage = input.slippagePct ?? SLIPPAGE_DEFAUT;\n const long = input.direction === 'long';\n const { sl, tps: niveaux } = buildLevels(\n input.entry,\n input.direction,\n input.slPct,\n input.tps.map((tp) => tp.pct),\n );\n // La borne d'exécution : sous le trigger en long, au-dessus en short.\n const borne = (trigger: number): number =>\n long === true ? trigger * (1 - slippage) : trigger * (1 + slippage);\n\n // LA SOMME DES PARTS DÉCIDE DE CE QUE FAIT LE DERNIER PALIER. AU-DESSUS de 80 %, il clôture : le\n // reliquat lui revient. À 80 % ou en dessous, il s'en tient à sa part et le reste court.\n const sommeParts = input.tps.reduce((total, tp) => total + tp.part, 0);\n const clotureIntegrale = sommeParts > SEUIL_CLOTURE;\n\n const legs: IProtectionLeg[] = [];\n let alloue = 0;\n niveaux.forEach((trigger, index) => {\n const dernier = index === niveaux.length - 1;\n const part = input.tps[index]?.part ?? 0;\n if (dernier === false && part <= 0) {\n return;\n }\n // Le dernier prend le RELIQUAT EXACT — mais seulement si l'ensemble vise la clôture.\n const size =\n dernier === true && clotureIntegrale === true\n ? roundToStep(input.size - alloue, input.lotSize, 'floor')\n : roundToStep(part * input.size, input.lotSize, 'floor');\n if (size <= 0) {\n return;\n }\n alloue += size;\n legs.push({\n triggerPrice: String(roundPrice(trigger, input.tickSize, input.lotSize)),\n size: String(size),\n price: String(roundPrice(borne(trigger), input.tickSize, input.lotSize)),\n });\n });\n\n return {\n sl: {\n triggerPrice: String(roundPrice(sl, input.tickSize, input.lotSize)),\n // Taille PLEINE, toujours : le stop couvre l'intégralité de la position.\n size: String(input.size),\n price: String(roundPrice(borne(sl), input.tickSize, input.lotSize)),\n },\n tps: legs,\n };\n}\n","import type { XgateEx } from '../enums/xgate-ex.enums';\nimport type { Direction } from '../helpers/protection';\n\n/** Côté d'un ordre. À ne pas confondre avec le SENS d'une position ({@link Direction}). */\nexport type Side = 'buy' | 'sell';\n\n/** Nature d'un ordre, telle qu'XGate la nomme. Le type natif exact reste dans `xtras`. */\nexport type OrderType =\n | 'limit'\n | 'market'\n | 'stop'\n | 'stopMarket'\n | 'takeProfit'\n | 'takeProfitMarket'\n | 'other';\n\n/**\n * ÉTAT D'UN ORDRE — **exactement les sept valeurs des SDK de venue**, orthographe comprise.\n *\n * `canceled` prend un seul L : c'est ce que rendent les huit SDK, unanimement. XGate écrivait\n * `cancelled` et perdait `partiallyFilled` et `expired` ; le `as OrderStatus` de la conversion\n * masquait l'écart au lieu de le signaler, si bien qu'un consommateur testant `=== 'cancelled'`\n * ne matchait **jamais** — un ordre annulé passait pour un ordre encore vivant.\n *\n * Seul `filled` a déplacé tout ce qui était demandé ; `partiallyFilled` en a déplacé une part et\n * garde le reste au carnet.\n */\nexport type OrderStatus =\n | 'open'\n | 'partiallyFilled'\n | 'filled'\n | 'canceled'\n | 'rejected'\n | 'expired'\n | 'other';\n\n/**\n * Les sept statuts, énumérés — pour VALIDER ce qu'une venue rend plutôt que de le caster.\n *\n * Un `as OrderStatus` sur une chaîne inconnue produit une valeur qui n'existe dans aucun `switch` et\n * que personne ne rattrape ; passer par cette liste fait retomber l'inconnu sur `other`, qui lui est\n * traité partout.\n */\nexport const ORDER_STATUSES: readonly OrderStatus[] = [\n 'open',\n 'partiallyFilled',\n 'filled',\n 'canceled',\n 'rejected',\n 'expired',\n 'other',\n];\n\n/** Durée de validité. `alo` = post-only : l'ordre est refusé s'il devait s'exécuter immédiatement. */\nexport type TimeInForce = 'gtc' | 'ioc' | 'fok' | 'alo';\n\n/**\n * UN ORDRE, tel qu'XGate le rend.\n *\n * `xex` dit d'où il vient : un identifiant d'ordre n'a de sens que chez la venue qui l'a émis, et\n * deux venues peuvent parfaitement produire le même.\n */\nexport interface IOrder {\n xex: XgateEx;\n /** Le symbole que la venue attend. */\n symbolXex: string;\n /** Type de marché. */\n kind: 'perp' | 'spot';\n /** L'identifiant de la venue. */\n id: string;\n /** L'identifiant fourni par l'appelant, quand il en a donné un. */\n clientId: string | null;\n side: Side;\n type: OrderType;\n /** `null` pour un ordre au marché. */\n price: string | null;\n /** Taille demandée, **en unités de base**. */\n size: string;\n /** Taille déjà exécutée, **en unités de base**. */\n filled: string;\n status: OrderStatus;\n tif: TimeInForce | null;\n /** L'ordre ne peut que réduire une position existante — jamais en ouvrir une. */\n reduceOnly: boolean | null;\n /** Moment où l'ordre a été soumis. */\n placedAt: Date;\n xtras?: Record<string, unknown>;\n}\n\n/**\n * UNE POSITION ouverte.\n *\n * `size` est **toujours positive** : le sens est porté par `side`. Mélanger les deux — une taille\n * négative pour un short — oblige chaque consommateur à se souvenir de la convention, et l'un d'eux\n * l'oubliera.\n */\nexport interface IPosition {\n xex: XgateEx;\n symbolXex: string;\n /** Nom canonique de l'actif, pour rapprocher la même position entre venues. */\n symbol: string;\n side: 'long' | 'short' | null;\n /** Taille **en unités de base**, toujours positive. */\n size: string;\n entryPrice: string | null;\n markPrice: string | null;\n unrealizedPnl: string | null;\n leverage: number | null;\n liquidationPrice: string | null;\n /** Marge immobilisée par la position. */\n margin: string | null;\n xtras?: Record<string, unknown>;\n}\n\n/** UNE EXÉCUTION du compte — ce qui a réellement été rempli, par opposition à ce qui a été demandé. */\nexport interface ITrade {\n xex: XgateEx;\n symbolXex: string;\n kind: 'perp' | 'spot';\n id: string;\n /** L'ordre dont provient cette exécution. */\n orderId: string;\n side: Side;\n price: string;\n size: string;\n fee: string;\n feeAsset: string | null;\n /** PnL réalisé, avant et après frais ; `null` quand la venue ne permet pas de l'établir. */\n grossPnl: string | null;\n netPnl: string | null;\n /** L'exécution était côté maker. `null` si la venue ne le dit pas. */\n maker: boolean | null;\n /** Moment de l'exécution. */\n filledAt: Date;\n xtras?: Record<string, unknown>;\n}\n\n/**\n * CE QU'ON DEMANDE POUR OUVRIR UNE POSITION PROTÉGÉE.\n *\n * **Les protections sont données en POURCENTAGES**, pas en prix : xgate calcule les niveaux depuis\n * le prix d'entrée. L'appelant n'a ni à arrondir au tick, ni à se souvenir que le stop d'un short\n * est au-dessus de l'entrée.\n *\n * **`slPct` est OBLIGATOIRE.** C'est la règle qui a motivé tout ce chemin : jamais un ordre sans\n * stop. Une position ouverte pendant que le serveur tombe reste protégée par la venue elle-même.\n */\nexport interface IEntryWithProtection {\n xex: XgateEx;\n /** Le symbole que la venue attend. */\n symbolXex: string;\n /** Sens de la POSITION à ouvrir. Le stop et les take-profits se posent au sens opposé. */\n direction: Direction;\n /** Taille à ouvrir, **en unités de base**. */\n size: number;\n /** Prix d'entrée visé. Sert de référence aux pourcentages, et de prix limite. */\n entry: number;\n /** Écart du stop au prix d'entrée, en fraction (0.02 = 2 %). Obligatoire. */\n slPct: number;\n /**\n * Les take-profits, dans l'ordre. `part` est la fraction de la position à sortir.\n *\n * Au-dessus de 80 % cumulés, le dernier absorbe le reliquat et la position ferme entièrement ;\n * à 80 % ou en dessous, le reste court.\n */\n tps: Array<{ pct: number; part: number }>;\n /** `ioc` pour entrer au marché (taker), `alo` pour poster (maker). Défaut : `ioc`. */\n tif?: TimeInForce;\n /** Identifiant applicatif, remonté tel quel par la venue quand elle le supporte. */\n clientId?: string;\n /** Pas de prix et de quantité de la paire — sans eux, la venue refuse des niveaux mal arrondis. */\n tickSize?: string | null;\n lotSize?: string | null;\n /** Écart entre déclenchement et borne d'exécution du déclenché. Défaut 0,5 %. */\n slippagePct?: number;\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport { XgateEx } from '../enums/xgate-ex.enums';\nimport { type Direction, buildProtection } from '../helpers/protection';\nimport { canonicalFromXex } from '../helpers/symbol-naming';\nimport { type IXexAccess, createAccountXex } from '../helpers/xex-registry';\nimport { ORDER_STATUSES } from '../interfaces/trading.interfaces';\nimport type {\n IEntryWithProtection,\n IOrder,\n IPosition,\n ITrade,\n OrderStatus,\n OrderType,\n Side,\n TimeInForce,\n} from '../interfaces/trading.interfaces';\n\n/** Une venue et de quoi y agir. */\nexport interface ITradingAccess extends IXexAccess {\n xex: XgateEx;\n}\n\n/**\n * LES VENUES OÙ L'OUVERTURE PROTÉGÉE A ÉTÉ PROUVÉE EN RÉEL — pas déduite du code, exécutée.\n *\n * Une venue n'entre ici qu'après un cycle complet observé sur son testnet : le lot part, la venue\n * répond, et le carnet montre l'entrée AVEC son stop. Tant que la preuve manque, ouvrir une position\n * par ce service reviendrait à parier qu'un chemin jamais exercé protège de l'argent réel.\n *\n * **aster en est absent, et c'est un constat mesuré** : son `/fapi/v3/batchOrders` coupe à ~3,26 s\n * (constante, quel que soit le nombre de legs — un lot invalide est pourtant rejeté en 264 ms) en\n * répondant « The request has timed out. », alors que la venue a DÉJÀ créé une partie des ordres.\n * Deux exécutions identiques ont produit deux sous-ensembles différents : une fois le stop et un\n * take-profit, une fois l'entrée seule. Chaque leg s'écrit on-chain (`newChainData.hash`), d'où le\n * dépassement. Un statut inconnu sur une ouverture protégée, c'est exactement la position nue que ce\n * service existe pour rendre impossible — donc on refuse d'ouvrir plutôt que d'ouvrir peut-être.\n */\nconst VENUES_PROUVEES: readonly XgateEx[] = [XgateEx.Pacifica, XgateEx.Hyperliquid];\n\n/**\n * LE TRADING : positions, ordres et exécutions — un seul service, et c'est délibéré.\n *\n * Les séparer laisserait diverger trois vues du même état : une position n'est que la somme des\n * ordres exécutés, et un ordre ne se lit pas sans savoir ce qu'il a ouvert.\n *\n * **JAMAIS UN ORDRE SANS STOP.** `openWithProtection` est la seule façon d'ouvrir une position par\n * ce service, et elle exige un `slPct`. La protection part avec l'entrée, en un geste que la venue\n * traite atomiquement : si l'entrée ne remplit pas, la protection ne naît jamais — aucun orphelin.\n * Poser le stop *après* l'entrée laisse une fenêtre pendant laquelle la position est nue, et cette\n * fenêtre s'ouvre précisément quand le serveur tombe.\n *\n * **L'appelant donne des POURCENTAGES**, jamais des prix : xgate calcule les niveaux, arrondit à la\n * grille de la paire, et garantit qu'au-dessus de 80 % de parts cumulées la position ferme\n * entièrement — sans reliquat.\n */\n@Injectable()\nexport class TradingService {\n private readonly logger = new Logger(TradingService.name);\n\n /**\n * OUVRE UNE POSITION AVEC SA PROTECTION, en un geste atomique.\n *\n * Le premier ordre rendu est **l'entrée** ; les suivants sont le stop et les take-profits, dans\n * l'ordre où ils ont été posés.\n *\n * Chaque venue applique son mécanisme natif — hyperliquid groupe les enfants sous l'entrée et les\n * annule lui-même si elle rate, pacifica les embarque dans l'ordre, aster envoie un lot de\n * conditionnels. Le SDK de la venue s'en charge ; xgate fournit des niveaux justes.\n */\n public async openWithProtection(\n access: ITradingAccess,\n input: IEntryWithProtection,\n ): Promise<IOrder[]> {\n if (VENUES_PROUVEES.includes(access.xex) === false) {\n throw new Error(\n `openWithProtection(${access.xex}) : l'ouverture protégée n'a pas été prouvée en réel sur cette venue. Seules ${VENUES_PROUVEES.join(', ')} le sont. Ouvrir ici reviendrait à parier qu'un chemin jamais exercé pose bien le stop.`,\n );\n }\n const protection = buildProtection({\n entry: input.entry,\n direction: input.direction,\n size: input.size,\n slPct: input.slPct,\n tps: input.tps,\n tickSize: input.tickSize,\n lotSize: input.lotSize,\n slippagePct: input.slippagePct,\n });\n // Le sens de l'ENTRÉE suit la position ; le SDK de la venue pose stop et take-profits au sens\n // opposé — c'est le contrat commun, et le lui rappeler ici le contredirait.\n const side: Side = input.direction === 'long' ? 'buy' : 'sell';\n const perp = this.perpOf(access);\n if (typeof perp.createEntryWithProtection !== 'function') {\n throw new Error(\n `openWithProtection(${access.xex}) : cette venue n'expose pas l'ouverture protégée atomique.`,\n );\n }\n const orders = await perp.createEntryWithProtection(\n {\n name: input.symbolXex,\n side,\n type: 'limit',\n size: String(input.size),\n price: String(input.entry),\n tif: input.tif ?? 'ioc',\n reduceOnly: false,\n clientId: input.clientId,\n },\n {\n name: input.symbolXex,\n side,\n sl: protection.sl,\n tps: protection.tps,\n clientId: input.clientId,\n },\n );\n return orders.map((order) => this.toOrder(order, access.xex));\n }\n\n /** Les positions ouvertes d'un compte. */\n public async positions(access: ITradingAccess, symbolXex?: string): Promise<IPosition[]> {\n const perp = this.perpOf(access);\n // On ne passe la requête QUE s'il y a un symbole : un `{ name: undefined }` se sérialise en\n // `instId=undefined` chez blofin, qui répond « Parameter instId error » — un filtre vide n'est\n // pas la même chose qu'aucun filtre.\n const positions = await perp.getPositions(\n symbolXex === undefined ? undefined : { name: symbolXex },\n );\n return positions.map((position) => ({\n xex: access.xex,\n symbolXex: position.name,\n symbol: canonicalFromXex(position.name, access.xex),\n side: position.side,\n size: position.size,\n entryPrice: position.entryPrice,\n markPrice: position.markPrice,\n unrealizedPnl: position.unrealizedPnl,\n leverage: position.leverage,\n liquidationPrice: position.liquidationPrice,\n margin: position.margin,\n xtras: position.xtras,\n }));\n }\n\n /** Les ordres encore au carnet. */\n public async openOrders(access: ITradingAccess): Promise<IOrder[]> {\n const orders = await this.perpOf(access).getOpens();\n return orders.map((order) => this.toOrder(order, access.xex));\n }\n\n /**\n * Les exécutions du compte — ce qui a réellement été rempli.\n *\n * À ne pas confondre avec les ordres : un ordre annoncé `filled` peut avoir été rempli en\n * plusieurs fois, à des prix différents. C'est ici que se lit le prix moyen réel.\n */\n public async trades(access: ITradingAccess): Promise<ITrade[]> {\n const trades = await this.perpOf(access).getUserTrades();\n return trades.map((trade) => ({\n xex: access.xex,\n symbolXex: trade.name,\n kind: trade.kind as 'perp' | 'spot',\n id: trade.id,\n orderId: trade.orderId,\n side: trade.side as Side,\n price: trade.price,\n size: trade.size,\n fee: trade.fee,\n feeAsset: trade.feeAsset,\n grossPnl: trade.grossPnl,\n netPnl: trade.netPnl,\n maker: trade.maker,\n filledAt: trade.filledAt,\n xtras: trade.xtras,\n }));\n }\n\n /**\n * FERME UNE POSITION ENTIÈREMENT, sur la taille que **la venue** déclare — jamais celle qu'on croit.\n *\n * C'est toute la différence : fermer la taille planifiée laisse un reliquat quand le fill a différé du\n * plan (une entrée partiellement remplie, un take-profit passé entre-temps), et ce reliquat reste\n * ouvert, sans stop, à la charge du compte. On relit donc la position juste avant de la fermer.\n *\n * Rend `null` quand il n'y a rien à fermer — un appel sur une position déjà close n'est pas une erreur.\n */\n public async closePosition(access: ITradingAccess, symbolXex: string): Promise<IOrder | null> {\n const perp = this.perpOf(access);\n const positions = await perp.getPositions({ name: symbolXex });\n const position = positions.find(\n (candidate) => candidate.name === symbolXex && Number(candidate.size) !== 0,\n );\n let closed: IOrder | null = null;\n if (position !== undefined) {\n // `side` porte le sens de la POSITION ; on sort au sens opposé, en `reduceOnly` pour qu'un\n // décalage de taille ne puisse jamais ouvrir une position inverse.\n const exit: Side = position.side === 'long' ? 'sell' : 'buy';\n const order = await perp.place({\n name: symbolXex,\n side: exit,\n type: 'market',\n size: position.size,\n reduceOnly: true,\n });\n closed = this.toOrder(order, access.xex);\n this.logger.log(\n `closePosition(${access.xex}/${symbolXex}) : ${position.size} fermé au marché`,\n );\n }\n return closed;\n }\n\n /**\n * LE PRIX DE SORTIE RÉEL d'une position, lu dans l'historique de la venue.\n *\n * Le prix planifié ne dit pas à quel prix on est sorti : un stop se déclenche au marché, un\n * take-profit peut remplir en plusieurs fois. Seul l'historique porte le prix réellement obtenu, et\n * c'est lui qui doit servir au calcul du résultat — sinon le PnL affiché est une fiction.\n *\n * On retient le **dernier** ordre de sortie rempli après `openedAt` : les précédents sont les\n * take-profits partiels, celui-là est la clôture.\n */\n public async exitPrice(\n access: ITradingAccess,\n symbolXex: string,\n direction: Direction,\n openedAt: Date,\n ): Promise<string | null> {\n const exit: Side = direction === 'long' ? 'sell' : 'buy';\n const history = await this.perpOf(access).getHistory({ name: symbolXex });\n const exits = history\n .filter((order) => order.name === symbolXex && order.side === exit)\n .filter((order) => order.status === 'filled' || order.status === 'partiallyFilled')\n .filter((order) => order.placedAt.getTime() > openedAt.getTime())\n .filter((order) => order.price !== null && Number(order.price) > 0)\n .sort((left, right) => right.placedAt.getTime() - left.placedAt.getTime());\n const last = exits[0];\n return last === undefined ? null : last.price;\n }\n\n /** Annule un ordre par son identifiant de venue. */\n public async cancel(access: ITradingAccess, symbolXex: string, orderId: string): Promise<void> {\n await this.perpOf(access).cancel({ name: symbolXex, id: orderId });\n }\n\n /** Le scope `perp()` de la venue, typé au plus près de ce que le contrat commun garantit. */\n private perpOf(access: ITradingAccess): {\n getPositions(query?: { name?: string }): Promise<IXexPosition[]>;\n getOpens(): Promise<IXexOrder[]>;\n getHistory(query?: { name?: string }): Promise<IXexOrder[]>;\n getUserTrades(): Promise<IXexTrade[]>;\n place(input: {\n name: string;\n side: Side;\n type: string;\n size: string;\n reduceOnly: boolean;\n }): Promise<IXexOrder>;\n cancel(input: { name: string; id: string }): Promise<unknown>;\n createEntryWithProtection?(entry: unknown, protection: unknown): Promise<IXexOrder[]>;\n } {\n const source = createAccountXex(access.xex, access) as unknown as {\n perp(): ReturnType<TradingService['perpOf']>;\n };\n return source.perp();\n }\n\n /**\n * Le statut de la venue, VALIDÉ — jamais casté. Une venue qui inventerait un statut rendrait sinon\n * une valeur absente de tous les `switch` en aval, et personne ne la rattraperait.\n */\n private toStatus(status: string): OrderStatus {\n return ORDER_STATUSES.includes(status as OrderStatus) === true\n ? (status as OrderStatus)\n : 'other';\n }\n\n private toOrder(order: IXexOrder, xex: XgateEx): IOrder {\n return {\n xex,\n symbolXex: order.name,\n kind: order.kind as 'perp' | 'spot',\n id: order.id,\n clientId: order.clientId,\n side: order.side as Side,\n type: order.type as OrderType,\n price: order.price,\n size: order.size,\n filled: order.filled,\n status: this.toStatus(order.status),\n tif: order.tif as TimeInForce | null,\n reduceOnly: order.reduceOnly,\n placedAt: order.placedAt,\n xtras: order.xtras,\n };\n }\n}\n\n/** Un ordre tel qu'un SDK de venue le rend : **clés longues et `Date`**. */\ninterface IXexOrder {\n name: string;\n kind: string;\n id: string;\n clientId: string | null;\n side: string;\n type: string;\n price: string | null;\n size: string;\n filled: string;\n status: string;\n tif: string | null;\n reduceOnly: boolean | null;\n placedAt: Date;\n xtras?: Record<string, unknown>;\n}\n\n/** Une position telle qu'un SDK de venue la rend. */\ninterface IXexPosition {\n name: string;\n side: 'long' | 'short' | null;\n size: string;\n entryPrice: string | null;\n markPrice: string | null;\n unrealizedPnl: string | null;\n leverage: number | null;\n liquidationPrice: string | null;\n margin: string | null;\n xtras?: Record<string, unknown>;\n}\n\n/** Une exécution telle qu'un SDK de venue la rend. */\ninterface IXexTrade {\n name: string;\n kind: string;\n id: string;\n orderId: string;\n side: string;\n price: string;\n size: string;\n fee: string;\n feeAsset: string | null;\n grossPnl: string | null;\n netPnl: string | null;\n maker: boolean | null;\n filledAt: Date;\n xtras?: Record<string, unknown>;\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport { type IXexAccess, WALLET_XEXES, createAccountXex } from '../helpers/xex-registry';\nimport type {\n IBalance,\n IEquityPoint,\n IMovement,\n MovementKind,\n MovementStatus,\n} from '../interfaces/wallet.interfaces';\n\n/** Une venue et de quoi y lire un compte. */\nexport interface IWalletAccess extends IXexAccess {\n xex: XgateEx;\n}\n\n/**\n * LE PORTEFEUILLE, toutes venues confondues : soldes, mouvements d'argent, valeur du compte.\n *\n * **Les positions n'en font pas partie** — elles appartiennent au bloc trading, indissociable des\n * ordres. Un portefeuille répond à « qu'est-ce que j'ai et d'où ça vient » ; une position répond à\n * « qu'est-ce que je risque », et les deux ne se lisent pas au même moment.\n *\n * **Ce service ne calcule rien.** Il n'additionne pas les soldes entre venues, ne convertit aucun\n * montant, ne reconstitue aucune courbe d'équité là où la venue n'en publie pas. Chaque ligne porte\n * son `xex` : c'est à l'appelant de décider ce qui s'additionne, parce que lui seul sait si deux\n * USDC à deux endroits sont fongibles pour son usage.\n *\n * **Lire un portefeuille est PUBLIC sur les DEX** : l'adresse suffit, aucune signature n'est\n * produite. Seuls les CEX exigent une clé, même en lecture.\n */\n@Injectable()\nexport class WalletService {\n private readonly logger = new Logger(WalletService.name);\n\n /** Les venues dont le portefeuille est lisible aujourd'hui. */\n public venues(): readonly XgateEx[] {\n return WALLET_XEXES;\n }\n\n /**\n * Les soldes d'un ou plusieurs comptes.\n *\n * Même politique d'échec que les autres services : sur un seul accès, l'échec est relancé ; sur\n * plusieurs, la liste partielle garde sa valeur et l'incident est journalisé. Rendre `[]` en\n * silence ferait passer une venue injoignable pour un compte vide — et un compte vide, ça se\n * regarde autrement qu'une panne.\n */\n public async balances(...accesses: IWalletAccess[]): Promise<IBalance[]> {\n return this.collect(accesses, 'balances', async (access) => {\n const balances = await createAccountXex(access.xex, access).account().getBalances();\n return balances.map((balance) => ({\n xex: access.xex,\n asset: balance.asset,\n total: balance.total,\n available: balance.available,\n usdValue: balance.usdValue,\n xtras: balance.xtras,\n }));\n });\n }\n\n /**\n * Les mouvements d'argent, triés du plus ancien au plus récent, montants **signés**.\n *\n * **Lève si la venue ne les sert pas** plutôt que de rendre une liste vide : « cette venue\n * n'expose pas son historique » et « ce compte n'a jamais bougé » appellent des suites très\n * différentes.\n */\n public async movements(...accesses: IWalletAccess[]): Promise<IMovement[]> {\n const collected = await this.collect(accesses, 'movements', async (access) => {\n const account = createAccountXex(access.xex, access).account();\n if (account.getMovements === undefined) {\n throw new Error(`movements(${access.xex}) : cette venue n'expose pas ses mouvements.`);\n }\n const movements = await account.getMovements();\n return movements.map((movement) => ({\n xex: access.xex,\n kind: movement.kind as MovementKind,\n amount: movement.amount,\n fee: movement.fee,\n asset: movement.asset,\n occurredAt: movement.occurredAt,\n status: movement.status as MovementStatus,\n externalId: movement.externalId,\n xtras: movement.xtras,\n }));\n });\n return collected.sort((left, right) => left.occurredAt.getTime() - right.occurredAt.getTime());\n }\n\n /**\n * La valeur du compte dans le temps.\n *\n * **Seules hyperliquid et pacifica la publient**, et XGate ne la fabrique pas ailleurs :\n * reconstituer une courbe depuis les mouvements et les prix donnerait une valeur qu'aucune venue\n * n'a cotée, avec une méthode incomparable à celle des deux qui la publient. Lève sur une venue\n * qui n'en sert pas.\n */\n public async equityHistory(...accesses: IWalletAccess[]): Promise<IEquityPoint[]> {\n const collected = await this.collect(accesses, 'equityHistory', async (access) => {\n const account = createAccountXex(access.xex, access).account();\n if (account.getEquityHistory === undefined) {\n throw new Error(\n `equityHistory(${access.xex}) : cette venue ne publie pas la valeur de compte dans le temps.`,\n );\n }\n const points = await account.getEquityHistory();\n return points.map((point) => ({\n xex: access.xex,\n measuredAt: point.measuredAt,\n equity: point.equity,\n }));\n });\n return collected.sort((left, right) => left.measuredAt.getTime() - right.measuredAt.getTime());\n }\n\n /** Le motif commun aux trois lectures : un accès seul relance, plusieurs journalisent. */\n private async collect<T>(\n accesses: IWalletAccess[],\n capacite: string,\n lire: (access: IWalletAccess) => Promise<T[]>,\n ): Promise<T[]> {\n if (accesses.length === 0) {\n throw new Error(`${capacite}() : aucun accès fourni — précise au moins une venue.`);\n }\n const solo = accesses.length === 1;\n const collected = await Promise.all(\n accesses.map(async (access) => {\n try {\n return await lire(access);\n } catch (error: unknown) {\n if (solo === true) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`${capacite}(${access.xex}) a échoué, venue ignorée : ${message}`);\n return [];\n }\n }),\n );\n return collected.flat();\n }\n}\n","import { Injectable, Logger } from '@nestjs/common';\nimport type { Timeframe } from '../enums/timeframe.enums';\nimport type { XgateEx } from '../enums/xgate-ex.enums';\nimport { toCandle } from '../helpers/candle-mapper';\nimport { createPublicXex } from '../helpers/xex-registry';\nimport type { ICandle } from '../interfaces/candle.interfaces';\n\n/** Couper le flux. */\nexport type Unsubscribe = () => void;\n\n/**\n * LES BOUGIES EN TEMPS RÉEL — le présent.\n *\n * Séparé de `CandlesService` à dessein : rejouer un historique et suivre un flux ne se\n * consomment pas de la même façon, et n'ont pas les mêmes coûts. Une application qui ne fait que\n * du backfill n'a aucune socket à ouvrir.\n */\n@Injectable()\nexport class WsCandlesService {\n private readonly logger = new Logger(WsCandlesService.name);\n\n /**\n * Souscrit aux bougies d'un marché chez une venue. Rend la fonction de désabonnement.\n *\n * Le handler reçoit **une bougie à la fois**, au format unifié — c'est la forme commune aux neuf\n * SDK. La bougie en cours est repoussée à chaque mise à jour tant qu'elle n'est pas close ;\n * `closedAt` dit jusqu'où elle porte.\n *\n * **Attention à extended** : sur un intervalle qu'elle ne connaît pas, sa façade rend un\n * désabonnement vide au lieu de lever. L'appelant se croit abonné et n'entend jamais rien — un\n * silence indiscernable d'un marché sans échange. C'est un défaut du SDK, pas d'XGate, et il est\n * dans la file des corrections.\n */\n public subscribe(\n xex: XgateEx,\n query: { symbol: string; interval: Timeframe },\n handler: (candle: ICandle) => void,\n ): Unsubscribe {\n const quote = this.quoteOf(query.symbol);\n this.logger.log(`souscription bougies ${query.symbol} ${query.interval} sur ${xex}`);\n return createPublicXex(xex)\n .ws()\n .subscribeCandles({ name: query.symbol, interval: query.interval }, (wire) => {\n handler(toCandle(wire, xex, query.interval, quote));\n });\n }\n\n /**\n * Souscrit au MÊME marché chez plusieurs venues, avec un seul handler.\n *\n * Chaque bougie porte son `xex` : l'appelant sait toujours qui parle. La fonction rendue coupe\n * tous les flux d'un coup — sinon il faudrait en garder N et n'en oublier aucun.\n */\n public subscribeAll(\n xexes: XgateEx[],\n query: { symbol: string; interval: Timeframe },\n handler: (candle: ICandle) => void,\n ): Unsubscribe {\n if (xexes.length === 0) {\n throw new Error('subscribeAll() : aucune venue demandée — précise au moins un XgateEx.');\n }\n const stops: Unsubscribe[] = [];\n for (const xex of xexes) {\n try {\n stops.push(this.subscribe(xex, query, handler));\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`souscription ${xex} refusée, venue ignorée : ${message}`);\n }\n }\n return () => {\n for (const stop of stops) {\n stop();\n }\n };\n }\n\n /** Voir `CandlesService.quoteOf` : la cotation se déduit du symbole natif, faute d'être publiée. */\n private quoteOf(symbolXex: string): string {\n const parts = symbolXex.split('-');\n const last = parts[parts.length - 1];\n return parts.length > 1 && last !== undefined ? last : '';\n }\n}\n","import { Module } from '@nestjs/common';\nimport { CandlesService } from './services/candles.service';\nimport { CatalogService } from './services/catalog.service';\nimport { PricesService } from './services/prices.service';\nimport { SpotCatalogService } from './services/spot-catalog.service';\nimport { TradingService } from './services/trading.service';\nimport { WalletService } from './services/wallet.service';\nimport { WsCandlesService } from './services/ws-candles.service';\n\n/**\n * Le module d'XGate : `imports: [XgateModule]`, puis on injecte le SEUL service dont on a besoin.\n *\n * Une capacité = un service, indépendant des autres. Le REST et le temps réel sont eux-mêmes\n * séparés (`CandlesService` / `WsCandlesService`) : rejouer le passé et suivre le présent ne se\n * consomment pas de la même façon, et une application qui ne fait que du backfill n'a aucune\n * socket à ouvrir.\n */\n@Module({\n providers: [\n CatalogService,\n CandlesService,\n WsCandlesService,\n PricesService,\n SpotCatalogService,\n WalletService,\n ],\n exports: [\n CatalogService,\n CandlesService,\n WsCandlesService,\n PricesService,\n SpotCatalogService,\n WalletService,\n ],\n})\nexport class XgateModule {}\n"]}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@blackcube/xgate-sdk",
3
+ "version": "0.20.0",
4
+ "description": "NestJS gateway over the Blackcube exchange SDKs: one injectable service, one unified catalog across CEX and DEX venues",
5
+ "license": "BSD-3-Clause",
6
+ "author": "Blackcube (https://blackcube.io)",
7
+ "keywords": ["xgate", "nestjs", "exchange", "cex", "dex", "perp", "perpetuals", "trading", "sdk"],
8
+ "type": "module",
9
+ "main": "./dist/index.cjs",
10
+ "module": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js",
16
+ "require": "./dist/index.cjs"
17
+ }
18
+ },
19
+ "sideEffects": false,
20
+ "files": ["dist", "doc", "README.md", "LICENSE"],
21
+ "engines": {
22
+ "node": ">=22"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "scripts": {
28
+ "build": "tsup",
29
+ "dev": "tsup --watch",
30
+ "test": "vitest run",
31
+ "typecheck": "tsc --noEmit",
32
+ "lint": "biome check .",
33
+ "prepublishOnly": "pnpm run lint && pnpm run typecheck && pnpm run build"
34
+ },
35
+ "dependencies": {
36
+ "@blackcube/aster-sdk": "^0.20.0",
37
+ "@blackcube/binance-sdk": "^0.20.0",
38
+ "@blackcube/blofin-sdk": "^0.20.0",
39
+ "@blackcube/bullet-sdk": "^0.55.0",
40
+ "@blackcube/bybit-sdk": "^0.20.0",
41
+ "@blackcube/extended-sdk": "^0.20.0",
42
+ "@blackcube/hyperliquid-sdk": "^0.20.0",
43
+ "@blackcube/lighter-sdk": "^0.20.0",
44
+ "@blackcube/pacifica-sdk": "^0.20.0",
45
+ "@blackcube/paradex-sdk": "^0.20.0",
46
+ "@nestjs/common": "^11.0.1"
47
+ },
48
+ "devDependencies": {
49
+ "@biomejs/biome": "^1.9.4",
50
+ "@types/node": "^22.10.2",
51
+ "reflect-metadata": "^0.2.2",
52
+ "rxjs": "^7.8.1",
53
+ "tsup": "^8.3.5",
54
+ "typescript": "^5.7.2",
55
+ "vitest": "^2.1.8"
56
+ }
57
+ }