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,14 @@
1
+ /**
2
+ * Pacifica-specific order types exposed through `perp -e pacifica trade …`.
3
+ *
4
+ * Of Pacifica's strategy order types (docs trading-on-pacifica/order-types/strategies) only Atomic
5
+ * has an API path: it is a batch of ordinary market orders (POST /orders/batch). Chase, OCO,
6
+ * Trailing Stop, Swarm, Trigger Swarm and Conditional Market / Limit have no documented API / SDK
7
+ * path (as of 2026-10-19): no REST endpoint, no signing operation type (api/signing/operation-types),
8
+ * no batch action type and no order-type code; the official python-sdk and Pacifica MCP server
9
+ * expose none. Nearest perp-cli equivalents: `trade twap` (≈ Swarm: a split market execution) and
10
+ * `trade tpsl` (≈ OCO bracket exit: position take-profit + stop-loss).
11
+ */
12
+ import type { Command } from "commander";
13
+ import type { ExchangeAdapter } from "../exchanges/interface.js";
14
+ export declare function registerPacificaOrderCommands(trade: Command, getAdapter: () => Promise<ExchangeAdapter>, isJson: () => boolean, isDryRun: () => boolean): void;
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Pacifica-specific order types exposed through `perp -e pacifica trade …`.
3
+ *
4
+ * Of Pacifica's strategy order types (docs trading-on-pacifica/order-types/strategies) only Atomic
5
+ * has an API path: it is a batch of ordinary market orders (POST /orders/batch). Chase, OCO,
6
+ * Trailing Stop, Swarm, Trigger Swarm and Conditional Market / Limit have no documented API / SDK
7
+ * path (as of 2026-10-19): no REST endpoint, no signing operation type (api/signing/operation-types),
8
+ * no batch action type and no order-type code; the official python-sdk and Pacifica MCP server
9
+ * expose none. Nearest perp-cli equivalents: `trade twap` (≈ Swarm: a split market execution) and
10
+ * `trade tpsl` (≈ OCO bracket exit: position take-profit + stop-loss).
11
+ */
12
+ import chalk from "chalk";
13
+ import { supportsDryRun } from "../cli-flags.js";
14
+ import { PerpError, extractErrorMessage, parsePositiveDecimal } from "../errors.js";
15
+ import { atomicBasketRiskCheck, atomicDryRunPlan, atomicIncompleteError, atomicLegClientOrderId, atomicLegPayload, atomicMarketsOf, executeAtomic, parseAtomicLegs, requireAtomicReduceOnly, sizeAtomicLegs, } from "../exchanges/pacifica-atomic.js";
16
+ import { resolveSlippage } from "../exchanges/slippage.js";
17
+ import { parseSlippageBpsFlag } from "./trade.js";
18
+ import { reduceOnlyGateOf, unsupported } from "../exchanges/trading-v2.js";
19
+ import { getVenue } from "../exchanges/venue.js";
20
+ import { quarantineEntryFor, quarantineSymbol } from "../bot/quarantine.js";
21
+ import { enforceOrderRisk } from "../trade-validator.js";
22
+ import { assessRisk, loadRiskLimits } from "../risk.js";
23
+ import { logExecution } from "../execution-log.js";
24
+ import { invalidateCache } from "../cache.js";
25
+ import { printJson, jsonOk } from "../utils.js";
26
+ export function registerPacificaOrderCommands(trade, getAdapter, isJson, isDryRun) {
27
+ supportsDryRun(trade.command("atomic"))
28
+ .description("Pacifica only: weighted basket of market orders across markets, sent in ONE batch request. Each leg fills or fails on its own (not all-or-nothing). "
29
+ + "An unconfirmed leg → ORDER_PENDING and its symbol is quarantined; a batch failure other than HTTP 400 / 429 (timeout, 5xx, unreadable answer) quarantines EVERY leg. "
30
+ + "Pacifica Chase / OCO / Trailing / Swarm have no documented API (as of 2026-10-19) — nearest here: trade twap (≈ Swarm), trade tpsl (≈ OCO)")
31
+ .requiredOption("--usd <total>", "Total basket notional in USD, split across the legs by weight at each mark price")
32
+ .option("--long <legs...>", "Buy legs: SYMBOL[:WEIGHT] (up to 5; no weight = 1)")
33
+ .option("--short <legs...>", "Sell legs: SYMBOL[:WEIGHT] (up to 5; no weight = 1)")
34
+ .option("--reduce-only", "Every leg reduce-only: --long legs must close shorts, --short legs must close longs")
35
+ .option("--slippage-bps <n>", "Per-leg slippage bound in bps (default: settings slippageBps.pacifica → defaultSlippageBps)")
36
+ .option("--force", "Bypass local risk-limit checks (maxPosition / total exposure) for every leg")
37
+ .action(async (opts) => {
38
+ const adapter = await getAdapter();
39
+ if (adapter.name !== "pacifica") {
40
+ const def = getVenue(adapter.name);
41
+ throw unsupported(def.displayName, "atomicOrder", "Atomic is a Pacifica basket order (POST /orders/batch of market orders)", "Run it on Pacifica (perp -e pacifica trade atomic …), or use perp trade multi for a cross-venue multi-leg order");
42
+ }
43
+ const pac = adapter;
44
+ const specs = parseAtomicLegs(opts.long ?? [], opts.short ?? []);
45
+ const usd = parsePositiveDecimal(opts.usd, "--usd");
46
+ const slippage = resolveSlippage("pacifica", parseSlippageBpsFlag(opts.slippageBps));
47
+ const reduceOnly = opts.reduceOnly === true;
48
+ const [info, prices] = await Promise.all([pac.sdk.getInfo(), pac.sdk.getPrices()]);
49
+ const legs = sizeAtomicLegs(specs, usd, atomicMarketsOf(specs, info, prices), slippage.bps);
50
+ // Pre-send gates (read only), before the dry-run return so a dry run reports the same refusal.
51
+ if (reduceOnly) {
52
+ const gate = reduceOnlyGateOf(adapter);
53
+ if (gate !== null)
54
+ throw gate;
55
+ invalidateCache("acct");
56
+ requireAtomicReduceOnly(legs, await adapter.getPositions());
57
+ }
58
+ else {
59
+ for (const l of legs) {
60
+ const q = quarantineEntryFor(l.symbol, (m) => { if (!isJson())
61
+ console.error(m); });
62
+ if (q !== null) {
63
+ throw new PerpError("RISK_VIOLATION", `${l.symbol} is quarantined by ${q.strategy}: ${q.reason}`, {
64
+ exchange: "pacifica",
65
+ remediation: `${q.remediation} — only reduce-only orders are accepted until a human clears it (perp strategy quarantine-clear ${l.symbol})`,
66
+ });
67
+ }
68
+ await enforceOrderRisk(adapter, { symbol: l.symbol, size: Number(l.size), price: Number(l.mark), force: opts.force });
69
+ }
70
+ // The basket as one order: total notional vs total exposure, new markets vs max positions.
71
+ if (opts.force !== true) {
72
+ const [balance, positions] = await Promise.all([adapter.getBalance(), adapter.getPositions()]);
73
+ const assessment = assessRisk([{ exchange: adapter.name, balance }], positions.map(position => ({ exchange: adapter.name, position })), loadRiskLimits());
74
+ atomicBasketRiskCheck(assessment, legs, positions);
75
+ }
76
+ }
77
+ const builder = await pac.builderDecision();
78
+ const builderCode = builder.applied === null ? null : builder.applied.identity;
79
+ const payloads = legs.map(l => atomicLegPayload(l, { reduceOnly, slippageBps: slippage.bps, clientOrderId: atomicLegClientOrderId(), builderCode }));
80
+ const legView = legs.map((l, i) => ({
81
+ symbol: l.symbol, side: l.side, weight: l.weight, share: Number(l.share.toFixed(6)), mark: l.mark, lotSize: l.lotSize,
82
+ size: l.size, notionalUsd: Number(l.notionalUsd.toFixed(2)), clientOrderId: payloads[i].client_order_id,
83
+ }));
84
+ const summary = { exchange: "pacifica", totalUsd: usd, reduceOnly, slippageBps: slippage.bps, legs: legView, builderFee: builder.applied, ...(builder.note !== null ? { builderNote: builder.note } : {}) };
85
+ if (isDryRun()) {
86
+ const tier = pac.activeSignerTier;
87
+ const plan = atomicDryRunPlan(payloads, {
88
+ account: pac.isReadOnly ? "<no signer configured: a live run needs a Pacifica key>" : pac.publicKey,
89
+ agentWallet: tier === "agent" ? "<agent wallet public key>" : null,
90
+ timestamp: Date.now(),
91
+ });
92
+ for (const l of legs) {
93
+ logExecution({ type: "multi_leg", exchange: "pacifica", symbol: l.symbol, side: l.side, size: l.size, status: "simulated", dryRun: true, meta: { atomic: true, reduceOnly } });
94
+ }
95
+ if (isJson())
96
+ return printJson(jsonOk({ dryRun: true, sent: false, action: "atomic_order", ...summary, request: plan }));
97
+ console.log(chalk.yellow("\n [DRY RUN] Would send a Pacifica Atomic basket (nothing signed or sent):"));
98
+ for (const l of legView)
99
+ console.log(chalk.gray(` ${l.side.toUpperCase().padEnd(4)} ${l.size} ${l.symbol} ≈ $${l.notionalUsd} (weight ${l.weight}, mark ${l.mark})`));
100
+ console.log(chalk.gray(` reduce-only: ${reduceOnly} · slippage: ${slippage.bps} bps per leg · builder: ${builder.applied === null ? "none" : builder.applied.identity}`));
101
+ console.log(chalk.gray(` ${plan.endpoint}\n${JSON.stringify(plan.body, null, 2).split("\n").map(s => ` ${s}`).join("\n")}`));
102
+ console.log(chalk.gray(` signed messages (create_market_order, one per leg):\n${plan.signedMessages.map(s => ` ${s}`).join("\n")}\n`));
103
+ return;
104
+ }
105
+ let outcomes;
106
+ let venueRaw;
107
+ try {
108
+ ({ outcomes, venueRaw } = await executeAtomic(pac, legs, payloads));
109
+ }
110
+ catch (e) {
111
+ for (const l of legs) {
112
+ logExecution({ type: "multi_leg", exchange: "pacifica", symbol: l.symbol, side: l.side, size: l.size, status: e instanceof PerpError && e.structured.code === "ORDER_PENDING" ? "unverified" : "failed", dryRun: false, error: extractErrorMessage(e), meta: { atomic: true, reduceOnly } });
113
+ if (!reduceOnly && e instanceof PerpError && e.structured.code === "ORDER_PENDING")
114
+ quarantineAtomicLeg(l.symbol, extractErrorMessage(e), isJson);
115
+ }
116
+ throw e;
117
+ }
118
+ for (const o of outcomes) {
119
+ logExecution({
120
+ type: "multi_leg", exchange: "pacifica", symbol: o.symbol, side: o.side, size: o.size,
121
+ status: o.status === "unknown" ? "unverified" : o.status === "filled" || o.status === "partiallyFilled" ? "success" : "failed",
122
+ dryRun: false, ...(o.detail !== null && o.status !== "filled" ? { error: o.detail } : {}),
123
+ meta: { atomic: true, reduceOnly, orderId: o.orderId, clientOrderId: o.clientOrderId, filledSize: o.filledSize },
124
+ });
125
+ if (o.status === "unknown" && !reduceOnly)
126
+ quarantineAtomicLeg(o.symbol, o.detail ?? "outcome unknown", isJson);
127
+ }
128
+ const incomplete = atomicIncompleteError(outcomes);
129
+ if (incomplete !== null)
130
+ throw incomplete;
131
+ if (isJson())
132
+ return printJson(jsonOk({ ...summary, outcomes, venueRaw }));
133
+ console.log(chalk.green(`\n Atomic basket filled on Pacifica ($${usd}):`));
134
+ for (const o of outcomes)
135
+ console.log(` ${chalk.green("FILLED")} ${o.side.toUpperCase().padEnd(4)} ${o.filledSize ?? o.size} ${o.symbol} order ${o.orderId ?? "-"}`);
136
+ console.log();
137
+ });
138
+ }
139
+ function quarantineAtomicLeg(symbol, reason, isJson) {
140
+ quarantineSymbol("trade-atomic", symbol, `atomic leg on ${symbol}: outcome unknown (${reason})`, `Check the position and fills: perp -e pacifica account positions / trade fills ${symbol}`, (m) => { if (!isJson())
141
+ console.error(m); });
142
+ }
@@ -16,10 +16,26 @@
16
16
  */
