@lifi/perps-sdk-provider-lighter 0.2.0-alpha.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 (405) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +176 -0
  3. package/dist/cjs/LighterProvider.d.ts +20 -0
  4. package/dist/cjs/LighterProvider.d.ts.map +1 -0
  5. package/dist/cjs/LighterProvider.js +592 -0
  6. package/dist/cjs/LighterProvider.js.map +1 -0
  7. package/dist/cjs/accountConfig.d.ts +3 -0
  8. package/dist/cjs/accountConfig.d.ts.map +1 -0
  9. package/dist/cjs/accountConfig.js +69 -0
  10. package/dist/cjs/accountConfig.js.map +1 -0
  11. package/dist/cjs/accountSummary.d.ts +3 -0
  12. package/dist/cjs/accountSummary.d.ts.map +1 -0
  13. package/dist/cjs/accountSummary.js +8 -0
  14. package/dist/cjs/accountSummary.js.map +1 -0
  15. package/dist/cjs/constants.d.ts +14 -0
  16. package/dist/cjs/constants.d.ts.map +1 -0
  17. package/dist/cjs/constants.js +18 -0
  18. package/dist/cjs/constants.js.map +1 -0
  19. package/dist/cjs/index.d.ts +11 -0
  20. package/dist/cjs/index.d.ts.map +1 -0
  21. package/dist/cjs/index.js +35 -0
  22. package/dist/cjs/index.js.map +1 -0
  23. package/dist/cjs/package.json +1 -0
  24. package/dist/cjs/signers/LighterKeyStore.d.ts +18 -0
  25. package/dist/cjs/signers/LighterKeyStore.d.ts.map +1 -0
  26. package/dist/cjs/signers/LighterKeyStore.js +54 -0
  27. package/dist/cjs/signers/LighterKeyStore.js.map +1 -0
  28. package/dist/cjs/signers/LighterReadOnlyTokenManager.d.ts +56 -0
  29. package/dist/cjs/signers/LighterReadOnlyTokenManager.d.ts.map +1 -0
  30. package/dist/cjs/signers/LighterReadOnlyTokenManager.js +127 -0
  31. package/dist/cjs/signers/LighterReadOnlyTokenManager.js.map +1 -0
  32. package/dist/cjs/signers/LighterSigner.d.ts +41 -0
  33. package/dist/cjs/signers/LighterSigner.d.ts.map +1 -0
  34. package/dist/cjs/signers/LighterSigner.js +175 -0
  35. package/dist/cjs/signers/LighterSigner.js.map +1 -0
  36. package/dist/cjs/signers/createAuthToken.d.ts +12 -0
  37. package/dist/cjs/signers/createAuthToken.d.ts.map +1 -0
  38. package/dist/cjs/signers/createAuthToken.js +28 -0
  39. package/dist/cjs/signers/createAuthToken.js.map +1 -0
  40. package/dist/cjs/signers/index.d.ts +11 -0
  41. package/dist/cjs/signers/index.d.ts.map +1 -0
  42. package/dist/cjs/signers/index.js +19 -0
  43. package/dist/cjs/signers/index.js.map +1 -0
  44. package/dist/cjs/signers/signActions.d.ts +15 -0
  45. package/dist/cjs/signers/signActions.d.ts.map +1 -0
  46. package/dist/cjs/signers/signActions.js +142 -0
  47. package/dist/cjs/signers/signActions.js.map +1 -0
  48. package/dist/cjs/signers/wasmLoader.d.ts +42 -0
  49. package/dist/cjs/signers/wasmLoader.d.ts.map +1 -0
  50. package/dist/cjs/signers/wasmLoader.js +166 -0
  51. package/dist/cjs/signers/wasmLoader.js.map +1 -0
  52. package/dist/cjs/types/account.d.ts +97 -0
  53. package/dist/cjs/types/account.d.ts.map +1 -0
  54. package/dist/cjs/types/account.js +3 -0
  55. package/dist/cjs/types/account.js.map +1 -0
  56. package/dist/cjs/types/action.d.ts +72 -0
  57. package/dist/cjs/types/action.d.ts.map +1 -0
  58. package/dist/cjs/types/action.js +23 -0
  59. package/dist/cjs/types/action.js.map +1 -0
  60. package/dist/cjs/types/activity.d.ts +73 -0
  61. package/dist/cjs/types/activity.d.ts.map +1 -0
  62. package/dist/cjs/types/activity.js +3 -0
  63. package/dist/cjs/types/activity.js.map +1 -0
  64. package/dist/cjs/types/asset.d.ts +122 -0
  65. package/dist/cjs/types/asset.d.ts.map +1 -0
  66. package/dist/cjs/types/asset.js +3 -0
  67. package/dist/cjs/types/asset.js.map +1 -0
  68. package/dist/cjs/types/fill.d.ts +29 -0
  69. package/dist/cjs/types/fill.d.ts.map +1 -0
  70. package/dist/cjs/types/fill.js +3 -0
  71. package/dist/cjs/types/fill.js.map +1 -0
  72. package/dist/cjs/types/index.d.ts +9 -0
  73. package/dist/cjs/types/index.d.ts.map +1 -0
  74. package/dist/cjs/types/index.js +25 -0
  75. package/dist/cjs/types/index.js.map +1 -0
  76. package/dist/cjs/types/order.d.ts +57 -0
  77. package/dist/cjs/types/order.d.ts.map +1 -0
  78. package/dist/cjs/types/order.js +3 -0
  79. package/dist/cjs/types/order.js.map +1 -0
  80. package/dist/cjs/types/submit.d.ts +39 -0
  81. package/dist/cjs/types/submit.d.ts.map +1 -0
  82. package/dist/cjs/types/submit.js +3 -0
  83. package/dist/cjs/types/submit.js.map +1 -0
  84. package/dist/cjs/types/ws.d.ts +84 -0
  85. package/dist/cjs/types/ws.d.ts.map +1 -0
  86. package/dist/cjs/types/ws.js +3 -0
  87. package/dist/cjs/types/ws.js.map +1 -0
  88. package/dist/cjs/utils/activityCursor.d.ts +12 -0
  89. package/dist/cjs/utils/activityCursor.d.ts.map +1 -0
  90. package/dist/cjs/utils/activityCursor.js +85 -0
  91. package/dist/cjs/utils/activityCursor.js.map +1 -0
  92. package/dist/cjs/utils/apiClient.d.ts +26 -0
  93. package/dist/cjs/utils/apiClient.d.ts.map +1 -0
  94. package/dist/cjs/utils/apiClient.js +94 -0
  95. package/dist/cjs/utils/apiClient.js.map +1 -0
  96. package/dist/cjs/utils/fetchDetailedAccount.d.ts +5 -0
  97. package/dist/cjs/utils/fetchDetailedAccount.d.ts.map +1 -0
  98. package/dist/cjs/utils/fetchDetailedAccount.js +22 -0
  99. package/dist/cjs/utils/fetchDetailedAccount.js.map +1 -0
  100. package/dist/cjs/utils/index.d.ts +16 -0
  101. package/dist/cjs/utils/index.d.ts.map +1 -0
  102. package/dist/cjs/utils/index.js +45 -0
  103. package/dist/cjs/utils/index.js.map +1 -0
  104. package/dist/cjs/utils/lighterAsset.d.ts +3 -0
  105. package/dist/cjs/utils/lighterAsset.d.ts.map +1 -0
  106. package/dist/cjs/utils/lighterAsset.js +12 -0
  107. package/dist/cjs/utils/lighterAsset.js.map +1 -0
  108. package/dist/cjs/utils/liquidation.d.ts +4 -0
  109. package/dist/cjs/utils/liquidation.d.ts.map +1 -0
  110. package/dist/cjs/utils/liquidation.js +16 -0
  111. package/dist/cjs/utils/liquidation.js.map +1 -0
  112. package/dist/cjs/utils/mapFill.d.ts +4 -0
  113. package/dist/cjs/utils/mapFill.d.ts.map +1 -0
  114. package/dist/cjs/utils/mapFill.js +61 -0
  115. package/dist/cjs/utils/mapFill.js.map +1 -0
  116. package/dist/cjs/utils/mapOpenPositions.d.ts +4 -0
  117. package/dist/cjs/utils/mapOpenPositions.d.ts.map +1 -0
  118. package/dist/cjs/utils/mapOpenPositions.js +9 -0
  119. package/dist/cjs/utils/mapOpenPositions.js.map +1 -0
  120. package/dist/cjs/utils/mapOrder.d.ts +15 -0
  121. package/dist/cjs/utils/mapOrder.d.ts.map +1 -0
  122. package/dist/cjs/utils/mapOrder.js +164 -0
  123. package/dist/cjs/utils/mapOrder.js.map +1 -0
  124. package/dist/cjs/utils/mapPosition.d.ts +4 -0
  125. package/dist/cjs/utils/mapPosition.d.ts.map +1 -0
  126. package/dist/cjs/utils/mapPosition.js +30 -0
  127. package/dist/cjs/utils/mapPosition.js.map +1 -0
  128. package/dist/cjs/utils/ohlcvInterval.d.ts +2 -0
  129. package/dist/cjs/utils/ohlcvInterval.d.ts.map +1 -0
  130. package/dist/cjs/utils/ohlcvInterval.js +26 -0
  131. package/dist/cjs/utils/ohlcvInterval.js.map +1 -0
  132. package/dist/cjs/utils/orderFormatting.d.ts +4 -0
  133. package/dist/cjs/utils/orderFormatting.d.ts.map +1 -0
  134. package/dist/cjs/utils/orderFormatting.js +23 -0
  135. package/dist/cjs/utils/orderFormatting.js.map +1 -0
  136. package/dist/cjs/utils/time.d.ts +3 -0
  137. package/dist/cjs/utils/time.d.ts.map +1 -0
  138. package/dist/cjs/utils/time.js +8 -0
  139. package/dist/cjs/utils/time.js.map +1 -0
  140. package/dist/cjs/utils/wireEncoding.d.ts +9 -0
  141. package/dist/cjs/utils/wireEncoding.d.ts.map +1 -0
  142. package/dist/cjs/utils/wireEncoding.js +87 -0
  143. package/dist/cjs/utils/wireEncoding.js.map +1 -0
  144. package/dist/cjs/websocket/LighterWsProvider.d.ts +45 -0
  145. package/dist/cjs/websocket/LighterWsProvider.d.ts.map +1 -0
  146. package/dist/cjs/websocket/LighterWsProvider.js +396 -0
  147. package/dist/cjs/websocket/LighterWsProvider.js.map +1 -0
  148. package/dist/esm/LighterProvider.d.ts +105 -0
  149. package/dist/esm/LighterProvider.d.ts.map +1 -0
  150. package/dist/esm/LighterProvider.js +684 -0
  151. package/dist/esm/LighterProvider.js.map +1 -0
  152. package/dist/esm/accountConfig.d.ts +14 -0
  153. package/dist/esm/accountConfig.d.ts.map +1 -0
  154. package/dist/esm/accountConfig.js +135 -0
  155. package/dist/esm/accountConfig.js.map +1 -0
  156. package/dist/esm/accountSummary.d.ts +12 -0
  157. package/dist/esm/accountSummary.d.ts.map +1 -0
  158. package/dist/esm/accountSummary.js +14 -0
  159. package/dist/esm/accountSummary.js.map +1 -0
  160. package/dist/esm/constants.d.ts +74 -0
  161. package/dist/esm/constants.d.ts.map +1 -0
  162. package/dist/esm/constants.js +73 -0
  163. package/dist/esm/constants.js.map +1 -0
  164. package/dist/esm/index.d.ts +22 -0
  165. package/dist/esm/index.d.ts.map +1 -0
  166. package/dist/esm/index.js +22 -0
  167. package/dist/esm/index.js.map +1 -0
  168. package/dist/esm/package.json +1 -0
  169. package/dist/esm/signers/LighterKeyStore.d.ts +24 -0
  170. package/dist/esm/signers/LighterKeyStore.d.ts.map +1 -0
  171. package/dist/esm/signers/LighterKeyStore.js +55 -0
  172. package/dist/esm/signers/LighterKeyStore.js.map +1 -0
  173. package/dist/esm/signers/LighterReadOnlyTokenManager.d.ts +157 -0
  174. package/dist/esm/signers/LighterReadOnlyTokenManager.d.ts.map +1 -0
  175. package/dist/esm/signers/LighterReadOnlyTokenManager.js +194 -0
  176. package/dist/esm/signers/LighterReadOnlyTokenManager.js.map +1 -0
  177. package/dist/esm/signers/LighterSigner.d.ts +94 -0
  178. package/dist/esm/signers/LighterSigner.d.ts.map +1 -0
  179. package/dist/esm/signers/LighterSigner.js +228 -0
  180. package/dist/esm/signers/LighterSigner.js.map +1 -0
  181. package/dist/esm/signers/createAuthToken.d.ts +36 -0
  182. package/dist/esm/signers/createAuthToken.d.ts.map +1 -0
  183. package/dist/esm/signers/createAuthToken.js +42 -0
  184. package/dist/esm/signers/createAuthToken.js.map +1 -0
  185. package/dist/esm/signers/index.d.ts +11 -0
  186. package/dist/esm/signers/index.d.ts.map +1 -0
  187. package/dist/esm/signers/index.js +7 -0
  188. package/dist/esm/signers/index.js.map +1 -0
  189. package/dist/esm/signers/signActions.d.ts +54 -0
  190. package/dist/esm/signers/signActions.d.ts.map +1 -0
  191. package/dist/esm/signers/signActions.js +193 -0
  192. package/dist/esm/signers/signActions.js.map +1 -0
  193. package/dist/esm/signers/wasmLoader.d.ts +79 -0
  194. package/dist/esm/signers/wasmLoader.d.ts.map +1 -0
  195. package/dist/esm/signers/wasmLoader.js +180 -0
  196. package/dist/esm/signers/wasmLoader.js.map +1 -0
  197. package/dist/esm/types/account.d.ts +113 -0
  198. package/dist/esm/types/account.d.ts.map +1 -0
  199. package/dist/esm/types/account.js +4 -0
  200. package/dist/esm/types/account.js.map +1 -0
  201. package/dist/esm/types/action.d.ts +146 -0
  202. package/dist/esm/types/action.d.ts.map +1 -0
  203. package/dist/esm/types/action.js +50 -0
  204. package/dist/esm/types/action.js.map +1 -0
  205. package/dist/esm/types/activity.d.ts +83 -0
  206. package/dist/esm/types/activity.d.ts.map +1 -0
  207. package/dist/esm/types/activity.js +4 -0
  208. package/dist/esm/types/activity.js.map +1 -0
  209. package/dist/esm/types/asset.d.ts +134 -0
  210. package/dist/esm/types/asset.d.ts.map +1 -0
  211. package/dist/esm/types/asset.js +3 -0
  212. package/dist/esm/types/asset.js.map +1 -0
  213. package/dist/esm/types/fill.d.ts +31 -0
  214. package/dist/esm/types/fill.d.ts.map +1 -0
  215. package/dist/esm/types/fill.js +3 -0
  216. package/dist/esm/types/fill.js.map +1 -0
  217. package/dist/esm/types/index.d.ts +9 -0
  218. package/dist/esm/types/index.d.ts.map +1 -0
  219. package/dist/esm/types/index.js +12 -0
  220. package/dist/esm/types/index.js.map +1 -0
  221. package/dist/esm/types/order.d.ts +61 -0
  222. package/dist/esm/types/order.d.ts.map +1 -0
  223. package/dist/esm/types/order.js +3 -0
  224. package/dist/esm/types/order.js.map +1 -0
  225. package/dist/esm/types/submit.d.ts +76 -0
  226. package/dist/esm/types/submit.d.ts.map +1 -0
  227. package/dist/esm/types/submit.js +3 -0
  228. package/dist/esm/types/submit.js.map +1 -0
  229. package/dist/esm/types/ws.d.ts +124 -0
  230. package/dist/esm/types/ws.d.ts.map +1 -0
  231. package/dist/esm/types/ws.js +11 -0
  232. package/dist/esm/types/ws.js.map +1 -0
  233. package/dist/esm/utils/activityCursor.d.ts +51 -0
  234. package/dist/esm/utils/activityCursor.d.ts.map +1 -0
  235. package/dist/esm/utils/activityCursor.js +99 -0
  236. package/dist/esm/utils/activityCursor.js.map +1 -0
  237. package/dist/esm/utils/apiClient.d.ts +65 -0
  238. package/dist/esm/utils/apiClient.d.ts.map +1 -0
  239. package/dist/esm/utils/apiClient.js +131 -0
  240. package/dist/esm/utils/apiClient.js.map +1 -0
  241. package/dist/esm/utils/fetchDetailedAccount.d.ts +11 -0
  242. package/dist/esm/utils/fetchDetailedAccount.d.ts.map +1 -0
  243. package/dist/esm/utils/fetchDetailedAccount.js +24 -0
  244. package/dist/esm/utils/fetchDetailedAccount.js.map +1 -0
  245. package/dist/esm/utils/index.d.ts +16 -0
  246. package/dist/esm/utils/index.d.ts.map +1 -0
  247. package/dist/esm/utils/index.js +17 -0
  248. package/dist/esm/utils/index.js.map +1 -0
  249. package/dist/esm/utils/lighterAsset.d.ts +4 -0
  250. package/dist/esm/utils/lighterAsset.d.ts.map +1 -0
  251. package/dist/esm/utils/lighterAsset.js +9 -0
  252. package/dist/esm/utils/lighterAsset.js.map +1 -0
  253. package/dist/esm/utils/liquidation.d.ts +22 -0
  254. package/dist/esm/utils/liquidation.d.ts.map +1 -0
  255. package/dist/esm/utils/liquidation.js +31 -0
  256. package/dist/esm/utils/liquidation.js.map +1 -0
  257. package/dist/esm/utils/mapFill.d.ts +10 -0
  258. package/dist/esm/utils/mapFill.d.ts.map +1 -0
  259. package/dist/esm/utils/mapFill.js +72 -0
  260. package/dist/esm/utils/mapFill.js.map +1 -0
  261. package/dist/esm/utils/mapOpenPositions.d.ts +11 -0
  262. package/dist/esm/utils/mapOpenPositions.d.ts.map +1 -0
  263. package/dist/esm/utils/mapOpenPositions.js +12 -0
  264. package/dist/esm/utils/mapOpenPositions.js.map +1 -0
  265. package/dist/esm/utils/mapOrder.d.ts +72 -0
  266. package/dist/esm/utils/mapOrder.d.ts.map +1 -0
  267. package/dist/esm/utils/mapOrder.js +219 -0
  268. package/dist/esm/utils/mapOrder.js.map +1 -0
  269. package/dist/esm/utils/mapPosition.d.ts +9 -0
  270. package/dist/esm/utils/mapPosition.d.ts.map +1 -0
  271. package/dist/esm/utils/mapPosition.js +34 -0
  272. package/dist/esm/utils/mapPosition.js.map +1 -0
  273. package/dist/esm/utils/ohlcvInterval.d.ts +3 -0
  274. package/dist/esm/utils/ohlcvInterval.d.ts.map +1 -0
  275. package/dist/esm/utils/ohlcvInterval.js +30 -0
  276. package/dist/esm/utils/ohlcvInterval.js.map +1 -0
  277. package/dist/esm/utils/orderFormatting.d.ts +29 -0
  278. package/dist/esm/utils/orderFormatting.d.ts.map +1 -0
  279. package/dist/esm/utils/orderFormatting.js +41 -0
  280. package/dist/esm/utils/orderFormatting.js.map +1 -0
  281. package/dist/esm/utils/time.d.ts +5 -0
  282. package/dist/esm/utils/time.d.ts.map +1 -0
  283. package/dist/esm/utils/time.js +5 -0
  284. package/dist/esm/utils/time.js.map +1 -0
  285. package/dist/esm/utils/wireEncoding.d.ts +72 -0
  286. package/dist/esm/utils/wireEncoding.d.ts.map +1 -0
  287. package/dist/esm/utils/wireEncoding.js +129 -0
  288. package/dist/esm/utils/wireEncoding.js.map +1 -0
  289. package/dist/esm/websocket/LighterWsProvider.d.ts +97 -0
  290. package/dist/esm/websocket/LighterWsProvider.d.ts.map +1 -0
  291. package/dist/esm/websocket/LighterWsProvider.js +489 -0
  292. package/dist/esm/websocket/LighterWsProvider.js.map +1 -0
  293. package/dist/types/LighterProvider.d.ts +105 -0
  294. package/dist/types/LighterProvider.d.ts.map +1 -0
  295. package/dist/types/accountConfig.d.ts +14 -0
  296. package/dist/types/accountConfig.d.ts.map +1 -0
  297. package/dist/types/accountSummary.d.ts +12 -0
  298. package/dist/types/accountSummary.d.ts.map +1 -0
  299. package/dist/types/constants.d.ts +74 -0
  300. package/dist/types/constants.d.ts.map +1 -0
  301. package/dist/types/index.d.ts +22 -0
  302. package/dist/types/index.d.ts.map +1 -0
  303. package/dist/types/signers/LighterKeyStore.d.ts +24 -0
  304. package/dist/types/signers/LighterKeyStore.d.ts.map +1 -0
  305. package/dist/types/signers/LighterReadOnlyTokenManager.d.ts +157 -0
  306. package/dist/types/signers/LighterReadOnlyTokenManager.d.ts.map +1 -0
  307. package/dist/types/signers/LighterSigner.d.ts +94 -0
  308. package/dist/types/signers/LighterSigner.d.ts.map +1 -0
  309. package/dist/types/signers/createAuthToken.d.ts +36 -0
  310. package/dist/types/signers/createAuthToken.d.ts.map +1 -0
  311. package/dist/types/signers/index.d.ts +11 -0
  312. package/dist/types/signers/index.d.ts.map +1 -0
  313. package/dist/types/signers/signActions.d.ts +54 -0
  314. package/dist/types/signers/signActions.d.ts.map +1 -0
  315. package/dist/types/signers/wasmLoader.d.ts +79 -0
  316. package/dist/types/signers/wasmLoader.d.ts.map +1 -0
  317. package/dist/types/types/account.d.ts +113 -0
  318. package/dist/types/types/account.d.ts.map +1 -0
  319. package/dist/types/types/action.d.ts +146 -0
  320. package/dist/types/types/action.d.ts.map +1 -0
  321. package/dist/types/types/activity.d.ts +83 -0
  322. package/dist/types/types/activity.d.ts.map +1 -0
  323. package/dist/types/types/asset.d.ts +134 -0
  324. package/dist/types/types/asset.d.ts.map +1 -0
  325. package/dist/types/types/fill.d.ts +31 -0
  326. package/dist/types/types/fill.d.ts.map +1 -0
  327. package/dist/types/types/index.d.ts +9 -0
  328. package/dist/types/types/index.d.ts.map +1 -0
  329. package/dist/types/types/order.d.ts +61 -0
  330. package/dist/types/types/order.d.ts.map +1 -0
  331. package/dist/types/types/submit.d.ts +76 -0
  332. package/dist/types/types/submit.d.ts.map +1 -0
  333. package/dist/types/types/ws.d.ts +124 -0
  334. package/dist/types/types/ws.d.ts.map +1 -0
  335. package/dist/types/utils/activityCursor.d.ts +51 -0
  336. package/dist/types/utils/activityCursor.d.ts.map +1 -0
  337. package/dist/types/utils/apiClient.d.ts +65 -0
  338. package/dist/types/utils/apiClient.d.ts.map +1 -0
  339. package/dist/types/utils/fetchDetailedAccount.d.ts +11 -0
  340. package/dist/types/utils/fetchDetailedAccount.d.ts.map +1 -0
  341. package/dist/types/utils/index.d.ts +16 -0
  342. package/dist/types/utils/index.d.ts.map +1 -0
  343. package/dist/types/utils/lighterAsset.d.ts +4 -0
  344. package/dist/types/utils/lighterAsset.d.ts.map +1 -0
  345. package/dist/types/utils/liquidation.d.ts +22 -0
  346. package/dist/types/utils/liquidation.d.ts.map +1 -0
  347. package/dist/types/utils/mapFill.d.ts +10 -0
  348. package/dist/types/utils/mapFill.d.ts.map +1 -0
  349. package/dist/types/utils/mapOpenPositions.d.ts +11 -0
  350. package/dist/types/utils/mapOpenPositions.d.ts.map +1 -0
  351. package/dist/types/utils/mapOrder.d.ts +72 -0
  352. package/dist/types/utils/mapOrder.d.ts.map +1 -0
  353. package/dist/types/utils/mapPosition.d.ts +9 -0
  354. package/dist/types/utils/mapPosition.d.ts.map +1 -0
  355. package/dist/types/utils/ohlcvInterval.d.ts +3 -0
  356. package/dist/types/utils/ohlcvInterval.d.ts.map +1 -0
  357. package/dist/types/utils/orderFormatting.d.ts +29 -0
  358. package/dist/types/utils/orderFormatting.d.ts.map +1 -0
  359. package/dist/types/utils/time.d.ts +5 -0
  360. package/dist/types/utils/time.d.ts.map +1 -0
  361. package/dist/types/utils/wireEncoding.d.ts +72 -0
  362. package/dist/types/utils/wireEncoding.d.ts.map +1 -0
  363. package/dist/types/websocket/LighterWsProvider.d.ts +97 -0
  364. package/dist/types/websocket/LighterWsProvider.d.ts.map +1 -0
  365. package/dist/wasm/lighter-signer.wasm +0 -0
  366. package/dist/wasm/wasm_exec.js +575 -0
  367. package/package.json +67 -0
  368. package/src/LighterProvider.ts +1167 -0
  369. package/src/accountConfig.ts +162 -0
  370. package/src/accountSummary.ts +22 -0
  371. package/src/constants.ts +89 -0
  372. package/src/index.ts +37 -0
  373. package/src/signers/LighterKeyStore.ts +79 -0
  374. package/src/signers/LighterReadOnlyTokenManager.ts +340 -0
  375. package/src/signers/LighterSigner.ts +400 -0
  376. package/src/signers/createAuthToken.ts +69 -0
  377. package/src/signers/index.ts +36 -0
  378. package/src/signers/signActions.ts +311 -0
  379. package/src/signers/wasmLoader.ts +293 -0
  380. package/src/types/account.ts +123 -0
  381. package/src/types/action.ts +163 -0
  382. package/src/types/activity.ts +94 -0
  383. package/src/types/asset.ts +146 -0
  384. package/src/types/fill.ts +42 -0
  385. package/src/types/index.ts +12 -0
  386. package/src/types/order.ts +65 -0
  387. package/src/types/submit.ts +83 -0
  388. package/src/types/ws.ts +136 -0
  389. package/src/utils/activityCursor.ts +150 -0
  390. package/src/utils/apiClient.ts +185 -0
  391. package/src/utils/fetchDetailedAccount.ts +46 -0
  392. package/src/utils/index.ts +38 -0
  393. package/src/utils/lighterAsset.ts +10 -0
  394. package/src/utils/liquidation.ts +39 -0
  395. package/src/utils/mapFill.ts +108 -0
  396. package/src/utils/mapOpenPositions.ts +18 -0
  397. package/src/utils/mapOrder.ts +258 -0
  398. package/src/utils/mapPosition.ts +42 -0
  399. package/src/utils/ohlcvInterval.ts +34 -0
  400. package/src/utils/orderFormatting.ts +46 -0
  401. package/src/utils/time.ts +6 -0
  402. package/src/utils/wireEncoding.ts +179 -0
  403. package/src/websocket/LighterWsProvider.ts +725 -0
  404. package/wasm/lighter-signer.wasm +0 -0
  405. package/wasm/wasm_exec.js +575 -0
