@nktkas/hyperliquid 0.24.3 → 0.25.0-beta.2

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 (358) hide show
  1. package/README.md +141 -32
  2. package/esm/mod.d.ts +11 -11
  3. package/esm/mod.d.ts.map +1 -1
  4. package/esm/mod.js +2 -1
  5. package/esm/mod.js.map +1 -0
  6. package/esm/src/clients/exchange.d.ts +29 -25
  7. package/esm/src/clients/exchange.d.ts.map +1 -1
  8. package/esm/src/clients/exchange.js +54 -53
  9. package/esm/src/clients/exchange.js.map +1 -0
  10. package/esm/src/clients/info.d.ts +80 -20
  11. package/esm/src/clients/info.d.ts.map +1 -1
  12. package/esm/src/clients/info.js +312 -73
  13. package/esm/src/clients/info.js.map +1 -0
  14. package/esm/src/clients/multiSign.d.ts +11 -7
  15. package/esm/src/clients/multiSign.d.ts.map +1 -1
  16. package/esm/src/clients/multiSign.js +2 -1
  17. package/esm/src/clients/multiSign.js.map +1 -0
  18. package/esm/src/clients/subscription.d.ts +9 -9
  19. package/esm/src/clients/subscription.d.ts.map +1 -1
  20. package/esm/src/clients/subscription.js +29 -27
  21. package/esm/src/clients/subscription.js.map +1 -0
  22. package/esm/src/{base.d.ts → errors.d.ts} +1 -1
  23. package/esm/src/errors.d.ts.map +1 -0
  24. package/esm/src/{base.js → errors.js} +1 -0
  25. package/esm/src/errors.js.map +1 -0
  26. package/esm/src/schemas/_base.d.ts +14 -0
  27. package/esm/src/schemas/_base.d.ts.map +1 -0
  28. package/esm/src/schemas/_base.js +15 -0
  29. package/esm/src/schemas/_base.js.map +1 -0
  30. package/esm/src/schemas/exchange/requests.d.ts +8953 -0
  31. package/esm/src/schemas/exchange/requests.d.ts.map +1 -0
  32. package/esm/src/schemas/exchange/requests.js +1414 -0
  33. package/esm/src/schemas/exchange/requests.js.map +1 -0
  34. package/esm/src/schemas/exchange/responses.d.ts +567 -0
  35. package/esm/src/schemas/exchange/responses.d.ts.map +1 -0
  36. package/esm/src/schemas/exchange/responses.js +244 -0
  37. package/esm/src/schemas/exchange/responses.js.map +1 -0
  38. package/esm/src/schemas/explorer/requests.d.ts +44 -0
  39. package/esm/src/schemas/explorer/requests.d.ts.map +1 -0
  40. package/esm/src/schemas/explorer/requests.js +33 -0
  41. package/esm/src/schemas/explorer/requests.js.map +1 -0
  42. package/esm/src/schemas/explorer/responses.d.ts +360 -0
  43. package/esm/src/schemas/explorer/responses.d.ts.map +1 -0
  44. package/esm/src/schemas/explorer/responses.js +57 -0
  45. package/esm/src/schemas/explorer/responses.js.map +1 -0
  46. package/esm/src/schemas/info/accounts.d.ts +2159 -0
  47. package/esm/src/schemas/info/accounts.d.ts.map +1 -0
  48. package/esm/src/schemas/info/accounts.js +623 -0
  49. package/esm/src/schemas/info/accounts.js.map +1 -0
  50. package/esm/src/schemas/info/assets.d.ts +974 -0
  51. package/esm/src/schemas/info/assets.d.ts.map +1 -0
  52. package/esm/src/schemas/info/assets.js +285 -0
  53. package/esm/src/schemas/info/assets.js.map +1 -0
  54. package/esm/src/schemas/info/markets.d.ts +155 -0
  55. package/esm/src/schemas/info/markets.d.ts.map +1 -0
  56. package/esm/src/schemas/info/markets.js +70 -0
  57. package/esm/src/schemas/info/markets.js.map +1 -0
  58. package/esm/src/schemas/info/orders.d.ts +957 -0
  59. package/esm/src/schemas/info/orders.d.ts.map +1 -0
  60. package/esm/src/schemas/info/orders.js +298 -0
  61. package/esm/src/schemas/info/orders.js.map +1 -0
  62. package/esm/src/schemas/info/requests.d.ts +924 -0
  63. package/esm/src/schemas/info/requests.d.ts.map +1 -0
  64. package/esm/src/schemas/info/requests.js +687 -0
  65. package/esm/src/schemas/info/requests.js.map +1 -0
  66. package/esm/src/schemas/info/validators.d.ts +326 -0
  67. package/esm/src/schemas/info/validators.d.ts.map +1 -0
  68. package/esm/src/schemas/info/validators.js +126 -0
  69. package/esm/src/schemas/info/validators.js.map +1 -0
  70. package/esm/src/schemas/info/vaults.d.ts +447 -0
  71. package/esm/src/schemas/info/vaults.d.ts.map +1 -0
  72. package/esm/src/schemas/info/vaults.js +111 -0
  73. package/esm/src/schemas/info/vaults.js.map +1 -0
  74. package/esm/src/schemas/mod.d.ts +101 -0
  75. package/esm/src/schemas/mod.d.ts.map +1 -0
  76. package/esm/src/schemas/mod.js +115 -0
  77. package/esm/src/schemas/mod.js.map +1 -0
  78. package/esm/src/schemas/subscriptions/requests.d.ts +332 -0
  79. package/esm/src/schemas/subscriptions/requests.d.ts.map +1 -0
  80. package/esm/src/schemas/subscriptions/requests.js +259 -0
  81. package/esm/src/schemas/subscriptions/requests.js.map +1 -0
  82. package/esm/src/schemas/subscriptions/responses.d.ts +3643 -0
  83. package/esm/src/schemas/subscriptions/responses.d.ts.map +1 -0
  84. package/esm/src/schemas/subscriptions/responses.js +234 -0
  85. package/esm/src/schemas/subscriptions/responses.js.map +1 -0
  86. package/esm/src/signing/_signTypedData/ethers.js +1 -0
  87. package/esm/src/signing/_signTypedData/ethers.js.map +1 -0
  88. package/esm/src/signing/_signTypedData/mod.js +1 -0
  89. package/esm/src/signing/_signTypedData/mod.js.map +1 -0
  90. package/esm/src/signing/_signTypedData/private_key.js +1 -0
  91. package/esm/src/signing/_signTypedData/private_key.js.map +1 -0
  92. package/esm/src/signing/_signTypedData/viem.js +1 -0
  93. package/esm/src/signing/_signTypedData/viem.js.map +1 -0
  94. package/esm/src/signing/mod.d.ts +109 -29
  95. package/esm/src/signing/mod.d.ts.map +1 -1
  96. package/esm/src/signing/mod.js +136 -29
  97. package/esm/src/signing/mod.js.map +1 -0
  98. package/esm/src/transports/base.d.ts +4 -4
  99. package/esm/src/transports/base.d.ts.map +1 -1
  100. package/esm/src/transports/base.js +3 -2
  101. package/esm/src/transports/base.js.map +1 -0
  102. package/esm/src/transports/http/http_transport.d.ts +3 -2
  103. package/esm/src/transports/http/http_transport.d.ts.map +1 -1
  104. package/esm/src/transports/http/http_transport.js +1 -0
  105. package/esm/src/transports/http/http_transport.js.map +1 -0
  106. package/esm/src/transports/websocket/_hyperliquid_event_target.d.ts +1 -1
  107. package/esm/src/transports/websocket/_hyperliquid_event_target.d.ts.map +1 -1
  108. package/esm/src/transports/websocket/_hyperliquid_event_target.js +1 -0
  109. package/esm/src/transports/websocket/_hyperliquid_event_target.js.map +1 -0
  110. package/esm/src/transports/websocket/_reconnecting_websocket.js +1 -0
  111. package/esm/src/transports/websocket/_reconnecting_websocket.js.map +1 -0
  112. package/esm/src/transports/websocket/_websocket_async_request.js +1 -0
  113. package/esm/src/transports/websocket/_websocket_async_request.js.map +1 -0
  114. package/esm/src/transports/websocket/websocket_transport.d.ts +9 -1
  115. package/esm/src/transports/websocket/websocket_transport.d.ts.map +1 -1
  116. package/esm/src/transports/websocket/websocket_transport.js +4 -0
  117. package/esm/src/transports/websocket/websocket_transport.js.map +1 -0
  118. package/package.json +6 -5
  119. package/script/mod.d.ts +11 -11
  120. package/script/mod.d.ts.map +1 -1
  121. package/script/mod.js +2 -1
  122. package/script/mod.js.map +1 -0
  123. package/script/src/clients/exchange.d.ts +29 -25
  124. package/script/src/clients/exchange.d.ts.map +1 -1
  125. package/script/src/clients/exchange.js +60 -59
  126. package/script/src/clients/exchange.js.map +1 -0
  127. package/script/src/clients/info.d.ts +80 -20
  128. package/script/src/clients/info.d.ts.map +1 -1
  129. package/script/src/clients/info.js +312 -73
  130. package/script/src/clients/info.js.map +1 -0
  131. package/script/src/clients/multiSign.d.ts +11 -7
  132. package/script/src/clients/multiSign.d.ts.map +1 -1
  133. package/script/src/clients/multiSign.js +2 -1
  134. package/script/src/clients/multiSign.js.map +1 -0
  135. package/script/src/clients/subscription.d.ts +9 -9
  136. package/script/src/clients/subscription.d.ts.map +1 -1
  137. package/script/src/clients/subscription.js +29 -27
  138. package/script/src/clients/subscription.js.map +1 -0
  139. package/script/src/{base.d.ts → errors.d.ts} +1 -1
  140. package/script/src/errors.d.ts.map +1 -0
  141. package/script/src/{base.js → errors.js} +1 -0
  142. package/script/src/errors.js.map +1 -0
  143. package/script/src/schemas/_base.d.ts +14 -0
  144. package/script/src/schemas/_base.d.ts.map +1 -0
  145. package/script/src/schemas/_base.js +51 -0
  146. package/script/src/schemas/_base.js.map +1 -0
  147. package/script/src/schemas/exchange/requests.d.ts +8953 -0
  148. package/script/src/schemas/exchange/requests.d.ts.map +1 -0
  149. package/script/src/schemas/exchange/requests.js +1450 -0
  150. package/script/src/schemas/exchange/requests.js.map +1 -0
  151. package/script/src/schemas/exchange/responses.d.ts +567 -0
  152. package/script/src/schemas/exchange/responses.d.ts.map +1 -0
  153. package/script/src/schemas/exchange/responses.js +280 -0
  154. package/script/src/schemas/exchange/responses.js.map +1 -0
  155. package/script/src/schemas/explorer/requests.d.ts +44 -0
  156. package/script/src/schemas/explorer/requests.d.ts.map +1 -0
  157. package/script/src/schemas/explorer/requests.js +69 -0
  158. package/script/src/schemas/explorer/requests.js.map +1 -0
  159. package/script/src/schemas/explorer/responses.d.ts +360 -0
  160. package/script/src/schemas/explorer/responses.d.ts.map +1 -0
  161. package/script/src/schemas/explorer/responses.js +93 -0
  162. package/script/src/schemas/explorer/responses.js.map +1 -0
  163. package/script/src/schemas/info/accounts.d.ts +2159 -0
  164. package/script/src/schemas/info/accounts.d.ts.map +1 -0
  165. package/script/src/schemas/info/accounts.js +659 -0
  166. package/script/src/schemas/info/accounts.js.map +1 -0
  167. package/script/src/schemas/info/assets.d.ts +974 -0
  168. package/script/src/schemas/info/assets.d.ts.map +1 -0
  169. package/script/src/schemas/info/assets.js +321 -0
  170. package/script/src/schemas/info/assets.js.map +1 -0
  171. package/script/src/schemas/info/markets.d.ts +155 -0
  172. package/script/src/schemas/info/markets.d.ts.map +1 -0
  173. package/script/src/schemas/info/markets.js +106 -0
  174. package/script/src/schemas/info/markets.js.map +1 -0
  175. package/script/src/schemas/info/orders.d.ts +957 -0
  176. package/script/src/schemas/info/orders.d.ts.map +1 -0
  177. package/script/src/schemas/info/orders.js +334 -0
  178. package/script/src/schemas/info/orders.js.map +1 -0
  179. package/script/src/schemas/info/requests.d.ts +924 -0
  180. package/script/src/schemas/info/requests.d.ts.map +1 -0
  181. package/script/src/schemas/info/requests.js +724 -0
  182. package/script/src/schemas/info/requests.js.map +1 -0
  183. package/script/src/schemas/info/validators.d.ts +326 -0
  184. package/script/src/schemas/info/validators.d.ts.map +1 -0
  185. package/script/src/schemas/info/validators.js +162 -0
  186. package/script/src/schemas/info/validators.js.map +1 -0
  187. package/script/src/schemas/info/vaults.d.ts +447 -0
  188. package/script/src/schemas/info/vaults.d.ts.map +1 -0
  189. package/script/src/schemas/info/vaults.js +147 -0
  190. package/script/src/schemas/info/vaults.js.map +1 -0
  191. package/script/src/schemas/mod.d.ts +101 -0
  192. package/script/src/schemas/mod.d.ts.map +1 -0
  193. package/script/src/schemas/mod.js +157 -0
  194. package/script/src/schemas/mod.js.map +1 -0
  195. package/script/src/schemas/subscriptions/requests.d.ts +332 -0
  196. package/script/src/schemas/subscriptions/requests.d.ts.map +1 -0
  197. package/script/src/schemas/subscriptions/requests.js +295 -0
  198. package/script/src/schemas/subscriptions/requests.js.map +1 -0
  199. package/script/src/schemas/subscriptions/responses.d.ts +3643 -0
  200. package/script/src/schemas/subscriptions/responses.d.ts.map +1 -0
  201. package/script/src/schemas/subscriptions/responses.js +270 -0
  202. package/script/src/schemas/subscriptions/responses.js.map +1 -0
  203. package/script/src/signing/_signTypedData/ethers.js +1 -0
  204. package/script/src/signing/_signTypedData/ethers.js.map +1 -0
  205. package/script/src/signing/_signTypedData/mod.js +1 -0
  206. package/script/src/signing/_signTypedData/mod.js.map +1 -0
  207. package/script/src/signing/_signTypedData/private_key.js +1 -0
  208. package/script/src/signing/_signTypedData/private_key.js.map +1 -0
  209. package/script/src/signing/_signTypedData/viem.js +1 -0
  210. package/script/src/signing/_signTypedData/viem.js.map +1 -0
  211. package/script/src/signing/mod.d.ts +109 -29
  212. package/script/src/signing/mod.d.ts.map +1 -1
  213. package/script/src/signing/mod.js +138 -33
  214. package/script/src/signing/mod.js.map +1 -0
  215. package/script/src/transports/base.d.ts +4 -4
  216. package/script/src/transports/base.d.ts.map +1 -1
  217. package/script/src/transports/base.js +4 -3
  218. package/script/src/transports/base.js.map +1 -0
  219. package/script/src/transports/http/http_transport.d.ts +3 -2
  220. package/script/src/transports/http/http_transport.d.ts.map +1 -1
  221. package/script/src/transports/http/http_transport.js +1 -0
  222. package/script/src/transports/http/http_transport.js.map +1 -0
  223. package/script/src/transports/websocket/_hyperliquid_event_target.d.ts +1 -1
  224. package/script/src/transports/websocket/_hyperliquid_event_target.d.ts.map +1 -1
  225. package/script/src/transports/websocket/_hyperliquid_event_target.js +1 -0
  226. package/script/src/transports/websocket/_hyperliquid_event_target.js.map +1 -0
  227. package/script/src/transports/websocket/_reconnecting_websocket.js +1 -0
  228. package/script/src/transports/websocket/_reconnecting_websocket.js.map +1 -0
  229. package/script/src/transports/websocket/_websocket_async_request.js +1 -0
  230. package/script/src/transports/websocket/_websocket_async_request.js.map +1 -0
  231. package/script/src/transports/websocket/websocket_transport.d.ts +9 -1
  232. package/script/src/transports/websocket/websocket_transport.d.ts.map +1 -1
  233. package/script/src/transports/websocket/websocket_transport.js +4 -0
  234. package/script/src/transports/websocket/websocket_transport.js.map +1 -0
  235. package/src/mod.ts +28 -0
  236. package/src/src/clients/exchange.ts +2246 -0
  237. package/src/src/clients/info.ts +2076 -0
  238. package/src/src/clients/multiSign.ts +183 -0
  239. package/src/src/clients/subscription.ts +841 -0
  240. package/src/src/errors.ts +7 -0
  241. package/src/src/schemas/_base.ts +43 -0
  242. package/src/src/schemas/exchange/requests.ts +3057 -0
  243. package/src/src/schemas/exchange/responses.ts +540 -0
  244. package/src/src/schemas/explorer/requests.ts +65 -0
  245. package/src/src/schemas/explorer/responses.ts +138 -0
  246. package/src/src/schemas/info/accounts.ts +1490 -0
  247. package/src/src/schemas/info/assets.ts +693 -0
  248. package/src/src/schemas/info/markets.ts +171 -0
  249. package/src/src/schemas/info/orders.ts +597 -0
  250. package/src/src/schemas/info/requests.ts +1369 -0
  251. package/src/src/schemas/info/validators.ts +299 -0
  252. package/src/src/schemas/info/vaults.ts +262 -0
  253. package/src/src/schemas/mod.ts +121 -0
  254. package/src/src/schemas/subscriptions/requests.ts +504 -0
  255. package/src/src/schemas/subscriptions/responses.ts +576 -0
  256. package/src/src/signing/_signTypedData/ethers.ts +59 -0
  257. package/src/src/signing/_signTypedData/mod.ts +121 -0
  258. package/src/src/signing/_signTypedData/private_key.ts +229 -0
  259. package/src/src/signing/_signTypedData/viem.ts +55 -0
  260. package/src/src/signing/mod.ts +572 -0
  261. package/src/src/transports/base.ts +54 -0
  262. package/src/src/transports/http/http_transport.ts +208 -0
  263. package/src/src/transports/websocket/_hyperliquid_event_target.ts +118 -0
  264. package/src/src/transports/websocket/_reconnecting_websocket.ts +404 -0
  265. package/src/src/transports/websocket/_websocket_async_request.ts +229 -0
  266. package/src/src/transports/websocket/websocket_transport.ts +394 -0
  267. package/esm/src/base.d.ts.map +0 -1
  268. package/esm/src/signing/_sorter.d.ts +0 -127
  269. package/esm/src/signing/_sorter.d.ts.map +0 -1
  270. package/esm/src/signing/_sorter.js +0 -693
  271. package/esm/src/types/exchange/requests.d.ts +0 -1345
  272. package/esm/src/types/exchange/requests.d.ts.map +0 -1
  273. package/esm/src/types/exchange/requests.js +0 -1
  274. package/esm/src/types/exchange/responses.d.ts +0 -233
  275. package/esm/src/types/exchange/responses.d.ts.map +0 -1
  276. package/esm/src/types/exchange/responses.js +0 -1
  277. package/esm/src/types/explorer/requests.d.ts +0 -32
  278. package/esm/src/types/explorer/requests.d.ts.map +0 -1
  279. package/esm/src/types/explorer/requests.js +0 -1
  280. package/esm/src/types/explorer/responses.d.ts +0 -58
  281. package/esm/src/types/explorer/responses.d.ts.map +0 -1
  282. package/esm/src/types/explorer/responses.js +0 -1
  283. package/esm/src/types/info/accounts.d.ts +0 -864
  284. package/esm/src/types/info/accounts.d.ts.map +0 -1
  285. package/esm/src/types/info/accounts.js +0 -1
  286. package/esm/src/types/info/assets.d.ts +0 -354
  287. package/esm/src/types/info/assets.d.ts.map +0 -1
  288. package/esm/src/types/info/assets.js +0 -1
  289. package/esm/src/types/info/markets.d.ts +0 -79
  290. package/esm/src/types/info/markets.d.ts.map +0 -1
  291. package/esm/src/types/info/markets.js +0 -1
  292. package/esm/src/types/info/orders.d.ts +0 -266
  293. package/esm/src/types/info/orders.d.ts.map +0 -1
  294. package/esm/src/types/info/orders.js +0 -1
  295. package/esm/src/types/info/requests.d.ts +0 -640
  296. package/esm/src/types/info/requests.d.ts.map +0 -1
  297. package/esm/src/types/info/requests.js +0 -1
  298. package/esm/src/types/info/validators.d.ts +0 -147
  299. package/esm/src/types/info/validators.d.ts.map +0 -1
  300. package/esm/src/types/info/validators.js +0 -1
  301. package/esm/src/types/info/vaults.d.ts +0 -119
  302. package/esm/src/types/info/vaults.d.ts.map +0 -1
  303. package/esm/src/types/info/vaults.js +0 -1
  304. package/esm/src/types/mod.d.ts +0 -38
  305. package/esm/src/types/mod.d.ts.map +0 -1
  306. package/esm/src/types/mod.js +0 -24
  307. package/esm/src/types/subscriptions/requests.d.ts +0 -154
  308. package/esm/src/types/subscriptions/requests.d.ts.map +0 -1
  309. package/esm/src/types/subscriptions/requests.js +0 -1
  310. package/esm/src/types/subscriptions/responses.d.ts +0 -238
  311. package/esm/src/types/subscriptions/responses.d.ts.map +0 -1
  312. package/esm/src/types/subscriptions/responses.js +0 -1
  313. package/script/src/base.d.ts.map +0 -1
  314. package/script/src/signing/_sorter.d.ts +0 -127
  315. package/script/src/signing/_sorter.d.ts.map +0 -1
  316. package/script/src/signing/_sorter.js +0 -696
  317. package/script/src/types/exchange/requests.d.ts +0 -1345
  318. package/script/src/types/exchange/requests.d.ts.map +0 -1
  319. package/script/src/types/exchange/requests.js +0 -2
  320. package/script/src/types/exchange/responses.d.ts +0 -233
  321. package/script/src/types/exchange/responses.d.ts.map +0 -1
  322. package/script/src/types/exchange/responses.js +0 -2
  323. package/script/src/types/explorer/requests.d.ts +0 -32
  324. package/script/src/types/explorer/requests.d.ts.map +0 -1
  325. package/script/src/types/explorer/requests.js +0 -2
  326. package/script/src/types/explorer/responses.d.ts +0 -58
  327. package/script/src/types/explorer/responses.d.ts.map +0 -1
  328. package/script/src/types/explorer/responses.js +0 -2
  329. package/script/src/types/info/accounts.d.ts +0 -864
  330. package/script/src/types/info/accounts.d.ts.map +0 -1
  331. package/script/src/types/info/accounts.js +0 -2
  332. package/script/src/types/info/assets.d.ts +0 -354
  333. package/script/src/types/info/assets.d.ts.map +0 -1
  334. package/script/src/types/info/assets.js +0 -2
  335. package/script/src/types/info/markets.d.ts +0 -79
  336. package/script/src/types/info/markets.d.ts.map +0 -1
  337. package/script/src/types/info/markets.js +0 -2
  338. package/script/src/types/info/orders.d.ts +0 -266
  339. package/script/src/types/info/orders.d.ts.map +0 -1
  340. package/script/src/types/info/orders.js +0 -2
  341. package/script/src/types/info/requests.d.ts +0 -640
  342. package/script/src/types/info/requests.d.ts.map +0 -1
  343. package/script/src/types/info/requests.js +0 -2
  344. package/script/src/types/info/validators.d.ts +0 -147
  345. package/script/src/types/info/validators.d.ts.map +0 -1
  346. package/script/src/types/info/validators.js +0 -2
  347. package/script/src/types/info/vaults.d.ts +0 -119
  348. package/script/src/types/info/vaults.d.ts.map +0 -1
  349. package/script/src/types/info/vaults.js +0 -2
  350. package/script/src/types/mod.d.ts +0 -38
  351. package/script/src/types/mod.d.ts.map +0 -1
  352. package/script/src/types/mod.js +0 -25
  353. package/script/src/types/subscriptions/requests.d.ts +0 -154
  354. package/script/src/types/subscriptions/requests.d.ts.map +0 -1
  355. package/script/src/types/subscriptions/requests.js +0 -2
  356. package/script/src/types/subscriptions/responses.d.ts +0 -238
  357. package/script/src/types/subscriptions/responses.d.ts.map +0 -1
  358. package/script/src/types/subscriptions/responses.js +0 -2