17
17
  import type { Command } from "commander";
18
18
  import type { ExchangeAdapter } from "../exchanges/index.js";
19
+ import type { DashboardExchange } from "../dashboard/index.js";
19
20
  import "../exchanges/venues/index.js";
21
+ /**
22
+ * HIP-3 dex names, read live from info `perpDexs` on the selected network (index 0 = the native
23
+ * dex, null). Throws on a failed or malformed read — never substitutes a fixed list (Rule #2):
24
+ * a stale list hides positions on any dex Hyperliquid adds later.
25
+ */
26
+ export declare function fetchHip3DexNames(): Promise<string[]>;
20
27
  /**
21
28
  * Open-position count on a HIP-3 dex; null = the read failed (unknown — NOT "no positions").
22
29
  * The failure is warned on stderr; callers must treat null as "may hold positions".
23
30
  */
24
31
  export declare function checkDexPositions(address: string, dex: string): Promise<number | null>;
32
+ /**
33
+ * HIP-3 dexes holding positions (or whose read failed) for `address`, scanning every dex of the
34
+ * live `perpDexs` list. `error` is set when the dex list itself could not be read: no dex is
35
+ * scanned and the caller must surface it (positions on HIP-3 dexes are unknown, not absent).
36
+ */
37
+ export declare function autoDetectHip3Dexes(address: string, getHLAdapterForDex: (dex: string) => Promise<ExchangeAdapter>, verbose: boolean): Promise<{
38
+ exchanges: DashboardExchange[];
39
+ error: string | null;
40
+ }>;
25
41
  export declare function registerPortfolioCommand(program: Command, getAdapterForExchange: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean, version: string, getHLAdapterForDex?: (dex: string) => Promise<ExchangeAdapter>): void;
