perp-cli 0.14.3 → 0.16.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 (363) hide show
  1. package/README.ja.md +243 -0
  2. package/README.ko.md +243 -0
  3. package/README.md +138 -405
  4. package/README.zh.md +243 -0
  5. package/dist/agent-wallet/store.d.ts +3 -2
  6. package/dist/agent-wallet/store.js +2 -2
  7. package/dist/api/public/hyperliquid.js +4 -0
  8. package/dist/api/public/lighter.d.ts +5 -0
  9. package/dist/api/public/lighter.js +5 -0
  10. package/dist/api/public/pacifica.js +85 -15
  11. package/dist/api/public/urls.d.ts +2 -0
  12. package/dist/api/public/urls.js +3 -0
  13. package/dist/arb/sizing.js +4 -2
  14. package/dist/arb/state.d.ts +6 -0
  15. package/dist/arb/utils.d.ts +1 -2
  16. package/dist/arb/utils.js +1 -2
  17. package/dist/balance-by-address.d.ts +39 -0
  18. package/dist/balance-by-address.js +44 -0
  19. package/dist/bot/apex/pulse.js +3 -0
  20. package/dist/bot/apex/radar.js +2 -1
  21. package/dist/bot/engine.d.ts +10 -1
  22. package/dist/bot/engine.js +14 -6
  23. package/dist/bot/indicators.d.ts +7 -0
  24. package/dist/bot/indicators.js +16 -0
  25. package/dist/bot/perp-order.d.ts +4 -4
  26. package/dist/bot/perp-order.js +5 -5
  27. package/dist/bot/quarantine.d.ts +1 -1
  28. package/dist/bot/quarantine.js +1 -1
  29. package/dist/bot/quote-rules.js +1 -1
  30. package/dist/bot/strategies/apex-strategy.js +3 -2
  31. package/dist/bot/strategies/engine-mm.js +6 -5
  32. package/dist/bot/strategies/funding-arb-strategy.js +12 -5
  33. package/dist/bot/strategies/funding-arb-utils.d.ts +15 -3
  34. package/dist/bot/strategies/funding-arb-utils.js +53 -20
  35. package/dist/bot/strategies/funding-auto.js +2 -2
  36. package/dist/bot/strategies/liquidation-mm.d.ts +1 -0
  37. package/dist/bot/strategies/liquidation-mm.js +8 -2
  38. package/dist/bot/strategies/momentum-breakout.d.ts +1 -0
  39. package/dist/bot/strategies/momentum-breakout.js +11 -3
  40. package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +1 -1
  41. package/dist/bot/strategies/spot-perp-arb-strategy.js +6 -4
  42. package/dist/bot/strategies/spot-perp-executor.d.ts +1 -1
  43. package/dist/bot/strategies/spot-perp-executor.js +3 -3
  44. package/dist/bot/strategy-types.d.ts +2 -1
  45. package/dist/bot/tui/BotDashboard.d.ts +1 -1
  46. package/dist/bot/tui/BotDashboard.js +1 -1
  47. package/dist/cache.js +12 -6
  48. package/dist/cli-program.d.ts +15 -0
  49. package/dist/cli-program.js +126 -6
  50. package/dist/commands/account.js +168 -24
  51. package/dist/commands/agent-import.d.ts +7 -0
  52. package/dist/commands/agent-import.js +30 -1
  53. package/dist/commands/agent.d.ts +6 -3
  54. package/dist/commands/agent.js +160 -12
  55. package/dist/commands/arb/index.js +23 -81
  56. package/dist/commands/arb-auto.d.ts +24 -0
  57. package/dist/commands/arb-auto.js +175 -97
  58. package/dist/commands/arb.d.ts +20 -0
  59. package/dist/commands/arb.js +107 -163
  60. package/dist/commands/backtest.js +23 -21
  61. package/dist/commands/bot.js +1 -1
  62. package/dist/commands/builder.js +1 -1
  63. package/dist/commands/funds.js +113 -3
  64. package/dist/commands/init.d.ts +1 -1
  65. package/dist/commands/init.js +27 -7
  66. package/dist/commands/manage.js +23 -21
  67. package/dist/commands/market.d.ts +48 -0
  68. package/dist/commands/market.js +395 -100
  69. package/dist/commands/options.d.ts +14 -0
  70. package/dist/commands/options.js +151 -0
  71. package/dist/commands/pacifica-earn.d.ts +5 -0
  72. package/dist/commands/pacifica-earn.js +455 -0
  73. package/dist/commands/pacifica-orders.d.ts +14 -0
  74. package/dist/commands/pacifica-orders.js +142 -0
  75. package/dist/commands/portfolio.d.ts +16 -0
  76. package/dist/commands/portfolio.js +83 -55
  77. package/dist/commands/rebalance.d.ts +1 -0
  78. package/dist/commands/rebalance.js +15 -0
  79. package/dist/commands/risk.d.ts +11 -2
  80. package/dist/commands/risk.js +39 -44
  81. package/dist/commands/trade.d.ts +1 -0
  82. package/dist/commands/trade.js +67 -62
  83. package/dist/commands/wallet.js +1 -1
  84. package/dist/config.d.ts +2 -2
  85. package/dist/config.js +15 -4
  86. package/dist/constants.js +6 -0
  87. package/dist/cross-chain-margin.d.ts +3 -1
  88. package/dist/cross-chain-margin.js +4 -2
  89. package/dist/dashboard/server.js +3 -3
  90. package/dist/dashboard/ui.js +9 -6
  91. package/dist/errors.d.ts +29 -1
  92. package/dist/errors.js +35 -3
  93. package/dist/exchanges/address.d.ts +8 -0
  94. package/dist/exchanges/address.js +47 -0
  95. package/dist/exchanges/arcus/adapter.d.ts +16 -12
  96. package/dist/exchanges/arcus/adapter.js +45 -18
  97. package/dist/exchanges/arcus/agent.d.ts +1 -1
  98. package/dist/exchanges/arcus/agent.js +1 -1
  99. package/dist/exchanges/arcus/client.js +1 -1
  100. package/dist/exchanges/arcus/public.js +3 -2
  101. package/dist/exchanges/arcus/signing.d.ts +2 -3
  102. package/dist/exchanges/arcus/signing.js +1 -1
  103. package/dist/exchanges/arcus/spot-chain.d.ts +1 -1
  104. package/dist/exchanges/arcus/spot-chain.js +1 -1
  105. package/dist/exchanges/arcus/spot-router.d.ts +1 -1
  106. package/dist/exchanges/arcus/spot-router.js +2 -2
  107. package/dist/exchanges/arcus/spot.d.ts +2 -3
  108. package/dist/exchanges/arcus/spot.js +2 -3
  109. package/dist/exchanges/aster-spot.d.ts +3 -3
  110. package/dist/exchanges/aster-spot.js +6 -9
  111. package/dist/exchanges/aster-typed-data.d.ts +1 -1
  112. package/dist/exchanges/aster-typed-data.js +1 -1
  113. package/dist/exchanges/aster.d.ts +30 -10
  114. package/dist/exchanges/aster.js +153 -53
  115. package/dist/exchanges/builder-fee.d.ts +1 -1
  116. package/dist/exchanges/builder-fee.js +13 -2
  117. package/dist/exchanges/canonical-symbols.d.ts +8 -2
  118. package/dist/exchanges/canonical-symbols.js +46 -2
  119. package/dist/exchanges/capabilities.d.ts +2 -0
  120. package/dist/exchanges/capabilities.js +2 -1
  121. package/dist/exchanges/derive/client.d.ts +37 -0
  122. package/dist/exchanges/derive/client.js +135 -0
  123. package/dist/exchanges/derive/options.d.ts +73 -0
  124. package/dist/exchanges/derive/options.js +379 -0
  125. package/dist/exchanges/extended/adapter.d.ts +96 -0
  126. package/dist/exchanges/extended/adapter.js +241 -0
  127. package/dist/exchanges/extended/client.d.ts +38 -0
  128. package/dist/exchanges/extended/client.js +88 -0
  129. package/dist/exchanges/extended/public.d.ts +133 -0
  130. package/dist/exchanges/extended/public.js +436 -0
  131. package/dist/exchanges/grvt/adapter.d.ts +100 -0
  132. package/dist/exchanges/grvt/adapter.js +283 -0
  133. package/dist/exchanges/grvt/client.d.ts +25 -0
  134. package/dist/exchanges/grvt/client.js +86 -0
  135. package/dist/exchanges/grvt/public.d.ts +151 -0
  136. package/dist/exchanges/grvt/public.js +438 -0
  137. package/dist/exchanges/hyperliquid-outcome.d.ts +2 -0
  138. package/dist/exchanges/hyperliquid-outcome.js +29 -1
  139. package/dist/exchanges/hyperliquid-spot.d.ts +9 -1
  140. package/dist/exchanges/hyperliquid-spot.js +24 -16
  141. package/dist/exchanges/hyperliquid.d.ts +199 -22
  142. package/dist/exchanges/hyperliquid.js +550 -202
  143. package/dist/exchanges/index.d.ts +1 -1
  144. package/dist/exchanges/index.js +1 -1
  145. package/dist/exchanges/interface.d.ts +157 -13
  146. package/dist/exchanges/interface.js +47 -0
  147. package/dist/exchanges/ioc-outcome.d.ts +1 -1
  148. package/dist/exchanges/ioc-outcome.js +1 -1
  149. package/dist/exchanges/legacy-trading-v2.d.ts +2 -2
  150. package/dist/exchanges/legacy-trading-v2.js +7 -2
  151. package/dist/exchanges/lighter-public.d.ts +67 -12
  152. package/dist/exchanges/lighter-public.js +250 -82
  153. package/dist/exchanges/lighter-signer.d.ts +1 -1
  154. package/dist/exchanges/lighter-signer.js +1 -1
  155. package/dist/exchanges/lighter-spot.d.ts +10 -5
  156. package/dist/exchanges/lighter-spot.js +105 -97
  157. package/dist/exchanges/lighter.d.ts +80 -21
  158. package/dist/exchanges/lighter.js +312 -105
  159. package/dist/exchanges/ondo/adapter.d.ts +38 -13
  160. package/dist/exchanges/ondo/adapter.js +86 -27
  161. package/dist/exchanges/ondo/agent.d.ts +1 -1
  162. package/dist/exchanges/ondo/agent.js +1 -1
  163. package/dist/exchanges/ondo/client.d.ts +1 -1
  164. package/dist/exchanges/ondo/client.js +2 -2
  165. package/dist/exchanges/ondo/public.d.ts +35 -3
  166. package/dist/exchanges/ondo/public.js +75 -32
  167. package/dist/exchanges/ondo/spot.d.ts +2 -2
  168. package/dist/exchanges/ondo/spot.js +3 -3
  169. package/dist/exchanges/options-interface.d.ts +158 -0
  170. package/dist/exchanges/options-interface.js +17 -0
  171. package/dist/exchanges/options-venues.d.ts +12 -0
  172. package/dist/exchanges/options-venues.js +35 -0
  173. package/dist/exchanges/outcome-interface.d.ts +4 -0
  174. package/dist/exchanges/pacifica-atomic.d.ts +211 -0
  175. package/dist/exchanges/pacifica-atomic.js +440 -0
  176. package/dist/exchanges/pacifica-decimal.d.ts +10 -0
  177. package/dist/exchanges/pacifica-decimal.js +51 -0
  178. package/dist/exchanges/pacifica-presigned.d.ts +53 -0
  179. package/dist/exchanges/pacifica-presigned.js +141 -0
  180. package/dist/exchanges/pacifica-print.d.ts +200 -0
  181. package/dist/exchanges/pacifica-print.js +417 -0
  182. package/dist/exchanges/pacifica-spot.d.ts +8 -2
  183. package/dist/exchanges/pacifica-spot.js +26 -18
  184. package/dist/exchanges/pacifica-typed-data.d.ts +1 -1
  185. package/dist/exchanges/pacifica-typed-data.js +1 -1
  186. package/dist/exchanges/pacifica-vaults.d.ts +130 -0
  187. package/dist/exchanges/pacifica-vaults.js +277 -0
  188. package/dist/exchanges/pacifica.d.ts +108 -10
  189. package/dist/exchanges/pacifica.js +428 -171
  190. package/dist/exchanges/precision.d.ts +3 -1
  191. package/dist/exchanges/precision.js +9 -2
  192. package/dist/exchanges/public-balances.d.ts +25 -0
  193. package/dist/exchanges/public-balances.js +158 -0
  194. package/dist/exchanges/public-positions.d.ts +43 -0
  195. package/dist/exchanges/public-positions.js +154 -0
  196. package/dist/exchanges/qfex/adapter.d.ts +69 -13
  197. package/dist/exchanges/qfex/adapter.js +102 -17
  198. package/dist/exchanges/qfex/agent.d.ts +1 -1
  199. package/dist/exchanges/qfex/agent.js +1 -1
  200. package/dist/exchanges/qfex/client.d.ts +1 -1
  201. package/dist/exchanges/qfex/client.js +1 -1
  202. package/dist/exchanges/qfex/errors.d.ts +1 -1
  203. package/dist/exchanges/qfex/errors.js +2 -2
  204. package/dist/exchanges/qfex/public.d.ts +65 -12
  205. package/dist/exchanges/qfex/public.js +131 -25
  206. package/dist/exchanges/qfex/signing.d.ts +2 -2
  207. package/dist/exchanges/qfex/signing.js +4 -5
  208. package/dist/exchanges/qfex/ws-transport.d.ts +3 -3
  209. package/dist/exchanges/qfex/ws-transport.js +3 -3
  210. package/dist/exchanges/rate-limiter.d.ts +1 -1
  211. package/dist/exchanges/rate-limiter.js +5 -2
  212. package/dist/exchanges/registry.d.ts +1 -1
  213. package/dist/exchanges/registry.js +1 -1
  214. package/dist/exchanges/risex/adapter.d.ts +160 -0
  215. package/dist/exchanges/risex/adapter.js +842 -0
  216. package/dist/exchanges/risex/agent.d.ts +154 -0
  217. package/dist/exchanges/risex/agent.js +384 -0
  218. package/dist/exchanges/risex/client.d.ts +47 -0
  219. package/dist/exchanges/risex/client.js +174 -0
  220. package/dist/exchanges/risex/nonce.d.ts +43 -0
  221. package/dist/exchanges/risex/nonce.js +212 -0
  222. package/dist/exchanges/risex/public.d.ts +64 -0
  223. package/dist/exchanges/risex/public.js +150 -0
  224. package/dist/exchanges/risex/session.d.ts +71 -0
  225. package/dist/exchanges/risex/session.js +171 -0
  226. package/dist/exchanges/risex/typed-data.d.ts +173 -0
  227. package/dist/exchanges/risex/typed-data.js +240 -0
  228. package/dist/exchanges/risex/units.d.ts +29 -0
  229. package/dist/exchanges/risex/units.js +104 -0
  230. package/dist/exchanges/slippage.d.ts +1 -1
  231. package/dist/exchanges/slippage.js +1 -1
  232. package/dist/exchanges/spot-factory.d.ts +3 -3
  233. package/dist/exchanges/spot-factory.js +8 -8
  234. package/dist/exchanges/spot-interface.d.ts +5 -5
  235. package/dist/exchanges/spot-interface.js +2 -2
  236. package/dist/exchanges/spot-result.d.ts +3 -3
  237. package/dist/exchanges/spot-result.js +3 -3
  238. package/dist/exchanges/trading-v2.d.ts +3 -3
  239. package/dist/exchanges/trading-v2.js +9 -6
  240. package/dist/exchanges/txflow/adapter.d.ts +61 -0
  241. package/dist/exchanges/txflow/adapter.js +150 -0
  242. package/dist/exchanges/txflow/client.d.ts +47 -0
  243. package/dist/exchanges/txflow/client.js +155 -0
  244. package/dist/exchanges/txflow/public.d.ts +87 -0
  245. package/dist/exchanges/txflow/public.js +355 -0
  246. package/dist/exchanges/unknown-symbol.d.ts +34 -0
  247. package/dist/exchanges/unknown-symbol.js +56 -0
  248. package/dist/exchanges/variational/adapter.d.ts +67 -0
  249. package/dist/exchanges/variational/adapter.js +137 -0
  250. package/dist/exchanges/variational/client.d.ts +21 -0
  251. package/dist/exchanges/variational/client.js +66 -0
  252. package/dist/exchanges/variational/public.d.ts +126 -0
  253. package/dist/exchanges/variational/public.js +430 -0
  254. package/dist/exchanges/venue-types.d.ts +52 -10
  255. package/dist/exchanges/venue-types.js +82 -11
  256. package/dist/exchanges/venue.d.ts +150 -11
  257. package/dist/exchanges/venue.js +126 -4
  258. package/dist/exchanges/venues/arcus.d.ts +1 -2
  259. package/dist/exchanges/venues/arcus.js +5 -4
  260. package/dist/exchanges/venues/aster.d.ts +2 -2
  261. package/dist/exchanges/venues/aster.js +6 -4
  262. package/dist/exchanges/venues/create-helpers.d.ts +1 -1
  263. package/dist/exchanges/venues/create-helpers.js +3 -3
  264. package/dist/exchanges/venues/extended.d.ts +21 -0
  265. package/dist/exchanges/venues/extended.js +96 -0
  266. package/dist/exchanges/venues/grvt.d.ts +30 -0
  267. package/dist/exchanges/venues/grvt.js +98 -0
  268. package/dist/exchanges/venues/hyperliquid.d.ts +2 -2
  269. package/dist/exchanges/venues/hyperliquid.js +19 -8
  270. package/dist/exchanges/venues/index.d.ts +7 -2
  271. package/dist/exchanges/venues/index.js +8 -2
  272. package/dist/exchanges/venues/lighter-family.d.ts +7 -9
  273. package/dist/exchanges/venues/lighter-family.js +9 -11
  274. package/dist/exchanges/venues/lighter-rh.d.ts +2 -2
  275. package/dist/exchanges/venues/lighter-rh.js +20 -8
  276. package/dist/exchanges/venues/lighter.d.ts +1 -1
  277. package/dist/exchanges/venues/lighter.js +18 -6
  278. package/dist/exchanges/venues/ondo.d.ts +1 -1
  279. package/dist/exchanges/venues/ondo.js +5 -3
  280. package/dist/exchanges/venues/pacifica.d.ts +2 -2
  281. package/dist/exchanges/venues/pacifica.js +15 -4
  282. package/dist/exchanges/venues/qfex.d.ts +1 -1
  283. package/dist/exchanges/venues/qfex.js +4 -2
  284. package/dist/exchanges/venues/risex-release.d.ts +14 -0
  285. package/dist/exchanges/venues/risex-release.js +14 -0
  286. package/dist/exchanges/venues/risex.d.ts +31 -0
  287. package/dist/exchanges/venues/risex.js +131 -0
  288. package/dist/exchanges/venues/txflow.d.ts +18 -0
  289. package/dist/exchanges/venues/txflow.js +77 -0
  290. package/dist/exchanges/venues/variational.d.ts +21 -0
  291. package/dist/exchanges/venues/variational.js +85 -0
  292. package/dist/execution-log.d.ts +1 -1
  293. package/dist/funding/legacy-public.d.ts +6 -4
  294. package/dist/funding/legacy-public.js +42 -41
  295. package/dist/funding/normalize.d.ts +1 -1
  296. package/dist/funding/normalize.js +1 -1
  297. package/dist/funding/rates.d.ts +95 -6
  298. package/dist/funding/rates.js +99 -19
  299. package/dist/index.js +7 -6
  300. package/dist/jobs.d.ts +1 -0
  301. package/dist/jobs.js +25 -1
  302. package/dist/landing.d.ts +2 -0
  303. package/dist/landing.js +1 -1
  304. package/dist/liquidity.d.ts +3 -1
  305. package/dist/liquidity.js +8 -4
  306. package/dist/mcp-pacifica-earn.d.ts +9 -0
  307. package/dist/mcp-pacifica-earn.js +64 -0
  308. package/dist/mcp-server.js +211 -68
  309. package/dist/pacifica/client.d.ts +69 -5
  310. package/dist/pacifica/client.js +181 -8
  311. package/dist/pacifica/rate-limit.d.ts +144 -0
  312. package/dist/pacifica/rate-limit.js +267 -0
  313. package/dist/pacifica/types/order.d.ts +2 -1
  314. package/dist/plan-executor.js +7 -2
  315. package/dist/positions-by-address.d.ts +34 -0
  316. package/dist/positions-by-address.js +46 -0
  317. package/dist/rebalance.js +1 -1
  318. package/dist/risk.d.ts +2 -1
  319. package/dist/risk.js +2 -4
  320. package/dist/settings.d.ts +32 -3
  321. package/dist/signer/ows-evm.d.ts +1 -1
  322. package/dist/signer/ows-evm.js +1 -1
  323. package/dist/smart-order.d.ts +1 -1
  324. package/dist/smart-order.js +1 -1
  325. package/dist/strategies/funding-arb.d.ts +1 -1
  326. package/dist/strategies/funding-arb.js +36 -63
  327. package/dist/trade-validator.js +2 -2
  328. package/dist/utils.d.ts +22 -3
  329. package/dist/utils.js +11 -3
  330. package/dist/venue-health.d.ts +40 -0
  331. package/dist/venue-health.js +75 -0
  332. package/dist/withdraw-gate.d.ts +1 -1
  333. package/dist/withdraw-gate.js +1 -1
  334. package/dist/ws/arcus-market.d.ts +9 -0
  335. package/dist/ws/arcus-market.js +9 -0
  336. package/dist/ws/aster-market.d.ts +33 -0
  337. package/dist/ws/aster-market.js +30 -0
  338. package/dist/ws/index.d.ts +980 -0
  339. package/dist/ws/index.js +4460 -0
  340. package/dist/ws/lighter-market.d.ts +14 -0
  341. package/dist/ws/lighter-market.js +13 -0
  342. package/dist/ws/node.d.ts +996 -0
  343. package/dist/ws/node.js +4554 -0
  344. package/dist/ws/pacifica-instrument.d.ts +8 -0
  345. package/dist/ws/pacifica-instrument.js +8 -0
  346. package/dist/ws/unclassified.d.ts +16 -0
  347. package/dist/ws/unclassified.js +43 -0
  348. package/dist/ws-cli/credentials.d.ts +62 -0
  349. package/dist/ws-cli/credentials.js +126 -0
  350. package/package.json +71 -10
  351. package/skills/perp-cli/SKILL.md +153 -36
  352. package/skills/perp-cli/package.json +15 -2
  353. package/skills/perp-cli/references/agent-operations.md +22 -12
  354. package/skills/perp-cli/references/commands.md +143 -17
  355. package/skills/perp-cli/references/strategies.md +38 -3
  356. package/skills/perp-cli/references/ws-library.md +523 -0
  357. package/skills/perp-cli/scripts/arb-monitor.sh +13 -10
  358. package/skills/perp-cli/scripts/funding-analysis.sh +57 -29
  359. package/skills/perp-cli/scripts/preflight.sh +27 -19
  360. package/skills/perp-cli/scripts/spot-perp-scan.sh +58 -38
  361. package/skills/perp-cli/scripts/validate-arb.sh +56 -39
  362. package/dist/cli-spec.d.ts +0 -46
  363. package/dist/cli-spec.js +0 -81
