@metamask-previews/perps-controller 10.0.0-preview-a42e8d0d2 → 10.0.0-preview-5a03e1b92

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.
Files changed (106) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/dist/constants/eventNames.cjs +6 -0
  3. package/dist/constants/eventNames.cjs.map +1 -1
  4. package/dist/constants/eventNames.d.cts +4 -0
  5. package/dist/constants/eventNames.d.cts.map +1 -1
  6. package/dist/constants/eventNames.d.mts +4 -0
  7. package/dist/constants/eventNames.d.mts.map +1 -1
  8. package/dist/constants/eventNames.mjs +6 -0
  9. package/dist/constants/eventNames.mjs.map +1 -1
  10. package/dist/index.cjs +86 -74
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +3 -1
  13. package/dist/index.d.cts.map +1 -1
  14. package/dist/index.d.mts +3 -1
  15. package/dist/index.d.mts.map +1 -1
  16. package/dist/index.mjs +2 -0
  17. package/dist/index.mjs.map +1 -1
  18. package/dist/perpsErrorCodes.cjs +16 -0
  19. package/dist/perpsErrorCodes.cjs.map +1 -1
  20. package/dist/perpsErrorCodes.d.cts +12 -0
  21. package/dist/perpsErrorCodes.d.cts.map +1 -1
  22. package/dist/perpsErrorCodes.d.mts +12 -0
  23. package/dist/perpsErrorCodes.d.mts.map +1 -1
  24. package/dist/perpsErrorCodes.mjs +16 -0
  25. package/dist/perpsErrorCodes.mjs.map +1 -1
  26. package/dist/providers/HyperLiquidProvider.cjs +674 -77
  27. package/dist/providers/HyperLiquidProvider.cjs.map +1 -1
  28. package/dist/providers/HyperLiquidProvider.d.cts +13 -0
  29. package/dist/providers/HyperLiquidProvider.d.cts.map +1 -1
  30. package/dist/providers/HyperLiquidProvider.d.mts +13 -0
  31. package/dist/providers/HyperLiquidProvider.d.mts.map +1 -1
  32. package/dist/providers/HyperLiquidProvider.mjs +676 -79
  33. package/dist/providers/HyperLiquidProvider.mjs.map +1 -1
  34. package/dist/selectors.cjs.map +1 -1
  35. package/dist/selectors.d.cts +17 -17
  36. package/dist/selectors.d.cts.map +1 -1
  37. package/dist/selectors.d.mts +17 -17
  38. package/dist/selectors.d.mts.map +1 -1
  39. package/dist/selectors.mjs.map +1 -1
  40. package/dist/services/HyperLiquidSubscriptionService.cjs +121 -11
  41. package/dist/services/HyperLiquidSubscriptionService.cjs.map +1 -1
  42. package/dist/services/HyperLiquidSubscriptionService.d.cts +21 -0
  43. package/dist/services/HyperLiquidSubscriptionService.d.cts.map +1 -1
  44. package/dist/services/HyperLiquidSubscriptionService.d.mts +21 -0
  45. package/dist/services/HyperLiquidSubscriptionService.d.mts.map +1 -1
  46. package/dist/services/HyperLiquidSubscriptionService.mjs +121 -11
  47. package/dist/services/HyperLiquidSubscriptionService.mjs.map +1 -1
  48. package/dist/services/TradingService.cjs +6 -2
  49. package/dist/services/TradingService.cjs.map +1 -1
  50. package/dist/services/TradingService.d.cts.map +1 -1
  51. package/dist/services/TradingService.d.mts.map +1 -1
  52. package/dist/services/TradingService.mjs +6 -2
  53. package/dist/services/TradingService.mjs.map +1 -1
  54. package/dist/types/index.cjs.map +1 -1
  55. package/dist/types/index.d.cts +69 -4
  56. package/dist/types/index.d.cts.map +1 -1
  57. package/dist/types/index.d.mts +69 -4
  58. package/dist/types/index.d.mts.map +1 -1
  59. package/dist/types/index.mjs.map +1 -1
  60. package/dist/types/perps-types.cjs.map +1 -1
  61. package/dist/types/perps-types.d.cts +35 -1
  62. package/dist/types/perps-types.d.cts.map +1 -1
  63. package/dist/types/perps-types.d.mts +35 -1
  64. package/dist/types/perps-types.d.mts.map +1 -1
  65. package/dist/types/perps-types.mjs.map +1 -1
  66. package/dist/utils/hyperLiquidAdapter.cjs +168 -10
  67. package/dist/utils/hyperLiquidAdapter.cjs.map +1 -1
  68. package/dist/utils/hyperLiquidAdapter.d.cts +35 -1
  69. package/dist/utils/hyperLiquidAdapter.d.cts.map +1 -1
  70. package/dist/utils/hyperLiquidAdapter.d.mts +35 -1
  71. package/dist/utils/hyperLiquidAdapter.d.mts.map +1 -1
  72. package/dist/utils/hyperLiquidAdapter.mjs +166 -11
  73. package/dist/utils/hyperLiquidAdapter.mjs.map +1 -1
  74. package/dist/utils/hyperLiquidValidation.cjs +160 -5
  75. package/dist/utils/hyperLiquidValidation.cjs.map +1 -1
  76. package/dist/utils/hyperLiquidValidation.d.cts +23 -4
  77. package/dist/utils/hyperLiquidValidation.d.cts.map +1 -1
  78. package/dist/utils/hyperLiquidValidation.d.mts +23 -4
  79. package/dist/utils/hyperLiquidValidation.d.mts.map +1 -1
  80. package/dist/utils/hyperLiquidValidation.mjs +160 -5
  81. package/dist/utils/hyperLiquidValidation.mjs.map +1 -1
  82. package/dist/utils/index.cjs +5 -1
  83. package/dist/utils/index.cjs.map +1 -1
  84. package/dist/utils/index.d.cts +2 -1
  85. package/dist/utils/index.d.cts.map +1 -1
  86. package/dist/utils/index.d.mts +2 -1
  87. package/dist/utils/index.d.mts.map +1 -1
  88. package/dist/utils/index.mjs +2 -1
  89. package/dist/utils/index.mjs.map +1 -1
  90. package/dist/utils/orderCalculations.cjs +363 -37
  91. package/dist/utils/orderCalculations.cjs.map +1 -1
  92. package/dist/utils/orderCalculations.d.cts +87 -2
  93. package/dist/utils/orderCalculations.d.cts.map +1 -1
  94. package/dist/utils/orderCalculations.d.mts +87 -2
  95. package/dist/utils/orderCalculations.d.mts.map +1 -1
  96. package/dist/utils/orderCalculations.mjs +359 -36
  97. package/dist/utils/orderCalculations.mjs.map +1 -1
  98. package/dist/utils/orderTypes.cjs +222 -0
  99. package/dist/utils/orderTypes.cjs.map +1 -0
  100. package/dist/utils/orderTypes.d.cts +114 -0
  101. package/dist/utils/orderTypes.d.cts.map +1 -0
  102. package/dist/utils/orderTypes.d.mts +114 -0
  103. package/dist/utils/orderTypes.d.mts.map +1 -0
  104. package/dist/utils/orderTypes.mjs +210 -0
  105. package/dist/utils/orderTypes.mjs.map +1 -0
  106. package/package.json +7 -6
