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
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Decimal-string precision helpers for order entry (design §10.10).
2
+ * Decimal-string precision helpers for order entry.
3
3
  *
4
4
  * All math is done on scaled BigInt to avoid float drift. Rules:
5
5
  * - limit prices that are not on tick are REJECTED (INVALID_TICK), never rounded;
@@ -24,6 +24,8 @@ export declare function requirePositive(value: string, what: string): void;
24
24
  export declare function requireOnTick(price: string, tickSize: string): string;
25
25
  /** Exact (a + b) / 2 of two plain decimal strings (no float arithmetic: 0.1 and 0.2 → "0.15"). */
26
26
  export declare function midDecimal(a: string, b: string): string;
27
+ /** Exact decimal a + b (plain decimal strings). */
28
+ export declare function addDecimal(a: string, b: string): string;
27
29
  /** Exact decimal a − b (plain decimal strings). */
28
30
  export declare function subDecimal(a: string, b: string): string;
29
31
  /** Exact decimal a × b (plain decimal strings). */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Decimal-string precision helpers for order entry (design §10.10).
2
+ * Decimal-string precision helpers for order entry.
3
3
  *
4
4
  * All math is done on scaled BigInt to avoid float drift. Rules:
5
5
  * - limit prices that are not on tick are REJECTED (INVALID_TICK), never rounded;
@@ -119,6 +119,13 @@ export function midDecimal(a, b) {
119
119
  const scale = Math.max(x.scale, y.scale) + 1; // one more digit holds the possible ".5"
120
120
  return format((rescale(x, scale) + rescale(y, scale)) / 2n, scale);
121
121
  }
122
+ /** Exact decimal a + b (plain decimal strings). */
123
+ export function addDecimal(a, b) {
124
+ const x = parseDecimal(a);
125
+ const y = parseDecimal(b);
126
+ const scale = Math.max(x.scale, y.scale);
127
+ return format(rescale(x, scale) + rescale(y, scale), scale);
128
+ }
122
129
  /** Exact decimal a − b (plain decimal strings). */