@@ -0,0 +1,438 @@
1
+ /**
2
+ * GRVT public market data — official Market Data REST API (api-docs.grvt.io/market_data_api/).
3
+ *
4
+ * Sources (all `POST https://market-data.grvt.io/full/v1/…`, no key):
5
+ * instruments {kind:["PERPETUAL"], is_active:true, limit} directory: instrument "BTC_USDT_Perp",
6
+ * base, quote, kind, settlement_period, tick_size, min_size, min_notional,
7
+ * funding_interval_hours (4 or 8, PER INSTRUMENT), asset_class (CRYPTO / EQUITY / COMMODITY),
8
+ * status. No leverage and no lot step (base_decimals is the signing multiplier, not a lot).
9
+ * ticker {instrument} one instrument per call (no bulk ticker exists): mark / index / last /
10
+ * best bid / best ask prices, open_interest (base), buy/sell_volume_24h_q (quote = USDT),
11
+ * funding_rate, next_funding_time (ns).
12
+ * margin_rules {instrument} risk_brackets → max_leverage of tier 1 (= maxLeverage) and the tiers.
13
+ *
14
+ * Mapping:
15
+ * - symbol = `base` ("BTC", "KPEPE"); venueSymbol = instrument ("BTC_USDT_Perp"); canonical via
16
+ * canonical-symbols.ts (crypto by ticker + denomination aliases, equity / etf by ticker,
17
+ * commodity explicit only). Pre-IPO markets (listed by GRVT as EQUITY) are preIpo → no canonical.
18
+ * - funding: `funding_rate` is the venue's INDICATIVE rate for the active interval (the interval
19
+ * ending at next_funding_time) — kind "predicted", a moving estimate. Its unit is PERCENT per the
20
+ * market's funding interval: settled history (/full/v1/funding, "percentage points") prints the
21
+ * same magnitude (BTC 0.01 = HL-style 0.01 %/8 h baseline; ticker 0.0063). The ticker schema says
22
+ * "centibeeps", which would make every rate 100× smaller than its own settled prints — not used.
23
+ * Converted to a fraction per interval by an exact × 0.01; basis perHours = funding_interval_hours.
24
+ * - volume24h = buy + sell 24 h quote volume (the docs' "24 hour volume"), exact decimal sum.
25
+ * - unknown / absent values are null; a ticker without a mark price, or whose read fails, drops
26
+ * that market (warned by name).
27
+ * - request budget: the registry path (fetchMarkets / fetchFundingRates) is price-only
28
+ * (instruments + one ticker per market, both reused in-process for 15 s); margin_rules (leverage)
29
+ * only for `market list` / `info`, file-cached 6 h per network.
30
+ */
31
+ import { getCached, invalidateCache, setCached } from "../../cache.js";
32
+ import { PerpError, extractErrorMessage } from "../../errors.js";
33
+ import { canonicalFor } from "../canonical-symbols.js";
34
+ import { addDecimal, mulDecimal } from "../precision.js";
35
+ import { symbolNotFound } from "../unknown-symbol.js";
36
+ import { networkConfig } from "../venue.js";
37
+ import { grvtVenue } from "../venues/grvt.js";
38
+ import { grvtShapeError, GrvtClient } from "./client.js";
39
+ /** Requests in flight at once for the per-instrument fan-out (ticker / margin_rules). */
40
+ export const GRVT_FANOUT_CONCURRENCY = 8;
41
+ /** In-process reuse of the directory and of each ticker (repeated MCP / dashboard reads). */
42
+ export const GRVT_SHORT_TTL_MS = 15_000;
43
+ /** margin_rules brackets change rarely: file cache (src/cache.ts), per network. */
44
+ export const GRVT_MARGIN_TTL_MS = 6 * 3_600_000;
45
+ /**
46
+ * GRVT EQUITY markets that may match other venues by ticker identity: exchange-listed shares and
47
+ * ETFs (or, for Korean / HK listings, the plain company name every venue uses). Reviewed against
48
+ * the live list 2026-10-19. Any other EQUITY market is explicit-only (canonical null) — a new
49
+ * pre-IPO or synthetic listing is never matched by a look-alike ticker.
50
+ */
51
+ const MATCHABLE_EQUITY = new Set([
52
+ "AAOI", "AAPL", "AMAT", "AMD", "AMZN", "ARM", "ASML", "ASTS", "AVGO", "AXTI", "BABA", "BE", "BMNR", "BX", "COHR", "COIN",
53
+ "CRCL", "CRDO", "CRM", "CRWD", "CRWV", "CSCO", "DELL", "DIS", "FLNC", "GLW", "GOOGL", "GS", "HANMI", "HD", "HOOD", "HYUNDAI",
54
+ "IBM", "INTC", "IREN", "JPM", "LGELECTRONICS", "LITE", "LLY", "META", "MINIMAX", "MRNA", "MRVL", "MSFT", "MSTR", "MU", "NAVER",
55
+ "NBIS", "NFLX", "NOK", "NOW", "NVDA", "NVO", "ONDS", "ORCL", "PLTR", "QCOM", "RKLB", "SAMSUNG", "SKHYNIX", "SMCI", "SNDK",
56
+ "STRC", "TSLA", "TSM", "UBER", "USAR", "V", "WDC", "WMT",
57
+ ]);
58
+ /** Exchange-traded funds among GRVT's EQUITY markets (matchable by ticker, labelled etf). */
59
+ const ETF = new Set([
60
+ "SPY", "QQQ", "IWM", "EWJ", "EWT", "EWY", "EWZ", "SOXL", "TQQQ", "SQQQ", "UVXY", "URNM", "XLE", "KORU", "KODEX200",
61
+ ]);
62
+ /** Pre-IPO private companies GRVT lists as EQUITY (a private valuation, not a listed share). */
63
+ const PRE_IPO = new Set(["ANTHROPIC", "OPENAI", "SPACEX"]);
64
+ /**
65
+ * Reviewed EQUITY markets that stay explicit-only without a warning: tickers that are not a plain
66
+ * exchange ticker or are ambiguous across venues (BRKB vs BRK.B, SKHY next to SKHYNIX, SAMSUNGEM,
67
+ * DRAM, …). Unreviewed tickers are explicit-only too, but warned once per process.
68
+ */
69
+ const EXPLICIT_ONLY_EQUITY = new Set([
70
+ "BBX", "BRKB", "CBRS", "DRAM", "KSTR", "PAYP", "QNTX", "SAMSUNGEM", "SKHY", "STXX",
71
+ ]);
72
+ const warnedUnreviewed = new Set();
73
+ const DEC = /^-?\d+(\.\d+)?$/;
74
+ function decOrNull(v) {
75
+ return typeof v === "string" && DEC.test(v) ? v : null;
76
+ }
77
+ function positiveDec(v, what) {
78
+ if (typeof v !== "string" || !/^\d+(\.\d+)?$/.test(v) || !(Number(v) > 0))
79
+ throw grvtShapeError(`${what} is not a positive decimal`);
80
+ return v;
81
+ }
82
+ function assetClassOf(symbol, raw) {
83
+ switch (raw) {
84
+ case "CRYPTO": return "crypto";
85
+ case "COMMODITY": return "commodity";
86
+ case "EQUITY": return PRE_IPO.has(symbol) ? "preIpo" : ETF.has(symbol) ? "etf" : "equity";
87
+ default: return null;
88
+ }
89
+ }
90
+ /**
91
+ * Cross-venue key: crypto / commodity / preIpo through canonical-symbols.ts; an equity / etf only
92
+ * when whitelisted (MATCHABLE_EQUITY / ETF), else null — warned once when not reviewed at all.
93
+ */
94
+ export function grvtCanonicalOf(symbol, assetClass, warn) {
95
+ if (assetClass !== "equity" && assetClass !== "etf")
96
+ return canonicalFor("grvt", symbol, assetClass);
97
+ if (MATCHABLE_EQUITY.has(symbol) || ETF.has(symbol))
98
+ return canonicalFor("grvt", symbol, assetClass);
99
+ if (!EXPLICIT_ONLY_EQUITY.has(symbol) && !warnedUnreviewed.has(symbol)) {
100
+ warnedUnreviewed.add(symbol);
101
+ warn(`[grvt] ${symbol}: equity market not yet reviewed for cross-venue matching — not matched (canonical null)`);
102
+ }
103
+ return null;
104
+ }
105
+ /**
106
+ * Parse one instruments row. Returns null for a row that is not a listed perpetual (another kind,
107
+ * an expiring settlement period); shape drift on a perpetual row is EXCHANGE_ERROR.
108
+ */
109
+ export function toGrvtMarketMeta(raw, warn) {
110
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw))
111
+ throw grvtShapeError("instruments row is not an object");
112
+ const r = raw;
113
+ if (r.kind !== "PERPETUAL" || r.settlement_period !== "PERPETUAL")
114
+ return null;
115
+ if (typeof r.instrument !== "string" || typeof r.base !== "string" || r.base === "" || typeof r.quote !== "string") {
116
+ throw grvtShapeError(`instrument row ${String(r.instrument)} has no instrument / base / quote`);
117
+ }
118
+ const symbol = r.base.toUpperCase();
119
+ const assetClass = assetClassOf(symbol, r.asset_class);
120
+ if (assetClass === null) {
121
+ warn(`[grvt] skipped ${r.instrument}: unknown asset_class "${String(r.asset_class)}"`);
122
+ return null;
123
+ }
124
+ const fi = r.funding_interval_hours;
125
+ if (typeof fi !== "number" || !Number.isInteger(fi) || fi <= 0)
126
+ throw grvtShapeError(`${r.instrument} funding_interval_hours ${String(fi)} is not a positive integer`);
127
+ return {
128
+ instrument: r.instrument,
129
+ symbol,
130
+ quote: r.quote,
131
+ tickSize: positiveDec(r.tick_size, `${r.instrument} tick_size`),
132
+ minSize: positiveDec(r.min_size, `${r.instrument} min_size`),
133
+ minNotional: decOrNull(r.min_notional),
134
+ fundingIntervalHours: fi,
135
+ assetClass,
136
+ canonical: grvtCanonicalOf(symbol, assetClass, warn),
137
+ tradable: r.status === "ACTIVE",
138
+ };
139
+ }
140
+ export async function fetchGrvtDirectory(client, warn) {
141
+ const rows = await client.result("instruments", { kind: ["PERPETUAL"], is_active: true, limit: 100000 });
142
+ if (!Array.isArray(rows))
143
+ throw grvtShapeError("instruments result is not an array");
144
+ const out = [];
145
+ const seen = new Set();
146
+ for (const row of rows) {
147
+ const m = toGrvtMarketMeta(row, warn);
148
+ if (m === null)
149
+ continue;
150
+ if (seen.has(m.symbol))
151
+ throw grvtShapeError(`instruments list ${m.symbol} twice`);
152
+ seen.add(m.symbol);
153
+ out.push(m);
154
+ }
155
+ return out;
156
+ }
157
+ /** Resolve a CLI symbol ("btc", "BTC-PERP", "BTC_USDT_Perp", "BTC-USDT") against the directory. */
158
+ export function resolveGrvtMarket(dir, input) {
159
+ const up = input.trim().toUpperCase().replace(/-PERP$/, "").replace(/_PERP$/, "").replace(/[-_/]USDT$/, "");
160
+ const hit = dir.find(m => m.symbol === up);
161
+ if (hit === undefined)
162
+ throw symbolNotFound("grvt", input);
163
+ return hit;
164
+ }
165
+ function nsToMsOrNull(v) {
166
+ if (typeof v !== "string" || !/^\d+$/.test(v))
167
+ return null;
168
+ const ms = Number(BigInt(v) / 1000000n);
169
+ return ms > 0 ? ms : null;
170
+ }
171
+ export function toGrvtTicker(raw) {
172
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw))
173
+ throw grvtShapeError("ticker result is not an object");
174
+ const r = raw;
175
+ const bv = decOrNull(r.buy_volume_24h_q);
176
+ const sv = decOrNull(r.sell_volume_24h_q);
177
+ return {
178
+ markPrice: decOrNull(r.mark_price),
179
+ indexPrice: decOrNull(r.index_price),
180
+ lastPrice: decOrNull(r.last_price),
181
+ bestBid: decOrNull(r.best_bid_price),
182
+ bestAsk: decOrNull(r.best_ask_price),
183
+ openInterest: decOrNull(r.open_interest),
184
+ volume24hQuote: bv !== null && sv !== null ? addDecimal(bv, sv) : null,
185
+ fundingPct: decOrNull(r.funding_rate),
186
+ nextFundingTime: nsToMsOrNull(r.next_funding_time),
187
+ };
188
+ }
189
+ /** Percent → fraction, exactly ("0.0063" → "0.000063"); null stays null (unknown, never 0). */
190
+ export function grvtPercentToFraction(pct) {
191
+ return pct === null ? null : mulDecimal(pct, "0.01");
192
+ }
193
+ export function grvtFundingBasis(m) {
194
+ return { kind: "perHours", hours: m.fundingIntervalHours };
195
+ }
196
+ /** risk_brackets → tier-1 max leverage and the notional tiers (ascending); null when unpublished. */
197
+ export function toGrvtLeverage(raw) {
198
+ const brackets = raw?.risk_brackets;
199
+ if (!Array.isArray(brackets) || brackets.length === 0)
200
+ return null;
201
+ const tiers = [];
202
+ for (const b of brackets) {
203
+ const x = b;
204
+ const floor = decOrNull(x.notional_floor);
205
+ if (floor === null || typeof x.max_leverage !== "number" || !Number.isInteger(x.max_leverage) || x.max_leverage <= 0)
206
+ return null;
207
+ tiers.push({ lowerBound: Number(floor), maxLeverage: x.max_leverage });
208
+ }
209
+ tiers.sort((a, b) => a.lowerBound - b.lowerBound);
210
+ return { maxLeverage: tiers[0].maxLeverage, tiers };
211
+ }
212
+ function isRateLimited(e) {
213
+ return e instanceof PerpError && e.structured.code === "RATE_LIMITED";
214
+ }
215
+ /**
216
+ * Run `fn` over `items` with `limit` in flight; each item settles on its own. RATE_LIMITED stops
217
+ * every worker (nothing new is sent) and is re-thrown — the venue told us to back off.
218
+ */
219
+ export async function settleEach(items, limit, fn) {
220
+ const out = new Array(items.length);
221
+ let next = 0;
222
+ let rateLimited = null;
223
+ const worker = async () => {
224
+ while (rateLimited === null && next < items.length) {
225
+ const i = next++;
226
+ try {
227
+ out[i] = { ok: true, value: await fn(items[i]) };
228
+ }
229
+ catch (e) {
230
+ if (isRateLimited(e)) {
231
+ rateLimited = e;
232
+ return;
233
+ }
234
+ out[i] = { ok: false, error: e };
235
+ }
236
+ }
237
+ };
238
+ await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
239
+ if (rateLimited !== null)
240
+ throw rateLimited;
241
+ return out;
242
+ }
243
+ function failureList(names, errors) {
244
+ return names.map((n, i) => `${n} (${extractErrorMessage(errors[i])})`).join(", ");
245
+ }
246
+ export function fetchGrvtTicker(client, instrument) {
247
+ return client.result("ticker", { instrument }).then(toGrvtTicker);
248
+ }
249
+ export function fetchGrvtLeverage(client, instrument) {
250
+ return client.post("margin_rules", { instrument }).then(toGrvtLeverage);
251
+ }
252
+ // ── Caches ──
253
+ const dirCache = new Map();
254
+ const tickerCache = new Map();
255
+ const marginCacheKey = (net) => `grvt-margin-rules-${net}`;
256
+ /** Drop every GRVT cache (in-process directory / tickers and the margin-rules file cache). */
257
+ export function clearGrvtCaches() {
258
+ dirCache.clear();
259
+ tickerCache.clear();
260
+ invalidateCache("grvt-");
261
+ }
262
+ /** The directory, reused for GRVT_SHORT_TTL_MS; a failed read is not kept. */
263
+ export function grvtDirectory(client, net, warn) {
264
+ const hit = dirCache.get(net);
265
+ if (hit !== undefined && Date.now() - hit.at < GRVT_SHORT_TTL_MS)
266
+ return hit.value;
267
+ const value = fetchGrvtDirectory(client, warn);
268
+ dirCache.set(net, { at: Date.now(), value });
269
+ value.catch(() => { if (dirCache.get(net)?.value === value)
270
+ dirCache.delete(net); });
271
+ return value;
272
+ }
273
+ /** One ticker, reused for GRVT_SHORT_TTL_MS. */
274
+ export async function grvtTicker(client, net, instrument) {
275
+ const key = `${net}|${instrument}`;
276
+ const hit = tickerCache.get(key);
277
+ if (hit !== undefined && Date.now() - hit.at < GRVT_SHORT_TTL_MS)
278
+ return hit.value;
279
+ const value = await fetchGrvtTicker(client, instrument);
280
+ tickerCache.set(key, { at: Date.now(), value });
281
+ return value;
282
+ }
283
+ function isEntry(v) {
284
+ if (v === null || typeof v !== "object")
285
+ return false;
286
+ const e = v;
287
+ return typeof e.fetchedAt === "number" && Number.isFinite(e.fetchedAt) && (e.leverage === null || (typeof e.leverage === "object" && e.leverage !== undefined));
288
+ }
289
+ /**
290
+ * Leverage per instrument from the margin-rules file cache (JSON: instrument → {fetchedAt, leverage}).
291
+ * Each entry expires on its own GRVT_MARGIN_TTL_MS after ITS read (rewriting the file never extends
292
+ * an old entry); expired, malformed and missing instruments are read again. A failed read leaves
293
+ * that market's leverage unknown (null entry, warned, not cached).
294
+ */
295
+ export async function grvtLeverageFor(client, net, instruments, warn) {
296
+ const now = Date.now();
297
+ const stored = getCached(marginCacheKey(net)) ?? {};
298
+ const cache = {};
299
+ for (const [inst, e] of Object.entries(stored)) {
300
+ if (isEntry(e) && e.fetchedAt <= now && now - e.fetchedAt < GRVT_MARGIN_TTL_MS)
301
+ cache[inst] = e;
302
+ }
303
+ const missing = instruments.filter(i => !(i in cache));
304
+ if (missing.length > 0) {
305
+ const settled = await settleEach(missing, GRVT_FANOUT_CONCURRENCY, i => fetchGrvtLeverage(client, i));
306
+ const failed = [];
307
+ const errors = [];
308
+ settled.forEach((r, k) => {
309
+ if (r.ok)
310
+ cache[missing[k]] = { fetchedAt: Date.now(), leverage: r.value };
311
+ else {
312
+ failed.push(missing[k]);
313
+ errors.push(r.error);
314
+ }
315
+ });
316
+ if (failed.length > 0)
317
+ warn(`[grvt] margin rules unreadable for ${failed.length} market(s) — max leverage unknown: ${failureList(failed, errors)}`);
318
+ // The file outlives every entry in it (each is younger than the TTL); entries expire by fetchedAt.
319
+ setCached(marginCacheKey(net), cache, GRVT_MARGIN_TTL_MS);
320
+ }
321
+ return Object.fromEntries(instruments.map(i => [i, i in cache ? cache[i] : null]));
322
+ }
323
+ export function toGrvtVenueMarket(m, t, entry) {
324
+ const lev = entry === null ? null : entry.leverage;
325
+ return {
326
+ venue: "grvt",
327
+ venueSymbol: m.instrument,
328
+ symbol: m.symbol,
329
+ canonical: m.canonical,
330
+ assetClass: m.assetClass,
331
+ settle: "USDT",
332
+ quoteAsset: m.quote,
333
+ markPrice: t.markPrice,
334
+ indexPrice: t.indexPrice,
335
+ fundingRate: grvtPercentToFraction(t.fundingPct),
336
+ fundingRateBasis: grvtFundingBasis(m),
337
+ settlementIntervalHours: m.fundingIntervalHours,
338
+ openInterest: t.openInterest,
339
+ volume24h: t.volume24hQuote,
340
+ tickSize: m.tickSize,
341
+ // No lot step is published (min_size is the minimum, base_decimals the signing multiplier).
342
+ stepSize: null,
343
+ minSize: m.minSize,
344
+ minNotional: m.minNotional,
345
+ maxLeverage: lev === null ? null : lev.maxLeverage,
346
+ marginTiers: lev === null ? null : lev.tiers,
347
+ leverageAsOf: entry === null ? null : new Date(entry.fetchedAt).toISOString(),
348
+ // Not mapped: the ticker's `session` ("REGULAR" on every market, 2026-10-19) is not in GRVT's
349
+ // documented schema and its other values / their meaning (open hours, price bands) are unknown.
350
+ session: null,
351
+ tradable: m.tradable,
352
+ lastPrice: t.lastPrice,
353
+ bestBid: t.bestBid,
354
+ bestAsk: t.bestAsk,
355
+ nextFundingTime: t.nextFundingTime,
356
+ };
357
+ }
358
+ const DEFAULT_DEPS = { warn: msg => process.stderr.write(`${msg}\n`) };
359
+ export function grvtClient(net) {
360
+ return new GrvtClient(networkConfig(grvtVenue, net).restUrl, net);
361
+ }
362
+ function priced(meta, t) {
363
+ return t.markPrice !== null && Number(t.markPrice) > 0 ? { meta, ticker: { ...t, markPrice: t.markPrice } } : null;
364
+ }
365
+ /**
366
+ * Directory + one ticker per market. A market whose ticker read fails, or has no mark price, is
367
+ * dropped with a warning naming it; the venue fails only when every ticker read fails (or on
368
+ * RATE_LIMITED).
369
+ */
370
+ async function fetchPriced(client, net, deps) {
371
+ const dir = await grvtDirectory(client, net, deps.warn);
372
+ const settled = await settleEach(dir, GRVT_FANOUT_CONCURRENCY, m => grvtTicker(client, net, m.instrument));
373
+ const out = [];
374
+ const unpriced = [];
375
+ const failed = [];
376
+ const errors = [];
377
+ dir.forEach((meta, i) => {
378
+ const r = settled[i];
379
+ if (!r.ok) {
380
+ failed.push(meta.symbol);
381
+ errors.push(r.error);
382
+ return;
383
+ }
384
+ const p = priced(meta, r.value);
385
+ if (p === null)
386
+ unpriced.push(meta.symbol);
387
+ else
388
+ out.push(p);
389
+ });
390
+ if (dir.length > 0 && failed.length === dir.length)
391
+ throw errors[0];
392
+ if (failed.length > 0)
393
+ deps.warn(`[grvt] ${failed.length} market(s) whose ticker could not be read are not shown: ${failureList(failed, errors)}`);
394
+ if (unpriced.length > 0)
395
+ deps.warn(`[grvt] ${unpriced.length} market(s) without a mark price are not shown: ${unpriced.join(", ")}`);
396
+ return out;
397
+ }
398
+ export async function fetchGrvtMarkets(net, opts, deps = DEFAULT_DEPS) {
399
+ const client = grvtClient(net);
400
+ const rows = await fetchPriced(client, net, deps);
401
+ const lev = opts.leverage ? await grvtLeverageFor(client, net, rows.map(p => p.meta.instrument), deps.warn) : null;
402
+ return rows.map(p => toGrvtVenueMarket(p.meta, p.ticker, lev === null ? null : lev[p.meta.instrument]));
403
+ }
404
+ /** One market: directory (cached) + its ticker + its margin rules — no fan-out. */
405
+ export async function fetchGrvtMarket(net, symbol, deps = DEFAULT_DEPS) {
406
+ const client = grvtClient(net);
407
+ const meta = resolveGrvtMarket(await grvtDirectory(client, net, deps.warn), symbol);
408
+ const p = priced(meta, await grvtTicker(client, net, meta.instrument));
409
+ if (p === null)
410
+ throw grvtShapeError(`${meta.instrument} ticker has no mark price`);
411
+ const lev = await grvtLeverageFor(client, net, [meta.instrument], deps.warn);
412
+ return toGrvtVenueMarket(p.meta, p.ticker, lev[meta.instrument]);
413
+ }
414
+ /** Funding rows (tickers only — no margin_rules): fraction per the market's own interval. */
415
+ export async function fetchGrvtFundingRates(net, deps = DEFAULT_DEPS) {
416
+ const out = [];
417
+ for (const { meta, ticker } of await fetchPriced(grvtClient(net), net, deps)) {
418
+ const rate = grvtPercentToFraction(ticker.fundingPct);
419
+ if (rate === null)
420
+ continue;
421
+ out.push({
422
+ venue: "grvt",
423
+ venueSymbol: meta.instrument,
424
+ symbol: meta.symbol,
425
+ canonical: meta.canonical,
426
+ rate,
427
+ basis: grvtFundingBasis(meta),
428
+ markPrice: ticker.markPrice,
429
+ nextFundingTime: ticker.nextFundingTime,
430
+ });
431
+ }
432
+ return out;
433
+ }
434
+ /** The registry's public data path (MCP get_prices / get_markets, market://prices, arb, dashboards): price-only. */
435
+ export const grvtPublicData = {
436
+ fetchMarkets: net => fetchGrvtMarkets(net, { leverage: false }),
437
+ fetchFundingRates: net => fetchGrvtFundingRates(net),
438
+ };
@@ -15,6 +15,8 @@
15
15
  */
