@lifi/perps-sdk-provider-hyperliquid 25.0.0 → 27.0.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.
Files changed (245) hide show
  1. package/dist/cjs/accountSummary.d.ts.map +1 -1
  2. package/dist/cjs/accountSummary.js +8 -14
  3. package/dist/cjs/accountSummary.js.map +1 -1
  4. package/dist/cjs/services/getAccount.d.ts.map +1 -1
  5. package/dist/cjs/services/getAccount.js +18 -16
  6. package/dist/cjs/services/getAccount.js.map +1 -1
  7. package/dist/cjs/services/getFills.d.ts.map +1 -1
  8. package/dist/cjs/services/getFills.js +2 -1
  9. package/dist/cjs/services/getFills.js.map +1 -1
  10. package/dist/cjs/services/getMarketSettings.d.ts.map +1 -1
  11. package/dist/cjs/services/getMarketSettings.js +1 -1
  12. package/dist/cjs/services/getMarketSettings.js.map +1 -1
  13. package/dist/cjs/services/getOrder.d.ts.map +1 -1
  14. package/dist/cjs/services/getOrder.js +14 -4
  15. package/dist/cjs/services/getOrder.js.map +1 -1
  16. package/dist/cjs/services/getOrders.d.ts.map +1 -1
  17. package/dist/cjs/services/getOrders.js +8 -28
  18. package/dist/cjs/services/getOrders.js.map +1 -1
  19. package/dist/cjs/services/getPortfolioHistory.d.ts.map +1 -1
  20. package/dist/cjs/services/getPortfolioHistory.js +17 -6
  21. package/dist/cjs/services/getPortfolioHistory.js.map +1 -1
  22. package/dist/cjs/services/getPositions.d.ts.map +1 -1
  23. package/dist/cjs/services/getPositions.js +4 -1
  24. package/dist/cjs/services/getPositions.js.map +1 -1
  25. package/dist/cjs/types/ws.d.ts +3 -2
  26. package/dist/cjs/types/ws.d.ts.map +1 -1
  27. package/dist/cjs/utils/index.d.ts +1 -1
  28. package/dist/cjs/utils/index.d.ts.map +1 -1
  29. package/dist/cjs/utils/index.js +3 -1
  30. package/dist/cjs/utils/index.js.map +1 -1
  31. package/dist/cjs/utils/liquidation.d.ts +5 -2
  32. package/dist/cjs/utils/liquidation.d.ts.map +1 -1
  33. package/dist/cjs/utils/liquidation.js +8 -8
  34. package/dist/cjs/utils/liquidation.js.map +1 -1
  35. package/dist/cjs/utils/mapActivity.d.ts.map +1 -1
  36. package/dist/cjs/utils/mapActivity.js +38 -23
  37. package/dist/cjs/utils/mapActivity.js.map +1 -1
  38. package/dist/cjs/utils/mapFill.d.ts +1 -1
  39. package/dist/cjs/utils/mapFill.d.ts.map +1 -1
  40. package/dist/cjs/utils/mapFill.js +17 -6
  41. package/dist/cjs/utils/mapFill.js.map +1 -1
  42. package/dist/cjs/utils/mapMarket.d.ts.map +1 -1
  43. package/dist/cjs/utils/mapMarket.js +9 -13
  44. package/dist/cjs/utils/mapMarket.js.map +1 -1
  45. package/dist/cjs/utils/mapMarketContext.d.ts.map +1 -1
  46. package/dist/cjs/utils/mapMarketContext.js +3 -12
  47. package/dist/cjs/utils/mapMarketContext.js.map +1 -1
  48. package/dist/cjs/utils/mapOrder.d.ts +3 -1
  49. package/dist/cjs/utils/mapOrder.d.ts.map +1 -1
  50. package/dist/cjs/utils/mapOrder.js +73 -19
  51. package/dist/cjs/utils/mapOrder.js.map +1 -1
  52. package/dist/cjs/utils/mapPosition.d.ts +1 -1
  53. package/dist/cjs/utils/mapPosition.d.ts.map +1 -1
  54. package/dist/cjs/utils/mapPosition.js +41 -15
  55. package/dist/cjs/utils/mapPosition.js.map +1 -1
  56. package/dist/cjs/utils/rowTimestamp.d.ts +2 -0
  57. package/dist/cjs/utils/rowTimestamp.d.ts.map +1 -0
  58. package/dist/cjs/utils/rowTimestamp.js +14 -0
  59. package/dist/cjs/utils/rowTimestamp.js.map +1 -0
  60. package/dist/cjs/utils/spotBalance.d.ts +3 -3
  61. package/dist/cjs/utils/spotBalance.d.ts.map +1 -1
  62. package/dist/cjs/utils/spotBalance.js +6 -5
  63. package/dist/cjs/utils/spotBalance.js.map +1 -1
  64. package/dist/cjs/utils/spotCollateral.d.ts.map +1 -1
  65. package/dist/cjs/utils/spotCollateral.js +3 -2
  66. package/dist/cjs/utils/spotCollateral.js.map +1 -1
  67. package/dist/cjs/utils/transferMargin.d.ts.map +1 -1
  68. package/dist/cjs/utils/transferMargin.js +16 -19
  69. package/dist/cjs/utils/transferMargin.js.map +1 -1
  70. package/dist/cjs/utils/venueTotals.d.ts +4 -5
  71. package/dist/cjs/utils/venueTotals.d.ts.map +1 -1
  72. package/dist/cjs/utils/venueTotals.js +18 -10
  73. package/dist/cjs/utils/venueTotals.js.map +1 -1
  74. package/dist/cjs/utils/withdrawableBalances.d.ts.map +1 -1
  75. package/dist/cjs/utils/withdrawableBalances.js +10 -10
  76. package/dist/cjs/utils/withdrawableBalances.js.map +1 -1
  77. package/dist/cjs/websocket/HyperliquidWsProvider.d.ts +1 -0
  78. package/dist/cjs/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  79. package/dist/cjs/websocket/HyperliquidWsProvider.js +144 -115
  80. package/dist/cjs/websocket/HyperliquidWsProvider.js.map +1 -1
  81. package/dist/esm/accountSummary.d.ts +2 -1
  82. package/dist/esm/accountSummary.d.ts.map +1 -1
  83. package/dist/esm/accountSummary.js +11 -13
  84. package/dist/esm/accountSummary.js.map +1 -1
  85. package/dist/esm/services/getAccount.d.ts.map +1 -1
  86. package/dist/esm/services/getAccount.js +23 -21
  87. package/dist/esm/services/getAccount.js.map +1 -1
  88. package/dist/esm/services/getFills.d.ts.map +1 -1
  89. package/dist/esm/services/getFills.js +2 -1
  90. package/dist/esm/services/getFills.js.map +1 -1
  91. package/dist/esm/services/getMarketSettings.d.ts.map +1 -1
  92. package/dist/esm/services/getMarketSettings.js +2 -2
  93. package/dist/esm/services/getMarketSettings.js.map +1 -1
  94. package/dist/esm/services/getOrder.d.ts.map +1 -1
  95. package/dist/esm/services/getOrder.js +14 -4
  96. package/dist/esm/services/getOrder.js.map +1 -1
  97. package/dist/esm/services/getOrders.d.ts.map +1 -1
  98. package/dist/esm/services/getOrders.js +12 -37
  99. package/dist/esm/services/getOrders.js.map +1 -1
  100. package/dist/esm/services/getPortfolioHistory.d.ts.map +1 -1
  101. package/dist/esm/services/getPortfolioHistory.js +19 -7
  102. package/dist/esm/services/getPortfolioHistory.js.map +1 -1
  103. package/dist/esm/services/getPositions.d.ts.map +1 -1
  104. package/dist/esm/services/getPositions.js +4 -1
  105. package/dist/esm/services/getPositions.js.map +1 -1
  106. package/dist/esm/services/getWithdrawableBalances.d.ts +1 -1
  107. package/dist/esm/services/getWithdrawableBalances.js +1 -1
  108. package/dist/esm/types/ws.d.ts +3 -2
  109. package/dist/esm/types/ws.d.ts.map +1 -1
  110. package/dist/esm/utils/index.d.ts +1 -1
  111. package/dist/esm/utils/index.d.ts.map +1 -1
  112. package/dist/esm/utils/index.js +1 -1
  113. package/dist/esm/utils/index.js.map +1 -1
  114. package/dist/esm/utils/liquidation.d.ts +23 -24
  115. package/dist/esm/utils/liquidation.d.ts.map +1 -1
  116. package/dist/esm/utils/liquidation.js +26 -31
  117. package/dist/esm/utils/liquidation.js.map +1 -1
  118. package/dist/esm/utils/mapActivity.d.ts +2 -1
  119. package/dist/esm/utils/mapActivity.d.ts.map +1 -1
  120. package/dist/esm/utils/mapActivity.js +41 -22
  121. package/dist/esm/utils/mapActivity.js.map +1 -1
  122. package/dist/esm/utils/mapFill.d.ts +3 -2
  123. package/dist/esm/utils/mapFill.d.ts.map +1 -1
  124. package/dist/esm/utils/mapFill.js +21 -9
  125. package/dist/esm/utils/mapFill.js.map +1 -1
  126. package/dist/esm/utils/mapMarket.d.ts +2 -2
  127. package/dist/esm/utils/mapMarket.d.ts.map +1 -1
  128. package/dist/esm/utils/mapMarket.js +12 -13
  129. package/dist/esm/utils/mapMarket.js.map +1 -1
  130. package/dist/esm/utils/mapMarketContext.d.ts.map +1 -1
  131. package/dist/esm/utils/mapMarketContext.js +3 -9
  132. package/dist/esm/utils/mapMarketContext.js.map +1 -1
  133. package/dist/esm/utils/mapOrder.d.ts +12 -2
  134. package/dist/esm/utils/mapOrder.d.ts.map +1 -1
  135. package/dist/esm/utils/mapOrder.js +81 -17
  136. package/dist/esm/utils/mapOrder.js.map +1 -1
  137. package/dist/esm/utils/mapPosition.d.ts +4 -2
  138. package/dist/esm/utils/mapPosition.d.ts.map +1 -1
  139. package/dist/esm/utils/mapPosition.js +44 -13
  140. package/dist/esm/utils/mapPosition.js.map +1 -1
  141. package/dist/esm/utils/rowTimestamp.d.ts +6 -0
  142. package/dist/esm/utils/rowTimestamp.d.ts.map +1 -0
  143. package/dist/esm/utils/rowTimestamp.js +14 -0
  144. package/dist/esm/utils/rowTimestamp.js.map +1 -0
  145. package/dist/esm/utils/spotBalance.d.ts +5 -5
  146. package/dist/esm/utils/spotBalance.d.ts.map +1 -1
  147. package/dist/esm/utils/spotBalance.js +9 -8
  148. package/dist/esm/utils/spotBalance.js.map +1 -1
  149. package/dist/esm/utils/spotCollateral.d.ts.map +1 -1
  150. package/dist/esm/utils/spotCollateral.js +3 -2
  151. package/dist/esm/utils/spotCollateral.js.map +1 -1
  152. package/dist/esm/utils/transferMargin.d.ts +2 -1
  153. package/dist/esm/utils/transferMargin.d.ts.map +1 -1
  154. package/dist/esm/utils/transferMargin.js +19 -18
  155. package/dist/esm/utils/transferMargin.js.map +1 -1
  156. package/dist/esm/utils/venueTotals.d.ts +7 -6
  157. package/dist/esm/utils/venueTotals.d.ts.map +1 -1
  158. package/dist/esm/utils/venueTotals.js +21 -8
  159. package/dist/esm/utils/venueTotals.js.map +1 -1
  160. package/dist/esm/utils/withdrawableBalances.d.ts +6 -4
  161. package/dist/esm/utils/withdrawableBalances.d.ts.map +1 -1
  162. package/dist/esm/utils/withdrawableBalances.js +17 -15
  163. package/dist/esm/utils/withdrawableBalances.js.map +1 -1
  164. package/dist/esm/websocket/HyperliquidWsProvider.d.ts +3 -0
  165. package/dist/esm/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  166. package/dist/esm/websocket/HyperliquidWsProvider.js +155 -115
  167. package/dist/esm/websocket/HyperliquidWsProvider.js.map +1 -1
  168. package/dist/types/accountSummary.d.ts +2 -1
  169. package/dist/types/accountSummary.d.ts.map +1 -1
  170. package/dist/types/services/getAccount.d.ts.map +1 -1
  171. package/dist/types/services/getFills.d.ts.map +1 -1
  172. package/dist/types/services/getMarketSettings.d.ts.map +1 -1
  173. package/dist/types/services/getOrder.d.ts.map +1 -1
  174. package/dist/types/services/getOrders.d.ts.map +1 -1
  175. package/dist/types/services/getPortfolioHistory.d.ts.map +1 -1
  176. package/dist/types/services/getPositions.d.ts.map +1 -1
  177. package/dist/types/services/getWithdrawableBalances.d.ts +1 -1
  178. package/dist/types/types/ws.d.ts +3 -2
  179. package/dist/types/types/ws.d.ts.map +1 -1
  180. package/dist/types/utils/index.d.ts +1 -1
  181. package/dist/types/utils/index.d.ts.map +1 -1
  182. package/dist/types/utils/liquidation.d.ts +23 -24
  183. package/dist/types/utils/liquidation.d.ts.map +1 -1
  184. package/dist/types/utils/mapActivity.d.ts +2 -1
  185. package/dist/types/utils/mapActivity.d.ts.map +1 -1
  186. package/dist/types/utils/mapFill.d.ts +3 -2
  187. package/dist/types/utils/mapFill.d.ts.map +1 -1
  188. package/dist/types/utils/mapMarket.d.ts +2 -2
  189. package/dist/types/utils/mapMarket.d.ts.map +1 -1
  190. package/dist/types/utils/mapMarketContext.d.ts.map +1 -1
  191. package/dist/types/utils/mapOrder.d.ts +12 -2
  192. package/dist/types/utils/mapOrder.d.ts.map +1 -1
  193. package/dist/types/utils/mapPosition.d.ts +4 -2
  194. package/dist/types/utils/mapPosition.d.ts.map +1 -1
  195. package/dist/types/utils/rowTimestamp.d.ts +6 -0
  196. package/dist/types/utils/rowTimestamp.d.ts.map +1 -0
  197. package/dist/types/utils/spotBalance.d.ts +5 -5
  198. package/dist/types/utils/spotBalance.d.ts.map +1 -1
  199. package/dist/types/utils/spotCollateral.d.ts.map +1 -1
  200. package/dist/types/utils/transferMargin.d.ts +2 -1
  201. package/dist/types/utils/transferMargin.d.ts.map +1 -1
  202. package/dist/types/utils/venueTotals.d.ts +7 -6
  203. package/dist/types/utils/venueTotals.d.ts.map +1 -1
  204. package/dist/types/utils/withdrawableBalances.d.ts +6 -4
  205. package/dist/types/utils/withdrawableBalances.d.ts.map +1 -1
  206. package/dist/types/websocket/HyperliquidWsProvider.d.ts +3 -0
  207. package/dist/types/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  208. package/package.json +3 -3
  209. package/src/HyperliquidProvider.ts +1 -1
  210. package/src/accountSummary.ts +35 -23
  211. package/src/services/getAccount.ts +50 -33
  212. package/src/services/getFills.ts +2 -1
  213. package/src/services/getMarketSettings.ts +6 -2
  214. package/src/services/getOrder.ts +18 -7
  215. package/src/services/getOrders.ts +14 -46
  216. package/src/services/getPortfolioHistory.ts +34 -7
  217. package/src/services/getPositions.ts +4 -3
  218. package/src/services/getWithdrawableBalances.ts +1 -1
  219. package/src/types/ws.ts +3 -2
  220. package/src/utils/index.ts +2 -0
  221. package/src/utils/liquidation.ts +46 -34
  222. package/src/utils/mapActivity.ts +48 -20
  223. package/src/utils/mapFill.ts +25 -9
  224. package/src/utils/mapMarket.ts +16 -12
  225. package/src/utils/mapMarketContext.ts +6 -8
  226. package/src/utils/mapOrder.ts +107 -20
  227. package/src/utils/mapPosition.ts +72 -14
  228. package/src/utils/rowTimestamp.ts +15 -0
  229. package/src/utils/spotBalance.ts +19 -16
  230. package/src/utils/spotCollateral.ts +15 -2
  231. package/src/utils/transferMargin.ts +40 -18
  232. package/src/utils/venueTotals.ts +51 -17
  233. package/src/utils/withdrawableBalances.ts +33 -15
  234. package/src/websocket/HyperliquidWsProvider.ts +224 -133
  235. package/dist/cjs/utils/decimal.d.ts +0 -3
  236. package/dist/cjs/utils/decimal.d.ts.map +0 -1
  237. package/dist/cjs/utils/decimal.js +0 -19
  238. package/dist/cjs/utils/decimal.js.map +0 -1
  239. package/dist/esm/utils/decimal.d.ts +0 -10
  240. package/dist/esm/utils/decimal.d.ts.map +0 -1
  241. package/dist/esm/utils/decimal.js +0 -19
  242. package/dist/esm/utils/decimal.js.map +0 -1
  243. package/dist/types/utils/decimal.d.ts +0 -10
  244. package/dist/types/utils/decimal.d.ts.map +0 -1
  245. package/src/utils/decimal.ts +0 -21