123
130
  export function subDecimal(a, b) {
124
131
  const x = parseDecimal(a);
@@ -195,7 +202,7 @@ export async function lotStepOf(adapter, symbol) {
195
202
  m = bare[0];
196
203
  }
197
204
  if (m === undefined) {
198
- throw new PerpError("SYMBOL_NOT_FOUND", `${adapter.name}: market ${symbol} not found — lot step unknown`, { exchange: adapter.name });
205
+ throw new PerpError("SYMBOL_NOT_FOUND", `${adapter.name}: market ${symbol} not found — lot step unknown`, { exchange: adapter.name, remediation: `List markets: perp -e ${adapter.name} market list` });
199
206
  }
200
207
  if (m.stepSize !== null)
201
208
  return m.stepSize;
@@ -0,0 +1,25 @@
1
+ import { type Network, type PublicBalanceResult, type PublicPositionsOptions } from "./venue.js";
2
+ import type { LighterVenueId } from "./venues/lighter-family.js";
3
+ /**
4
+ * Hyperliquid: info `clearinghouseState` (native perp dex), `userAbstraction` (the mode decides
5
+ * the equity source, exactly as the keyed getBalance) and `spotClearinghouseState` (spot rows;
6
+ * in unified / portfolio mode also the collateral), plus `meta.collateralToken` when the mode needs
7
+ * it. HIP-3: `clearinghouseState {dex}` for every dex of `perpDexs` (paced: hlDexFanOut); a dex
8
+ * balance is that dex's own perp clearinghouse, listed when its account value is non-zero. A dex
9
+ * (or the dex list) that fails is an `errors` entry ("hip3:<dex>") — unknown, never zero; the
10
+ * native, mode or spot read failing fails the venue.
11
+ */
12
+ export declare function hyperliquidBalanceByAddress(net: Network, address: string): Promise<PublicBalanceResult>;
13
+ /**
14
+ * Lighter / Lighter RH: `GET /account?by=l1_address` lists every account of the address; each
15
+ * account's balance (lighterBalanceOf) and spot assets (lighterSpotBalanceRows) are one
16
+ * `accounts` row. `accountIndex` narrows to one account (not one of the address's →
17
+ * INVALID_PARAMS); no account → empty `accounts` + note.
18
+ */
19
+ export declare function lighterBalanceByAddress(venue: LighterVenueId, net: Network, address: string, opts: PublicPositionsOptions): Promise<PublicBalanceResult>;
20
+ /**
21
+ * Pacifica: `GET /account?account=` (balance + spot_balances), `/positions?account=` and
22
+ * `/info/prices` — pacificaBalanceOf with the keyed extras (usdcBalance, borrowedUsdc,
23
+ * pendingInterest, availableToWithdraw). The address is validated as base58 32 bytes by the caller.
24
+ */
25
+ export declare function pacificaBalanceByAddress(net: Network, address: string): Promise<PublicBalanceResult>;
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Balances of ANY account by its public address, keyless (`perp account balance --address`, MCP
3
+ * get_balance_by_address). Wired into the registry through VenuePublicData.fetchBalanceByAddress;
4
+ * the balance comes from the same functions as the keyed adapters' getBalance (hlBalanceOf /
5
+ * lighterBalanceOf / pacificaBalanceOf) and the spot rows from the keyed spot mappers
6
+ * (hlSpotBalanceRows / lighterSpotBalanceRows / pacificaSpotBalanceRows). Read-only GETs / info
7
+ * POSTs; nothing is signed, no key is loaded.
8
+ */
9
+ import { classifyError, PerpError } from "../errors.js";
10
+ import { getVenue, networkConfig } from "./venue.js";
11
+ import { chooseLighterAccounts, hlDexFanOut, hlDexNamesOf, lighterAccountIndexOf } from "./public-positions.js";
12
+ function errorOf(e, venue) {
13
+ const s = classifyError(e, venue);
14
+ return { code: s.code, message: s.message };
15
+ }
16
+ /** A venue read failure as a typed error (the public HTTP helpers throw plain Errors on HTTP status). */
17
+ async function typed(venue, read) {
18
+ try {
19
+ return await read();
20
+ }
21
+ catch (e) {
22
+ if (e instanceof PerpError)
23
+ throw e;
24
+ const s = classifyError(e, venue);
25
+ throw new PerpError(s.code === "UNKNOWN" ? "EXCHANGE_ERROR" : s.code, s.message, { exchange: venue });
26
+ }
27
+ }
28
+ const EMPTY = { balance: null, spot: null, accounts: null, dexes: null, accountMode: null };
29
+ /** spotClearinghouseState `balances`, every row with a coin and numeric total / hold (unreadable → EXCHANGE_ERROR, never "0"). */
30
+ function hlSpotBalancesOf(state) {
31
+ const balances = state?.balances;
32
+ if (!Array.isArray(balances))
33
+ throw new PerpError("EXCHANGE_ERROR", "hyperliquid spotClearinghouseState: no balances array", { exchange: "hyperliquid" });
34
+ for (const b of balances) {
35
+ const num = (v) => typeof v === "string" && v !== "" && Number.isFinite(Number(v));
36
+ if (typeof b?.coin !== "string" || !num(b.total) || !num(b.hold)) {
37
+ throw new PerpError("EXCHANGE_ERROR", `hyperliquid spotClearinghouseState: unreadable balance row ${JSON.stringify(b).slice(0, 160)}`, { exchange: "hyperliquid" });
38
+ }
39
+ }
40
+ return balances;
41
+ }
42
+ /**
43
+ * Hyperliquid: info `clearinghouseState` (native perp dex), `userAbstraction` (the mode decides
44
+ * the equity source, exactly as the keyed getBalance) and `spotClearinghouseState` (spot rows;
45
+ * in unified / portfolio mode also the collateral), plus `meta.collateralToken` when the mode needs
46
+ * it. HIP-3: `clearinghouseState {dex}` for every dex of `perpDexs` (paced: hlDexFanOut); a dex
47
+ * balance is that dex's own perp clearinghouse, listed when its account value is non-zero. A dex
48
+ * (or the dex list) that fails is an `errors` entry ("hip3:<dex>") — unknown, never zero; the
49
+ * native, mode or spot read failing fails the venue.
50
+ */
51
+ export async function hyperliquidBalanceByAddress(net, address) {
52
+ const { legacyPublicJson } = await import("../funding/legacy-public.js");
53
+ const { legacyPublicBaseUrl } = await import("../api/public/urls.js");
54
+ const { hlAbstractionModeOf, hlBalanceOf, hlSpotUsdPricesOf } = await import("./hyperliquid.js");
55
+ const { hlSpotBalanceRows } = await import("./hyperliquid-spot.js");
56
+ const url = `${legacyPublicBaseUrl("hyperliquid", net)}/info`;
57
+ const post = (body, label) => legacyPublicJson("hyperliquid", url, label, {
58
+ method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body),
59
+ });
60
+ const errors = {};
61
+ const [native, modeRaw, spotState, dexList] = await Promise.all([
62
+ typed("hyperliquid", () => post({ type: "clearinghouseState", user: address }, "info clearinghouseState")),
63
+ typed("hyperliquid", () => post({ type: "userAbstraction", user: address }, "info userAbstraction")),
64
+ typed("hyperliquid", () => post({ type: "spotClearinghouseState", user: address }, "info spotClearinghouseState")),
65
+ post({ type: "perpDexs" }, "info perpDexs").then(hlDexNamesOf, (e) => { errors.hip3 = errorOf(e, "hyperliquid"); return []; }),
66
+ ]);
67
+ const mode = hlAbstractionModeOf(modeRaw);
68
+ const balances = hlSpotBalancesOf(spotState);
69
+ let token = null;
70
+ const collateralToken = () => (token ??= typed("hyperliquid", () => post({ type: "meta" }, "info meta")).then((meta) => {
71
+ const idx = meta?.collateralToken;
72
+ if (typeof idx !== "number" || !Number.isInteger(idx) || idx < 0) {
73
+ throw new PerpError("EXCHANGE_ERROR", `Hyperliquid meta has no collateralToken (got ${JSON.stringify(idx)})`, { exchange: "hyperliquid" });
74
+ }
75
+ return idx;
76
+ }));
77
+ const nativeState = native;
78
+ const { balance, notes } = await hlBalanceOf(nativeState, mode, "", {
79
+ collateralToken,
80
+ spotCollateralRow: async () => {
81
+ const t = await collateralToken();
82
+ return { row: balances.find(b => Number(b.token) === t) ?? null, balances, token: t, portfolioMarginRatio: spotState.portfolioMarginRatio };
83
+ },
84
+ spotUsdPrices: async () => hlSpotUsdPricesOf(await typed("hyperliquid", () => post({ type: "spotMetaAndAssetCtxs" }, "info spotMetaAndAssetCtxs"))),
85
+ });
86
+ const unified = mode === "unified" || mode === "portfolio";
87
+ const out = notes.map(n => n.trim());
88
+ if (unified) {
89
+ const av = nativeState.marginSummary?.accountValue;
90
+ const wd = nativeState.withdrawable;
91
+ const perp = [typeof av === "string" ? `accountValue ${av}` : null, typeof wd === "string" ? `withdrawable ${wd}` : null].filter(x => x !== null).join(", ");
92
+ out.push(`account mode ${mode}: equity / available come from the spot clearinghouse (the collateral balance already moves with perp PnL), as in the keyed balance; the native perp clearinghouse${perp !== "" ? ` (${perp})` : ""} is a per-dex view and is not added`);
93
+ }
94
+ // HIP-3 dex balances: the dex's own perp clearinghouse (standard semantics — the mode-dependent
95
+ // spot collateral is the top-level balance, never repeated per dex).
96
+ const dexes = {};
97
+ const noSpotRead = () => { throw new PerpError("EXCHANGE_ERROR", "internal: a HIP-3 dex balance read asked for spot state", { exchange: "hyperliquid" }); };
98
+ const perDex = await hlDexFanOut(net, dexList, dex => post({ type: "clearinghouseState", user: address, dex }, `info clearinghouseState dex ${dex}`));
99
+ for (const [i, res] of perDex.entries()) {
100
+ const dex = dexList[i];
101
+ if (res.status === "rejected") {
102
+ errors[`hip3:${dex}`] = errorOf(res.reason, "hyperliquid");
103
+ continue;
104
+ }
105
+ try {
106
+ const b = (await hlBalanceOf(res.value, "standard", dex, { collateralToken: noSpotRead, spotCollateralRow: noSpotRead, spotUsdPrices: noSpotRead })).balance;
107
+ if (Number(b.equity) !== 0)
108
+ dexes[dex] = b;
109
+ }
110
+ catch (e) {
111
+ errors[`hip3:${dex}`] = errorOf(e, "hyperliquid");
112
+ }
113
+ }
114
+ return { balance, spot: hlSpotBalanceRows({ balances }, mode), accounts: null, dexes, accountMode: mode, errors, notes: out };
115
+ }
116
+ /**
117
+ * Lighter / Lighter RH: `GET /account?by=l1_address` lists every account of the address; each
118
+ * account's balance (lighterBalanceOf) and spot assets (lighterSpotBalanceRows) are one
119
+ * `accounts` row. `accountIndex` narrows to one account (not one of the address's →
120
+ * INVALID_PARAMS); no account → empty `accounts` + note.
121
+ */
122
+ export async function lighterBalanceByAddress(venue, net, address, opts) {
123
+ const { fetchLighterAccountsByL1 } = await import("./lighter-public.js");
124
+ const { lighterBalanceOf } = await import("./lighter.js");
125
+ const { lighterSpotBalanceRows } = await import("./lighter-spot.js");
126
+ const collateral = networkConfig(getVenue(venue), net).collateral[0];
127
+ if (collateral === undefined)
128
+ throw new PerpError("INVALID_PARAMS", `${venue} network config has no collateral`);
129
+ const accounts = await fetchLighterAccountsByL1(venue, net, address);
130
+ if (accounts === null || accounts.length === 0) {
131
+ return { ...EMPTY, accounts: [], errors: {}, notes: [`no ${venue === "lighter" ? "Lighter" : "Lighter RH"} account for this address`] };
132
+ }
133
+ const rows = chooseLighterAccounts(venue, accounts, opts.accountIndex).map(a => ({
134
+ accountIndex: lighterAccountIndexOf(a),
135
+ balance: lighterBalanceOf(a, venue, collateral),
136
+ spot: lighterSpotBalanceRows(a, venue, collateral),
137
+ }));
138
+ return { ...EMPTY, accounts: rows, errors: {}, notes: [] };
139
+ }
140
+ /**
141
+ * Pacifica: `GET /account?account=` (balance + spot_balances), `/positions?account=` and
142
+ * `/info/prices` — pacificaBalanceOf with the keyed extras (usdcBalance, borrowedUsdc,
143
+ * pendingInterest, availableToWithdraw). The address is validated as base58 32 bytes by the caller.
144
+ */
145
+ export async function pacificaBalanceByAddress(net, address) {
146
+ const { PacificaClient } = await import("../pacifica/client.js");
147
+ const { pacificaBalanceOf } = await import("./pacifica.js");
148
+ const { pacificaSpotBalanceRows } = await import("./pacifica-spot.js");
149
+ const client = new PacificaClient({ network: net });
150
+ const [info, positions, prices] = await typed("pacifica", () => Promise.all([client.getAccount(address), client.getPositions(address), client.getPrices()]));
151
+ return {
152
+ ...EMPTY,
153
+ balance: pacificaBalanceOf(info, positions, prices),
154
+ spot: pacificaSpotBalanceRows(info),
155
+ errors: {},
156
+ notes: [],
157
+ };
158
+ }
@@ -0,0 +1,43 @@
1
+ import { type Network, type PublicPositionsOptions, type PublicPositionsResult } from "./venue.js";
2
+ import type { LighterVenueId } from "./venues/lighter-family.js";
3
+ /** Requests of a HIP-3 per-dex fan-out in flight at once. */
4
+ export declare const HL_DEX_FANOUT_CONCURRENCY = 4;
5
+ /** Burst of the fan-out's token bucket, in HL weight units (clearinghouseState weighs 2 → 10 requests). */
6
+ export declare const HL_DEX_FANOUT_BURST = 20;
7
+ /**
8
+ * One `read` per HIP-3 dex, paced: at most HL_DEX_FANOUT_CONCURRENCY in flight, and every request
9
+ * takes its weight from a token bucket refilled at the venue's documented rate
10
+ * (hyperliquidVenue.rateLimit: 1200 weight / min = 20 / s; clearinghouseState weight 2) with a
11
+ * small burst (HL_DEX_FANOUT_BURST) instead of the full minute's budget. HL mainnet lists ~10
12
+ * dexes (no wait); testnet ~272, where an unpaced burst got HTTP 429 on 62 of 272 reads at
13
+ * concurrency 4 — paced, the fan-out takes ~25 s instead. A failed read stays that dex's
14
+ * (settled per dex); nothing is retried.
15
+ */
16
+ export declare function hlDexFanOut<T>(net: Network, dexes: readonly string[], read: (dex: string) => Promise<T>): Promise<PromiseSettledResult<T>[]>;
17
+ /** Hyperliquid `perpDexs` → HIP-3 dex names (the first entry, null, is the native dex). */
18
+ export declare function hlDexNamesOf(j: unknown): string[];
19
+ /** Account index of a Lighter account object. */
20
+ export declare function lighterAccountIndexOf(a: Record<string, unknown>): number;
21
+ /** The accounts of an address a read covers: every one, or the one of `accountIndex` (not one of the address's → INVALID_PARAMS). */
22
+ export declare function chooseLighterAccounts(venue: LighterVenueId, accounts: Array<Record<string, unknown>>, accountIndex: number | undefined): Array<Record<string, unknown>>;
23
+ /**
24
+ * Hyperliquid: info `clearinghouseState` {user} for the native perp dex plus {user, dex} for every
25
+ * HIP-3 dex of `perpDexs` (one request each, paced: hlDexFanOut). Native rows carry `dex: null`, HIP-3 rows their dex
26
+ * and the prefixed coin ("xyz:GOLD"), as the HIP-3 market rows. A dex (or the dex list) that
27
+ * fails is an `errors` entry ("hip3:<dex>") — its rows are unknown, never silently dropped; the
28
+ * native read failing fails the venue.
29
+ */
30
+ export declare function hyperliquidPositionsByAddress(net: Network, address: string): Promise<PublicPositionsResult>;
31
+ /**
32
+ * Lighter / Lighter RH: `GET /account?by=l1_address` lists every account of the address
33
+ * (sub-accounts); each account's rows are tagged with its `accountIndex`. The venue keeps a
34
+ * zero-size row for every market an account ever touched: those are dropped by the mapper.
35
+ * `accountIndex` narrows to one account (not one of the address's → INVALID_PARAMS).
36
+ */
37
+ export declare function lighterPositionsByAddress(venue: LighterVenueId, net: Network, address: string, opts: PublicPositionsOptions): Promise<PublicPositionsResult>;
38
+ /**
39
+ * Pacifica: `GET /positions?account=` marked with `/info/prices`; unresolved leverage from the
40
+ * account's public `/account/settings`, then the market max (`/info`). The address is validated
41
+ * as base58 32 bytes by the caller (an EVM address would be answered `success: true, []`).
42
+ */
43
+ export declare function pacificaPositionsByAddress(net: Network, address: string): Promise<PublicPositionsResult>;
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Open positions of ANY account by its public address, keyless (`perp account positions
3
+ * --address`, MCP get_positions_by_address). Wired into the registry through
4
+ * VenuePublicData.fetchPositionsByAddress (+ addressKind); the rows come from the same mappers as
5
+ * the keyed adapters' getPositions (hlPositionRows / lighterPositionRows / pacificaPositionRows),
6
+ * with plain symbols. Read-only GETs / info POSTs; nothing is signed, no key is loaded.
7
+ */
8
+ import { classifyError, PerpError } from "../errors.js";
9
+ import { getVenue } from "./venue.js";
10
+ import { acquireRate } from "./rate-limiter.js";
11
+ function errorOf(e, venue) {
12
+ const s = classifyError(e, venue);
13
+ return { code: s.code, message: s.message };
14
+ }
15
+ /** A venue read failure as a typed error (the public HTTP helpers throw plain Errors on HTTP status). */
16
+ async function typed(venue, read) {
17
+ try {
18
+ return await read();
19
+ }
20
+ catch (e) {
21
+ if (e instanceof PerpError)
22
+ throw e;
23
+ const s = classifyError(e, venue);
24
+ throw new PerpError(s.code === "UNKNOWN" ? "EXCHANGE_ERROR" : s.code, s.message, { exchange: venue });
25
+ }
26
+ }
27
+ /** Requests of a HIP-3 per-dex fan-out in flight at once. */
28
+ export const HL_DEX_FANOUT_CONCURRENCY = 4;
29
+ /** Burst of the fan-out's token bucket, in HL weight units (clearinghouseState weighs 2 → 10 requests). */
30
+ export const HL_DEX_FANOUT_BURST = 20;
31
+ /**
32
+ * One `read` per HIP-3 dex, paced: at most HL_DEX_FANOUT_CONCURRENCY in flight, and every request
33
+ * takes its weight from a token bucket refilled at the venue's documented rate
34
+ * (hyperliquidVenue.rateLimit: 1200 weight / min = 20 / s; clearinghouseState weight 2) with a
35
+ * small burst (HL_DEX_FANOUT_BURST) instead of the full minute's budget. HL mainnet lists ~10
36
+ * dexes (no wait); testnet ~272, where an unpaced burst got HTTP 429 on 62 of 272 reads at
37
+ * concurrency 4 — paced, the fan-out takes ~25 s instead. A failed read stays that dex's
38
+ * (settled per dex); nothing is retried.
39
+ */
40
+ export async function hlDexFanOut(net, dexes, read) {
41
+ const venuePolicy = getVenue("hyperliquid").rateLimit;
42
+ const policy = { capacity: HL_DEX_FANOUT_BURST, refillPerSec: venuePolicy.refillPerSec, weights: venuePolicy.weights };
43
+ const out = new Array(dexes.length);
44
+ let next = 0;
45
+ const worker = async () => {
46
+ while (next < dexes.length) {
47
+ const i = next++;
48
+ try {
49
+ await acquireRate(`hyperliquid:hip3-fanout:${net}`, policy, "clearinghouseState");
50
+ out[i] = { status: "fulfilled", value: await read(dexes[i]) };
51
+ }
52
+ catch (e) {
53
+ out[i] = { status: "rejected", reason: e };
54
+ }
55
+ }
56
+ };
57
+ await Promise.all(Array.from({ length: Math.min(HL_DEX_FANOUT_CONCURRENCY, dexes.length) }, worker));
58
+ return out;
59
+ }
60
+ /** Hyperliquid `perpDexs` → HIP-3 dex names (the first entry, null, is the native dex). */
61
+ export function hlDexNamesOf(j) {
62
+ if (!Array.isArray(j))
63
+ throw new PerpError("EXCHANGE_ERROR", "hyperliquid perpDexs: expected an array", { exchange: "hyperliquid" });
64
+ return j.flatMap(d => (d !== null && typeof d === "object" && typeof d.name === "string" ? [d.name] : []));
65
+ }
66
+ /** Account index of a Lighter account object. */
67
+ export function lighterAccountIndexOf(a) {
68
+ return Number(a.account_index ?? a.index);
69
+ }
70
+ /** The accounts of an address a read covers: every one, or the one of `accountIndex` (not one of the address's → INVALID_PARAMS). */
71
+ export function chooseLighterAccounts(venue, accounts, accountIndex) {
72
+ if (accountIndex === undefined)
73
+ return accounts;
74
+ const chosen = accounts.filter(a => lighterAccountIndexOf(a) === accountIndex);
75
+ if (chosen.length === 0) {
76
+ throw new PerpError("INVALID_PARAMS", `${venue}: account index ${accountIndex} is not an account of this address (accounts: ${accounts.map(lighterAccountIndexOf).join(", ")})`, {
77
+ exchange: venue, remediation: `Use one of: ${accounts.map(lighterAccountIndexOf).join(", ")} — or drop --account-index for every account`,
78
+ });
79
+ }
80
+ return chosen;
81
+ }
82
+ /**
83
+ * Hyperliquid: info `clearinghouseState` {user} for the native perp dex plus {user, dex} for every
84
+ * HIP-3 dex of `perpDexs` (one request each, paced: hlDexFanOut). Native rows carry `dex: null`, HIP-3 rows their dex
85
+ * and the prefixed coin ("xyz:GOLD"), as the HIP-3 market rows. A dex (or the dex list) that
86
+ * fails is an `errors` entry ("hip3:<dex>") — its rows are unknown, never silently dropped; the
87
+ * native read failing fails the venue.
88
+ */
89
+ export async function hyperliquidPositionsByAddress(net, address) {
90
+ const { legacyPublicJson } = await import("../funding/legacy-public.js");
91
+ const { legacyPublicBaseUrl } = await import("../api/public/urls.js");
92
+ const { hlPositionRows } = await import("./hyperliquid.js");
93
+ const url = `${legacyPublicBaseUrl("hyperliquid", net)}/info`;
94
+ const post = (body, label) => legacyPublicJson("hyperliquid", url, label, {
95
+ method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body),
96
+ });
97
+ const errors = {};
98
+ const [native, dexList] = await Promise.all([
99
+ typed("hyperliquid", () => post({ type: "clearinghouseState", user: address }, "info clearinghouseState")),
100
+ post({ type: "perpDexs" }, "info perpDexs").then(hlDexNamesOf, (e) => { errors.hip3 = errorOf(e, "hyperliquid"); return []; }),
101
+ ]);
102
+ const positions = hlPositionRows(native, coin => coin).map(r => ({ ...r, dex: null }));
103
+ const perDex = await hlDexFanOut(net, dexList, dex => post({ type: "clearinghouseState", user: address, dex }, `info clearinghouseState dex ${dex}`));
104
+ perDex.forEach((res, i) => {
105
+ const dex = dexList[i];
106
+ if (res.status === "rejected") {
107
+ errors[`hip3:${dex}`] = errorOf(res.reason, "hyperliquid");
108
+ return;
109
+ }
110
+ try {
111
+ positions.push(...hlPositionRows(res.value, coin => (coin.includes(":") ? coin : `${dex}:${coin}`)).map(r => ({ ...r, dex })));
112
+ }
113
+ catch (e) {
114
+ errors[`hip3:${dex}`] = errorOf(e, "hyperliquid");
115
+ }
116
+ });
117
+ return { positions, errors, notes: [] };
118
+ }
119
+ /**
120
+ * Lighter / Lighter RH: `GET /account?by=l1_address` lists every account of the address
121
+ * (sub-accounts); each account's rows are tagged with its `accountIndex`. The venue keeps a
122
+ * zero-size row for every market an account ever touched: those are dropped by the mapper.
123
+ * `accountIndex` narrows to one account (not one of the address's → INVALID_PARAMS).
124
+ */
125
+ export async function lighterPositionsByAddress(venue, net, address, opts) {
126
+ const { fetchLighterAccountsByL1, fetchLighterPriceDecimals } = await import("./lighter-public.js");
127
+ const { lighterPositionRows } = await import("./lighter.js");
128
+ const accounts = await fetchLighterAccountsByL1(venue, net, address);
129
+ if (accounts === null || accounts.length === 0)
130
+ return { positions: [], errors: {}, notes: [`no ${venue === "lighter" ? "Lighter" : "Lighter RH"} account for this address`] };
131
+ const chosen = chooseLighterAccounts(venue, accounts, opts.accountIndex);
132
+ const open = (a) => (Array.isArray(a.positions) ? a.positions : []).some(p => Number(p.position) !== 0);
133
+ // Price decimals only when there is a mark to format (an account without positions costs one read).
134
+ const decimals = chosen.some(open) ? await fetchLighterPriceDecimals(venue, net) : new Map();
135
+ const positions = chosen.flatMap(a => lighterPositionRows(a.positions, venue, sym => decimals.get(sym.toUpperCase())).map(r => ({ ...r, accountIndex: lighterAccountIndexOf(a) })));
136
+ return { positions, errors: {}, notes: [] };
137
+ }
138
+ /**
139
+ * Pacifica: `GET /positions?account=` marked with `/info/prices`; unresolved leverage from the
140
+ * account's public `/account/settings`, then the market max (`/info`). The address is validated
141
+ * as base58 32 bytes by the caller (an EVM address would be answered `success: true, []`).
142
+ */
143
+ export async function pacificaPositionsByAddress(net, address) {
144
+ const { PacificaClient } = await import("../pacifica/client.js");
145
+ const { pacificaPositionRows } = await import("./pacifica.js");
146
+ const { legacyPublicData } = await import("../funding/legacy-public.js");
147
+ const client = new PacificaClient({ network: net });
148
+ const [rows, prices] = await typed("pacifica", () => Promise.all([client.getPositions(address), client.getPrices()]));
149
+ const positions = await pacificaPositionRows(rows, prices, {
150
+ accountSettings: () => client.getAccountSettings(address),
151
+ maxLeverage: async () => new Map((await legacyPublicData("pacifica").fetchMarkets(net)).map(m => [m.symbol, m.maxLeverage])),
152
+ });
153
+ return { positions, errors: {}, notes: [] };
154
+ }
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * QFEX adapter — native TradingV2 + legacy ExchangeAdapter shim
3
- * (design §14.3–§14.6 as amended by §14.12; §10.7, §10.10, §13).
4
3
  *