16
16
  import type { OutcomeAdapter, OutcomeMarketInfo, OutcomeOrderbook, OutcomePosition, OutcomeView, OutcomeViewUnderlying } from "./outcome-interface.js";
17
17
  import type { HyperliquidAdapter } from "./hyperliquid.js";
18
+ /** Bid–ask width (probability points) above which a side's mid-implied probability is flagged. */
19
+ export declare const OUTCOME_WIDE_SPREAD = 0.2;
18
20
  export declare class HyperliquidOutcomeAdapter implements OutcomeAdapter {
19
21
  readonly name = "hyperliquid";
20
22
  private _hl;
@@ -22,6 +22,16 @@ const MIN_ORDER_QUOTE = 10;
22
22
  const MAX_SIDE = 9;
23
23
  /** Asset id offset spans `[100_000_000, 200_000_000)`, so encoding < 100M. */
24
24
  const MAX_ENCODING = 99_999_999;
25
+ /** A numeric description field; a malformed one is left unset (unknown) with a warning, never NaN. */
26
+ function finiteOrWarn(val, key, description) {
27
+ const n = val.trim() === "" ? NaN : Number(val);
28
+ if (Number.isFinite(n))
29
+ return n;
30
+ process.stderr.write(`[hyperliquid] outcome description ${key} "${val}" is not a number — target price unknown (${description.slice(0, 120)})\n`);
31
+ return undefined;
32
+ }
33
+ /** Bid–ask width (probability points) above which a side's mid-implied probability is flagged. */
34
+ export const OUTCOME_WIDE_SPREAD = 0.2;
25
35
  export class HyperliquidOutcomeAdapter {
26
36
  name = "hyperliquid";
27
37
  _hl;
@@ -76,9 +86,17 @@ export class HyperliquidOutcomeAdapter {
76
86
  else if (key === "expiry")
77
87
  out.expiryMs = HyperliquidOutcomeAdapter._parseExpiry(val);
78
88
  else if (key === "targetPrice")
79
- out.targetPrice = Number(val);
89
+ out.targetPrice = finiteOrWarn(val, key, description);
80
90
  else if (key === "period")
81
91
  out.period = val;
92
+ // Template outcomes (name "template:binaryPrice", live 2026-10-19):
93
+ // "perp:ZEC|priceDescription:…|seconds:90|threshold:1057|time:20261023-0800".
94
+ else if (key === "perp" && out.underlying === undefined)
95
+ out.underlying = val;
96
+ else if (key === "threshold" && out.targetPrice === undefined)
97
+ out.targetPrice = finiteOrWarn(val, key, description);
98
+ else if (key === "time" && out.expiryMs === undefined)
99
+ out.expiryMs = HyperliquidOutcomeAdapter._parseExpiry(val);
82
100
  }
83
101
  return out;
84
102
  }
@@ -315,6 +333,9 @@ export class HyperliquidOutcomeAdapter {
315
333
  });
