perp-cli 0.12.15 → 0.14.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 (361) hide show
  1. package/README.md +278 -31
  2. package/dist/_stderr-filter.js +0 -1
  3. package/dist/agent-wallet/lighter-keystore.d.ts +11 -0
  4. package/dist/agent-wallet/lighter-keystore.js +92 -10
  5. package/dist/agent-wallet/passphrase.d.ts +1 -1
  6. package/dist/agent-wallet/passphrase.js +16 -3
  7. package/dist/agent-wallet/store.d.ts +37 -4
  8. package/dist/agent-wallet/store.js +132 -0
  9. package/dist/agent-wallet/venue-keystore.d.ts +14 -0
  10. package/dist/agent-wallet/venue-keystore.js +121 -0
  11. package/dist/api/public/hyperliquid.js +4 -4
  12. package/dist/api/public/lighter.js +5 -5
  13. package/dist/api/public/pacifica.d.ts +0 -1
  14. package/dist/api/public/pacifica.js +4 -13
  15. package/dist/api/public/urls.d.ts +9 -1
  16. package/dist/api/public/urls.js +35 -13
  17. package/dist/arb/file-lock.d.ts +11 -0
  18. package/dist/arb/file-lock.js +74 -0
  19. package/dist/arb/index.d.ts +2 -2
  20. package/dist/arb/index.js +2 -2
  21. package/dist/arb/pending-entry.d.ts +32 -0
  22. package/dist/arb/pending-entry.js +77 -0
  23. package/dist/arb/sizing.d.ts +6 -13
  24. package/dist/arb/sizing.js +12 -51
  25. package/dist/arb/state.d.ts +45 -12
  26. package/dist/arb/state.js +214 -42
  27. package/dist/arb/utils.d.ts +20 -6
  28. package/dist/arb/utils.js +43 -39
  29. package/dist/bot/conditions.d.ts +4 -4
  30. package/dist/bot/conditions.js +11 -6
  31. package/dist/bot/config.d.ts +17 -1
  32. package/dist/bot/config.js +48 -12
  33. package/dist/bot/engine.d.ts +61 -2
  34. package/dist/bot/engine.js +610 -144
  35. package/dist/bot/fill-size.d.ts +7 -0
  36. package/dist/bot/fill-size.js +18 -0
  37. package/dist/bot/index.d.ts +1 -1
  38. package/dist/bot/index.js +1 -1
  39. package/dist/bot/perp-order.d.ts +99 -0
  40. package/dist/bot/perp-order.js +299 -0
  41. package/dist/bot/presets.d.ts +7 -0
  42. package/dist/bot/presets.js +17 -0
  43. package/dist/bot/quarantine.d.ts +75 -0
  44. package/dist/bot/quarantine.js +190 -0
  45. package/dist/bot/quote-rules.d.ts +23 -0
  46. package/dist/bot/quote-rules.js +69 -0
  47. package/dist/bot/strategies/avellaneda-mm.d.ts +32 -2
  48. package/dist/bot/strategies/avellaneda-mm.js +72 -70
  49. package/dist/bot/strategies/basis-arb.js +2 -0
  50. package/dist/bot/strategies/dca-strategy.d.ts +2 -0
  51. package/dist/bot/strategies/dca-strategy.js +5 -0
  52. package/dist/bot/strategies/funding-arb-strategy.d.ts +43 -3
  53. package/dist/bot/strategies/funding-arb-strategy.js +571 -380
  54. package/dist/bot/strategies/funding-arb-utils.d.ts +48 -23
  55. package/dist/bot/strategies/funding-arb-utils.js +159 -195
  56. package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
  57. package/dist/bot/strategies/funding-arb-v2.js +258 -47
  58. package/dist/bot/strategies/funding-auto.js +215 -194
  59. package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +14 -0
  60. package/dist/bot/strategies/spot-perp-arb-strategy.js +217 -50
  61. package/dist/bot/strategies/spot-perp-executor.d.ts +73 -19
  62. package/dist/bot/strategies/spot-perp-executor.js +319 -120
  63. package/dist/bot/strategies/spot-perp-state.d.ts +43 -0
  64. package/dist/bot/strategies/spot-perp-state.js +70 -0
  65. package/dist/bot/strategy-types.d.ts +6 -0
  66. package/dist/bot/tui/BotDashboard.d.ts +7 -2
  67. package/dist/bot/tui/BotDashboard.js +6 -3
  68. package/dist/bridge-engine.d.ts +120 -19
  69. package/dist/bridge-engine.js +710 -422
  70. package/dist/bridge-signers.d.ts +54 -0
  71. package/dist/bridge-signers.js +161 -0
  72. package/dist/cache.d.ts +4 -1
  73. package/dist/cache.js +11 -0
  74. package/dist/cli-flags.d.ts +50 -0
  75. package/dist/cli-flags.js +104 -0
  76. package/dist/cli-program.d.ts +35 -0
  77. package/dist/cli-program.js +286 -0
  78. package/dist/cli-spec.js +1 -1
  79. package/dist/client-id-tracker.js +11 -2
  80. package/dist/commands/account.d.ts +7 -1
  81. package/dist/commands/account.js +74 -84
  82. package/dist/commands/agent-import.d.ts +114 -0
  83. package/dist/commands/agent-import.js +794 -0
  84. package/dist/commands/agent.d.ts +232 -2
  85. package/dist/commands/agent.js +1858 -955
  86. package/dist/commands/alerts.d.ts +1 -0
  87. package/dist/commands/alerts.js +31 -28
  88. package/dist/commands/arb/index.d.ts +60 -1
  89. package/dist/commands/arb/index.js +381 -384
  90. package/dist/commands/arb-auto.d.ts +132 -4
  91. package/dist/commands/arb-auto.js +1474 -967
  92. package/dist/commands/arb.d.ts +3 -1
  93. package/dist/commands/arb.js +62 -30
  94. package/dist/commands/bot.js +127 -47
  95. package/dist/commands/bridge.d.ts +2 -1
  96. package/dist/commands/bridge.js +339 -192
  97. package/dist/commands/builder.d.ts +55 -0
  98. package/dist/commands/builder.js +387 -0
  99. package/dist/commands/funds-onchain.d.ts +32 -0
  100. package/dist/commands/funds-onchain.js +286 -0
  101. package/dist/commands/funds.d.ts +25 -0
  102. package/dist/commands/funds.js +939 -245
  103. package/dist/commands/history.js +72 -45
  104. package/dist/commands/init.d.ts +107 -15
  105. package/dist/commands/init.js +1116 -249
  106. package/dist/commands/jobs.d.ts +2 -1
  107. package/dist/commands/jobs.js +77 -15
  108. package/dist/commands/manage.js +136 -91
  109. package/dist/commands/market.d.ts +4 -1
  110. package/dist/commands/market.js +35 -32
  111. package/dist/commands/outcome.d.ts +10 -0
  112. package/dist/commands/outcome.js +396 -0
  113. package/dist/commands/plan.js +52 -12
  114. package/dist/commands/portfolio.d.ts +1 -0
  115. package/dist/commands/portfolio.js +60 -72
  116. package/dist/commands/rebalance.d.ts +2 -1
  117. package/dist/commands/rebalance.js +33 -43
  118. package/dist/commands/risk.js +46 -19
  119. package/dist/commands/settings.d.ts +1 -0
  120. package/dist/commands/settings.js +246 -51
  121. package/dist/commands/trade.d.ts +50 -0
  122. package/dist/commands/trade.js +1943 -695
  123. package/dist/commands/wallet.d.ts +30 -1
  124. package/dist/commands/wallet.js +316 -337
  125. package/dist/config.d.ts +60 -9
  126. package/dist/config.js +252 -82
  127. package/dist/constants.d.ts +29 -4
  128. package/dist/constants.js +62 -20
  129. package/dist/dashboard/server.d.ts +2 -0
  130. package/dist/dashboard/server.js +9 -6
  131. package/dist/dashboard/ui.js +6 -4
  132. package/dist/dashboard/ws-feeds.js +20 -8
  133. package/dist/dex-asset-map.js +5 -3
  134. package/dist/errors.d.ts +122 -0
  135. package/dist/errors.js +194 -0
  136. package/dist/event-stream.js +75 -19
  137. package/dist/evm-master-tx.d.ts +59 -0
  138. package/dist/evm-master-tx.js +154 -0
  139. package/dist/exchanges/adapter-network.d.ts +10 -0
  140. package/dist/exchanges/adapter-network.js +10 -0
  141. package/dist/exchanges/arcus/adapter.d.ts +196 -0
  142. package/dist/exchanges/arcus/adapter.js +1092 -0
  143. package/dist/exchanges/arcus/agent.d.ts +55 -0
  144. package/dist/exchanges/arcus/agent.js +188 -0
  145. package/dist/exchanges/arcus/client.d.ts +63 -0
  146. package/dist/exchanges/arcus/client.js +285 -0
  147. package/dist/exchanges/arcus/public.d.ts +65 -0
  148. package/dist/exchanges/arcus/public.js +138 -0
  149. package/dist/exchanges/arcus/signing.d.ts +176 -0
  150. package/dist/exchanges/arcus/signing.js +288 -0
  151. package/dist/exchanges/arcus/spot-chain.d.ts +60 -0
  152. package/dist/exchanges/arcus/spot-chain.js +241 -0
  153. package/dist/exchanges/arcus/spot-router.d.ts +177 -0
  154. package/dist/exchanges/arcus/spot-router.js +183 -0
  155. package/dist/exchanges/arcus/spot.d.ts +109 -0
  156. package/dist/exchanges/arcus/spot.js +645 -0
  157. package/dist/exchanges/aster-funds.d.ts +127 -0
  158. package/dist/exchanges/aster-funds.js +252 -0
  159. package/dist/exchanges/aster-nonce.d.ts +2 -0
  160. package/dist/exchanges/aster-nonce.js +107 -0
  161. package/dist/exchanges/aster-spot.d.ts +117 -0
  162. package/dist/exchanges/aster-spot.js +686 -0
  163. package/dist/exchanges/aster-typed-data.d.ts +133 -7
  164. package/dist/exchanges/aster-typed-data.js +121 -7
  165. package/dist/exchanges/aster.d.ts +219 -6
  166. package/dist/exchanges/aster.js +1023 -153
  167. package/dist/exchanges/builder-fee.d.ts +254 -0
  168. package/dist/exchanges/builder-fee.js +421 -0
  169. package/dist/exchanges/canonical-symbols.d.ts +32 -0
  170. package/dist/exchanges/canonical-symbols.js +96 -0
  171. package/dist/exchanges/capabilities.d.ts +7 -1
  172. package/dist/exchanges/capabilities.js +7 -0
  173. package/dist/exchanges/hl-signing.d.ts +165 -0
  174. package/dist/exchanges/hl-signing.js +158 -0
  175. package/dist/exchanges/hyperliquid-outcome.d.ts +211 -0
  176. package/dist/exchanges/hyperliquid-outcome.js +646 -0
  177. package/dist/exchanges/hyperliquid-spot.d.ts +44 -5
  178. package/dist/exchanges/hyperliquid-spot.js +308 -117
  179. package/dist/exchanges/hyperliquid.d.ts +462 -57
  180. package/dist/exchanges/hyperliquid.js +1575 -449
  181. package/dist/exchanges/index.d.ts +6 -0
  182. package/dist/exchanges/index.js +4 -0
  183. package/dist/exchanges/interface.d.ts +63 -10
  184. package/dist/exchanges/interface.js +18 -3
  185. package/dist/exchanges/ioc-outcome.d.ts +44 -0
  186. package/dist/exchanges/ioc-outcome.js +57 -0
  187. package/dist/exchanges/legacy-trading-v2.d.ts +39 -0
  188. package/dist/exchanges/legacy-trading-v2.js +940 -0
  189. package/dist/exchanges/lighter-public.d.ts +43 -0
  190. package/dist/exchanges/lighter-public.js +276 -0
  191. package/dist/exchanges/lighter-rh-deposit.d.ts +62 -0
  192. package/dist/exchanges/lighter-rh-deposit.js +185 -0
  193. package/dist/exchanges/lighter-signer.d.ts +123 -0
  194. package/dist/exchanges/lighter-signer.js +215 -0
  195. package/dist/exchanges/lighter-spot.d.ts +91 -13
  196. package/dist/exchanges/lighter-spot.js +595 -233
  197. package/dist/exchanges/lighter.d.ts +611 -39
  198. package/dist/exchanges/lighter.js +1890 -649
  199. package/dist/exchanges/ondo/adapter.d.ts +166 -0
  200. package/dist/exchanges/ondo/adapter.js +835 -0
  201. package/dist/exchanges/ondo/agent.d.ts +31 -0
  202. package/dist/exchanges/ondo/agent.js +101 -0
  203. package/dist/exchanges/ondo/client.d.ts +60 -0
  204. package/dist/exchanges/ondo/client.js +297 -0
  205. package/dist/exchanges/ondo/funds.d.ts +122 -0
  206. package/dist/exchanges/ondo/funds.js +290 -0
  207. package/dist/exchanges/ondo/public.d.ts +95 -0
  208. package/dist/exchanges/ondo/public.js +214 -0
  209. package/dist/exchanges/ondo/spot.d.ts +127 -0
  210. package/dist/exchanges/ondo/spot.js +392 -0
  211. package/dist/exchanges/outcome-interface.d.ts +140 -0
  212. package/dist/exchanges/outcome-interface.js +16 -0
  213. package/dist/exchanges/pacifica-spot.d.ts +83 -0
  214. package/dist/exchanges/pacifica-spot.js +324 -0
  215. package/dist/exchanges/pacifica-typed-data.d.ts +30 -13
  216. package/dist/exchanges/pacifica-typed-data.js +57 -9
  217. package/dist/exchanges/pacifica.d.ts +288 -4
  218. package/dist/exchanges/pacifica.js +1013 -76
  219. package/dist/exchanges/precision.d.ts +47 -0
  220. package/dist/exchanges/precision.js +209 -0
  221. package/dist/exchanges/qfex/adapter.d.ts +341 -0
  222. package/dist/exchanges/qfex/adapter.js +1383 -0
  223. package/dist/exchanges/qfex/agent.d.ts +58 -0
  224. package/dist/exchanges/qfex/agent.js +188 -0
  225. package/dist/exchanges/qfex/client.d.ts +54 -0
  226. package/dist/exchanges/qfex/client.js +138 -0
  227. package/dist/exchanges/qfex/errors.d.ts +70 -0
  228. package/dist/exchanges/qfex/errors.js +236 -0
  229. package/dist/exchanges/qfex/public.d.ts +122 -0
  230. package/dist/exchanges/qfex/public.js +268 -0
  231. package/dist/exchanges/qfex/signing.d.ts +160 -0
  232. package/dist/exchanges/qfex/signing.js +283 -0
  233. package/dist/exchanges/qfex/ws-transport.d.ts +294 -0
  234. package/dist/exchanges/qfex/ws-transport.js +798 -0
  235. package/dist/exchanges/rate-limiter.d.ts +12 -0
  236. package/dist/exchanges/rate-limiter.js +64 -0
  237. package/dist/exchanges/registry.d.ts +14 -2
  238. package/dist/exchanges/registry.js +20 -32
  239. package/dist/exchanges/slippage.d.ts +37 -0
  240. package/dist/exchanges/slippage.js +70 -0
  241. package/dist/exchanges/spot-factory.d.ts +166 -0
  242. package/dist/exchanges/spot-factory.js +431 -0
  243. package/dist/exchanges/spot-interface.d.ts +98 -4
  244. package/dist/exchanges/spot-interface.js +27 -1
  245. package/dist/exchanges/spot-result.d.ts +79 -0
  246. package/dist/exchanges/spot-result.js +273 -0
  247. package/dist/exchanges/trading-v2.d.ts +117 -0
  248. package/dist/exchanges/trading-v2.js +301 -0
  249. package/dist/exchanges/venue-types.d.ts +231 -0
  250. package/dist/exchanges/venue-types.js +87 -0
  251. package/dist/exchanges/venue.d.ts +211 -0
  252. package/dist/exchanges/venue.js +132 -0
  253. package/dist/exchanges/venues/arcus.d.ts +8 -0
  254. package/dist/exchanges/venues/arcus.js +109 -0
  255. package/dist/exchanges/venues/aster.d.ts +49 -0
  256. package/dist/exchanges/venues/aster.js +176 -0
  257. package/dist/exchanges/venues/create-helpers.d.ts +18 -0
  258. package/dist/exchanges/venues/create-helpers.js +199 -0
  259. package/dist/exchanges/venues/hyperliquid.d.ts +23 -0
  260. package/dist/exchanges/venues/hyperliquid.js +126 -0
  261. package/dist/exchanges/venues/index.d.ts +16 -0
  262. package/dist/exchanges/venues/index.js +26 -0
  263. package/dist/exchanges/venues/lighter-family.d.ts +92 -0
  264. package/dist/exchanges/venues/lighter-family.js +166 -0
  265. package/dist/exchanges/venues/lighter-rh.d.ts +7 -0
  266. package/dist/exchanges/venues/lighter-rh.js +58 -0
  267. package/dist/exchanges/venues/lighter.d.ts +6 -0
  268. package/dist/exchanges/venues/lighter.js +55 -0
  269. package/dist/exchanges/venues/ondo.d.ts +34 -0
  270. package/dist/exchanges/venues/ondo.js +140 -0
  271. package/dist/exchanges/venues/pacifica.d.ts +8 -0
  272. package/dist/exchanges/venues/pacifica.js +95 -0
  273. package/dist/exchanges/venues/qfex.d.ts +28 -0
  274. package/dist/exchanges/venues/qfex.js +132 -0
  275. package/dist/execution-log.js +10 -2
  276. package/dist/funding/legacy-public.d.ts +31 -0
  277. package/dist/funding/legacy-public.js +369 -0
  278. package/dist/funding/normalize.d.ts +28 -16
  279. package/dist/funding/normalize.js +61 -29
  280. package/dist/funding/rates.d.ts +46 -11
  281. package/dist/funding/rates.js +228 -277
  282. package/dist/guardrail/perp-guardrail.js +0 -0
  283. package/dist/index.js +157 -682
  284. package/dist/jobs.d.ts +84 -6
  285. package/dist/jobs.js +239 -41
  286. package/dist/landing.d.ts +14 -0
  287. package/dist/landing.js +36 -0
  288. package/dist/mcp-server.d.ts +14 -4
  289. package/dist/mcp-server.js +820 -402
  290. package/dist/mcp-trade-guard.d.ts +16 -0
  291. package/dist/mcp-trade-guard.js +59 -0
  292. package/dist/pacifica/client.d.ts +41 -10
  293. package/dist/pacifica/client.js +167 -63
  294. package/dist/pacifica/constants.d.ts +0 -1
  295. package/dist/pacifica/constants.js +0 -5
  296. package/dist/pacifica/deposit.d.ts +30 -3
  297. package/dist/pacifica/deposit.js +119 -2
  298. package/dist/pacifica/faucet.d.ts +29 -0
  299. package/dist/pacifica/faucet.js +100 -0
  300. package/dist/pacifica/sign-tx.d.ts +15 -0
  301. package/dist/pacifica/sign-tx.js +41 -0
  302. package/dist/pacifica/types/account.d.ts +28 -4
  303. package/dist/pacifica/types/lake.d.ts +20 -5
  304. package/dist/pacifica/types/lake.js +4 -0
  305. package/dist/pacifica/types/market.d.ts +5 -0
  306. package/dist/pacifica/types/order.d.ts +4 -1
  307. package/dist/pacifica/types/position.d.ts +9 -1
  308. package/dist/plan-executor.d.ts +10 -5
  309. package/dist/plan-executor.js +165 -54
  310. package/dist/position-history.js +13 -0
  311. package/dist/rebalance.d.ts +26 -2
  312. package/dist/rebalance.js +86 -7
  313. package/dist/relay-swap.d.ts +232 -0
  314. package/dist/relay-swap.js +630 -0
  315. package/dist/risk.js +5 -1
  316. package/dist/secret-env.d.ts +15 -0
  317. package/dist/secret-env.js +36 -0
  318. package/dist/settings.d.ts +169 -6
  319. package/dist/settings.js +219 -38
  320. package/dist/signer/ows-evm.d.ts +13 -0
  321. package/dist/signer/ows-evm.js +22 -0
  322. package/dist/smart-order.d.ts +9 -0
  323. package/dist/smart-order.js +53 -8
  324. package/dist/strategies/dca.d.ts +6 -2
  325. package/dist/strategies/dca.js +66 -26
  326. package/dist/strategies/funding-arb.d.ts +11 -1
  327. package/dist/strategies/funding-arb.js +202 -89
  328. package/dist/strategies/grid.d.ts +31 -0
  329. package/dist/strategies/grid.js +89 -11
  330. package/dist/strategies/pending-stop.d.ts +8 -0
  331. package/dist/strategies/pending-stop.js +12 -0
  332. package/dist/strategies/split-order.d.ts +45 -17
  333. package/dist/strategies/split-order.js +157 -91
  334. package/dist/strategies/trailing-stop.d.ts +2 -1
  335. package/dist/strategies/trailing-stop.js +52 -6
  336. package/dist/strategies/twap.d.ts +6 -1
  337. package/dist/strategies/twap.js +39 -11
  338. package/dist/trade-validator.d.ts +22 -0
  339. package/dist/trade-validator.js +119 -15
  340. package/dist/utils/numeric.d.ts +46 -0
  341. package/dist/utils/numeric.js +79 -0
  342. package/dist/utils.d.ts +19 -0
  343. package/dist/utils.js +96 -19
  344. package/dist/withdraw-gate.d.ts +49 -0
  345. package/dist/withdraw-gate.js +131 -0
  346. package/package.json +15 -6
  347. package/skills/perp-cli/SKILL.md +338 -134
  348. package/skills/perp-cli/references/agent-operations.md +181 -92
  349. package/skills/perp-cli/references/commands.md +475 -132
  350. package/skills/perp-cli/references/strategies.md +44 -38
  351. package/skills/perp-cli/scripts/arb-monitor.sh +22 -17
  352. package/skills/perp-cli/scripts/funding-analysis.sh +20 -10
  353. package/skills/perp-cli/scripts/preflight.sh +21 -14
  354. package/skills/perp-cli/scripts/spot-perp-scan.sh +12 -7
  355. package/skills/perp-cli/scripts/validate-arb.sh +33 -16
  356. package/vendor/lighter-signer/SHA256SUMS +2 -0
  357. package/vendor/lighter-signer/VERSION +8 -0
  358. package/vendor/lighter-signer/lighter-signer.wasm +0 -0
  359. package/vendor/lighter-signer/wasm_exec.js +575 -0
  360. package/dist/bot/strategies/funding-arb-v2-strategy.d.ts +0 -42
  361. package/dist/bot/strategies/funding-arb-v2-strategy.js +0 -864