@@ -1,7 +1,7 @@
1
1
  import chalk from "chalk";
2
2
  import { makeTable, formatUsd, formatPnl, printJson, jsonOk, jsonError, withJsonErrors } from "../utils.js";
3
3
  import { isDexCapable } from "../exchanges/capabilities.js";
4
- import { getVenue, listVenuesWithRole, tryResolveVenueId } from "../exchanges/venue.js";
4
+ import { getVenue, listVenuesWithRole, roleUnsupported, tryResolveVenueId } from "../exchanges/venue.js";
5
5
  import { PerpError } from "../errors.js";
6
6
  import "../exchanges/venues/index.js";
7
7
  import { listConfiguredAccountVenues } from "../config.js";
@@ -9,6 +9,7 @@ import { HYPERLIQUID_API_URL } from "../api/public/urls.js";
9
9
  import { fetchValuedSpotBalances, getSpotAdapter, spotSharesPerpMargin, withSpotTimeout } from "../exchanges/spot-factory.js";
10
10
  import { scrubSecretEnv } from "../secret-env.js";
11
11
  import { networkOf } from "../cli-flags.js";
12
+ import { probeVenueHealth } from "../venue-health.js";
12
13
  // ── Types ──────────────────────────────────────────────────────────────────
13
14
  /** Venues with an account API (market-data-only venues are excluded). */