@@ -1,12 +1,11 @@
1
- import { calculateTransferable, DecodeChain, getMarketRegistry, isActiveMarket, isActiveOrderStatus, numberToDecimalString, PerpsError, ReconnectingWebSocket, resolveSubscribeQuote, toAssetDisplay, toPerpsMarketDisplay, WsProviderBase, wsLog, } from '@lifi/perps-sdk';
1
+ import { addDecimalString, calculateTransferable, compareDecimalStrings, DecodeChain, getMarketRegistry, isActiveMarket, isActiveOrderStatus, isDecimalString, isDecimalStringZero, PerpsError, ReconnectingWebSocket, resolveSubscribeQuote, safeAddDecimalString, safeDecimalStringToNumber, safeDivideDecimalString, safeIsDecimalStringGreaterThan, safeIsDecimalStringZero, safeMultiplyDecimalString, safeNumberToDecimalString, safeSubtractDecimalString, subtractDecimalString, toAssetDisplay, toPerpsMarketDisplay, unknownToDecimalString, WsProviderBase, wsLog, } from '@lifi/perps-sdk';
2
2
  import { PerpsErrorCode } from '@lifi/perps-types';
3
- import Big from 'big.js';
4
3
  import { isAddress } from 'viem';
5
- import { DEFAULT_HYPERLIQUID_API_URL, HYPERLIQUID_FEE_TIER_FALLBACK, SPOT_MARKET_ID, } from '../constants.js';
4
+ import { DEFAULT_HYPERLIQUID_API_URL, HYPERLIQUID_FEE_TIER_FALLBACK, PROVIDER_KEY, SPOT_MARKET_ID, } from '../constants.js';
6
5
  import { requireAccountExists } from '../services/getAccountExists.js';