@@ -1,8 +1,19 @@
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";
5
+ import { PerpError } from "../errors.js";
6
+ import "../exchanges/venues/index.js";
7
+ import { listConfiguredAccountVenues } from "../config.js";
8
+ import { HYPERLIQUID_API_URL } from "../api/public/urls.js";
9
+ import { fetchValuedSpotBalances, getSpotAdapter, spotSharesPerpMargin, withSpotTimeout } from "../exchanges/spot-factory.js";
10
+ import { scrubSecretEnv } from "../secret-env.js";
11
+ import { networkOf } from "../cli-flags.js";
4
12
  // ── Types ──────────────────────────────────────────────────────────────────
5
- const EXCHANGES = ["pacifica", "hyperliquid", "lighter", "aster"];
13
+ /** Venues with an account API (market-data-only venues are excluded). */
14
+ function accountVenueIds() {
15
+ return listVenuesWithRole("account").map(v => v.id);
16
+ }
6
17
  // ── Helpers ────────────────────────────────────────────────────────────────
7
18
  function exAbbr(e) {
8
19
  return e === "pacifica" ? "PAC"
@@ -37,12 +48,14 @@ function parseList(input) {
37
48
  return input.split(",").map(s => s.trim()).filter(Boolean);
38
49
  }
39
50
  function validateExchanges(list) {
40
- const lower = list.map(s => s.toLowerCase());
41
- const unknown = lower.filter(e => !EXCHANGES.includes(e));
51
+ const valid = accountVenueIds();
52
+ // Aliases (hl, pac, lt, lrh, ast, …) resolve through the venue registry, like every other command.
53
+ const ids = list.map(s => tryResolveVenueId(s) ?? s.toLowerCase());
54
+ const unknown = ids.filter(e => !valid.includes(e));
42
55
  if (unknown.length > 0) {
43
- throw new Error(`Unknown exchange: ${unknown.join(", ")}. Valid: ${EXCHANGES.join(", ")}`);
56
+ 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)` });
44
57
  }
45
- return lower;
58
+ return ids;
46
59
  }
47
60
  /** Resolve global -e flag from program (only counts when the user typed it). */
48
61
  function explicitExchange(program) {
@@ -52,8 +65,8 @@ function explicitExchange(program) {
52
65
  }
53
66
  return undefined;
54
67
  }
55
- function resolveOptions(program, flags, stdinOpts) {
56
- // 1) Build exchange list. Precedence: stdin > --exchanges > -e > all.
68
+ function resolveOptions(program, flags, stdinOpts, defaultExchanges) {
69
+ // 1) Build exchange list. Precedence: stdin > --exchanges > -e > all configured.
57
70
  let exchangeList;
58
71
  let scope = "all";
59
72
  if (stdinOpts.exchanges && stdinOpts.exchanges.length > 0) {
@@ -73,7 +86,7 @@ function resolveOptions(program, flags, stdinOpts) {
73
86
  scope = "single";
74
87
  }
75
88
  else {
76
- exchangeList = [...EXCHANGES];
89
+ exchangeList = [...defaultExchanges];
77
90
  }
78
91
  }
79
92
  // If only 1 entry and we got there via -e or stdin.exchange, scope is single.
@@ -135,7 +148,10 @@ function resolveOptions(program, flags, stdinOpts) {
135
148
  watchIntervalMs = n;
136
149
  }
137
150
  }
138
- const dexList = stdinOpts.dex ?? parseList(flags.dex);
151
+ // --dexes, or the root --dex when typed on the command line (backward compatible: `portfolio --serve --dex xyz`
152
+ // reaches the root option, which used to swallow a subcommand --dex silently).
153
+ const rootDex = program.getOptionValueSource("dex") === "cli" ? String(program.opts().dex) : undefined;
154
+ const dexList = stdinOpts.dex ?? parseList(flags.dexes ?? rootDex);
139
155
  const autoDex = stdinOpts.autoDex ?? (flags.autoDex !== false);
140
156
  return {
141
157
  exchangeList,
@@ -149,6 +165,7 @@ function resolveOptions(program, flags, stdinOpts) {
149
165
  pollIntervalMs,
150
166
  dexList,
151
167
  autoDex,
168
+ network: networkOf(program),
152
169
  };
153
170
  }
154
171
  // ── Per-exchange snapshot fetch ────────────────────────────────────────────
@@ -161,64 +178,32 @@ async function fetchExchangeEntry(exName, getAdapter) {
161
178
  adapter.getOpenOrders(),
162
179
  adapter.getFundingPayments(200).catch(() => []),
163
180
  ]);
164
- // Spot balances (only HL + LT). Pacifica/Aster are perp-only.
181
+ // Spot balances for every venue with def.spot (§12). Failures are surfaced on
182
+ // stderr (the perp snapshot is still reported); unpriced tokens are excluded + warned.
165
183
  let spot = [];
166
184
  let isUnified = false;
185
+ const def = getVenue(exName);
186
+ const warn = (msg) => process.stderr.write(`[warn] ${msg}\n`);
167
187
  try {
168
- if (exName === "hyperliquid") {
169
- const { HyperliquidSpotAdapter } = await import("../exchanges/hyperliquid-spot.js");
170
- const { HyperliquidAdapter } = await import("../exchanges/hyperliquid.js");
171
- const hlSpot = new HyperliquidSpotAdapter(adapter);
172
- await hlSpot.init();
173
- const [raw, markets] = await Promise.all([hlSpot.getSpotBalances(), hlSpot.getSpotMarkets()]);
174
- const priceMap = new Map(markets.map(m => [m.baseToken.toUpperCase(), Number(m.markPrice)]));
175
- const strip = (t) => t.replace(/-SPOT$/i, "").toUpperCase();
176
- spot = raw.filter(b => Number(b.total) > 0).map(b => {
177
- const base = strip(b.token);
178
- return {
179
- token: b.token,
180
- total: b.total,
181
- available: b.available,
182
- held: b.held,
183
- valueUsd: base === "USDC" ? Number(b.total) : (priceMap.get(base) ?? 0) * Number(b.total),
184
- };
185
- });
186
- // Read the actual abstraction mode (populated during init()) instead
187
- // of assuming all main HL accounts are unified. Standard/default mode
188
- // accounts must include spot USDC in totalAccountValueUsd because
189
- // perp equity does NOT contain it. HIP-3 dex accounts always run
190
- // standard semantics. Codex v0.12.12 final QA #1.
191
- const hlAdapter = adapter;
192
- const dexScoped = isDexCapable(adapter) && !!adapter.dex;
193
- isUnified = !dexScoped && hlAdapter.isUnifiedAccount === true;
194
- }
195
- else if (exName === "lighter") {
196
- const { LighterAdapter } = await import("../exchanges/lighter.js");
197
- const { LighterSpotAdapter } = await import("../exchanges/lighter-spot.js");
198
- const ltSpot = new LighterSpotAdapter(adapter);
199
- await ltSpot.init();
200
- const [raw, markets] = await Promise.all([ltSpot.getSpotBalances(), ltSpot.getSpotMarkets()]);
201
- const priceMap = new Map(markets.map(m => [m.baseToken.toUpperCase(), Number(m.markPrice)]));
202
- spot = raw.filter(b => Number(b.total) > 0).map(b => ({
203
- token: b.token,
204
- total: b.total,
205
- available: b.available,
206
- held: b.held,
207
- valueUsd: b.token === "USDC" || b.token === "USDC_SPOT"
208
- ? Number(b.total)
209
- : (priceMap.get(b.token.toUpperCase()) ?? 0) * Number(b.total),
188
+ const spotAdapter = await withSpotTimeout(getSpotAdapter(def.id, adapter), `${def.displayName} spot adapter`);
189
+ if (spotAdapter !== null) {
190
+ // Wallet-model venues (Arcus) price only held tokens; bounded by SPOT_VALUATION_TIMEOUT_MS.
191
+ spot = (await fetchValuedSpotBalances(def, spotAdapter, warn)).map(b => ({
192
+ token: b.token, total: b.total, available: b.available, held: b.held, valueUsd: b.valueUsd, shared: b.shared,
210
193
  }));
194
+ // HIP-3 dex accounts always run standard (non-unified) semantics. Codex v0.12.12 final QA #1.
195
+ const dexScoped = isDexCapable(adapter) && !!adapter.dex;
196
+ isUnified = !dexScoped && spotSharesPerpMargin(def, adapter);
211
197
  }
212
198
  }
213
- catch { /* spot not available */ }
199
+ catch (e) {
200
+ warn(`${def.displayName} spot balances unavailable: ${e instanceof Error ? e.message : String(e)}`);
201
+ }
214
202
  const cutoff24h = Date.now() - 24 * 60 * 60 * 1000;
215
203
  const recent = fundingPayments.filter(f => f.time >= cutoff24h);
216
204
  const funding24h = recent.reduce((sum, f) => sum + Number(f.payment), 0);
217
- // For unified accounts (HL), USDC spot is already in perp equity.
218
- // Account value = perp equity + non-USDC spot; for non-unified, equity + all spot.
219
- const nonUsdcSpotValue = isUnified
220
- ? spot.filter(b => b.token.replace(/-SPOT$/i, "").toUpperCase() !== "USDC").reduce((s, b) => s + b.valueUsd, 0)
221
- : spot.reduce((s, b) => s + b.valueUsd, 0);
205
+ // Shared balances (Pacifica USDC, HL unified USDC) are already in perp equity (§12.4 #8).
206
+ const nonUsdcSpotValue = spot.filter(b => !b.shared).reduce((s, b) => s + b.valueUsd, 0);
222
207
  return {
223
208
  name: exName,
224
209
  connected: true,
@@ -236,6 +221,7 @@ async function fetchExchangeEntry(exName, getAdapter) {
236
221
  size: p.size,
237
222
  entryPrice: p.entryPrice,
238
223
  markPrice: p.markPrice,
224
+ liquidationPrice: p.liquidationPrice,
239
225
  unrealizedPnl: p.unrealizedPnl,
240
226
  leverage: p.leverage,
241
227
  })),
@@ -291,7 +277,7 @@ function buildTotals(entries) {
291
277
  };
292
278
  }
293
279
  // ── Health check (no balance fetches) ──────────────────────────────────────
294
- async function runHealth(exchangeList) {
280
+ async function runHealth(exchangeList, network) {
295
281
  const { pingPacifica, pingHyperliquid, pingLighter } = await import("../shared-api.js");
296
282
  const pingAster = async () => {
297
283
  const start = Date.now();
@@ -312,11 +298,13 @@ async function runHealth(exchangeList) {
312
298
  const exchanges = {};
313
299
  await Promise.all(exchangeList.map(async (ex) => {
314
300
  const fn = pingMap[ex];
315
- if (!fn) {
316
- exchanges[ex] = "error";
317
- return;
318
- }
319
301
  try {
302
+ if (!fn) {
303
+ // No dedicated ping helper: probe the venue's public market-data path.
304
+ await getVenue(ex).publicData.fetchMarkets(network);
305
+ exchanges[ex] = "ok";
306
+ return;
307
+ }
320
308
  const r = await fn();
321
309
  exchanges[ex] = r.ok ? "ok" : "error";
322
310
  }
@@ -542,17 +530,17 @@ async function runWatch(resolved, version, getAdapterForExchange, isJsonMode) {
542
530
  process.stdout.write("");
543
531
  renderSnapshotText(data);
544
532
  if (resolved.health)
545
- renderHealthText(await runHealth(resolved.exchangeList));
533
+ renderHealthText(await runHealth(resolved.exchangeList, resolved.network));
546
534
  }
547
535
  await new Promise(r => setTimeout(r, resolved.watchIntervalMs));
548
536
  }
549
537
  }
550
538
  // ── Serve mode (delegates to dashboard infra) ──────────────────────────────
551
539
  const KNOWN_HIP3_DEXES = ["xyz", "flx", "hyna", "km", "cash", "vntl"];
552
- const HL_INFO_URL = "https://api.hyperliquid.xyz/info";
553
540
  async function checkDexPositions(address, dex) {
554
541
  try {
555
- const res = await fetch(HL_INFO_URL, {
542
+ // Selected network (review L7): follows -n via setSharedApiNetwork (live binding).
543
+ const res = await fetch(HYPERLIQUID_API_URL, {
556
544
  method: "POST",
557
545
  headers: { "Content-Type": "application/json" },
558
546
  body: JSON.stringify({ type: "clearinghouseState", user: address, dex }),
@@ -689,9 +677,9 @@ async function runServe(resolved, getAdapterForExchange, isJsonMode, getHLAdapte
689
677
  const { exec } = await import("child_process");
690
678
  const url = `http://localhost:${dashboard.port}`;
691
679
  if (process.platform === "darwin")
692
- exec(`open ${url}`);
680
+ exec(`open ${url}`, { env: scrubSecretEnv(process.env) });
693
681
  else if (process.platform === "linux")
694
- exec(`xdg-open ${url}`);
682
+ exec(`xdg-open ${url}`, { env: scrubSecretEnv(process.env) });
695
683
  }
696
684
  catch { /* user can open manually */ }
697
685
  const ac = new AbortController();
@@ -713,13 +701,13 @@ export function registerPortfolioCommand(program, getAdapterForExchange, isJson,
713
701
  program
714
702
  .command("portfolio")
715
703
  .description("Unified account view: balances, positions, risk (replaces account balance / status / dashboard)")
716
- .option("--exchanges <list>", "Comma-separated exchanges to include (default: all). Plural to avoid collision with global -e/--exchange.")
704
+ .option("--exchanges <list>", "Comma-separated exchanges to include (default: all with configured credentials). Plural to avoid collision with global -e/--exchange.")
717
705
  .option("--arb", "Include top arb opportunities (replaces 'perp status')")
718
706
  .option("--health", "Connectivity-only mode (replaces 'perp status --health')")
719
707
  .option("--watch [interval]", "Poll mode: clear screen + redraw each tick (ms; default 5000)")
720
708
  .option("--serve [port]", "Serve web dashboard (replaces 'perp dashboard')")
721
709
  .option("--interval <ms>", "Poll interval for --watch / --serve (default 5000)")
722
- .option("--dex <list>", "Comma-separated HIP-3 dex names (only with --serve)")
710
+ .option("--dexes <list>", "Comma-separated HIP-3 dex names (only with --serve; the root --dex <name> is also accepted)")
723
711
  .option("--no-auto-dex", "Disable HIP-3 dex auto-detection")
724
712
  .option("--stdin", "Read JSON args from stdin (overrides flag args)")
725
713
  .action(async (flags) => {
@@ -742,7 +730,7 @@ export function registerPortfolioCommand(program, getAdapterForExchange, isJson,
742
730
  }
743
731
  let resolved;
744
732
  try {
745
- resolved = resolveOptions(program, flags, stdinOpts);
733
+ resolved = resolveOptions(program, flags, stdinOpts, await listConfiguredAccountVenues());
746
734
  }
747
735
  catch (err) {
748
736
  const msg = err instanceof Error ? err.message : String(err);
@@ -763,7 +751,7 @@ export function registerPortfolioCommand(program, getAdapterForExchange, isJson,
763
751
  }
764
752
  await withJsonErrors(json, async () => {
765
753
  if (resolved.health) {
766
- const health = await runHealth(resolved.exchangeList);
754
+ const health = await runHealth(resolved.exchangeList, resolved.network);
767
755
  const data = {
768
756
  version,
769
757
  scope: resolved.scope,
@@ -1,3 +1,4 @@
1
1
  import { Command } from "commander";
2
2
  import type { ExchangeAdapter } from "../exchanges/index.js";
3
- export declare function registerFundsRebalanceCommands(parent: Command, getAdapterForExchange: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean): void;
3
+ import type { Network } from "../exchanges/venue-types.js";
4
+ export declare function registerFundsRebalanceCommands(parent: Command, getAdapterForExchange: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean, getNetwork: () => Network): void;
@@ -1,9 +1,10 @@
1
+ import { isDryRun, supportsDryRun } from "../cli-flags.js";
1
2
  import chalk from "chalk";
2
3
  import { formatUsd, makeTable, printJson, jsonOk } from "../utils.js";
3
4
  import { fetchAllBalances, computeRebalancePlan, describeBridgeRoute, estimateMoveTime, } from "../rebalance.js";
4
- import { EXCHANGE_TO_CHAIN, executeBestBridge } from "../bridge-engine.js";
5
- import { loadPrivateKey, parseSolanaKeypair } from "../config.js";
6
- export function registerFundsRebalanceCommands(parent, getAdapterForExchange, isJson) {
5
+ import { EXCHANGE_TO_CHAIN, assertBridgeNetwork, executeBestBridge } from "../bridge-engine.js";
6
+ import { cliCredentialOptsFrom } from "../config.js";
7
+ export function registerFundsRebalanceCommands(parent, getAdapterForExchange, isJson, getNetwork) {
7
8
  const rebalance = parent.command("rebalance").description("Cross-exchange balance management");
8
9
  // ── rebalance check ──
9
10
  rebalance
@@ -118,16 +119,19 @@ export function registerFundsRebalanceCommands(parent, getAdapterForExchange, is
118
119
  console.log(chalk.yellow(` To execute: perp funds rebalance execute --exchanges ${opts.exchanges}\n`));
119
120
  });
120
121
  // ── rebalance execute ──
121
- rebalance
122
- .command("execute")
123
- .description("Execute rebalancing (withdraw → bridge → deposit)")
122
+ supportsDryRun(rebalance.command("execute"))
123
+ .description("Execute rebalancing (withdraw → bridge → deposit; supports --dry-run)")
124
124
  .option("--exchanges <list>", "Comma-separated exchanges", "pacifica,hyperliquid,lighter,aster")
125
125
  .option("--min-move <usd>", "Minimum move amount ($)", "50")
126
126
  .option("--reserve <usd>", "Keep at least this much per exchange ($)", "20")
127
- .option("--dry-run", "Show what would happen without executing")
128
127
  .option("--withdraw-only", "Only execute withdrawals (manual bridge + deposit)")
129
128
  .option("--auto-bridge", "Auto-bridge via deBridge DLN (withdraw → bridge in one step)")
130
- .action(async (opts) => {
129
+ .action(async (opts, cmd) => {
130
+ const dryRun = isDryRun(cmd);
131
+ // --auto-bridge signs mainnet-only bridge routes: refuse -n testnet up front,
132
+ // before any withdrawal is sent (not after, as a per-move bridge_failed).
133
+ if (opts.autoBridge && !dryRun)
134
+ assertBridgeNetwork(getNetwork());
131
135
  const exchangeNames = opts.exchanges.split(",").map((e) => e.trim());
132
136
  const adapters = new Map();
133
137
  for (const name of exchangeNames) {
@@ -159,7 +163,7 @@ export function registerFundsRebalanceCommands(parent, getAdapterForExchange, is
159
163
  for (const move of plan.moves) {
160
164
  if (!isJson())
161
165
  console.log(chalk.white.bold(` $${move.amount} ${move.from} → ${move.to}`));
162
- if (opts.dryRun) {
166
+ if (dryRun) {
163
167
  if (!isJson())
164
168
  console.log(chalk.yellow(" [DRY RUN] Skipped.\n"));
165
169
  moveResults.push({ from: move.from, to: move.to, amount: move.amount, status: "dry_run" });
@@ -202,8 +206,11 @@ export function registerFundsRebalanceCommands(parent, getAdapterForExchange, is
202
206
  if (!isJson())
203
207
  console.log(chalk.gray(` Step 2: Bridging $${move.amount} (${srcChain} → ${dstChain})...`));
204
208
  // Load keys for bridge
205
- const { senderAddress, recipientAddress, signerKey, dstSignerKey } = await loadBridgeKeys(move.from, move.to, srcChain, dstChain);
206
- const result = await executeBestBridge(srcChain, dstChain, move.amount, signerKey, senderAddress, recipientAddress, dstSignerKey);
209
+ let root = cmd;
210
+ while (root.parent)
211
+ root = root.parent;
212
+ const { senderAddress, recipientAddress, signer, dstSigner } = await loadBridgeKeys(move.from, move.to, srcChain, dstChain, cliCredentialOptsFrom(root.opts()));
213
+ const result = await executeBestBridge(getNetwork(), srcChain, dstChain, move.amount, signer, senderAddress, recipientAddress, dstSigner);
207
214
  if (!isJson()) {
208
215
  console.log(chalk.green(` Bridge submitted! TX: ${result.txHash}`));
209
216
  console.log(chalk.gray(` Provider: ${result.provider} | ~$${result.amountOut} arriving on ${dstChain}\n`));
@@ -227,7 +234,7 @@ export function registerFundsRebalanceCommands(parent, getAdapterForExchange, is
227
234
  }
228
235
  }
229
236
  if (isJson())
230
- return printJson(jsonOk({ status: "executed", moves: moveResults }));
237
+ return printJson(jsonOk({ status: dryRun ? "simulated" : "executed", dryRun, sent: !dryRun, moves: moveResults }));
231
238
  console.log(chalk.cyan(" Rebalance initiated. Monitor progress with 'perp funds rebalance check'.\n"));
232
239
  });
233
240
  }
@@ -244,7 +251,8 @@ async function executeWithdraw(move, adapters) {
244
251
  if (!hasPacificaSdk(adapter))
245
252
  throw new Error("Invalid adapter");
246
253
  const sdk = adapter.sdk;
247
- await sdk.withdraw({ amount: String(move.amount), dest_address: adapter.publicKey }, adapter.publicKey, adapter.signer);
254
+ // Pacifica withdraws to the account owner's own wallet (no destination field).
255
+ await sdk.withdraw({ amount: String(move.amount) }, adapter.publicKey, adapter.signer);
248
256
  break;
249
257
  }
250
258
  case "hyperliquid": {
@@ -264,35 +272,17 @@ async function executeWithdraw(move, adapters) {
264
272
  }
265
273
  }
266
274
  /**
267
- * Load sender/recipient addresses and signer key for a bridge move.
275
+ * Sender / recipient (the master wallet's own public addresses) and master signers for a bridge
276
+ * move: OWS vault signers for an OWS master, raw-key signers for a raw-key master.
268
277
  */
269
- async function loadBridgeKeys(srcExchange, dstExchange, srcChain, dstChain) {
270
- let senderAddress;
271
- let recipientAddress;
272
- let signerKey;
273
- let dstSignerKey;
274
- // Source
275
- if (srcChain === "solana") {
276
- const pk = await loadPrivateKey("pacifica");
277
- senderAddress = parseSolanaKeypair(pk).publicKey.toBase58();
278
- signerKey = pk;
279
- }
280
- else {
281
- const pk = await loadPrivateKey(srcExchange);
282
- const { ethers } = await import("ethers");
283
- senderAddress = new ethers.Wallet(pk).address;
284
- signerKey = pk;
285
- }
286
- // Destination
287
- if (dstChain === "solana") {
288
- const pk = await loadPrivateKey("pacifica");
289
- recipientAddress = parseSolanaKeypair(pk).publicKey.toBase58();
290
- }
291
- else {
292
- const pk = await loadPrivateKey(dstExchange);
293
- const { ethers } = await import("ethers");
294
- recipientAddress = new ethers.Wallet(pk).address;
295
- dstSignerKey = pk; // For auto receiveMessage
296
- }
297
- return { senderAddress, recipientAddress, signerKey, dstSignerKey };
278
+ async function loadBridgeKeys(srcExchange, dstExchange, srcChain, dstChain, credOpts) {
279
+ const { bridgeAddress, bridgeVenueForChain, loadBridgeSigner } = await import("../bridge-signers.js");
280
+ const srcVenue = bridgeVenueForChain(srcChain, srcExchange);
281
+ const dstVenue = bridgeVenueForChain(dstChain, dstExchange);
282
+ return {
283
+ senderAddress: await bridgeAddress(srcChain, srcVenue, credOpts),
284
+ recipientAddress: await bridgeAddress(dstChain, dstVenue, credOpts),
285
+ signer: await loadBridgeSigner(srcChain, srcVenue, credOpts),
286
+ dstSigner: await loadBridgeSigner(dstChain, dstVenue, credOpts),
287
+ };
298
288
  }
@@ -1,3 +1,4 @@
1
+ import { listConfiguredAccountVenues } from "../config.js";
1
2
  import chalk from "chalk";
2
3
  import { printJson, jsonOk, makeTable, formatUsd, withJsonErrors } from "../utils.js";
3
4
  import { loadRiskLimits, saveRiskLimits, assessRisk, getLiquidationDistances } from "../risk.js";
@@ -59,19 +60,26 @@ export async function runHealthCheck(isJson) {
59
60
  const overall = allOk ? chalk.green("ALL HEALTHY") : chalk.red("ISSUES DETECTED");
60
61
  console.log(`\n Overall: ${overall}\n`);
61
62
  }
62
- const EXCHANGES = ["pacifica", "hyperliquid", "lighter", "aster"];
63
+ /**
64
+ * Exchange filter (comma-separated) of a risk subcommand: the ROOT `-e, --exchange`, only when given
65
+ * on the command line. Commander hands `--exchange` to the root wherever it appears, so a subcommand
66
+ * copy was never set (the filter was silently dropped); the root's default venue is never a filter.
67
+ */
68
+ function cliExchangeFilter(root) {
69
+ return root.getOptionValueSource("exchange") === "cli" ? String(root.opts().exchange) : undefined;
70
+ }
63
71
  export function registerRiskCommands(program, getAdapterForExchange, isJson) {
64
72
  const risk = program.command("risk").description("Risk management and guardrails");
65
73
  // ── risk status ──
66
74
  risk
67
75
  .command("status")
68
- .description("Assess current risk across all exchanges")
69
- .option("--exchange <exchanges>", "Comma-separated exchanges (default: all)")
70
- .action(async (opts) => {
76
+ .description("Assess current risk across all exchanges — -e <ex1,ex2> limits the exchanges (default: all with configured credentials)")
77
+ .action(async () => {
78
+ const exchangeFilter = cliExchangeFilter(program);
71
79
  await withJsonErrors(isJson(), async () => {
72
- const exchanges = opts.exchange
73
- ? opts.exchange.split(",").map(e => e.trim())
74
- : [...EXCHANGES];
80
+ const exchanges = exchangeFilter
81
+ ? exchangeFilter.split(",").map(e => e.trim())
82
+ : await listConfiguredAccountVenues();
75
83
  const balances = [];
76
84
  const positions = [];
77
85
  const results = await Promise.allSettled(exchanges.map(async (ex) => {
@@ -120,6 +128,9 @@ export function registerRiskCommands(program, getAdapterForExchange, isJson) {
120
128
  : chalk.green;
121
129
  console.log(` Min Liq Distance: ${ldColor(`${assessment.metrics.minLiquidationDistancePct.toFixed(1)}%`)}`);
122
130
  }
131
+ for (const { exchange, position: p } of positions.filter(x => x.position.liquidationPrice === null)) {
132
+ console.log(` Liq Price ${exchange}:${p.symbol}: ${chalk.yellow("unknown (venue does not report)")} — not in Min Liq Distance`);
133
+ }
123
134
  if (assessment.violations.length > 0) {
124
135
  console.log(chalk.red.bold("\n Violations"));
125
136
  const vRows = assessment.violations.map(v => {
@@ -222,15 +233,15 @@ export function registerRiskCommands(program, getAdapterForExchange, isJson) {
222
233
  // ── risk check ── (pre-trade check, for agent use)
223
234
  risk
224
235
  .command("check")
225
- .description("Pre-trade risk check (for agents)")
236
+ .description("Pre-trade risk check (for agents) — -e <ex1,ex2> limits the exchanges (default: all with configured credentials)")
226
237
  .requiredOption("--notional <usd>", "Order notional value in USD")
227
238
  .requiredOption("--leverage <n>", "Order leverage")
228
- .option("--exchange <exchanges>", "Comma-separated exchanges (default: all)")
229
239
  .action(async (opts) => {
240
+ const exchangeFilter = cliExchangeFilter(program);
230
241
  await withJsonErrors(isJson(), async () => {
231
- const exchanges = opts.exchange
232
- ? opts.exchange.split(",").map(e => e.trim())
233
- : [...EXCHANGES];
242
+ const exchanges = exchangeFilter
243
+ ? exchangeFilter.split(",").map(e => e.trim())
244
+ : await listConfiguredAccountVenues();
234
245
  const balances = [];
235
246
  const positions = [];
236
247
  await Promise.allSettled(exchanges.map(async (ex) => {
@@ -260,14 +271,14 @@ export function registerRiskCommands(program, getAdapterForExchange, isJson) {
260
271
  // ── risk liquidation-distance ──
261
272
  risk
262
273
  .command("liquidation-distance")
263
- .description("Show % distance from liquidation price for all positions")
274
+ .description("Show % distance from liquidation price for all positions — -e <ex1,ex2> limits the exchanges (default: all with configured credentials)")
264
275
  .alias("liq-dist")
265
- .option("--exchange <exchanges>", "Comma-separated exchanges (default: all)")
266
- .action(async (opts) => {
276
+ .action(async () => {
277
+ const exchangeFilter = cliExchangeFilter(program);
267
278
  await withJsonErrors(isJson(), async () => {
268
- const exchanges = opts.exchange
269
- ? opts.exchange.split(",").map(e => e.trim())
270
- : [...EXCHANGES];
279
+ const exchanges = exchangeFilter
280
+ ? exchangeFilter.split(",").map(e => e.trim())
281
+ : await listConfiguredAccountVenues();
271
282
  const positions = [];
272
283
  const results = await Promise.allSettled(exchanges.map(async (ex) => {
273
284
  const adapter = await getAdapterForExchange(ex);
@@ -291,9 +302,14 @@ export function registerRiskCommands(program, getAdapterForExchange, isJson) {
291
302
  }
292
303
  const limits = loadRiskLimits();
293
304
  const distances = getLiquidationDistances(positions, limits);
305
+ // Positions whose venue reports no liquidation price are listed explicitly, never dropped.
306
+ const unknownLiq = positions
307
+ .filter(({ position: p }) => p.liquidationPrice === null)
308
+ .map(({ exchange, position: p }) => ({ exchange, symbol: p.symbol, side: p.side, markPrice: Number(p.markPrice), liquidationPrice: null }));
294
309
  if (isJson()) {
295
310
  return printJson(jsonOk({
296
311
  positions: distances,
312
+ unknownLiquidation: unknownLiq,
297
313
  limits: {
298
314
  minLiquidationDistance: limits.minLiquidationDistance,
299
315
  },
@@ -318,6 +334,17 @@ export function registerRiskCommands(program, getAdapterForExchange, isJson) {
318
334
  statusColor(d.status.toUpperCase()),
319
335
  ];
320
336
  });
337
+ for (const u of unknownLiq) {
338
+ rows.push([
339
+ chalk.white(u.exchange),
340
+ chalk.white.bold(u.symbol),
341
+ u.side === "long" ? chalk.green("LONG") : chalk.red("SHORT"),
342
+ `$${formatUsd(u.markPrice)}`,
343
+ chalk.yellow("unknown (venue does not report)"),
344
+ chalk.gray("n/a"),
345
+ chalk.yellow("UNKNOWN"),
346
+ ]);
347
+ }
321
348
  console.log(makeTable(["Exchange", "Symbol", "Side", "Mark Price", "Liq Price", "Distance", "Status"], rows));
322
349
  // Summary warnings
323
350
  const critical = distances.filter(d => d.status === "critical");
@@ -338,7 +365,7 @@ export function registerRiskCommands(program, getAdapterForExchange, isJson) {
338
365
  });
339
366
  program
340
367
  .command("health")
341
- .description("Adapter health check across all 4 DEX")
368
+ .description("Adapter health check: ping venue REST endpoints (connectivity + latency)")
342
369
  .action(async () => {
343
370
  await runHealthCheck(isJson);
344
371
  });
@@ -1,3 +1,4 @@
1
1
  import { Command } from "commander";
2
2
  import type { ExchangeAdapter } from "../exchanges/index.js";
3
+ import "../exchanges/venues/index.js";
3
4
  export declare function registerSettingsCommands(program: Command, isJson: () => boolean, getAdapterForExchange?: (exchange: string) => Promise<ExchangeAdapter>): void;