316
334
  }
317
335
  const parsed = HyperliquidOutcomeAdapter.parseDescription(meta.description);
336
+ // A template binary-price outcome is a price binary (Yes = underlying ≥ threshold at `time`).
337
+ if (parsed.class === undefined && meta.name === "template:binaryPrice")
338
+ parsed.class = "priceBinary";
318
339
  // Fetch books for every side in parallel + allMids once for mids and
319
340
  // the underlying symbol's mark price.
320
341
  const allMidsPromise = this._infoPost({ type: "allMids" });
@@ -336,6 +357,13 @@ export class HyperliquidOutcomeAdapter {
336
357
  bestBid: trimmed.bestBid,
337
358
  bestAsk: trimmed.bestAsk,
338
359
  impliedProb: mid !== undefined ? Number(mid) : undefined,
360
+ // Template side names ("template:Yes") without the prefix; `name` stays as published.
361
+ label: spec.name.replace(/^template:/, ""),
362
+ // The mid of a book wider than OUTCOME_WIDE_SPREAD (e.g. 0.0257 / 0.9899) says little
363
+ // about the probability: flagged, never dropped.
364
+ wideSpread: trimmed.bestBid !== undefined && trimmed.bestAsk !== undefined
365
+ ? Number(trimmed.bestAsk) - Number(trimmed.bestBid) > OUTCOME_WIDE_SPREAD
366
+ : null,
339
367
  };