5
4
  * Signer: imported HMAC API key only (QfexAgentMeta → ~/.perp/qfex-agents/<publicKey>.json).
6
5
  * Orders go over the Trade WebSocket (ws-transport.ts); account reads, leverage and transfers
@@ -42,7 +41,7 @@
42
41
  import { PerpError } from "../../errors.js";
43
42
  import type { WithdrawGate } from "../../settings.js";
44
43
  import { type BuilderFeeGate } from "../builder-fee.js";
45
- import type { ExchangeAdapter, ExchangeBalance, ExchangeFundingPayment, ExchangeKline, ExchangeMarketInfo, ExchangeOrder, ExchangePosition, ExchangeTrade } from "../interface.js";
44
+ import type { ExchangeAdapter, ExchangeBalance, ExchangeFundingPayment, ExchangeKline, ExchangeMarketInfo, ExchangeOrder, ExchangePosition, ExchangeTrade, ExchangePublicTrade, FundingHistoryRow } from "../interface.js";
46
45
  import type { IocOutcome } from "../ioc-outcome.js";
47
46
  import type { ResolvedSlippage } from "../slippage.js";
48
47
  import type { TradingV2 } from "../trading-v2.js";
@@ -52,6 +51,20 @@ import { type QfexReduceOnlyWire } from "./signing.js";
52
51
  import { qfexPendingRemediation, type QfexOrderResponse } from "./ws-transport.js";