7
6
  import { HlAbstractionMode } from '../types/index.js';
8
- import { toWireBig } from '../utils/decimal.js';
9
7
  import { assetIsOutcome, decodeCompressedJson, decodeFastAssetCtxs, hlInfoOptions, infoRequest, isOpenAssetPosition, mapFill, mapMarketContext, mapOrder, mapPosition, partitionSpotBalances, perpsTotals, priceStepToAggregation, spotAssetFromToken, spotBalance, spotPriceById, sumUnrealizedPnl, } from '../utils/index.js';
8
+ import { mapOrderRow } from '../utils/mapOrder.js';
10
9
  /** HL's compact `l2` snapshot carries 20 levels per side. */
11
10
  const HL_L2_BOOK_MAX_LEVELS_PER_SIDE = 20;
12
11
  const normalizeHlAddress = (address) => isAddress(address, { strict: false }) ? address.toLowerCase() : address;
@@ -390,7 +389,14 @@ export class HyperliquidWsProvider extends WsProviderBase {
390
389
  if (priceStep === undefined) {
391
390
  return {};
392
391
  }
393
- return priceStepToAggregation(priceStep, this.orderbookReferencePrice(marketId));
392
+ const referencePrice = this.orderbookReferencePrice(marketId);
393
+ const referencePriceNumber = referencePrice === undefined
394
+ ? undefined
395
+ : safeDecimalStringToNumber(referencePrice);
396
+ if (referencePriceNumber === undefined) {
397
+ return {};
398
+ }
399
+ return priceStepToAggregation(priceStep, referencePriceNumber);
394
400
  }