@@ -0,0 +1,404 @@
1
+ // deno-lint-ignore-file no-explicit-any
2
+ import { TransportError } from "../base.js";
3
+
4
+ type MaybePromise<T> = T | Promise<T>;
5
+
6
+ /** Configuration options for the `ReconnectingWebSocket`. */
7
+ export interface ReconnectingWebSocketOptions {
8
+ /**
9
+ * Maximum number of reconnection attempts.
10
+ * @defaultValue `3`
11
+ */
12
+ maxRetries?: number;
13
+
14
+ /**
15
+ * Maximum time in ms to wait for a connection to open.
16
+ * Set to `null` to disable.
17
+ * @defaultValue `10_000`
18
+ */
19
+ connectionTimeout?: number | null;
20
+
21
+ /**
22
+ * Delay between reconnection attempts in ms.
23
+ * May be a number or a function that returns a number.
24
+ * @param attempt - The current attempt number.
25
+ * @defaultValue `(attempt) => Math.min(~~(1 << attempt) * 150, 10_000)` - Exponential backoff (max 10s)
26
+ */
27
+ connectionDelay?: number | ((attempt: number, signal: AbortSignal) => MaybePromise<number>);
28
+
29
+ /**
30
+ * Custom logic to determine if reconnection is required.
31
+ * @param event - The close event that occurred during the connection.
32
+ * @returns A boolean indicating if reconnection should be attempted.
33
+ * @defaultValue `() => true` - Always reconnect
34
+ */
35
+ shouldReconnect?: (event: CloseEvent, signal: AbortSignal) => MaybePromise<boolean>;
36
+
37
+ /**
38
+ * Message buffering strategy between reconnection attempts.
39
+ * @defaultValue `new FIFOMessageBuffer()`
40
+ */
41
+ messageBuffer?: MessageBufferStrategy;
42
+ }
43
+
44
+ /** Message buffer strategy interface. */
45
+ export interface MessageBufferStrategy {
46
+ push(data: string | ArrayBufferLike | Blob | ArrayBufferView, signal?: AbortSignal): void;
47
+ [Symbol.iterator](): Iterator<string | ArrayBufferLike | Blob | ArrayBufferView>;
48
+ }
49
+
50
+ /** Simple FIFO (First In, First Out) buffer implementation. */
51
+ class FIFOMessageBuffer implements MessageBufferStrategy {
52
+ queue: {
53
+ data: string | ArrayBufferLike | Blob | ArrayBufferView;
54
+ signal?: AbortSignal;
55
+ }[] = [];
56
+
57
+ push(data: string | ArrayBufferLike | Blob | ArrayBufferView, signal?: AbortSignal): void {
58
+ this.queue.push({ data, signal });
59
+ }
60
+
61
+ *[Symbol.iterator](): Iterator<string | ArrayBufferLike | Blob | ArrayBufferView> {
62
+ while (this.queue.length > 0) {
63
+ const { data, signal } = this.queue.shift()!;
64
+ if (signal?.aborted) continue;
65
+ yield data;
66
+ }
67
+ }
68
+ }
69
+
70
+ /** Error thrown when reconnection problems occur. */
71
+ export class ReconnectingWebSocketError extends TransportError {
72
+ constructor(
73
+ public code:
74
+ | "RECONNECTION_LIMIT_REACHED"
75
+ | "RECONNECTION_STOPPED_BY_USER"
76
+ | "USER_INITIATED_CLOSE"
77
+ | "UNKNOWN_ERROR",
78
+ cause?: unknown,
79
+ ) {
80
+ super(`Error when reconnecting WebSocket: ${code}`);
81
+ this.name = "ReconnectingWebSocketError";
82
+ this.cause = cause;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * A WebSocket that automatically reconnects when disconnected.
88
+ * Fully compatible with standard WebSocket API.
89
+ */
90
+ export class ReconnectingWebSocket implements WebSocket {
91
+ protected _socket: WebSocket;
92
+ protected _protocols?: string | string[];
93
+ protected _listeners: {
94
+ type: string;
95
+ listener: EventListenerOrEventListenerObject;
96
+ options?: boolean | AddEventListenerOptions;
97
+ listenerProxy: EventListenerOrEventListenerObject;
98
+ }[] = [];
99
+ protected _attempt = 0;
100
+ reconnectOptions: Required<ReconnectingWebSocketOptions>;
101
+ readonly reconnectAbortController: AbortController = new AbortController();
102
+
103
+ constructor(url: string | URL, protocols?: string | string[], options?: ReconnectingWebSocketOptions) {
104
+ this.reconnectOptions = {
105
+ maxRetries: options?.maxRetries ?? 3,
106
+ connectionTimeout: options?.connectionTimeout === undefined ? 10_000 : options.connectionTimeout,
107
+ connectionDelay: options?.connectionDelay ?? ((n) => Math.min(~~(1 << n) * 150, 10_000)),
108
+ shouldReconnect: options?.shouldReconnect ?? (() => true),
109
+ messageBuffer: options?.messageBuffer ?? new FIFOMessageBuffer(),
110
+ };
111
+
112
+ this._socket = this._createSocket(url, protocols);
113
+ this._protocols = protocols;
114
+ this._setupEventListeners();
115
+ }
116
+
117
+ protected _createSocket(url: string | URL, protocols?: string | string[]): WebSocket {
118
+ const socket = new WebSocket(url, protocols);
119
+ if (this.reconnectOptions.connectionTimeout === null) return socket;
120
+
121
+ const timeoutId = setTimeout(() => {
122
+ socket.removeEventListener("open", openHandler);
123
+ socket.removeEventListener("close", closeHandler);
124
+ socket.close(3008, "Timeout"); // https://www.iana.org/assignments/websocket/websocket.xml#close-code-number
125
+ }, this.reconnectOptions.connectionTimeout);
126
+
127
+ const openHandler = () => {
128
+ socket.removeEventListener("close", closeHandler);
129
+ clearTimeout(timeoutId);
130
+ };
131
+ const closeHandler = () => {
132
+ socket.removeEventListener("open", openHandler);
133
+ clearTimeout(timeoutId);
134
+ };
135
+
136
+ socket.addEventListener("open", openHandler, { once: true });
137
+ socket.addEventListener("close", closeHandler, { once: true });
138
+
139
+ return socket;
140
+ }
141
+
142
+ /** Initializes the internal event listeners for the socket. */
143
+ protected _setupEventListeners() {
144
+ this._socket.addEventListener("open", this._open, { once: true });
145
+ this._socket.addEventListener("close", this._close, { once: true });
146
+ }
147
+ protected _open: () => void = () => {
148
+ // Reset the attempt counter
149
+ this._attempt = 0;
150
+
151
+ // Send all buffered messages
152
+ for (const message of this.reconnectOptions.messageBuffer) {
153
+ this._socket.send(message);
154
+ }
155
+ };
156
+ protected _close = async (event: CloseEvent) => {
157
+ try {
158
+ // If the event was triggered but the socket is not closing, ignore it
159
+ if (
160
+ this._socket.readyState !== ReconnectingWebSocket.CLOSING &&
161
+ this._socket.readyState !== ReconnectingWebSocket.CLOSED
162
+ ) return;
163
+
164
+ // If the instance is terminated, do not attempt to reconnect
165
+ if (this.reconnectAbortController.signal.aborted) return;
166
+
167
+ // Check if reconnection should be attempted
168
+ if (++this._attempt > this.reconnectOptions.maxRetries) {
169
+ this._cleanup("RECONNECTION_LIMIT_REACHED");
170
+ return;
171
+ }
172
+
173
+ const userDecision = await this.reconnectOptions.shouldReconnect(
174
+ event,
175
+ this.reconnectAbortController.signal,
176
+ );
177
+ if (this.reconnectAbortController.signal.aborted) return;
178
+ if (!userDecision) {
179
+ this._cleanup("RECONNECTION_STOPPED_BY_USER");
180
+ return;
181
+ }
182
+
183
+ // Delay before reconnecting
184
+ const reconnectDelay = typeof this.reconnectOptions.connectionDelay === "number"
185
+ ? this.reconnectOptions.connectionDelay
186
+ : await this.reconnectOptions.connectionDelay(this._attempt, this.reconnectAbortController.signal);
187
+ if (this.reconnectAbortController.signal.aborted) return;
188
+ await delay(reconnectDelay, this.reconnectAbortController.signal);
189
+
190
+ // Create a new WebSocket instance
191
+ const { onclose, onerror, onmessage, onopen } = this._socket;
192
+ this._socket = this._createSocket(this._socket.url, this._protocols);
193
+
194
+ // Reconnect all listeners
195
+ this._setupEventListeners();
196
+
197
+ this._listeners.forEach(({ type, listenerProxy, options }) => {
198
+ this._socket.addEventListener(type, listenerProxy, options);
199
+ });
200
+
201
+ this._socket.onclose = onclose;
202
+ this._socket.onerror = onerror;
203
+ this._socket.onmessage = onmessage;
204
+ this._socket.onopen = onopen;
205
+ } catch (error) {
206
+ this._cleanup("UNKNOWN_ERROR", error);
207
+ }
208
+ };
209
+
210
+ /** Clean up internal resources. */
211
+ protected _cleanup(code: ConstructorParameters<typeof ReconnectingWebSocketError>[0], cause?: unknown) {
212
+ this.reconnectAbortController.abort(new ReconnectingWebSocketError(code, cause));
213
+ this._listeners = [];
214
+ this._socket.close();
215
+ }
216
+
217
+ // WebSocket property implementations
218
+ get url(): string {
219
+ return this._socket.url;
220
+ }
221
+ get readyState(): number {
222
+ return this._socket.readyState;
223
+ }
224
+ get bufferedAmount(): number {
225
+ return this._socket.bufferedAmount;
226
+ }
227
+ get extensions(): string {
228
+ return this._socket.extensions;
229
+ }
230
+ get protocol(): string {
231
+ return this._socket.protocol;
232
+ }
233
+ get binaryType(): BinaryType {
234
+ return this._socket.binaryType;
235
+ }
236
+ set binaryType(value: BinaryType) {
237
+ this._socket.binaryType = value;
238
+ }
239
+
240
+ readonly CONNECTING = 0;
241
+ readonly OPEN = 1;
242
+ readonly CLOSING = 2;
243
+ readonly CLOSED = 3;
244
+
245
+ static readonly CONNECTING = 0;
246
+ static readonly OPEN = 1;
247
+ static readonly CLOSING = 2;
248
+ static readonly CLOSED = 3;
249
+
250
+ get onclose(): ((this: WebSocket, ev: CloseEvent) => any) | null {
251
+ return this._socket.onclose;
252
+ }
253
+ set onclose(value: ((this: WebSocket, ev: CloseEvent) => any) | null) {
254
+ this._socket.onclose = value;
255
+ }
256
+
257
+ get onerror(): ((this: WebSocket, ev: Event) => any) | null {
258
+ return this._socket.onerror;
259
+ }
260
+ set onerror(value: ((this: WebSocket, ev: Event) => any) | null) {
261
+ this._socket.onerror = value;
262
+ }
263
+
264
+ get onmessage(): ((this: WebSocket, ev: MessageEvent<any>) => any) | null {
265
+ return this._socket.onmessage;
266
+ }
267
+ set onmessage(value: ((this: WebSocket, ev: MessageEvent<any>) => any) | null) {
268
+ this._socket.onmessage = value;
269
+ }
270
+
271
+ get onopen(): ((this: WebSocket, ev: Event) => any) | null {
272
+ return this._socket.onopen;
273
+ }
274
+ set onopen(value: ((this: WebSocket, ev: Event) => any) | null) {
275
+ this._socket.onopen = value;
276
+ }
277
+
278
+ /**
279
+ * @param permanently - If `true`, the connection will be permanently closed. Default is `true`.
280
+ */
281
+ close(code?: number, reason?: string, permanently: boolean = true): void {
282
+ this._socket.close(code, reason);
283
+ if (permanently) this._cleanup("USER_INITIATED_CLOSE");
284
+ }
285
+
286
+ /**
287
+ * @param signal - `AbortSignal` to cancel sending a message if it was in the buffer.
288
+ * @note If the connection is not open, the data will be buffered and sent when the connection is established.
289
+ */
290
+ send(data: string | ArrayBufferLike | Blob | ArrayBufferView, signal?: AbortSignal): void {
291
+ if (signal?.aborted) return;
292
+ if (this._socket.readyState !== ReconnectingWebSocket.OPEN && !this.reconnectAbortController.signal.aborted) {
293
+ this.reconnectOptions.messageBuffer.push(data, signal);
294
+ } else {
295
+ this._socket.send(data);
296
+ }
297
+ }
298
+
299
+ addEventListener<K extends keyof WebSocketEventMap>(
300
+ type: K,
301
+ listener:
302
+ | ((this: ReconnectingWebSocket, ev: WebSocketEventMap[K]) => any)
303
+ | { handleEvent: (event: WebSocketEventMap[K]) => any },
304
+ options?: boolean | AddEventListenerOptions,
305
+ ): void;
306
+ addEventListener(
307
+ type: string,
308
+ listener: EventListenerOrEventListenerObject,
309
+ options?: boolean | AddEventListenerOptions,
310
+ ): void {
311
+ // Wrap the listener to handle reconnection
312
+ let listenerProxy: EventListenerOrEventListenerObject;
313
+ if (this.reconnectAbortController.signal.aborted) {
314
+ // If the instance is terminated, use the original listener
315
+ listenerProxy = listener;
316
+ } else {
317
+ // Check if the listener is already registered
318
+ const index = this._listeners.findIndex((e) => listenersMatch(e, { type, listener, options }));
319
+ if (index !== -1) {
320
+ // Use the existing listener proxy
321
+ listenerProxy = this._listeners[index].listenerProxy;
322
+ } else {
323
+ // Wrap the original listener to follow the once option when reconnecting
324
+ listenerProxy = (event: Event) => {
325
+ try {
326
+ if (typeof listener === "function") {
327
+ listener.call(this, event);
328
+ } else {
329
+ listener.handleEvent(event);
330
+ }
331
+ } finally {
332
+ // If the listener is marked as once, remove it after the first invocation
333
+ if (typeof options === "object" && options.once === true) {
334
+ const index = this._listeners.findIndex((e) =>
335
+ listenersMatch(e, { type, listener, options })
336
+ );
337
+ if (index !== -1) {
338
+ this._listeners.splice(index, 1);
339
+ }
340
+ }
341
+ }
342
+ };
343
+ this._listeners.push({ type, listener, options, listenerProxy });
344
+ }
345
+ }
346
+
347
+ // Add the wrapped (or original) listener
348
+ this._socket.addEventListener(type, listenerProxy, options);
349
+ }
350
+
351
+ removeEventListener<K extends keyof WebSocketEventMap>(
352
+ type: K,
353
+ listener:
354
+ | ((this: ReconnectingWebSocket, ev: WebSocketEventMap[K]) => any)
355
+ | { handleEvent: (event: WebSocketEventMap[K]) => any },
356
+ options?: boolean | EventListenerOptions,
357
+ ): void;
358
+ removeEventListener(
359
+ type: string,
360
+ listener: EventListenerOrEventListenerObject,
361
+ options?: boolean | EventListenerOptions,
362
+ ): void {
363
+ // Remove a wrapped listener, not an original listener
364
+ const index = this._listeners.findIndex((e) => listenersMatch(e, { type, listener, options }));
365
+ if (index !== -1) {
366
+ const { listenerProxy } = this._listeners[index];
367
+ this._socket.removeEventListener(type, listenerProxy, options);
368
+ this._listeners.splice(index, 1);
369
+ } else {
370
+ // If the wrapped listener is not found, remove the original listener
371
+ this._socket.removeEventListener(type, listener, options);
372
+ }
373
+ }
374
+
375
+ dispatchEvent(event: Event): boolean {
376
+ return this._socket.dispatchEvent(event);
377
+ }
378
+ }
379
+
380
+ function listenersMatch(
381
+ a: { type: string; listener: EventListenerOrEventListenerObject; options?: boolean | AddEventListenerOptions },
382
+ b: { type: string; listener: EventListenerOrEventListenerObject; options?: boolean | AddEventListenerOptions },
383
+ ): boolean {
384
+ // EventTarget only compares capture in options, even if one is an object and the other is boolean
385
+ const aCapture = Boolean(typeof a.options === "object" ? a.options.capture : a.options);
386
+ const bCapture = Boolean(typeof b.options === "object" ? b.options.capture : b.options);
387
+ return a.type === b.type && a.listener === b.listener && aCapture === bCapture;
388
+ }
389
+
390
+ function delay(ms: number, signal?: AbortSignal): Promise<void> {
391
+ if (signal?.aborted) return Promise.reject(signal.reason);
392
+ return new Promise((resolve, reject) => {
393
+ const onAbort = () => {
394
+ clearTimeout(timer);
395
+ reject(signal?.reason);
396
+ };
397
+ const onTimeout = () => {
398
+ signal?.removeEventListener("abort", onAbort);
399
+ resolve();
400
+ };
401
+ const timer = setTimeout(onTimeout, ms);
402
+ signal?.addEventListener("abort", onAbort, { once: true });
403
+ });
404
+ }
@@ -0,0 +1,229 @@
1
+ import type { ReconnectingWebSocket } from "./_reconnecting_websocket.js";
2
+ import type { HyperliquidEventMap, HyperliquidEventTarget } from "./_hyperliquid_event_target.js";
3
+ import { WebSocketRequestError } from "./websocket_transport.js";
4
+
5
+ interface PostRequest {
6
+ method: "post";
7
+ id: number;
8
+ request: unknown;
9
+ }
10
+
11
+ interface SubscribeUnsubscribeRequest {
12
+ method: "subscribe" | "unsubscribe";
13
+ subscription: unknown;
14
+ }
15
+
16
+ interface PingRequest {
17
+ method: "ping";
18
+ }
19
+
20
+ /**
21
+ * Manages WebSocket requests to the Hyperliquid API.
22
+ * Handles request creation, sending, and mapping responses to their corresponding requests.
23
+ */
24
+ export class WebSocketAsyncRequest {
25
+ protected lastId: number = 0;
26
+ protected queue: {
27
+ id: number | string;
28
+ // deno-lint-ignore no-explicit-any
29
+ resolve: (value?: any) => void;
30
+ // deno-lint-ignore no-explicit-any
31
+ reject: (reason?: any) => void;
32
+ }[] = [];
33
+ lastRequestTime: number = 0;
34
+
35
+ /**
36
+ * Creates a new WebSocket async request handler.
37
+ * @param socket - WebSocket connection instance for sending requests to the Hyperliquid WebSocket API
38
+ * @param hlEvents - Used to recognize Hyperliquid responses and match them with sent requests
39
+ */
40
+ constructor(protected socket: ReconnectingWebSocket, hlEvents: HyperliquidEventTarget) {
41
+ // Monitor responses and match the pending request
42
+ hlEvents.addEventListener("subscriptionResponse", (event) => {
43
+ const detail = (event as HyperliquidEventMap["subscriptionResponse"]).detail;
44
+
45
+ // Use a stringified request as an id
46
+ const id = WebSocketAsyncRequest.requestToId(detail);
47
+ this.queue.findLast((item) => item.id === id)?.resolve(detail);
48
+ });
49
+ hlEvents.addEventListener("post", (event) => {
50
+ const detail = (event as HyperliquidEventMap["post"]).detail;
51
+
52
+ const data = detail.response.type === "info" ? detail.response.payload.data : detail.response.payload;
53
+ this.queue.findLast((item) => item.id === detail.id)?.resolve(data);
54
+ });
55
+ hlEvents.addEventListener("pong", () => {
56
+ this.queue.findLast((item) => item.id === "ping")?.resolve();
57
+ });
58
+ hlEvents.addEventListener("error", (event) => {
59
+ const detail = (event as HyperliquidEventMap["error"]).detail;
60
+
61
+ try {
62
+ // Error event doesn't have an id, use original request to match
63
+ const request = detail.match(/{.*}/)?.[0];
64
+ if (!request) return;
65
+
66
+ const parsedRequest = JSON.parse(request) as Record<string, unknown>;
67
+
68
+ // For `post` requests
69
+ if ("id" in parsedRequest && typeof parsedRequest.id === "number") {
70
+ this.queue.findLast((item) => item.id === parsedRequest.id)
71
+ ?.reject(new WebSocketRequestError(`Server error: ${detail}`, { cause: detail }));
72
+ return;
73
+ }
74
+
75
+ // For `subscribe` and `unsubscribe` requests
76
+ if (
77
+ "subscription" in parsedRequest &&
78
+ typeof parsedRequest.subscription === "object" && parsedRequest.subscription !== null
79
+ ) {
80
+ const id = WebSocketAsyncRequest.requestToId(parsedRequest);
81
+ this.queue.findLast((item) => item.id === id)
82
+ ?.reject(new WebSocketRequestError(`Server error: ${detail}`, { cause: detail }));
83
+ return;
84
+ }
85
+
86
+ // For `Already subscribed` and `Invalid subscription` requests
87
+ if (detail.startsWith("Already subscribed") || detail.startsWith("Invalid subscription")) {
88
+ const id = WebSocketAsyncRequest.requestToId({
89
+ method: "subscribe",
90
+ subscription: parsedRequest,
91
+ });
92
+ this.queue.findLast((item) => item.id === id)
93
+ ?.reject(new WebSocketRequestError(`Server error: ${detail}`, { cause: detail }));
94
+ return;
95
+ }
96
+ // For `Already unsubscribed` requests
97
+ if (detail.startsWith("Already unsubscribed")) {
98
+ const id = WebSocketAsyncRequest.requestToId({
99
+ method: "unsubscribe",
100
+ subscription: parsedRequest,
101
+ });
102
+ this.queue.findLast((item) => item.id === id)
103
+ ?.reject(new WebSocketRequestError(`Server error: ${detail}`, { cause: detail }));
104
+ return;
105
+ }
106
+
107
+ // For unknown requests
108
+ const id = WebSocketAsyncRequest.requestToId(parsedRequest);
109
+ this.queue.findLast((item) => item.id === id)
110
+ ?.reject(new WebSocketRequestError(`Server error: ${detail}`, { cause: detail }));
111
+ } catch {
112
+ // Ignore JSON parsing errors
113
+ }
114
+ });
115
+
116
+ // Throws all pending requests if the connection is dropped
117
+ socket.addEventListener("close", () => {
118
+ this.queue.forEach(({ reject }) => {
119
+ reject(new WebSocketRequestError("WebSocket connection closed."));
120
+ });
121
+ this.queue = [];
122
+ });
123
+ }
124
+
125
+ /**
126
+ * Sends a request to the Hyperliquid API.
127
+ * @returns A promise that resolves with the parsed JSON response body.
128
+ */
129
+ async request(method: "ping", signal?: AbortSignal): Promise<void>;
130
+ async request<T>(method: "post" | "subscribe" | "unsubscribe", payload: unknown, signal?: AbortSignal): Promise<T>;
131
+ async request<T>(
132
+ method: "post" | "subscribe" | "unsubscribe" | "ping",
133
+ payload_or_signal?: unknown | AbortSignal,
134
+ maybeSignal?: AbortSignal,
135
+ ): Promise<T> {
136
+ const payload = payload_or_signal instanceof AbortSignal ? undefined : payload_or_signal;
137
+ const signal = payload_or_signal instanceof AbortSignal ? payload_or_signal : maybeSignal;
138
+
139
+ // Reject the request if the signal is aborted
140
+ if (signal?.aborted) return Promise.reject(signal.reason);
141
+ // or if the WebSocket connection is permanently closed
142
+ if (this.socket.reconnectAbortController.signal.aborted) {
143
+ return Promise.reject(this.socket.reconnectAbortController.signal.reason);
144
+ }
145
+
146
+ // Create a request
147
+ let id: string | number;
148
+ let request: SubscribeUnsubscribeRequest | PostRequest | PingRequest;
149
+ if (method === "post") {
150
+ id = ++this.lastId;
151
+ request = { method, id, request: payload };
152
+ } else if (method === "ping") {
153
+ id = "ping";
154
+ request = { method };
155
+ } else {
156
+ request = { method, subscription: payload };
157
+ id = WebSocketAsyncRequest.requestToId(request);
158
+ }
159
+
160
+ // Send the request
161
+ this.socket.send(JSON.stringify(request), signal);
162
+ this.lastRequestTime = Date.now();
163
+
164
+ // Wait for a response
165
+ const { promise, resolve, reject } = Promise.withResolvers<T>();
166
+ this.queue.push({ id, resolve, reject });
167
+
168
+ const onAbort = () => reject(signal?.reason);
169
+ signal?.addEventListener("abort", onAbort, { once: true });
170
+
171
+ return await promise.finally(() => {
172
+ const index = this.queue.findLastIndex((item) => item.id === id);
173
+ if (index !== -1) this.queue.splice(index, 1);
174
+
175
+ signal?.removeEventListener("abort", onAbort);
176
+ });
177
+ }
178
+
179
+ /** Normalizes an object and then converts it to a string. */
180
+ static requestToId(value: unknown): string {
181
+ const lowerHex = deepLowerHex(value);
182
+ const sorted = deepSortKeys(lowerHex);
183
+ return JSON.stringify(sorted); // Also removes undefined
184
+ }
185
+ }
186
+
187
+ /** Deeply converts hexadecimal strings in an object/array to lowercase. */
188
+ function deepLowerHex(obj: unknown): unknown {
189
+ if (typeof obj === "string") {
190
+ return /^(0X[0-9a-fA-F]*|0x[0-9a-fA-F]*[A-F][0-9a-fA-F]*)$/.test(obj) ? obj.toLowerCase() : obj;
191
+ }
192
+
193
+ if (Array.isArray(obj)) {
194
+ return obj.map((value) => deepLowerHex(value));
195
+ }
196
+
197
+ if (typeof obj === "object" && obj !== null) {
198
+ const result: Record<string, unknown> = {};
199
+ const entries = Object.entries(obj);
200
+
201
+ for (const [key, value] of entries) {
202
+ result[key] = deepLowerHex(value);
203
+ }
204
+
205
+ return result;
206
+ }
207
+
208
+ return obj;
209
+ }
210
+
211
+ /** Deeply sort the keys of an object. */
212
+ function deepSortKeys<T>(obj: T): T {
213
+ if (typeof obj !== "object" || obj === null) {
214
+ return obj;
215
+ }
216
+
217
+ if (Array.isArray(obj)) {
218
+ return obj.map(deepSortKeys) as T;
219
+ }
220
+
221
+ const result: Record<string, unknown> = {};
222
+ const keys = Object.keys(obj).sort();
223
+
224
+ for (const key of keys) {
225
+ result[key] = deepSortKeys((obj as Record<string, unknown>)[key]);
226
+ }
227
+
228
+ return result as T;
229
+ }