14
15
  function accountVenueIds() {
@@ -51,6 +52,12 @@ function validateExchanges(list) {
51
52
  const valid = accountVenueIds();
52
53
  // Aliases (hl, pac, lt, lrh, ast, …) resolve through the venue registry, like every other command.
53
54
  const ids = list.map(s => tryResolveVenueId(s) ?? s.toLowerCase());
55
+ // A registered market-data-only venue (TxFlow) is a capability refusal, not an unknown name.
56
+ for (const id of ids) {
57
+ const known = tryResolveVenueId(id);
58
+ if (known !== null && !valid.includes(known))
59
+ throw roleUnsupported(getVenue(known), "account");
60
+ }
54
61
  const unknown = ids.filter(e => !valid.includes(e));
55
62
  if (unknown.length > 0) {
56
63
  throw new PerpError("INVALID_PARAMS", `Unknown exchange: ${unknown.join(", ")}`, { remediation: `Use one of: ${valid.join(", ")} (or an alias such as hl, pac, lt, lrh, ast)` });
@@ -178,7 +185,7 @@ async function fetchExchangeEntry(exName, getAdapter) {
178
185
  adapter.getOpenOrders(),
179
186
  adapter.getFundingPayments(200).then((rows) => ({ rows, error: null }), (e) => ({ rows: null, error: e instanceof Error ? e.message : String(e) })),
180
187
  ]);
181
- // Spot balances for every venue with def.spot (§12). Failures are surfaced on
188
+ // Spot balances for every venue with def.spot. Failures are surfaced on
182
189
  // stderr (the perp snapshot is still reported); unpriced tokens are excluded + warned.
183
190
  let spot = [];
184
191
  let isUnified = false;
@@ -205,7 +212,7 @@ async function fetchExchangeEntry(exName, getAdapter) {
205
212
  const funding24h = fundingPayments.rows === null
206
213
  ? null
207
214
  : fundingPayments.rows.filter(f => f.time >= cutoff24h).reduce((sum, f) => sum + Number(f.payment), 0);
208
- // Shared balances (Pacifica USDC, HL unified USDC) are already in perp equity (§12.4 #8).
215
+ // Shared balances (Pacifica USDC, HL unified USDC) are already in perp equity.
209
216
  const nonUsdcSpotValue = spot.filter(b => !b.shared).reduce((s, b) => s + b.valueUsd, 0);
210
217
  return {
211
218
  name: exName,
@@ -288,46 +295,16 @@ function buildTotals(entries) {
288
295
  }
289
296
  // ── Health check (no balance fetches) ──────────────────────────────────────
290
297
  async function runHealth(exchangeList, network) {
291
- const { pingPacifica, pingHyperliquid, pingLighter } = await import("../shared-api.js");
292
- const pingAster = async () => {
293
- const start = Date.now();
294
- try {
295
- const res = await fetch("https://fapi.asterdex.com/fapi/v1/time");
296
- return { ok: res.ok, latencyMs: Date.now() - start, status: res.status };
297
- }
298
- catch {
299
- return { ok: false, latencyMs: Date.now() - start, status: 0 };
300
- }
301
- };
302
- const pingMap = {
303
- pacifica: pingPacifica,
304
- hyperliquid: pingHyperliquid,
305
- lighter: pingLighter,
306
- aster: pingAster,
307
- };
298
+ // The same probe as `perp health` and the MCP health_check, over the selected venues.
299
+ const h = await probeVenueHealth(network, exchangeList);
308
300
  const exchanges = {};
309
- await Promise.all(exchangeList.map(async (ex) => {
310
- const fn = pingMap[ex];
311
- try {
312
- if (!fn) {
313
- // No dedicated ping helper: probe the venue's public market-data path.
314
- await getVenue(ex).publicData.fetchMarkets(network);
315
- exchanges[ex] = "ok";
316
- return;
317
- }
318
- const r = await fn();
319
- exchanges[ex] = r.ok ? "ok" : "error";
320
- }
321
- catch {
322
- exchanges[ex] = "error";
323
- }
324
- }));
325
- const anyDown = Object.values(exchanges).some(v => v === "error");
326
- return { exchanges, anyDown };
301
+ for (const p of h.probes)
302
+ exchanges[p.venue] = p.ok ? "ok" : "error";
303
+ return { exchanges, anyDown: !h.allHealthy, probes: h.probes, skipped: h.skipped };
327
304
  }
328
305
  // ── Arb top-5 ──────────────────────────────────────────────────────────────
329
306
  async function fetchArbTop() {
330
- const { fetchAllFundingRates, TOP_SYMBOLS } = await import("../funding-rates.js");
307
+ const { fetchAllFundingRates, isRankedComparison, TOP_SYMBOLS } = await import("../funding-rates.js");
331
308
  const { saveFundingSnapshot, getHistoricalAverages } = await import("../funding-history.js");
332
309
  let snapshot;
333
310
  try {
@@ -346,6 +323,7 @@ async function fetchArbTop() {
346
323
  }
347
324
  catch { /* ignore */ }
348
325
  const top = snapshot.symbols
326
+ .filter(isRankedComparison)
349
327
  .filter(s => s.maxSpreadAnnual >= 5)
350
328
  .sort((a, b) => b.maxSpreadAnnual - a.maxSpreadAnnual)
351
329
  .slice(0, 5);
@@ -514,11 +492,15 @@ function renderSnapshotText(data) {
514
492
  }
515
493
  function renderHealthText(h) {
516
494
  console.log(chalk.cyan.bold("\n Exchange Health Check\n"));
517
- const rows = Object.entries(h.exchanges).map(([ex, status]) => {
518
- const statusIcon = status === "ok" ? chalk.green("OK") : chalk.red("DOWN");
519
- return [chalk.white.bold(ex), statusIcon];
520
- });
521
- console.log(makeTable(["Exchange", "Status"], rows));
495
+ const rows = h.probes.map((p) => [
496
+ chalk.white.bold(p.venue),
497
+ p.ok ? chalk.green("OK") : chalk.red("DOWN"),
498
+ `${p.latencyMs}ms`,
499
+ p.error !== null ? chalk.red(p.error) : chalk.gray("-"),
500
+ ]);
501
+ console.log(makeTable(["Exchange", "Status", "Latency", "Error"], rows));
502
+ for (const s of h.skipped)
503
+ console.log(chalk.gray(` ${s.venue}: not probed — ${s.reason}`));
522
504
  const overall = h.anyDown ? chalk.red("ISSUES DETECTED") : chalk.green("ALL HEALTHY");
523
505
  console.log(`\n Overall: ${overall}\n`);
524
506
  }
@@ -546,7 +528,31 @@ async function runWatch(resolved, version, getAdapterForExchange, isJsonMode) {
546
528
  }
547
529
  }
548
530
  // ── Serve mode (delegates to dashboard infra) ──────────────────────────────
549
- const KNOWN_HIP3_DEXES = ["xyz", "flx", "hyna", "km", "cash", "vntl"];
531
+ /**
532
+ * HIP-3 dex names, read live from info `perpDexs` on the selected network (index 0 = the native
533
+ * dex, null). Throws on a failed or malformed read — never substitutes a fixed list (Rule #2):
534
+ * a stale list hides positions on any dex Hyperliquid adds later.
535
+ */
536
+ export async function fetchHip3DexNames() {
537
+ const res = await fetch(HYPERLIQUID_API_URL, {
538
+ method: "POST",
539
+ headers: { "Content-Type": "application/json" },
540
+ body: JSON.stringify({ type: "perpDexs" }),
541
+ });
542
+ if (!res.ok)
543
+ throw new Error(`info perpDexs HTTP ${res.status}`);
544
+ const list = await res.json();
545
+ if (!Array.isArray(list))
546
+ throw new Error("info perpDexs: expected an array");
547
+ return list.flatMap((d, i) => {
548
+ if (d === null)
549
+ return [];
550
+ const name = d?.name;
551
+ if (typeof name !== "string" || name === "")
552
+ throw new Error(`info perpDexs: entry ${i} has no dex name`);
553
+ return [name];
554
+ });
555
+ }
550
556
  /**
551
557
  * Open-position count on a HIP-3 dex; null = the read failed (unknown — NOT "no positions").
552
558
  * The failure is warned on stderr; callers must treat null as "may hold positions".
@@ -577,8 +583,22 @@ export async function checkDexPositions(address, dex) {
577
583
  return null;
578
584
  }
579
585
  }
580
- async function autoDetectHip3Dexes(address, getHLAdapterForDex, verbose) {
581
- const checks = await Promise.all(KNOWN_HIP3_DEXES.map(async (dex) => ({
586
+ /**
587
+ * HIP-3 dexes holding positions (or whose read failed) for `address`, scanning every dex of the
588
+ * live `perpDexs` list. `error` is set when the dex list itself could not be read: no dex is
589
+ * scanned and the caller must surface it (positions on HIP-3 dexes are unknown, not absent).
590
+ */
591
+ export async function autoDetectHip3Dexes(address, getHLAdapterForDex, verbose) {
592
+ let dexes;
593
+ try {
594
+ dexes = await fetchHip3DexNames();
595
+ }
596
+ catch (e) {
597
+ const error = `HIP-3 dex list unavailable — HIP-3 positions unknown, pass --dexes to monitor specific dexes: ${e instanceof Error ? e.message : String(e)}`;
598
+ process.stderr.write(`[warn] hl:hip3 ${error}\n`);
599
+ return { exchanges: [], error };
600
+ }
601
+ const checks = await Promise.all(dexes.map(async (dex) => ({
582
602
  dex,
583
603
  count: await checkDexPositions(address, dex),
584
604
  })));
@@ -599,7 +619,7 @@ async function autoDetectHip3Dexes(address, getHLAdapterForDex, verbose) {
599
619
  process.stderr.write(`[warn] hl:${dex} left out of the dashboard (adapter unavailable${count === null ? ", positions unknown" : `, ${count} position(s)`}): ${e instanceof Error ? e.message : String(e)}\n`);
600
620
  }
601
621
  }
602
- return results;
622
+ return { exchanges: results, error: null };
603
623
  }
604
624
  async function runServe(resolved, getAdapterForExchange, isJsonMode, getHLAdapterForDex) {
605
625
  const { startDashboard } = await import("../dashboard/index.js");
@@ -635,7 +655,9 @@ async function runServe(resolved, getAdapterForExchange, isJsonMode, getHLAdapte
635
655
  const hlAddr = hlEx ? hlEx.adapter.address ?? "" : "";
636
656
  if (hlAddr) {
637
657
  const detected = await autoDetectHip3Dexes(hlAddr, getHLAdapterForDex, false);
638
- exchanges.push(...detected);
658
+ exchanges.push(...detected.exchanges);
659
+ if (detected.error !== null)
660
+ errors["hl:hip3"] = detected.error;
639
661
  }
640
662
  }
641
663
  }
@@ -694,8 +716,10 @@ async function runServe(resolved, getAdapterForExchange, isJsonMode, getHLAdapte
694
716
  if (hlAddr) {
695
717
  console.log(chalk.gray(" Scanning HIP-3 dexes for active positions..."));
696
718
  const detected = await autoDetectHip3Dexes(hlAddr, getHLAdapterForDex, true);
697
- exchanges.push(...detected);
698
- if (!detected.length)
719
+ exchanges.push(...detected.exchanges);
720
+ if (detected.error !== null)
721
+ console.log(chalk.yellow(` ✗ hl:hip3: ${detected.error.slice(0, 120)}`));
722
+ else if (!detected.exchanges.length)
699
723
  console.log(chalk.gray(" No active HIP-3 dex positions found"));
700
724
  }
701
725
  }
@@ -773,7 +797,11 @@ export function registerPortfolioCommand(program, getAdapterForExchange, isJson,
773
797
  catch (err) {
774
798
  const msg = err instanceof Error ? err.message : String(err);
775
799
  if (json) {
776
- console.log(JSON.stringify(jsonError("INVALID_PARAMS", msg)));
800
+ // A typed refusal (CAPABILITY_UNSUPPORTED for a market-data-only venue) keeps its code.
801
+ const s = err instanceof PerpError ? err.structured : null;
802
+ console.log(JSON.stringify(s !== null
803
+ ? jsonError(s.code, msg, { ...(s.details !== undefined ? { details: s.details } : {}), ...(s.remediation !== undefined ? { remediation: s.remediation } : {}) })
804
+ : jsonError("INVALID_PARAMS", msg)));
777
805
  }
778
806
  else {
779
807
  console.error(chalk.red(`Error: ${msg}`));
@@ -794,9 +822,9 @@ export function registerPortfolioCommand(program, getAdapterForExchange, isJson,
794
822
  version,
795
823
  scope: resolved.scope,
796
824
  mode: "health",
797
- exchanges: [],
798
- totals: { equity: 0, spotValueUsd: 0, accountValueUsd: 0, marginUsed: 0, marginPct: 0, funding24h: 0, fundingIncomplete: false },
799
- risk: { level: "LOW", marginPct: 0 },
825
+ exchanges: null,
826
+ totals: null,
827
+ risk: null,
800
828
  health,
801
829
  };
802
830
  if (json)
@@ -1,4 +1,5 @@
1
1
  import { Command } from "commander";
2
2
  import type { ExchangeAdapter } from "../exchanges/index.js";
3
3
  import type { Network } from "../exchanges/venue-types.js";
4
+ import "../exchanges/venues/index.js";
4
5
  export declare function registerFundsRebalanceCommands(parent: Command, getAdapterForExchange: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean, getNetwork: () => Network): void;
@@ -5,6 +5,10 @@ import { PerpError } from "../errors.js";
5
5
  import { fetchBalancesWithFailures, computeRebalancePlan, describeBridgeRoute, estimateMoveTime, } from "../rebalance.js";
6
6
  import { EXCHANGE_TO_CHAIN, assertBridgeNetwork, executeBestBridge } from "../bridge-engine.js";
7
7
  import { cliCredentialOptsFrom } from "../config.js";
8
+ import { getVenue, roleUnsupported, tryResolveVenueId } from "../exchanges/venue.js";
9
+ import { unsupported } from "../exchanges/trading-v2.js";
10
+ import { RISEX_FUNDS_APP_ONLY } from "../exchanges/venues/risex.js";
11
+ import "../exchanges/venues/index.js";
8
12
  export function registerFundsRebalanceCommands(parent, getAdapterForExchange, isJson, getNetwork) {
9
13
  const rebalance = parent.command("rebalance").description("Cross-exchange balance management");
10
14
  // ── rebalance check ──
@@ -149,6 +153,17 @@ export function registerFundsRebalanceCommands(parent, getAdapterForExchange, is
149
153
  if (opts.autoBridge && !dryRun)
150
154
  assertBridgeNetwork(getNetwork());
151
155
  const exchangeNames = opts.exchanges.split(",").map((e) => e.trim());
156
+ // Venues whose funds perp-cli cannot move are refused before any read (no partial plan).
157
+ for (const name of exchangeNames) {
158
+ const id = tryResolveVenueId(name);
159
+ if (id === null)
160
+ continue;
161
+ const def = getVenue(id);
162
+ if (!def.roles.includes("account"))
163
+ throw roleUnsupported(def, "rebalance");
164
+ if (id === "risex")
165
+ throw unsupported("risex", "rebalance", RISEX_FUNDS_APP_ONLY.reason, RISEX_FUNDS_APP_ONLY.remediation);
166
+ }
152
167
  const adapters = new Map();
153
168
  const unavailable = [];
154
169
  for (const name of exchangeNames) {
@@ -1,5 +1,14 @@
1
1
  import { Command } from "commander";
2
2
  import type { ExchangeAdapter } from "../exchanges/index.js";
3
- /** Run health check and return results + render. Exported for use by status --health. */
4
- export declare function runHealthCheck(isJson: () => boolean): Promise<void>;
3
+ import type { Network } from "../exchanges/venue-types.js";
4
+ /**
5
+ * `perp health`: probe EVERY registered venue on the selected network — the same probe as
6
+ * `perp portfolio --health` and the MCP health_check (src/venue-health.ts). A venue with no
7
+ * environment on that network is listed under `skipped`, never silently dropped.
8
+ *
9
+ * JSON: `exchanges` = official-API venues, `unofficial` = venues read through an undocumented API
10
+ * (TxFlow), each entry with `error` (null when ok). `healthy` = every OFFICIAL venue is up — an
11
+ * unofficial API being down does not make the CLI unhealthy; `allHealthy` includes them.
12
+ */
13
+ export declare function runHealthCheck(isJson: () => boolean, network: Network): Promise<void>;
5
14
  export declare function registerRiskCommands(program: Command, getAdapterForExchange: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean): void;
@@ -2,61 +2,55 @@ import { listConfiguredAccountVenues } from "../config.js";
2
2
  import chalk from "chalk";
3
3
  import { printJson, jsonOk, makeTable, formatUsd, withJsonErrors } from "../utils.js";
4
4
  import { loadRiskLimits, saveRiskLimits, assessRisk, getLiquidationDistances } from "../risk.js";
5
- import { pingPacifica, pingHyperliquid, pingLighter } from "../shared-api.js";
6
- /** Run health check and return results + render. Exported for use by status --health. */
7
- export async function runHealthCheck(isJson) {
8
- const pingAster = async () => {
9
- const start = Date.now();
10
- try {
11
- const res = await fetch("https://fapi.asterdex.com/fapi/v1/time");
12
- return { ok: res.ok, latencyMs: Date.now() - start, status: res.status };
13
- }
14
- catch {
15
- return { ok: false, latencyMs: Date.now() - start, status: 0 };
16
- }
17
- };
18
- const [pacPing, hlPing, ltPing, astPing] = await Promise.all([
19
- pingPacifica(),
20
- pingHyperliquid(),
21
- pingLighter(),
22
- pingAster(),
23
- ]);
24
- const toPingResult = (name, p) => ({
25
- exchange: name,
5
+ import { probeVenueHealth } from "../venue-health.js";
6
+ import { networkOf } from "../cli-flags.js";
7
+ /**
8
+ * `perp health`: probe EVERY registered venue on the selected network — the same probe as
9
+ * `perp portfolio --health` and the MCP health_check (src/venue-health.ts). A venue with no
10
+ * environment on that network is listed under `skipped`, never silently dropped.
11
+ *
12
+ * JSON: `exchanges` = official-API venues, `unofficial` = venues read through an undocumented API
13
+ * (TxFlow), each entry with `error` (null when ok). `healthy` = every OFFICIAL venue is up — an
14
+ * unofficial API being down does not make the CLI unhealthy; `allHealthy` includes them.
15
+ */
16
+ export async function runHealthCheck(isJson, network) {
17
+ const health = await probeVenueHealth(network);
18
+ const checks = health.probes.map(p => ({
19
+ exchange: p.venue,
26
20
  status: p.ok ? "ok" : "down",
27
21
  latency_ms: p.latencyMs,
28
- error: p.ok ? undefined : `HTTP ${p.status}`,
29
- });
30
- const checks = [
31
- toPingResult("pacifica", pacPing),
32
- toPingResult("hyperliquid", hlPing),
33
- toPingResult("lighter", ltPing),
34
- toPingResult("aster", astPing),
35
- ];
36
- const allOk = checks.every(c => c.status === "ok");
22
+ error: p.error,
23
+ probe: p.probe,
24
+ apiStatus: p.apiStatus,
25
+ }));
26
+ const official = checks.filter(c => c.apiStatus === "official");
27
+ const unofficial = checks.filter(c => c.apiStatus === "unofficial");
28
+ const allOk = official.every(c => c.status === "ok");
37
29
  if (isJson()) {
38
- return printJson(jsonOk({ healthy: allOk, exchanges: checks }));
30
+ return printJson(jsonOk({ healthy: allOk, allHealthy: health.allHealthy, network, exchanges: official, unofficial, skipped: health.skipped }));
39
31
  }
40
- console.log(chalk.cyan.bold("\n Exchange Health Check\n"));
32
+ console.log(chalk.cyan.bold(`\n Exchange Health Check (${network})\n`));
41
33
  const rows = checks.map(c => {
42
- const statusIcon = c.status === "ok"
43
- ? chalk.green("OK")
44
- : c.status === "degraded"
45
- ? chalk.yellow("DEGRADED")
46
- : chalk.red("DOWN");
34
+ const statusIcon = c.status === "ok" ? chalk.green("OK") : chalk.red("DOWN");
47
35
  const latency = c.latency_ms < 500
48
36
  ? chalk.green(`${c.latency_ms}ms`)
49
37
  : c.latency_ms < 2000
50
38
  ? chalk.yellow(`${c.latency_ms}ms`)
51
39
  : chalk.red(`${c.latency_ms}ms`);
52
40
  return [
53
- chalk.white.bold(c.exchange),
41
+ chalk.white.bold(c.exchange) + (c.apiStatus === "unofficial" ? chalk.yellow(" *") : ""),
54
42
  statusIcon,
55
43
  latency,
56
- c.error ? chalk.red(c.error) : chalk.gray("-"),
44
+ c.probe,
45
+ c.error !== null ? chalk.red(c.error) : chalk.gray("-"),
57
46
  ];
58
47
  });
59
- console.log(makeTable(["Exchange", "Status", "Latency", "Error"], rows));
48
+ console.log(makeTable(["Exchange", "Status", "Latency", "Probe", "Error"], rows));
49
+ if (unofficial.length > 0) {
50
+ console.log(chalk.yellow(" * unofficial (undocumented) venue API — may change without notice; not counted in Overall"));
51
+ }
52
+ for (const s of health.skipped)
53
+ console.log(chalk.gray(` ${s.venue}: not probed — ${s.reason}`));
60
54
  const overall = allOk ? chalk.green("ALL HEALTHY") : chalk.red("ISSUES DETECTED");
61
55
  console.log(`\n Overall: ${overall}\n`);
62
56
  }
@@ -122,11 +116,12 @@ export function registerRiskCommands(program, getAdapterForExchange, isJson) {
122
116
  console.log(` Positions: ${assessment.metrics.positionCount}`);
123
117
  console.log(` Largest Position: $${formatUsd(assessment.metrics.largestPositionUsd)}`);
124
118
  console.log(` Max Leverage Used: ${assessment.metrics.maxLeverageUsed}x`);
125
- if (assessment.metrics.minLiquidationDistancePct >= 0) {
126
- const ldColor = assessment.metrics.minLiquidationDistancePct < assessment.limits.minLiquidationDistance
119
+ const minLiq = assessment.metrics.minLiquidationDistancePct;
120
+ if (minLiq !== null) {
121
+ const ldColor = minLiq < assessment.limits.minLiquidationDistance
127
122
  ? chalk.red
128
123
  : chalk.green;
129
- console.log(` Min Liq Distance: ${ldColor(`${assessment.metrics.minLiquidationDistancePct.toFixed(1)}%`)}`);
124
+ console.log(` Min Liq Distance: ${ldColor(`${minLiq.toFixed(1)}%`)}`);
130
125
  }
131
126
  for (const { exchange, position: p } of positions.filter(x => x.position.liquidationPrice === null)) {
132
127
  console.log(` Liq Price ${exchange}:${p.symbol}: ${chalk.yellow("unknown (venue does not report)")} — not in Min Liq Distance`);
@@ -367,6 +362,6 @@ export function registerRiskCommands(program, getAdapterForExchange, isJson) {
367
362
  .command("health")
368
363
  .description("Adapter health check: ping venue REST endpoints (connectivity + latency)")
369
364
  .action(async () => {
370
- await runHealthCheck(isJson);
365
+ await runHealthCheck(isJson, networkOf(program));
371
366
  });
372
367
  }
@@ -3,6 +3,7 @@ import type { ExchangeAdapter } from "../exchanges/index.js";
3
3
  import { PerpError } from "../errors.js";
4
4
  import "../exchanges/venues/index.js";
5
5
  import { type CancelResult } from "../exchanges/venue-types.js";
6
+ export declare function parseSlippageBpsFlag(raw: string | undefined): number | null;
6
7
  /**
7
8
  * A cancel with anything not proven cancelled (failed, or pending / still listed open after the
8
9
  * verifying re-list) → ORDER_PARTIAL carrying the whole result (non-zero exit, also in --json);