395
401
  orderbookAggregationKey(marketId, priceStep) {
396
402
  const aggregation = this.orderbookAggregation(marketId, priceStep);
@@ -702,25 +708,26 @@ export class HyperliquidWsProvider extends WsProviderBase {
702
708
  });
703
709
  }
704
710
  /** Latest mid per `Market.id` across asset-context and fast feeds. */
711
+ /** Latest mid per `Market.id` across asset-context and fast feeds; a mid
712
+ * that does not match the decimal pattern is left out. */
705
713
  mergedMids() {
706
714
  const map = new Map();
715
+ const setMid = (id, mid) => {
716
+ const decimal = mid == null ? undefined : wsFigureToDecimalString(mid);
717
+ if (isDecimalString(decimal)) {
718
+ map.set(id, decimal);
719
+ }
720
+ };
707
721
  for (const byMarketId of this.perpCtxBySubDex.values()) {
708
722
  for (const [id, ctx] of Object.entries(byMarketId)) {
709
- const mid = ctx.midPx ?? ctx.markPx;
710
- map.set(id, Number(mid));
723
+ setMid(id, ctx.midPx ?? ctx.markPx);
711
724
  }
712
725
  }
713
726
  for (const [id, ctx] of Object.entries(this.spotCtxByMarketId)) {
714
- const mid = ctx.midPx ?? ctx.markPx;
715
- if (mid != null) {
716
- map.set(id, Number(mid));
717
- }
727
+ setMid(id, ctx.midPx ?? ctx.markPx);
718
728
  }
719
729
  for (const [id, fast] of Object.entries(this.fastCtxByMarketId)) {
720
- const mid = fast.midPx ?? fast.markPx;
721
- if (mid != null) {
722
- map.set(id, Number(mid));
723
- }
730
+ setMid(id, fast.midPx ?? fast.markPx);
724
731
  }
725
732
  return map;
726
733
  }