53
52
  /** A mark price entry older than this (its own venue `time`) is not used as an order reference. */
54
53
  export declare const QFEX_MARK_MAX_AGE_MS = 10000;
54
+ /**
55
+ * Candle volumes. GET /candles returns `usdVolume` (populated) and `baseTokenVolume`, which the
56
+ * venue leaves "0" on traded candles (live 2026-10-14, CL-USD 1HOUR: baseTokenVolume "0" with
57
+ * usdVolume "52534.48506" on every row; startingOpenInterest / orderbookMidPrice* are "0" too).
58
+ * A base "0" next to a non-zero USD volume is a placeholder → null (unknown), never 0; when
59
+ * both are zero the candle really had no volume.
60
+ */
61
+ export declare function qfexCandleVolumes(c: {
62
+ baseTokenVolume: unknown;
63
+ usdVolume: unknown;
64
+ }): {
65
+ volume: string | null;
66
+ quoteVolume: string;
67
+ };
55
68
  /** Client order id sent to the venue: a fresh UUID (the format the venue itself generates). */
56
69
  export declare function qfexClientOrderId(): string;
57
70
  /**
@@ -64,6 +77,22 @@ export declare function qfexClientOrderId(): string;
64
77
  * quantity) are quantized to the instrument tick / lot instead (fromWireNumber).
65
78
  */