@@ -0,0 +1,725 @@
1
+ import {
2
+ cachePromise,
3
+ getMarketRegistry,
4
+ type MarketRegistry,
5
+ type PerpsSDKClient,
6
+ type ProviderGetQuoteParams,
7
+ type QuoteListener,
8
+ ReconnectingWebSocket,
9
+ resolveRetryPolicy,
10
+ resolveSubscribeQuote,
11
+ WsProviderBase,
12
+ type WsProviderFactory,
13
+ wsLog,
14
+ } from '@lifi/perps-sdk'
15
+ import type { Fill, Position, Subscription } from '@lifi/perps-types'
16
+ import type { Address } from 'viem'
17
+ import { LIGHTER_BASE_FEE_TIER, LIGHTER_PROVIDER_KEY } from '../constants.js'
18
+ import type {
19
+ LtAccountPosition,
20
+ LtOrder,
21
+ LtTrade,
22
+ LtWsAccountAllOrdersMessage,
23
+ LtWsAccountAllPositionsMessage,
24
+ LtWsAccountAllTradesMessage,
25
+ LtWsMarketStats,
26
+ LtWsMarketStatsAllMessage,
27
+ LtWsMessage,
28
+ LtWsOrderBook,
29
+ LtWsOrderBookMessage,
30
+ LtWsSpotMarketStats,
31
+ LtWsSpotMarketStatsAllMessage,
32
+ } from '../types/index.js'
33
+ import { LIGHTER_RETRY_DEFAULTS, LighterApiClient } from '../utils/apiClient.js'
34
+ import {
35
+ classifyAndMapOrders,
36
+ fetchDetailedAccount,
37
+ mapFill,
38
+ mapPosition,
39
+ } from '../utils/index.js'
40
+
41
+ // Public channels: `prices` (market_stats/all + spot_market_stats/all),
42
+ // `orderbook` (order_book/N).
43
+ // Authenticated channels (require an `authProvider` option):
44
+ // - orderUpdates → account_all_orders/{account_index}
45
+ // - fills → account_all_trades/{account_index}
46
+ // - positions → account_all_positions/{account_index}
47
+ //
48
+ // Auth pattern (per Lighter WS spec): the subscribe payload carries the
49
+ // token directly — `{ type: "subscribe", channel: "...", auth: "<token>" }`.
50
+ // Tokens are created via the Lighter WASM signer (CreateAuthToken) by the
51
+ // caller; we re-request a fresh token on every subscribe send so reconnects
52
+ // after the original token expires automatically pick up a new one.
53
+ //
54
+ // account_index is resolved per-address via `/api/v1/account?by=l1_address`
55
+ // and cached for the lifetime of the provider — Lighter's account index is
56
+ // stable for a given L1 wallet.
57
+ //
58
+ // The market identity for each `market_id` is resolved from the shared
59
+ // MarketRegistry. The canonical `Market.id` for Lighter is `String(market_id)`
60
+ // ("0", "1", …); the full market identity (base/quote `Asset`) is carried
61
+ // verbatim onto mapped orders/fills/positions.
62
+ //
63
+ // Orderbook is stateful: the first message is a full snapshot, subsequent
64
+ // messages are deltas where size=0 deletes a level.
65
+
66
+ const DEFAULT_WS_URL = 'wss://mainnet.zklighter.elliot.ai/stream'
67
+ const DEFAULT_REST_URL = 'https://mainnet.zklighter.elliot.ai'
68
+
69
+ const LIGHTER_AUTH_CHANNEL = {
70
+ orderUpdates: 'account_all_orders',
71
+ fills: 'account_all_trades',
72
+ positions: 'account_all_positions',
73
+ } as const
74
+
75
+ /** Channels whose handlers resolve market identity from the registry. */
76
+ function channelNeedsMarkets(channel: Subscription['channel']): boolean {
77
+ return (
78
+ channel === 'orderUpdates' || channel === 'fills' || channel === 'positions'
79
+ )
80
+ }
81
+
82
+ /**
83
+ * Creates a fresh Lighter auth token for the given L1 address, or returns
84
+ * `undefined` if no API key is registered. Called both on initial subscribe
85
+ * and on every reconnect, so it must always return a token valid for at
86
+ * least the next few minutes.
87
+ *
88
+ * @public
89
+ */
90
+ export type LighterAuthProvider = (
91
+ address: Address
92
+ ) => Promise<string | undefined>
93
+
94
+ interface SubState {
95
+ /** Raw Lighter WS channel name (e.g. `order_book/5`, `account_all_orders/42`). */
96
+ channel: string
97
+ /** L1 address that triggered this sub (auth channels only). */
98
+ address?: Address
99
+ /** Whether this channel needs an `auth` field on each subscribe send. */
100
+ needsAuth: boolean
101
+ }
102
+
103
+ interface OrderbookState {
104
+ bids: Map<string, string>
105
+ asks: Map<string, string>
106
+ assetId: string
107
+ }
108
+
109
+ /**
110
+ * Options for {@link lighterWsProvider} / {@link LighterWsProvider}.
111
+ *
112
+ * @public
113
+ */
114
+ export interface LighterWsProviderOptions {
115
+ /** REST base URL for `/api/v1/account` lookups. Defaults to mainnet. */
116
+ restUrl?: string
117
+ /**
118
+ * Async function returning a Lighter auth token for an address. Required
119
+ * for authenticated channels (orderUpdates, positions). Without it those
120
+ * subscriptions will throw at subscribe time.
121
+ */
122
+ authProvider?: LighterAuthProvider
123
+ }
124
+
125
+ /**
126
+ * Lighter realtime WS provider (extends {@link WsProviderBase}): subscribes to
127
+ * Lighter's WS channels (orderbook, prices, orders, positions), attaching auth
128
+ * tokens to gated channels. Construct via {@link lighterWsProvider}.
129
+ *
130
+ * @public
131
+ */
132
+ export class LighterWsProvider extends WsProviderBase<SubState> {
133
+ private readonly api: LighterApiClient
134
+ private readonly authProvider: LighterAuthProvider | undefined
135
+ private readonly client: PerpsSDKClient | undefined
136
+
137
+ private readonly orderbooks = new Map<number, OrderbookState>()
138
+ private lastPricesByAssetId: Record<string, string> = {}
139
+
140
+ /**
141
+ * `address → market_id → Position`. `subscribed/...` frames reseed it and
142
+ * `update/...` frames upsert into it (zero size deletes), so every emission
143
+ * is a full open-position snapshot per the `PositionsEvent` contract.
144
+ */
145
+ private readonly positionsByAddress = new Map<string, Map<number, Position>>()
146
+
147
+ private readonly registry: MarketRegistry | undefined
148
+
149
+ private readonly accountIndexCache = new Map<string, number>()
150
+ private readonly accountIndexPromises = new Map<string, Promise<number>>()
151
+
152
+ constructor(
153
+ wsUrl: string = DEFAULT_WS_URL,
154
+ providerKey = 'lighter',
155
+ options: LighterWsProviderOptions = {},
156
+ client?: PerpsSDKClient
157
+ ) {
158
+ super(
159
+ new ReconnectingWebSocket(wsUrl, { pingPayload: '{"type":"ping"}' }),
160
+ providerKey
161
+ )
162
+ this.api = new LighterApiClient(options.restUrl ?? DEFAULT_REST_URL, {
163
+ policy: resolveRetryPolicy(
164
+ LIGHTER_RETRY_DEFAULTS,
165
+ client?.config.retry,
166
+ LIGHTER_PROVIDER_KEY
167
+ ),
168
+ fetchImpl: client?.config.fetch,
169
+ })
170
+ this.authProvider = options.authProvider
171
+ this.client = client
172
+ this.registry = client && getMarketRegistry(client, providerKey)
173
+ }
174
+
175
+ async subscribeQuote(
176
+ params: ProviderGetQuoteParams,
177
+ onQuote: QuoteListener
178
+ ): Promise<() => void> {
179
+ const client = this.client
180
+ if (client === undefined) {
181
+ throw new Error(
182
+ 'LighterWsProvider: PerpsSDKClient not provided; cannot stream quotes. ' +
183
+ 'Construct via `lighterWsProvider({...})` and register with PerpsWsClient.'
184
+ )
185
+ }
186
+ return resolveSubscribeQuote(
187
+ client,
188
+ this.providerKey,
189
+ this,
190
+ params,
191
+ LIGHTER_BASE_FEE_TIER,
192
+ onQuote
193
+ )
194
+ }
195
+
196
+ protected async openChannel(sub: Subscription): Promise<() => void> {
197
+ // Lighter has no live OHLC channel — there's nothing to subscribe to.
198
+ // Return a no-op unsubscribe so the caller's UX (chart still rendering
199
+ // from REST history + price-tick mid line) is unaffected, instead of
200
+ // throwing and surfacing a console error on every chart mount.
201
+ if (sub.channel === 'candle') {
202
+ return () => {}
203
+ }
204
+
205
+ // Only the auth channels (orders/fills/positions) resolve markets.
206
+ // `prices`/`orderbook` are keyed purely by `String(market_id)`, so gating
207
+ // them on the registry sync would let a failed `/markets` fetch kill live
208
+ // price ticks.
209
+ if (channelNeedsMarkets(sub.channel)) {
210
+ await this.registry?.sync()
211
+ }
212
+
213
+ const wireChannels = await this.resolveChannel(sub)
214
+
215
+ // Registry keyed by the wire channel (unique per sub), so replay-failure
216
+ // logs name the channel the venue knows. A logical sub may fan out to
217
+ // several wire channels (e.g. `prices`), each registered independently.
218
+ for (const { channel, needsAuth, address } of wireChannels) {
219
+ await this.registerSub(channel, { channel, needsAuth, address })
220
+ }
221
+ await this.rws.ready()
222
+
223
+ return () => {
224
+ for (const { channel } of wireChannels) {
225
+ this.unregisterSub(channel)
226
+ this.rws.send(JSON.stringify({ type: 'unsubscribe', channel }))
227
+ }
228
+ if (sub.channel === 'orderbook') {
229
+ const id = Number(sub.marketId)
230
+ if (Number.isFinite(id)) {
231
+ this.orderbooks.delete(id)
232
+ }
233
+ }
234
+ if (sub.channel === 'positions') {
235
+ this.positionsByAddress.delete(sub.address.toLowerCase())
236
+ }
237
+ }
238
+ }
239
+
240
+ protected override onClose(): void {
241
+ this.orderbooks.clear()
242
+ this.lastPricesByAssetId = {}
243
+ this.positionsByAddress.clear()
244
+ }
245
+
246
+ protected async sendSubscribe({
247
+ channel,
248
+ needsAuth,
249
+ address,
250
+ }: SubState): Promise<void> {
251
+ const payload: { type: 'subscribe'; channel: string; auth?: string } = {
252
+ type: 'subscribe',
253
+ channel,
254
+ }
255
+ if (needsAuth) {
256
+ if (!this.authProvider || !address) {
257
+ throw new Error(
258
+ `Lighter WS channel '${channel}' requires authentication but no authProvider was supplied.`
259
+ )
260
+ }
261
+ const token = await this.authProvider(address)
262
+ if (!token) {
263
+ throw new Error(
264
+ `Lighter WS channel '${channel}' requires authentication but no token was available for ${address}.`
265
+ )
266
+ }
267
+ payload.auth = token
268
+ }
269
+ this.rws.send(JSON.stringify(payload))
270
+ }
271
+
272
+ protected toKey(sub: Subscription): string {
273
+ switch (sub.channel) {
274
+ case 'prices':
275
+ return 'prices'
276
+ case 'orderbook':
277
+ return `orderbook:${sub.marketId}`
278
+ case 'orderUpdates':
279
+ return `orderUpdates:${sub.address.toLowerCase()}`
280
+ case 'fills':
281
+ return `fills:${sub.address.toLowerCase()}`
282
+ case 'positions':
283
+ return `positions:${sub.address.toLowerCase()}`
284
+ // No live wire sub (openChannel returns a no-op), but a stable key is
285
+ // still needed so the base's fan-out registry can track/release it.
286
+ case 'candle':
287
+ return `candle:${sub.marketId}:${sub.interval}`
288
+ case 'spotBalances':
289
+ throw new Error(`Lighter WS does not support channel: ${sub.channel}.`)
290
+ }
291
+ }
292
+
293
+ private async resolveChannel(sub: Subscription): Promise<
294
+ Array<{
295
+ channel: string
296
+ needsAuth: boolean
297
+ address?: Address
298
+ }>
299
+ > {
300
+ if (sub.channel === 'prices') {
301
+ // Lighter splits stats across two wire channels: perp markets on
302
+ // `market_stats/all`, spot markets on `spot_market_stats/all`. Both feed
303
+ // the single aggregated `prices` emit.
304
+ return [
305
+ { channel: 'market_stats/all', needsAuth: false },
306
+ { channel: 'spot_market_stats/all', needsAuth: false },
307
+ ]
308
+ }
309
+ if (sub.channel === 'orderbook') {
310
+ const id = Number(sub.marketId)
311
+ if (!Number.isFinite(id)) {
312
+ throw new Error(
313
+ `Lighter WS: unknown market for marketId '${sub.marketId}'. ` +
314
+ 'MarketId must be a numeric market_id string.'
315
+ )
316
+ }
317
+ return [{ channel: `order_book/${id}`, needsAuth: false }]
318
+ }
319
+ if (
320
+ sub.channel === 'orderUpdates' ||
321
+ sub.channel === 'fills' ||
322
+ sub.channel === 'positions'
323
+ ) {
324
+ const accountIndex = await this.resolveAccountIndex(sub.address)
325
+ const lighterChannel = LIGHTER_AUTH_CHANNEL[sub.channel]
326
+ // `account_all_trades` is publicly readable per the Lighter WS spec —
327
+ // an auth token only filters events to the user's own account, which
328
+ // we don't currently use to scope further. Skip the token here so a
329
+ // user without a registered API key still gets their fill stream.
330
+ const needsAuth = sub.channel !== 'fills'
331
+ return [
332
+ {
333
+ channel: `${lighterChannel}/${accountIndex}`,
334
+ needsAuth,
335
+ address: sub.address,
336
+ },
337
+ ]
338
+ }
339
+ throw new Error(
340
+ `Lighter WS does not support channel: ${(sub as { channel: string }).channel}.`
341
+ )
342
+ }
343
+
344
+ private async resolveAccountIndex(address: Address): Promise<number> {
345
+ const addressKey = address.toLowerCase()
346
+ const cached = this.accountIndexCache.get(addressKey)
347
+ if (cached !== undefined) {
348
+ return cached
349
+ }
350
+ const idx = await cachePromise(
351
+ () => this.accountIndexPromises.get(addressKey),
352
+ (p) => {
353
+ if (p === undefined) {
354
+ this.accountIndexPromises.delete(addressKey)
355
+ } else {
356
+ this.accountIndexPromises.set(addressKey, p)
357
+ }
358
+ },
359
+ () => this.fetchAccountIndex(address)
360
+ )
361
+ this.accountIndexCache.set(addressKey, idx)
362
+ return idx
363
+ }
364
+
365
+ private async fetchAccountIndex(address: Address): Promise<number> {
366
+ const account = await fetchDetailedAccount(this.api, address)
367
+ return account.index
368
+ }
369
+
370
+ protected handleMessage(raw: string): void {
371
+ let msg: LtWsMessage
372
+ try {
373
+ msg = JSON.parse(raw) as LtWsMessage
374
+ } catch {
375
+ wsLog.parseFailure(LIGHTER_PROVIDER_KEY, raw)
376
+ return
377
+ }
378
+
379
+ if (!isValidLighterFrame(msg)) {
380
+ wsLog.parseFailure(LIGHTER_PROVIDER_KEY, raw)
381
+ return
382
+ }
383
+
384
+ try {
385
+ this.dispatch(msg)
386
+ } catch (error) {
387
+ wsLog.handlerFailure(LIGHTER_PROVIDER_KEY, error)
388
+ }
389
+ }
390
+
391
+ private dispatch(msg: LtWsMessage): void {
392
+ if (msg.type === 'ping') {
393
+ this.rws.send(JSON.stringify({ type: 'pong' }))
394
+ return
395
+ }
396
+
397
+ if (
398
+ msg.type === 'subscribed/market_stats' ||
399
+ msg.type === 'update/market_stats'
400
+ ) {
401
+ this.handleMarketStats((msg as LtWsMarketStatsAllMessage).market_stats)
402
+ return
403
+ }
404
+
405
+ if (
406
+ msg.type === 'subscribed/spot_market_stats' ||
407
+ msg.type === 'update/spot_market_stats'
408
+ ) {
409
+ this.handleMarketStats(
410
+ (msg as LtWsSpotMarketStatsAllMessage).spot_market_stats
411
+ )
412
+ return
413
+ }
414
+
415
+ if (
416
+ msg.type === 'subscribed/order_book' ||
417
+ msg.type === 'update/order_book'
418
+ ) {
419
+ this.handleOrderBook(
420
+ msg as LtWsOrderBookMessage,
421
+ msg.type === 'subscribed/order_book'
422
+ )
423
+ return
424
+ }
425
+
426
+ if (
427
+ msg.type === 'subscribed/account_all_orders' ||
428
+ msg.type === 'update/account_all_orders'
429
+ ) {
430
+ this.handleAccountOrders(msg as LtWsAccountAllOrdersMessage)
431
+ return
432
+ }
433
+
434
+ if (
435
+ msg.type === 'subscribed/account_all_trades' ||
436
+ msg.type === 'update/account_all_trades'
437
+ ) {
438
+ this.handleAccountTrades(msg as LtWsAccountAllTradesMessage)
439
+ return
440
+ }
441
+
442
+ if (
443
+ msg.type === 'subscribed/account_all_positions' ||
444
+ msg.type === 'update/account_all_positions'
445
+ ) {
446
+ this.handleAccountPositions(
447
+ msg as LtWsAccountAllPositionsMessage,
448
+ msg.type === 'subscribed/account_all_positions'
449
+ )
450
+ return
451
+ }
452
+ }
453
+
454
+ private handleAccountOrders(msg: LtWsAccountAllOrdersMessage): void {
455
+ const address = this.addressFromChannel(msg.channel, 'account_all_orders')
456
+ if (!address) {
457
+ return
458
+ }
459
+ const raw = collectAuthChannelItems<LtOrder>(msg, 'orders')
460
+ // A registry miss warns once and schedules a cooldown-gated refetch (the
461
+ // id may have listed after the snapshot); the order is skipped.
462
+ const data = classifyAndMapOrders(raw, (marketIndex) =>
463
+ this.registry?.get(String(marketIndex))
464
+ )
465
+ this.emit(`orderUpdates:${address}`, {
466
+ channel: 'orderUpdates',
467
+ data,
468
+ })
469
+ }
470
+
471
+ private handleAccountTrades(msg: LtWsAccountAllTradesMessage): void {
472
+ const address = this.addressFromChannel(msg.channel, 'account_all_trades')
473
+ if (!address) {
474
+ return
475
+ }
476
+ const accountIndex = this.accountIndexCache.get(address)
477
+ if (accountIndex === undefined) {
478
+ return
479
+ }
480
+ const raw = collectAuthChannelItems<LtTrade>(msg, 'trades')
481
+ const fills: Fill[] = raw.flatMap((t) => {
482
+ const market = this.registry?.get(String(t.market_id))
483
+ return market ? [mapFill(t, accountIndex, market)] : []
484
+ })
485
+ this.emit(`fills:${address}`, { channel: 'fills', data: fills })
486
+ }
487
+
488
+ private handleAccountPositions(
489
+ msg: LtWsAccountAllPositionsMessage,
490
+ isSnapshot: boolean
491
+ ): void {
492
+ const address = this.addressFromChannel(
493
+ msg.channel,
494
+ 'account_all_positions'
495
+ )
496
+ if (!address) {
497
+ return
498
+ }
499
+ const raw = collectAuthChannelItems<LtAccountPosition>(msg, 'positions')
500
+ let state = this.positionsByAddress.get(address)
501
+ if (!state || isSnapshot) {
502
+ state = new Map()
503
+ this.positionsByAddress.set(address, state)
504
+ }
505
+ for (const p of raw) {
506
+ if (Number.parseFloat(p.position) === 0) {
507
+ state.delete(p.market_id)
508
+ } else {
509
+ const market = this.registry?.get(String(p.market_id))
510
+ if (market) {
511
+ state.set(p.market_id, mapPosition(p, market))
512
+ }
513
+ }
514
+ }
515
+ this.emit(`positions:${address}`, {
516
+ channel: 'positions',
517
+ data: [...state.values()],
518
+ })
519
+ }
520
+
521
+ /**
522
+ * Reverse-lookup the L1 address from an auth-channel message. The subscribe
523
+ * payload uses `/` (e.g. `account_all_orders/42`) but the server sends
524
+ * responses with `:` (e.g. `account_all_orders:42`) — both forms are
525
+ * accepted here so reconnect resubscriptions and live updates both route
526
+ * correctly.
527
+ */
528
+ private addressFromChannel(
529
+ channel: string | undefined,
530
+ prefix: string
531
+ ): string | null {
532
+ if (!channel?.startsWith(prefix)) {
533
+ return null
534
+ }
535
+ const sep = channel[prefix.length]
536
+ if (sep !== '/' && sep !== ':') {
537
+ return null
538
+ }
539
+ const idx = Number(channel.slice(prefix.length + 1))
540
+ if (!Number.isFinite(idx)) {
541
+ return null
542
+ }
543
+ for (const [addr, cachedIdx] of this.accountIndexCache) {
544
+ if (cachedIdx === idx) {
545
+ return addr
546
+ }
547
+ }
548
+ return null
549
+ }
550
+
551
+ private handleMarketStats(
552
+ stats?: Record<string, LtWsMarketStats | LtWsSpotMarketStats>
553
+ ): void {
554
+ if (!stats) {
555
+ return
556
+ }
557
+ const updates: Record<string, string> = {}
558
+ for (const entry of Object.values(stats)) {
559
+ updates[String(entry.market_id)] = entry.last_trade_price
560
+ }
561
+ if (Object.keys(updates).length === 0) {
562
+ return
563
+ }
564
+ this.lastPricesByAssetId = { ...this.lastPricesByAssetId, ...updates }
565
+ this.emit('prices', {
566
+ channel: 'prices',
567
+ data: this.lastPricesByAssetId,
568
+ })
569
+ }
570
+
571
+ private handleOrderBook(
572
+ msg: LtWsOrderBookMessage,
573
+ isSnapshot: boolean
574
+ ): void {
575
+ const marketId = this.marketIdFromChannel(msg.channel)
576
+ if (marketId === null) {
577
+ return
578
+ }
579
+ const assetId = String(marketId)
580
+
581
+ let state = this.orderbooks.get(marketId)
582
+ if (!state || isSnapshot) {
583
+ state = { bids: new Map(), asks: new Map(), assetId }
584
+ this.orderbooks.set(marketId, state)
585
+ }
586
+
587
+ applyLevels(state.bids, msg.order_book.bids)
588
+ applyLevels(state.asks, msg.order_book.asks)
589
+
590
+ this.emit(`orderbook:${assetId}`, {
591
+ channel: 'orderbook',
592
+ data: {
593
+ provider: this.providerKey,
594
+ marketId: assetId,
595
+ bids: mapToLevels(
596
+ state.bids,
597
+ (a, b) => Number(b.price) - Number(a.price)
598
+ ),
599
+ asks: mapToLevels(
600
+ state.asks,
601
+ (a, b) => Number(a.price) - Number(b.price)
602
+ ),
603
+ timestamp: Date.now(),
604
+ },
605
+ })
606
+ }
607
+
608
+ private marketIdFromChannel(channel: string | undefined): number | null {
609
+ if (!channel?.startsWith('order_book')) {
610
+ return null
611
+ }
612
+ const tail = channel.slice('order_book'.length)
613
+ if (tail.length < 2 || (tail[0] !== '/' && tail[0] !== ':')) {
614
+ return null
615
+ }
616
+ const n = Number(tail.slice(1))
617
+ return Number.isFinite(n) ? n : null
618
+ }
619
+ }
620
+
621
+ const isObject = (v: unknown): v is Record<string, unknown> =>
622
+ typeof v === 'object' && v !== null
623
+
624
+ /**
625
+ * Minimal presence/type check of the discriminating `type` and the required
626
+ * fields the matching handler dereferences without its own guard (currently
627
+ * only `order_book`, whose handler reads `order_book.bids`/`.asks`). A frame
628
+ * that parses but fails this is a bad frame (log + skip), distinct from a
629
+ * handler that throws on otherwise-shaped data. Frames for other types pass
630
+ * through to `dispatch`, which ignores types it does not recognise.
631
+ */
632
+ function isValidLighterFrame(msg: LtWsMessage): boolean {
633
+ if (typeof msg.type !== 'string') {
634
+ return false
635
+ }
636
+ if (
637
+ msg.type === 'subscribed/order_book' ||
638
+ msg.type === 'update/order_book'
639
+ ) {
640
+ const book = (msg as { order_book?: unknown }).order_book
641
+ return (
642
+ isObject(book) && Array.isArray(book.bids) && Array.isArray(book.asks)
643
+ )
644
+ }
645
+ return true
646
+ }
647
+
648
+ function applyLevels(
649
+ book: Map<string, string>,
650
+ levels: LtWsOrderBook['bids']
651
+ ): void {
652
+ for (const level of levels) {
653
+ if (level.size === '0' || Number(level.size) === 0) {
654
+ book.delete(level.price)
655
+ } else {
656
+ book.set(level.price, level.size)
657
+ }
658
+ }
659
+ }
660
+
661
+ function mapToLevels(
662
+ book: Map<string, string>,
663
+ compare: (a: { price: string }, b: { price: string }) => number
664
+ ): Array<{ price: string; size: string }> {
665
+ const levels = [...book].map(([price, size]) => ({ price, size }))
666
+ levels.sort(compare)
667
+ return levels
668
+ }
669
+
670
+ /**
671
+ * Extract items from an auth-channel payload field. The field may be:
672
+ * - a flat array (e.g. `trades: []` on the initial subscribed snapshot)
673
+ * - an object indexed by market index with array values
674
+ * (e.g. `orders: { "0": [Order] }` on update messages)
675
+ * - an object indexed by market index with single-object values
676
+ * (e.g. `positions: { "0": Position }` — one position per market)
677
+ *
678
+ * All three shapes are flattened to a single T[] for uniform downstream
679
+ * handling. Returns undefined when the field is absent, so the caller can
680
+ * fall back to a nested `data` wrapper (kept for compatibility with older
681
+ * Lighter WS versions).
682
+ */
683
+ function extractItems<T>(value: unknown): T[] | undefined {
684
+ if (value === undefined || value === null) {
685
+ return undefined
686
+ }
687
+ if (Array.isArray(value)) {
688
+ return value as T[]
689
+ }
690
+ if (typeof value === 'object') {
691
+ return Object.values(value as Record<string, unknown>).flatMap((v) =>
692
+ Array.isArray(v) ? (v as T[]) : [v as T]
693
+ )
694
+ }
695
+ return undefined
696
+ }
697
+
698
+ function collectAuthChannelItems<T>(
699
+ msg: { [k: string]: unknown },
700
+ field: string
701
+ ): T[] {
702
+ return (
703
+ extractItems<T>(msg[field]) ??
704
+ extractItems<T>(
705
+ (msg.data as Record<string, unknown> | undefined)?.[field]
706
+ ) ??
707
+ []
708
+ )
709
+ }
710
+
711
+ /**
712
+ * `WsProviderFactory` constructor for Lighter — pass to
713
+ * `new PerpsWsClient(client, { wsProviders: { lighter: lighterWsProvider({ authProvider }) } })`.
714
+ *
715
+ * Closes over the per-instance options (auth provider, restUrl override) so
716
+ * `PerpsWsClient` can call the returned factory with just
717
+ * `({ provider, wsUrl, markets })` at subscribe time. `markets` is
718
+ * unused — Lighter advertises a single venue, no sub-DEX filtering.
719
+ *
720
+ * @public
721
+ */
722
+ export const lighterWsProvider =
723
+ (options?: LighterWsProviderOptions): WsProviderFactory =>
724
+ ({ provider, wsUrl, client }) =>
725
+ new LighterWsProvider(wsUrl, provider, options, client)