340
368
  });
341
369
  const midSum = HyperliquidOutcomeAdapter._computeMidSum(sides);
@@ -9,6 +9,14 @@
9
9
  import type { SpotAdapter, SpotMarketInfo, SpotBalance, SpotHoldingPrice, SpotHoldingPricesCapable } from "./spot-interface.js";
10
10
  import type { ResolvedSlippage } from "./slippage.js";
11
11
  import type { HyperliquidAdapter } from "./hyperliquid.js";
12
+ import { type HlAbstractionMode } from "./hyperliquid.js";
13
+ /**
14
+ * Spot balances from info `spotClearinghouseState` — the one mapper behind the keyed spot adapter
15
+ * and the keyless `account balance --address`. `shared` (already counted in the perp equity) follows
16
+ * the account mode: unified → the USDC row; portfolio margin → USDC and every eligible collateral row
17
+ * (the venue marks those with `ltv`); any other mode, or `null` (a HIP-3 dex scope / mode unknown) → none.
18
+ */
19
+ export declare function hlSpotBalanceRows(state: Record<string, unknown>, mode: HlAbstractionMode | null): SpotBalance[];
12
20
  export declare class HyperliquidSpotAdapter implements SpotAdapter, SpotHoldingPricesCapable {
13
21
  readonly name = "hyperliquid";
14
22
  private _hl;
@@ -74,7 +82,7 @@ export declare class HyperliquidSpotAdapter implements SpotAdapter, SpotHoldingP
74
82
  * `usdClassTransfer`. No signer → NO_SIGNER_AVAILABLE from the adapter.
75
83
  */
76
84
  private _moveUsdc;
77
- /** SpotTransferCapable (§12.4 #4): USDC only; an `err` response is thrown, not returned. */
85
+ /** SpotTransferCapable: USDC only; an `err` response is thrown, not returned. */
78
86
  transferToSpot(amount: string, asset: string): Promise<unknown>;
79
87
  transferToPerp(amount: string, asset: string): Promise<unknown>;
80
88
  private _checkedTransfer;
@@ -16,6 +16,24 @@ import { invalidateCache } from "../cache.js";
16
16
  function hlRoundSize(size, szDecimals) {
17
17
  return floorSize(size, hlSizeStep(szDecimals));
18
18
  }
19
+ /**
20
+ * Spot balances from info `spotClearinghouseState` — the one mapper behind the keyed spot adapter
21
+ * and the keyless `account balance --address`. `shared` (already counted in the perp equity) follows
22
+ * the account mode: unified → the USDC row; portfolio margin → USDC and every eligible collateral row
23
+ * (the venue marks those with `ltv`); any other mode, or `null` (a HIP-3 dex scope / mode unknown) → none.
24
+ */
25
+ export function hlSpotBalanceRows(state, mode) {
26
+ const balances = (state?.balances ?? []);
27
+ return balances.map((b) => ({
28
+ token: String(b.coin ?? ""),
29
+ total: String(b.total ?? "0"),
30
+ available: String(Number(b.total ?? 0) - Number(b.hold ?? 0)),
31
+ held: String(b.hold ?? "0"),
32
+ entryNtl: b.entryNtl !== undefined ? String(b.entryNtl) : undefined,
33
+ shared: (mode === "unified" || mode === "portfolio") && String(b.coin ?? "") === "USDC"
34
+ || mode === "portfolio" && Object.prototype.hasOwnProperty.call(b, "ltv"),
35
+ }));
36
+ }
19
37
  export class HyperliquidSpotAdapter {
20
38
  name = "hyperliquid";
21
39
  _hl;
@@ -181,18 +199,8 @@ export class HyperliquidSpotAdapter {
181
199
  // in the funding-arb bot (and a misleading empty holdings table).
182
200
  // Reuse cached spot clearinghouse state (shared with getBalance() — saves 1 API call).
183
201
  const state = await this._hl._getSpotClearinghouseState({ fresh });
184
- const balances = (state?.balances ?? []);
185
- // Unified HL accounts: spot USDC IS the perp margin (§12.4 #8) — mark it shared.
186
- // HIP-3 dex-scoped adapters report dex equity, which never contains spot USDC.
187
- const unified = this._hl.isUnifiedAccount === true && this._hl.dex === "";
188
- return balances.map((b) => ({
189
- token: String(b.coin ?? ""),
190
- total: String(b.total ?? "0"),
191
- available: String(Number(b.total ?? 0) - Number(b.hold ?? 0)),
192
- held: String(b.hold ?? "0"),
193
- entryNtl: b.entryNtl !== undefined ? String(b.entryNtl) : undefined,
194
- shared: unified && String(b.coin ?? "") === "USDC",
195
- }));
202
+ // HIP-3 dex-scoped adapters report dex equity, which never contains spot balances.
203
+ return hlSpotBalanceRows(state, this._hl.dex === "" ? this._hl.accountMode : null);
196
204
  }
197
205
  async spotMarketOrder(symbol, side, size, opts) {
198
206
  const what = `spot ${side} ${size} ${symbol}`;
@@ -204,7 +212,7 @@ export class HyperliquidSpotAdapter {
204
212
  await this.init();
205
213
  const resolved = this._resolveBase(symbol);
206
214
  base = resolved.base;
207
- // Reference = l2Book mid (the same single declared source as HL perps, §13.10 #5).
215
+ // Reference = l2Book mid (the same single declared source as HL perps).
208
216
  // Either side missing → PRICE_STALE (no one-sided "mid").
209
217
  const book = await this.getSpotOrderbook(symbol);
210
218
  const bestAsk = Number(book.asks[0]?.[0]);
@@ -213,7 +221,7 @@ export class HyperliquidSpotAdapter {
213
221
  throw new PerpError("PRICE_STALE", `Hyperliquid spot ${base}: no two-sided l2Book to derive the mid reference price`, { exchange: "hyperliquid" });
214
222
  }
215
223
  const midPrice = (bestAsk + bestBid) / 2;
216
- // Protection = mid ± the resolved slippage bound (design §13.2), aligned toward
224
+ // Protection = mid ± the resolved slippage bound, aligned toward
217
225
  // crossing on HL's spot grid (5 sig figs, max 8 − szDecimals decimals).
218
226
  const isBuy = side === "buy";
219
227
  const szDec = this.getSpotDecimals(base).size;
@@ -254,7 +262,7 @@ export class HyperliquidSpotAdapter {
254
262
  await this.init();
255
263
  const { base, spotIndex } = this._resolveBase(symbol);
256
264
  const assetIndex = 10000 + spotIndex;
257
- // TIF aliases (§13.3): GTC/IOC/ALO or native spellings; FOK → CAPABILITY_UNSUPPORTED.
265
+ // TIF aliases: GTC/IOC/ALO or native spellings; FOK → CAPABILITY_UNSUPPORTED.
258
266
  const tif = hlTif(opts?.tif ?? "GTC");
259
267
  const szDec = this.getSpotDecimals(base).size;
260
268
  const what = `spot limit ${side} ${size} ${base} @ ${price}`;
@@ -388,7 +396,7 @@ export class HyperliquidSpotAdapter {
388
396
  invalidateCache("acct"); // perp and spot balances both moved
389
397
  }
390
398
  }
391
- /** SpotTransferCapable (§12.4 #4): USDC only; an `err` response is thrown, not returned. */
399
+ /** SpotTransferCapable: USDC only; an `err` response is thrown, not returned. */
392
400
  async transferToSpot(amount, asset) {
393
401
  return this._checkedTransfer(amount, asset, false);
394
402
  }