@@ -732,21 +739,22 @@ export class HyperliquidWsProvider extends WsProviderBase {
732
739
  const book = this.latestOrderbookByMarketId.get(marketId);
733
740
  const bid = book?.bids[0]?.price;
734
741
  const ask = book?.asks[0]?.price;
735
- if (bid !== undefined && ask !== undefined) {
736
- return (Number(bid) + Number(ask)) / 2;
742
+ if (bid === undefined || ask === undefined) {
743
+ return undefined;
737
744
  }
738
- return Number.NaN;
745
+ const sum = safeAddDecimalString(bid, ask);
746
+ return sum === undefined ? undefined : safeDivideDecimalString(sum, '2');
739
747
  }
740
748
  handleL2Book(data) {
741
749
  const book = {
742
750
  provider: this.providerKey,
743
751
  marketId: data.coin,
744
752
  bids: data.levels[0]
745
- .slice(0, HL_L2_BOOK_MAX_LEVELS_PER_SIDE)
746
- .map((l) => ({ price: l.px, size: l.sz })),
753
+ .flatMap((l) => toOrderbookLevel(data.coin, l.px, l.sz))
754
+ .slice(0, HL_L2_BOOK_MAX_LEVELS_PER_SIDE),
747
755
  asks: data.levels[1]
748
- .slice(0, HL_L2_BOOK_MAX_LEVELS_PER_SIDE)
749
- .map((l) => ({ price: l.px, size: l.sz })),
756
+ .flatMap((l) => toOrderbookLevel(data.coin, l.px, l.sz))
757
+ .slice(0, HL_L2_BOOK_MAX_LEVELS_PER_SIDE),
750
758
  timestamp: data.time,
751
759
  };
752
760
  this.latestOrderbookByMarketId.set(data.coin, book);
@@ -782,8 +790,8 @@ export class HyperliquidWsProvider extends WsProviderBase {
782
790
  const book = {
783
791
  provider: this.providerKey,
784
792
  marketId: delta.c,
785
- bids: applyCompressedL2Side(previous.bids, delta.l[0], delta.r?.[0] ?? [], 'bid'),
786
- asks: applyCompressedL2Side(previous.asks, delta.l[1], delta.r?.[1] ?? [], 'ask'),
793
+ bids: applyCompressedL2Side(delta.c, previous.bids, delta.l[0], delta.r?.[0] ?? [], 'bid'),
794
+ asks: applyCompressedL2Side(delta.c, previous.asks, delta.l[1], delta.r?.[1] ?? [], 'ask'),
787
795
  timestamp: delta.t,
788
796
  };
789
797
  this.latestOrderbookByMarketId.set(delta.c, book);
@@ -846,40 +854,25 @@ export class HyperliquidWsProvider extends WsProviderBase {
846
854
  if (epoch !== this.orderUpdatesEpoch || key !== this.orderUpdatesKey) {
847
855
  return;
848
856
  }
857
+ const user = key.slice('orderUpdates:'.length);
849
858
  const mapped = await Promise.all(data.map(async (update) => {
850
859
  const basic = update.order;
851
- if (assetIsOutcome(basic.coin)) {
860
+ if (assetIsOutcome(basic.coin) ||
861
+ this.registry?.get(basic.coin) === undefined) {
852
862
  return undefined;
853
863
  }
854
- const market = this.registry?.get(basic.coin);
855
- if (market === undefined) {
856
- return undefined;
864
+ let detail;
865
+ try {
866
+ detail = await this.readOrderDetail(user, client, update);
857
867
  }
858
- const response = await infoRequest(this.orderApiUrl, {
859
- type: 'orderStatus',
860
- user: key.slice('orderUpdates:'.length),
861
- oid: basic.oid,
862
- }, hlInfoOptions(client));
863
- if (response.status !== 'order') {
864
- throw new PerpsError(PerpsErrorCode.OrderNotFound, `Hyperliquid order metadata not found: ${basic.oid}`);
868
+ catch (error) {
869
+ if (!(error instanceof PerpsError)) {
870
+ throw error;
871
+ }
872
+ wsLog.droppedRow(this.providerKey, 'order', `${basic.oid}: ${error.message}`);
873
+ return undefined;
865
874
  }
866
- // REST supplies execution metadata; the stream owns this event's lifecycle and quantities.
867
- const detail = {
868
- status: update.status,
869
- statusTimestamp: update.statusTimestamp,
870
- order: {
871
- ...response.order.order,
872
- coin: basic.coin,
873
- side: basic.side,
874
- limitPx: basic.limitPx,
875
- sz: basic.sz,
876
- oid: basic.oid,
877
- timestamp: basic.timestamp,
878
- origSz: basic.origSz,
879
- cloid: basic.cloid ?? response.order.order.cloid,
880
- },
881
- };
882
- return mapOrder(detail, market);
875
+ return mapOrderRow(basic.coin, this.registry, (resolved) => mapOrder(detail, resolved));
883
876
  }));
884
877
  if (epoch !== this.orderUpdatesEpoch || key !== this.orderUpdatesKey) {
885
878
  return;
@@ -898,6 +891,29 @@ export class HyperliquidWsProvider extends WsProviderBase {
898
891
  this.emit(key, { channel: 'orderUpdates', data: { orders, terminated } });
899
892
  });
900
893
  }
894
+ // REST supplies execution metadata; the stream owns this event's lifecycle and quantities.
895
+ async readOrderDetail(user, client, update) {
896
+ const basic = update.order;
897
+ const response = await infoRequest(this.orderApiUrl, { type: 'orderStatus', user, oid: basic.oid }, hlInfoOptions(client));
898
+ if (response.status !== 'order') {
899
+ throw new PerpsError(PerpsErrorCode.OrderNotFound, `Hyperliquid order metadata not found: ${basic.oid}`);
900
+ }
901
+ return {
902
+ status: update.status,
903
+ statusTimestamp: update.statusTimestamp,
904
+ order: {
905
+ ...response.order.order,
906
+ coin: basic.coin,
907
+ side: basic.side,
908
+ limitPx: basic.limitPx,
909
+ sz: basic.sz,
910
+ oid: basic.oid,
911
+ timestamp: basic.timestamp,
912
+ origSz: basic.origSz,
913
+ cloid: basic.cloid ?? response.order.order.cloid,
914
+ },
915
+ };
916
+ }
901
917
  handleUserFills(data) {
902
918
  // The snapshot frame carries up to ~2000 historical fills and is replayed
903
919
  // on every reconnect via `replaySubs`; emitting them would surface old
@@ -911,7 +927,8 @@ export class HyperliquidWsProvider extends WsProviderBase {
911
927
  return [];
912
928
  }
913
929
  const market = this.registry?.get(f.coin);
914
- return market ? [mapFill(f, market)] : [];
930
+ const mapped = market && mapFill(f, market);
931
+ return mapped ? [mapped] : [];
915
932
  });
916
933
  this.emit(`userFills:${data.user.toLowerCase()}`, {
917
934
  channel: 'fills',
@@ -925,9 +942,10 @@ export class HyperliquidWsProvider extends WsProviderBase {
925
942
  return [];
926
943
  }
927
944
  const market = this.registry?.get(ap.position.coin);
928
- return market
929
- ? [mapPosition(ap, toPerpsMarketDisplay(market))]
930
- : [];
945
+ const position = market === undefined
946
+ ? undefined
947
+ : mapPosition(ap, toPerpsMarketDisplay(market));
948
+ return position === undefined ? [] : [position];
931
949
  }));
932
950
  this.emit(`positions:${data.user.toLowerCase()}`, {
933
951
  channel: 'positions',
@@ -935,20 +953,17 @@ export class HyperliquidWsProvider extends WsProviderBase {
935
953
  });
936
954
  const { accountValue, marginUsed } = perpsTotals(data.clearinghouseStates.map(([, state]) => state));
937
955
  const key = data.user.toLowerCase();
938
- this.latestPerpsByUser.set(key, {
939
- positions,
940
- marginUsed: marginUsed.toFixed(),
941
- });
956
+ this.latestPerpsByUser.set(key, { positions, marginUsed });
942
957
  this.emitUnifiedSummary(key);
943
958
  // Equity semantics, matching the REST summary: `accountValue` already
944
959
  // carries locked margin and unrealized PnL. Spot balances stream apart,
945
960
  // so this portfolio value covers perps equity only — which is why the
946
961
  // frame is gated on the abstraction mode below.
947
962
  this.emitSummaryIfModeAllows(data.user, {
948
- portfolioValue: accountValue.toFixed(),
949
- availableMargin: accountValue.minus(marginUsed).toFixed(),
950
- marginUsed: marginUsed.toFixed(),
951
- unrealizedPnl: sumUnrealizedPnl(positions).toFixed(),
963
+ portfolioValue: accountValue,
964
+ availableMargin: subtractDecimalString(accountValue, marginUsed),
965
+ marginUsed,
966
+ unrealizedPnl: sumUnrealizedPnl(positions),
952
967
  });
953
968
  }
954
969
  /** Whether the mode's collateral lives per-dex, making the perps-only
@@ -1039,14 +1054,14 @@ export class HyperliquidWsProvider extends WsProviderBase {
1039
1054
  const portfolioValue = [
1040
1055
  ...pipeline.spot.collateralBalances,
1041
1056
  ...pipeline.spot.balances,
1042
- ].reduce((sum, balance) => sum.plus(toWireBig(balance.valueUsd, 'spotBalance.valueUsd')), new Big(0));
1057
+ ].reduce((sum, balance) => addDecimalString(sum, unknownToDecimalString(balance.valueUsd, 'spotBalance.valueUsd', this.providerKey)), '0');
1043
1058
  this.emit(`accountSummary:${key}`, {
1044
1059
  channel: 'accountSummary',
1045
1060
  data: {
1046
- portfolioValue: portfolioValue.toFixed(),
1061
+ portfolioValue,
1047
1062
  availableMargin: pipeline.availableMargin,
1048
1063
  marginUsed: perps.marginUsed,
1049
- unrealizedPnl: sumUnrealizedPnl(perps.positions).toFixed(),
1064
+ unrealizedPnl: sumUnrealizedPnl(perps.positions),
1050
1065
  },
1051
1066
  });
1052
1067
  }
@@ -1172,24 +1187,27 @@ export class HyperliquidWsProvider extends WsProviderBase {
1172
1187
  }
1173
1188
  const markets = this.registry?.activeMarkets ?? [];
1174
1189
  const priceById = spotPriceById(markets, this.mergedMids());
1175
- const balances = data.spotState.balances.filter((balance) => !assetIsOutcome(balance.coin) &&
1176
- toWireBig(balance.total, 'spotState.balances.total').gt(0));
1190
+ const balances = data.spotState.balances
1191
+ .filter((balance) => !assetIsOutcome(balance.coin))
1192
+ .filter((balance) => safeIsDecimalStringGreaterThan(balance.total, '0') === true)
1193
+ .map((balance) => ({ balance, total: balance.total }));
1177
1194
  const pipeline = this.unifiedSummaryByUser.get(user);
1178
1195
  // Known spot markets await a price; unlisted tokens keep their unpriced balance.
1179
- if (balances.some((balance) => priceById.get(String(balance.token)) === 0)) {
1196
+ if (balances.some(({ balance }) => {
1197
+ const price = priceById.get(String(balance.token));
1198
+ return price !== undefined && isDecimalStringZero(price);
1199
+ })) {
1180
1200
  if (pipeline !== undefined) {
1181
1201
  pipeline.spot = undefined;
1182
1202
  }
1183
1203
  return;
1184
1204
  }
1185
- const rows = balances.map((balance) => {
1186
- const total = toWireBig(balance.total, 'spotState.balances.total');
1205
+ const rows = balances.map(({ balance, total }) => {
1206
+ const free = safeSubtractDecimalString(total, balance.hold);
1187
1207
  return {
1188
- ...spotBalance(spotAssetFromToken(balance), balance.total, priceById),
1208
+ ...spotBalance(spotAssetFromToken(balance), total, priceById),
1189
1209
  locked: balance.hold,
1190
- transferable: calculateTransferable(total
1191
- .minus(toWireBig(balance.hold, 'spotState.balances.hold'))
1192
- .toFixed(), total.toFixed()),
1210
+ transferable: free === undefined ? '0' : calculateTransferable(free, total),
1193
1211
  };
1194
1212
  });
1195
1213
  this.emit(`spotState:${user}`, {
@@ -1229,29 +1247,22 @@ export class HyperliquidWsProvider extends WsProviderBase {
1229
1247
  }
1230
1248
  }
1231
1249
  const isObject = (v) => typeof v === 'object' && v !== null;
1232
- const toMarketContextString = (value) => {
1233
- if (typeof value === 'number') {
1234
- return numberToDecimalString(value);
1235
- }
1236
- return typeof value === 'string' ? value : undefined;
1237
- };
1250
+ /** A WebSocket figure as a string; a venue number is spelled as a decimal
1251
+ * string, and a non-finite number gives `undefined`. */
1252
+ function wsFigureToDecimalString(value) {
1253
+ return typeof value === 'number' ? safeNumberToDecimalString(value) : value;
1254
+ }
1255
+ function optionalWsFigureToDecimalString(value) {
1256
+ return value == null ? undefined : wsFigureToDecimalString(value);
1257
+ }
1238
1258
  const toMarketCapString = (price, circulatingSupply) => {
1239
- const priceString = toMarketContextString(price);
1240
- const supplyString = toMarketContextString(circulatingSupply);
1241
- if (priceString === undefined || supplyString === undefined) {
1242
- return undefined;
1243
- }
1244
- try {
1245
- const parsedPrice = new Big(priceString);
1246
- const parsedSupply = new Big(supplyString);
1247
- if (parsedPrice.lte(0) || parsedSupply.lte(0)) {
1248
- return undefined;
1249
- }
1250
- return parsedPrice.times(parsedSupply).toFixed();
1251
- }
1252
- catch {
1259
+ const supply = optionalWsFigureToDecimalString(circulatingSupply);
1260
+ if (supply === undefined ||
1261
+ safeIsDecimalStringGreaterThan(price, '0') !== true ||
1262
+ safeIsDecimalStringGreaterThan(supply, '0') !== true) {
1253
1263
  return undefined;
1254
1264
  }
1265
+ return safeMultiplyDecimalString(price, supply);
1255
1266
  };
1256
1267
  function mergePerpAssetCtx(previous, marketId, update) {
1257
1268
  const funding = update.funding ?? previous?.funding;
@@ -1285,15 +1296,13 @@ function allDexsAssetCtxEntries(data) {
1285
1296
  return data.assetCtxs ?? data.ctxs ?? [];
1286
1297
  }
1287
1298
  function activePerpAssetCtx(marketId, ctx) {
1288
- const funding = toMarketContextString(ctx.funding);
1289
- const openInterest = toMarketContextString(ctx.openInterest);
1290
- const dayNtlVlm = toMarketContextString(ctx.dayNtlVlm);
1291
- const prevDayPx = toMarketContextString(ctx.prevDayPx);
1292
- const markPx = toMarketContextString(ctx.markPx);
1293
- const midPx = ctx.midPx === undefined || ctx.midPx === null
1294
- ? null
1295
- : (toMarketContextString(ctx.midPx) ?? null);
1296
- const oraclePx = toMarketContextString(ctx.oraclePx);
1299
+ const funding = optionalWsFigureToDecimalString(ctx.funding);
1300
+ const openInterest = optionalWsFigureToDecimalString(ctx.openInterest);
1301
+ const dayNtlVlm = optionalWsFigureToDecimalString(ctx.dayNtlVlm);
1302
+ const prevDayPx = optionalWsFigureToDecimalString(ctx.prevDayPx);
1303
+ const markPx = optionalWsFigureToDecimalString(ctx.markPx);
1304
+ const midPx = optionalWsFigureToDecimalString(ctx.midPx) ?? null;
1305
+ const oraclePx = optionalWsFigureToDecimalString(ctx.oraclePx);
1297
1306
  if (funding === undefined ||
1298
1307
  openInterest === undefined ||
1299
1308
  dayNtlVlm === undefined ||
@@ -1314,8 +1323,8 @@ function activePerpAssetCtx(marketId, ctx) {
1314
1323
  };
1315
1324
  }
1316
1325
  function mapSpotMarketContext(marketId, ctx, fast) {
1317
- const midPrice = toMarketContextString(ctx.midPx ?? ctx.markPx);
1318
- const markPrice = toMarketContextString(ctx.markPx);
1326
+ const midPrice = optionalWsFigureToDecimalString(ctx.midPx ?? ctx.markPx);
1327
+ const markPrice = optionalWsFigureToDecimalString(ctx.markPx);
1319
1328
  if (midPrice === undefined || markPrice === undefined) {
1320
1329
  return undefined;
1321
1330
  }
@@ -1324,18 +1333,43 @@ function mapSpotMarketContext(marketId, ctx, fast) {
1324
1333
  marketId,
1325
1334
  midPrice: fast?.midPx != null ? fast.midPx : midPrice,
1326
1335
  markPrice: emittedMarkPrice,
1327
- prevDayPrice: toMarketContextString(ctx.prevDayPx),
1328
- volume24h: toMarketContextString(ctx.dayNtlVlm),
1336
+ prevDayPrice: optionalWsFigureToDecimalString(ctx.prevDayPx),
1337
+ volume24h: optionalWsFigureToDecimalString(ctx.dayNtlVlm),
1329
1338
  marketCap: toMarketCapString(emittedMarkPrice, ctx.circulatingSupply),
1330
1339
  };
1331
1340
  }
1341
+ const BOOK_LEVEL_ROW = 'order book level';
1342
+ /** The venue price when it is a decimal string, or `undefined` after a logged skip. */
1343
+ function bookLevelPrice(marketId, price) {
1344
+ if (!isDecimalString(price)) {
1345
+ wsLog.skippedRow(PROVIDER_KEY, BOOK_LEVEL_ROW, 'price', price, marketId);
1346
+ return undefined;
1347
+ }
1348
+ return price;
1349
+ }
1350
+ /** The venue size when it is a decimal string, or `undefined` after a logged skip. */
1351
+ function bookLevelSize(marketId, size) {
1352
+ if (!isDecimalString(size)) {
1353
+ wsLog.skippedRow(PROVIDER_KEY, BOOK_LEVEL_ROW, 'size', size, marketId);
1354
+ return undefined;
1355
+ }
1356
+ return size;
1357
+ }
1358
+ /** One order-book level, or none when the venue price or size is invalid. */
1359
+ function toOrderbookLevel(marketId, price, size) {
1360
+ const levelPrice = bookLevelPrice(marketId, price);
1361
+ const levelSize = levelPrice === undefined ? undefined : bookLevelSize(marketId, size);
1362
+ return levelPrice === undefined || levelSize === undefined
1363
+ ? []
1364
+ : [{ price: levelPrice, size: levelSize }];
1365
+ }
1332
1366
  function compactRemovalPrice(levels, value) {
1333
1367
  if (typeof value === 'number') {
1334
1368
  return levels[value]?.price;
1335
1369
  }
1336
1370
  return typeof value === 'string' ? value : value.p;
1337
1371
  }
1338
- function applyCompressedL2Side(previous, updates, removals, side) {
1372
+ function applyCompressedL2Side(marketId, previous, updates, removals, side) {
1339
1373
  const byPrice = new Map(previous.map((level) => [level.price, level.size]));
1340
1374
  for (const removal of removals) {
1341
1375
  const price = compactRemovalPrice(previous, removal);
@@ -1344,18 +1378,24 @@ function applyCompressedL2Side(previous, updates, removals, side) {
1344
1378
  }
1345
1379
  }
1346
1380
  for (const update of updates) {
1347
- if (Number(update.s) === 0) {
1348
- byPrice.delete(update.p);
1381
+ const price = bookLevelPrice(marketId, update.p);
1382
+ if (price === undefined) {
1383
+ continue;
1384
+ }
1385
+ const size = bookLevelSize(marketId, update.s);
1386
+ // A level whose new size is unknown must not keep its old size.
1387
+ if (size === undefined || safeIsDecimalStringZero(size) !== false) {
1388
+ byPrice.delete(price);
1349
1389
  }
1350
1390
  else {
1351
- byPrice.set(update.p, update.s);
1391
+ byPrice.set(price, size);
1352
1392
  }
1353
1393
  }
1354
1394
  return [...byPrice.entries()]
1355
1395
  .map(([price, size]) => ({ price, size }))
1356
1396
  .sort((a, b) => side === 'bid'
1357
- ? Number(b.price) - Number(a.price)
1358
- : Number(a.price) - Number(b.price))
1397
+ ? compareDecimalStrings(b.price, a.price)
1398
+ : compareDecimalStrings(a.price, b.price))
1359
1399
  .slice(0, HL_L2_BOOK_MAX_LEVELS_PER_SIDE);
1360
1400
  }
1361
1401
  /**