66
79
  export declare function plainNumber(n: unknown, what: string): string;
80
+ /**
81
+ * A QFEX `market list` / `market info` row: the legacy shape plus listing state and quote currency.
82
+ * Prices are in `quoteAsset`; `volume24h` is USD (null for a non-USD quote — no FX rate published),
83
+ * `volume24hQuote` the venue's 24h notional in `quoteAsset`.
84
+ */
85
+ export type QfexMarketRow = ExchangeMarketInfo & {
86
+ tradable: boolean;
87
+ status: QfexListedStatus;
88
+ quoteAsset: string;
89
+ volume24hQuote: string | null;
90
+ };
91
+ /**
92
+ * Why a LISTED (ACTIVE) market is or is not tradable through perp-cli: "active" = tradable;
93
+ * "nonUsdQuote" = quoted in KRW / JPY / CNY / SEK / HKD — readable, never ordered (USD sizing).
94
+ */
95
+ export type QfexListedStatus = "active" | "nonUsdQuote";
67
96
  export interface QfexAddOrderWire {
68
97
  symbol: string;
69
98
  side: "BUY" | "SELL";
@@ -130,6 +159,12 @@ export declare class QfexAdapter implements ExchangeAdapter, TradingV2 {
130
159
  private session;
131
160
  private instrumentMap;
132
161
  private instrumentOf;
162
+ /**
163
+ * Instrument of a per-symbol read (book / funding / kline / info). Unknown → SYMBOL_NOT_FOUND; a
164
+ * listed market whose /refdata status is not ACTIVE (DELISTED, INACTIVE) → SYMBOL_NOT_FOUND with
165
+ * that status: it is not a live market. A non-USD ACTIVE market is readable (only orders refuse it).
166
+ */
167
+ private readableInstrument;
133
168
  /** Refuse a market perp-cli must not send orders to, and an order type / TIF the symbol does not list. */
134
169
  private requireOrderable;
135
170
  /** Mark of ONE symbol from a single-symbol MDS subscription; PRICE_STALE when absent or older than QFEX_MARK_MAX_AGE_MS. */
@@ -215,7 +250,7 @@ export declare class QfexAdapter implements ExchangeAdapter, TradingV2 {
215
250
  /**
216
251
  * A reduce-only order the venue did not confirm as reduce-only (`reduce_only` false or absent
217
252
  * on an order frame — reduceOnlyUnconfirmedFrame — or on a read-back row of THIS order). The order may be live as a normal order, so the outcome is
218
- * unknown (ORDER_PENDING, design §14.12 #4e: never re-sent). It is NOT cancelled here: a cancel
253
+ * unknown (ORDER_PENDING, never re-sent). It is NOT cancelled here: a cancel
219
254
  * is a second mutating command with its own unknown outcome and cannot undo a fill.
220
255
  */
221
256
  private reduceOnlyEchoError;
@@ -225,7 +260,7 @@ export declare class QfexAdapter implements ExchangeAdapter, TradingV2 {
225
260
  private orderResultOf;
226
261
  /**
227
262
  * Final filled size of an order placed through the legacy shim — non-null ONLY when a venue
228
- * terminal frame was received (design §14.12 #3). A pending result is never an outcome.
263
+ * terminal frame was received. A pending result is never an outcome.
229
264
  */
230
265
  iocOutcomeOf(raw: unknown): IocOutcome | null;
231
266
  amendOrder(req: AmendRequest): Promise<OrderResult>;
@@ -248,19 +283,40 @@ export declare class QfexAdapter implements ExchangeAdapter, TradingV2 {
248
283
  private settleUnknownStopCancel;
249
284
  private settledByHistory;
250
285
  applyLeverage(symbol: string, leverage: number, marginMode: "cross" | "isolated" | null): Promise<LeverageResult>;
251
- getMarkets(): Promise<ExchangeMarketInfo[]>;
286
+ /**
287
+ * Legacy rows plus `tradable` (ACTIVE and USD-quoted, qfexTradable), the /refdata `status`
288
+ * (ACTIVE / INACTIVE / DELISTED) and `quoteAsset` (prices of KRW / JPY / CNY / SEK / HKD markets
289
+ * are in that currency, not USD).
290
+ */
291
+ getMarkets(): Promise<QfexMarketRow[]>;
292
+ private marketRow;
293
+ /**
294
+ * `market info` of one ACTIVE market. fundingRate is the CURRENT /md/contracts `funding_rate`
295
+ * (the rate `market funding` / `arb scan --rates --include-unverified` show), labelled fundingVerified false: QFEX funding
296
+ * is unverified (Q4, display only). Market lists keep fundingRate null so no scan / bot reads it.
297
+ */
298
+ getMarketInfo(symbol: string): Promise<QfexMarketRow & {
299
+ fundingVerified: false;
300
+ fundingNote: string;
301
+ marketClosed: boolean | null;
302
+ }>;
303
+ /** `quoteAsset`: level prices are in the market's quote currency (KRW for SAMSUNG-KRW). */
252
304
  getOrderbook(symbol: string): Promise<{
253
305
  bids: [string, string][];
254
306
  asks: [string, string][];
307
+ quoteAsset: string;
255
308
  }>;
256
- getRecentTrades(): Promise<ExchangeTrade[]>;
257
- /** Hourly funding windows (GET /funding/{symbol}, intervalMinutes 60). `limit` = hours back (default 24). */
258
- getFundingHistory(symbol: string, limit?: number): Promise<{
259
- time: number;
260
- rate: string;
261
- price: string | null;
262
- }[]>;
263
- getKlines(symbol: string, interval: string, startTime: number, endTime: number): Promise<ExchangeKline[]>;
309
+ getRecentTrades(): Promise<ExchangePublicTrade[]>;
310
+ /**
311
+ * Hourly funding windows (GET /funding/{symbol}, intervalMinutes 60). `limit` = hours back (default 24).
312
+ * A window outside the market's funding hours (qfexFundingWindowClosed) is `rate: null,
313
+ * marketClosed: true` — the venue prints 0 there, which is "no funding", not a 0 rate.
314
+ */
315
+ getFundingHistory(symbol: string, limit?: number): Promise<FundingHistoryRow[]>;
316
+ /** Rows carry `quoteAsset`: OHLC prices are in the market's quote currency (KRW for SAMSUNG-KRW). */
317
+ getKlines(symbol: string, interval: string, startTime: number, endTime: number): Promise<Array<ExchangeKline & {
318
+ quoteAsset: string;
319
+ }>>;
264
320
  private userPositions;
265
321
  /** Equity is the venue's own figure (GET /user/subaccounts/equity) — never recomputed from balance fields. */
266
322
  getBalance(): Promise<ExchangeBalance>;