@@ -23,6 +23,7 @@ const errorUtils_js_1 = require("../utils/errorUtils.cjs");
23
23
  const hyperLiquidAdapter_js_1 = require("../utils/hyperLiquidAdapter.cjs");
24
24
  const hyperLiquidOrderBookProcessor_js_1 = require("../utils/hyperLiquidOrderBookProcessor.cjs");
25
25
  const marketDataTransform_js_1 = require("../utils/marketDataTransform.cjs");
26
+ const orderTypes_js_1 = require("../utils/orderTypes.cjs");
26
27
  /**
27
28
  * Service for managing HyperLiquid WebSocket subscriptions
28
29
  * Implements singleton subscription architecture with reference counting
@@ -604,6 +605,29 @@ class HyperLiquidSubscriptionService {
604
605
  isPositionsCacheInitialized() {
605
606
  return __classPrivateFieldGet(this, _HyperLiquidSubscriptionService_positionsCacheInitialized, "f");
606
607
  }
608
+ /**
609
+ * Get the cached positions for one DEX, or null when that DEX has published
610
+ * none this session.
611
+ *
612
+ * A DEX only enters this map once its `clearinghouseState` subscription has
613
+ * published, so `null` means the absence of a symbol proves nothing about
614
+ * whether a position exists there.
615
+ *
616
+ * Prefer this over `getCachedPositions()` when a decision depends on whether a
617
+ * specific symbol is absent. The aggregate is only rebuilt once *every*
618
+ * expected DEX has published (`#aggregateAndNotifySubscribers`), so after a
619
+ * reconnect — which resets `#initializedDexs` without clearing these caches —
620
+ * the aggregate can sit frozen at its pre-reconnect contents while this map
621
+ * keeps receiving per-DEX updates. Deciding "covered" from this map and then
622
+ * reading the symbol from the aggregate would mix a fresh answer with stale
623
+ * data.
624
+ *
625
+ * @param dexName - DEX identifier, or '' for the main DEX.
626
+ * @returns That DEX's cached positions, or null if it has not published.
627
+ */
628
+ getCachedPositionsForDex(dexName) {
629
+ return __classPrivateFieldGet(this, _HyperLiquidSubscriptionService_dexPositionsCache, "f").get(dexName) ?? null;
630
+ }
607
631
  /**
608
632
  * Get cached positions from WebSocket subscription
609
633
  *
@@ -1169,7 +1193,11 @@ async function _HyperLiquidSubscriptionService_waitForDexDiscovery(timeoutMs = 5
1169
1193
  return '0';
1170
1194
  }
1171
1195
  return positions
1172
- .map((pos) => `${pos.symbol}:${pos.size}:${pos.entryPrice}:${pos.leverage.value}:${pos.takeProfitPrice ?? ''}:${pos.stopLossPrice ?? ''}:${pos.takeProfitCount}:${pos.stopLossCount}:${pos.unrealizedPnl}:${pos.returnOnEquity}:${pos.liquidationPrice ?? ''}:${pos.marginUsed || ''}`)
1196
+ .map((pos) => `${pos.symbol}:${pos.size}:${pos.entryPrice}:${pos.leverage.value}:${pos.takeProfitPrice ?? ''}:${pos.stopLossPrice ?? ''}:${pos.takeProfitCount}:${pos.stopLossCount}:${pos.unrealizedPnl}:${pos.returnOnEquity}:${pos.liquidationPrice ?? ''}:${pos.marginUsed || ''}:${
1197
+ // Trigger arrays are part of the emitted shape, so a standalone or
1198
+ // partial trigger appearing/disappearing has to change the hash —
1199
+ // otherwise subscribers never receive the updated arrays.
1200
+ (0, orderTypes_js_1.hashTriggerOrders)(pos.takeProfitOrders)}:${(0, orderTypes_js_1.hashTriggerOrders)(pos.stopLossOrders)}`)
1173
1201
  .join('|');
1174
1202
  }, _HyperLiquidSubscriptionService_hashOrders = function _HyperLiquidSubscriptionService_hashOrders(orders) {
1175
1203
  if (!orders || orders.length === 0) {
@@ -1183,12 +1211,44 @@ async function _HyperLiquidSubscriptionService_waitForDexDiscovery(timeoutMs = 5
1183
1211
  }, _HyperLiquidSubscriptionService_extractTPSLFromOrders = function _HyperLiquidSubscriptionService_extractTPSLFromOrders(orders, positions, cachedProcessedOrders) {
1184
1212
  const tpslMap = new Map();
1185
1213
  const tpslCountMap = new Map();
1214
+ // Complete per-symbol trigger order view, including quantity-scoped (partial)
1215
+ // TP/SL orders that the scalar tpslMap prices cannot represent.
1216
+ const triggerOrderMap = new Map();
1217
+ const addTriggerOrder = (symbol, triggerOrder) => {
1218
+ if (!triggerOrder) {
1219
+ return;
1220
+ }
1221
+ const existing = triggerOrderMap.get(symbol) ?? {
1222
+ takeProfitOrders: [],
1223
+ stopLossOrders: [],
1224
+ };
1225
+ if (triggerOrder.direction === 'take_profit') {
1226
+ existing.takeProfitOrders.push(triggerOrder);
1227
+ }
1228
+ else {
1229
+ existing.stopLossOrders.push(triggerOrder);
1230
+ }
1231
+ triggerOrderMap.set(symbol, existing);
1232
+ };
1186
1233
  // If cached processed orders provided, extract TP/SL from them directly
1187
1234
  if (cachedProcessedOrders) {
1235
+ // Hoisted out of the per-order loop: this runs on every order-update tick.
1236
+ const positionsBySymbol = new Map(positions.map((position) => [position.symbol, position]));
1188
1237
  cachedProcessedOrders.forEach((order) => {
1189
1238
  // Use triggerPrice for TP/SL (trigger condition price), falling back to price
1190
1239
  // This ensures consistency with raw SDK order processing which uses triggerPx
1191
1240
  const tpslPrice = order.triggerPrice ?? order.price;
1241
+ // Collected before the position-bound filter below: partial TP/SL orders
1242
+ // are standalone (not position-bound) and still belong to this view.
1243
+ // A trigger that is another order's child does not — same rule as the
1244
+ // REST path in HyperLiquidProvider.getPositions.
1245
+ if (order.isTrigger && order.reduceOnly && !order.parentOrderId) {
1246
+ addTriggerOrder(order.symbol, (0, orderTypes_js_1.buildPositionTriggerOrderFromOrder)({
1247
+ order,
1248
+ positionSize: positionsBySymbol.get(order.symbol)?.size ?? '0',
1249
+ entryPrice: positionsBySymbol.get(order.symbol)?.entryPrice,
1250
+ }));
1251
+ }
1192
1252
  if (order.isTrigger && tpslPrice) {
1193
1253
  // When UsePositionBoundTpsl is enabled, only position-bound TP/SL orders
1194
1254
  // should be shown on positions — skip normalTpsl children of limit orders
@@ -1246,10 +1306,25 @@ async function _HyperLiquidSubscriptionService_waitForDexDiscovery(timeoutMs = 5
1246
1306
  }
1247
1307
  }
1248
1308
  });
1249
- return { tpslMap, tpslCountMap, processedOrders: cachedProcessedOrders };
1309
+ return {
1310
+ tpslMap,
1311
+ tpslCountMap,
1312
+ triggerOrderMap,
1313
+ processedOrders: cachedProcessedOrders,
1314
+ };
1250
1315
  }
1251
1316
  // Process raw SDK orders
1252
1317
  const processedOrders = [];
1318
+ // TP/SL children of a pending parent order are listed both nested under the
1319
+ // parent and as top-level entries. Map each child back to its parent so the
1320
+ // converted order carries the link and the position trigger view can exclude
1321
+ // them: they protect that order, not a position.
1322
+ const parentIdByChildId = new Map();
1323
+ orders.forEach((order) => {
1324
+ order.children?.forEach((child) => {
1325
+ parentIdByChildId.set(child.oid, order.oid);
1326
+ });
1327
+ });
1253
1328
  orders.forEach((order) => {
1254
1329
  let position;
1255
1330
  let positionForCoin;
@@ -1320,19 +1395,48 @@ async function _HyperLiquidSubscriptionService_waitForDexDiscovery(timeoutMs = 5
1320
1395
  }
1321
1396
  // Convert ALL open orders to Order format
1322
1397
  const convertedOrder = (0, hyperLiquidAdapter_js_1.adaptOrderFromSDK)(order, position ?? positionForCoin);
1398
+ const parentOrderId = parentIdByChildId.get(order.oid);
1399
+ if (parentOrderId !== undefined) {
1400
+ convertedOrder.parentOrderId = parentOrderId.toString();
1401
+ }
1323
1402
  processedOrders.push(convertedOrder);
1403
+ if (convertedOrder.isTrigger &&
1404
+ convertedOrder.reduceOnly &&
1405
+ !convertedOrder.parentOrderId) {
1406
+ addTriggerOrder(convertedOrder.symbol, (0, orderTypes_js_1.buildPositionTriggerOrderFromOrder)({
1407
+ order: convertedOrder,
1408
+ positionSize: (position ?? positionForCoin)?.size ?? '0',
1409
+ entryPrice: (position ?? positionForCoin)?.entryPrice,
1410
+ }));
1411
+ }
1324
1412
  });
1325
- return { tpslMap, tpslCountMap, processedOrders };
1326
- }, _HyperLiquidSubscriptionService_mergeTPSLIntoPositions = function _HyperLiquidSubscriptionService_mergeTPSLIntoPositions(positions, tpslMap, tpslCountMap) {
1413
+ return { tpslMap, tpslCountMap, triggerOrderMap, processedOrders };
1414
+ }, _HyperLiquidSubscriptionService_mergeTPSLIntoPositions = function _HyperLiquidSubscriptionService_mergeTPSLIntoPositions(positions, tpslMap, tpslCountMap, triggerOrderMap) {
1327
1415
  return positions.map((position) => {
1328
1416
  const tpsl = tpslMap.get(position.symbol) ?? {};
1329
1417
  const tpslCount = tpslCountMap.get(position.symbol) ?? {};
1418
+ const triggerOrders = triggerOrderMap?.get(position.symbol);
1419
+ const takeProfitOrders = triggerOrders?.takeProfitOrders ?? [];
1420
+ const stopLossOrders = triggerOrders?.stopLossOrders ?? [];
1330
1421
  return {
1331
1422
  ...position,
1332
1423
  takeProfitPrice: tpsl.takeProfitPrice ?? undefined,
1333
1424
  stopLossPrice: tpsl.stopLossPrice ?? undefined,
1334
- takeProfitCount: tpslCount.takeProfitCount ?? 0,
1335
- stopLossCount: tpslCount.stopLossCount ?? 0,
1425
+ // Counts come from the same arrays as the REST path, so both transports
1426
+ // report one definition. Orders whose placement type the exchange did
1427
+ // not name (HyperLiquid's ambiguous 'Trigger') are absent from both,
1428
+ // where the legacy count included them.
1429
+ // Keyed on the map, not on this symbol's entry: a symbol with no
1430
+ // entry has no triggers, and falling back to the legacy count there
1431
+ // would report a count beside an empty array.
1432
+ takeProfitCount: triggerOrderMap
1433
+ ? takeProfitOrders.length
1434
+ : (tpslCount.takeProfitCount ?? 0),
1435
+ stopLossCount: triggerOrderMap
1436
+ ? stopLossOrders.length
1437
+ : (tpslCount.stopLossCount ?? 0),
1438
+ takeProfitOrders,
1439
+ stopLossOrders,
1336
1440
  };
1337
1441
  });
1338
1442
  }, _HyperLiquidSubscriptionService_aggregateAccountStates = function _HyperLiquidSubscriptionService_aggregateAccountStates() {
@@ -1869,10 +1973,16 @@ async function _HyperLiquidSubscriptionService_createClearinghouseSubscription(u
1869
1973
  const cachedOrders = __classPrivateFieldGet(this, _HyperLiquidSubscriptionService_dexOrdersCache, "f").get(cacheKey) ?? [];
1870
1974
  // Re-extract TP/SL from cached orders for the new positions
1871
1975
  // This ensures TP/SL data persists across clearinghouseState updates
1872
- let positionsWithTPSL = positions;
1976
+ // Default the trigger arrays so "no triggers" and "not streamed yet"
1977
+ // look the same to consumers as they do on the REST path.
1978
+ let positionsWithTPSL = positions.map((position) => ({
1979
+ ...position,
1980
+ takeProfitOrders: position.takeProfitOrders ?? [],
1981
+ stopLossOrders: position.stopLossOrders ?? [],
1982
+ }));
1873
1983
  if (cachedOrders.length > 0) {
1874
- const { tpslMap, tpslCountMap } = __classPrivateFieldGet(this, _HyperLiquidSubscriptionService_instances, "m", _HyperLiquidSubscriptionService_extractTPSLFromOrders).call(this, [], positions, cachedOrders);
1875
- positionsWithTPSL = __classPrivateFieldGet(this, _HyperLiquidSubscriptionService_instances, "m", _HyperLiquidSubscriptionService_mergeTPSLIntoPositions).call(this, positions, tpslMap, tpslCountMap);
1984
+ const { tpslMap, tpslCountMap, triggerOrderMap } = __classPrivateFieldGet(this, _HyperLiquidSubscriptionService_instances, "m", _HyperLiquidSubscriptionService_extractTPSLFromOrders).call(this, [], positions, cachedOrders);
1985
+ positionsWithTPSL = __classPrivateFieldGet(this, _HyperLiquidSubscriptionService_instances, "m", _HyperLiquidSubscriptionService_mergeTPSLIntoPositions).call(this, positions, tpslMap, tpslCountMap, triggerOrderMap);
1876
1986
  }
1877
1987
  // Update account state
1878
1988
  const accountState = (0, hyperLiquidAdapter_js_1.adaptAccountStateFromSDK)(data.clearinghouseState);
@@ -1953,12 +2063,12 @@ async function _HyperLiquidSubscriptionService_createOpenOrdersSubscription(user
1953
2063
  // Get cached positions for TP/SL processing
1954
2064
  const cachedPositions = __classPrivateFieldGet(this, _HyperLiquidSubscriptionService_dexPositionsCache, "f").get(cacheKey) ?? [];
1955
2065
  // Extract TP/SL and process orders
1956
- const { tpslMap, tpslCountMap, processedOrders: orders, } = __classPrivateFieldGet(this, _HyperLiquidSubscriptionService_instances, "m", _HyperLiquidSubscriptionService_extractTPSLFromOrders).call(this, data.orders, cachedPositions);
2066
+ const { tpslMap, tpslCountMap, triggerOrderMap, processedOrders: orders, } = __classPrivateFieldGet(this, _HyperLiquidSubscriptionService_instances, "m", _HyperLiquidSubscriptionService_extractTPSLFromOrders).call(this, data.orders, cachedPositions);
1957
2067
  // Update orders cache with processed orders
1958
2068
  __classPrivateFieldGet(this, _HyperLiquidSubscriptionService_dexOrdersCache, "f").set(cacheKey, orders);
1959
2069
  // Update positions with TP/SL if we have positions
1960
2070
  if (cachedPositions.length > 0) {
1961
- const positionsWithTPSL = __classPrivateFieldGet(this, _HyperLiquidSubscriptionService_instances, "m", _HyperLiquidSubscriptionService_mergeTPSLIntoPositions).call(this, cachedPositions, tpslMap, tpslCountMap);
2071
+ const positionsWithTPSL = __classPrivateFieldGet(this, _HyperLiquidSubscriptionService_instances, "m", _HyperLiquidSubscriptionService_mergeTPSLIntoPositions).call(this, cachedPositions, tpslMap, tpslCountMap, triggerOrderMap);
1962
2072
  __classPrivateFieldGet(this, _HyperLiquidSubscriptionService_dexPositionsCache, "f").set(cacheKey, positionsWithTPSL);
1963
2073
  }
1964
2074
  // Mark this DEX as initialized (has sent first data)