perp-cli 0.12.17 → 0.14.1

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.md +247 -27
  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 +303 -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 +70 -92
  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 +248 -0
  101. package/dist/commands/funds.d.ts +25 -0
  102. package/dist/commands/funds.js +925 -245
  103. package/dist/commands/history.js +102 -54
  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 +357 -459
  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 +240 -6
  166. package/dist/exchanges/aster.js +1139 -195
  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 +152 -0
  200. package/dist/exchanges/ondo/adapter.js +811 -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 +95 -0
  206. package/dist/exchanges/ondo/funds.js +198 -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 +293 -4
  218. package/dist/exchanges/pacifica.js +1044 -90
  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 +1386 -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 +29 -0
  270. package/dist/exchanges/venues/ondo.js +136 -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 +39 -0
  274. package/dist/exchanges/venues/qfex.js +140 -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 +156 -684
  284. package/dist/jobs.d.ts +84 -6
  285. package/dist/jobs.js +239 -41
  286. package/dist/landing.d.ts +7 -2
  287. package/dist/landing.js +19 -3
  288. package/dist/mcp-server.d.ts +14 -4
  289. package/dist/mcp-server.js +821 -403
  290. package/dist/mcp-trade-guard.d.ts +16 -0
  291. package/dist/mcp-trade-guard.js +59 -0
  292. package/dist/onchain-balances.d.ts +40 -0
  293. package/dist/onchain-balances.js +173 -0
  294. package/dist/pacifica/client.d.ts +41 -10
  295. package/dist/pacifica/client.js +167 -63
  296. package/dist/pacifica/constants.d.ts +0 -1
  297. package/dist/pacifica/constants.js +0 -5
  298. package/dist/pacifica/deposit.d.ts +30 -3
  299. package/dist/pacifica/deposit.js +119 -2
  300. package/dist/pacifica/faucet.d.ts +29 -0
  301. package/dist/pacifica/faucet.js +100 -0
  302. package/dist/pacifica/sign-tx.d.ts +15 -0
  303. package/dist/pacifica/sign-tx.js +41 -0
  304. package/dist/pacifica/types/account.d.ts +28 -4
  305. package/dist/pacifica/types/lake.d.ts +20 -5
  306. package/dist/pacifica/types/lake.js +4 -0
  307. package/dist/pacifica/types/market.d.ts +5 -0
  308. package/dist/pacifica/types/order.d.ts +4 -1
  309. package/dist/pacifica/types/position.d.ts +9 -1
  310. package/dist/plan-executor.d.ts +10 -5
  311. package/dist/plan-executor.js +165 -54
  312. package/dist/position-history.js +13 -0
  313. package/dist/rebalance.d.ts +26 -2
  314. package/dist/rebalance.js +86 -7
  315. package/dist/relay-swap.d.ts +232 -0
  316. package/dist/relay-swap.js +630 -0
  317. package/dist/risk.js +5 -1
  318. package/dist/secret-env.d.ts +15 -0
  319. package/dist/secret-env.js +36 -0
  320. package/dist/settings.d.ts +169 -6
  321. package/dist/settings.js +219 -38
  322. package/dist/signer/ows-evm.d.ts +13 -0
  323. package/dist/signer/ows-evm.js +22 -0
  324. package/dist/smart-order.d.ts +9 -0
  325. package/dist/smart-order.js +53 -8
  326. package/dist/strategies/dca.d.ts +6 -2
  327. package/dist/strategies/dca.js +66 -26
  328. package/dist/strategies/funding-arb.d.ts +11 -1
  329. package/dist/strategies/funding-arb.js +202 -89
  330. package/dist/strategies/grid.d.ts +31 -0
  331. package/dist/strategies/grid.js +89 -11
  332. package/dist/strategies/pending-stop.d.ts +8 -0
  333. package/dist/strategies/pending-stop.js +12 -0
  334. package/dist/strategies/split-order.d.ts +45 -17
  335. package/dist/strategies/split-order.js +157 -91
  336. package/dist/strategies/trailing-stop.d.ts +2 -1
  337. package/dist/strategies/trailing-stop.js +52 -6
  338. package/dist/strategies/twap.d.ts +6 -1
  339. package/dist/strategies/twap.js +39 -11
  340. package/dist/trade-validator.d.ts +22 -0
  341. package/dist/trade-validator.js +119 -15
  342. package/dist/utils/numeric.d.ts +46 -0
  343. package/dist/utils/numeric.js +79 -0
  344. package/dist/utils.d.ts +19 -0
  345. package/dist/utils.js +96 -19
  346. package/dist/withdraw-gate.d.ts +49 -0
  347. package/dist/withdraw-gate.js +131 -0
  348. package/package.json +15 -6
  349. package/skills/perp-cli/SKILL.md +159 -55
  350. package/skills/perp-cli/references/agent-operations.md +112 -58
  351. package/skills/perp-cli/references/commands.md +269 -73
  352. package/skills/perp-cli/references/strategies.md +34 -31
  353. package/skills/perp-cli/scripts/arb-monitor.sh +10 -5
  354. package/skills/perp-cli/scripts/funding-analysis.sh +14 -9
  355. package/skills/perp-cli/scripts/preflight.sh +21 -14
  356. package/skills/perp-cli/scripts/spot-perp-scan.sh +12 -7
  357. package/skills/perp-cli/scripts/validate-arb.sh +31 -15
  358. package/vendor/lighter-signer/SHA256SUMS +2 -0
  359. package/vendor/lighter-signer/VERSION +8 -0
  360. package/vendor/lighter-signer/lighter-signer.wasm +0 -0
  361. package/vendor/lighter-signer/wasm_exec.js +575 -0
  362. package/dist/bot/strategies/funding-arb-v2-strategy.d.ts +0 -42
  363. package/dist/bot/strategies/funding-arb-v2-strategy.js +0 -864
@@ -1,2 +1,3 @@
1
1
  import { Command } from "commander";
2
- export declare function registerBackgroundCommands(program: Command, isJson: () => boolean): void;
2
+ import type { ExchangeAdapter } from "../exchanges/index.js";
3
+ export declare function registerBackgroundCommands(program: Command, isJson: () => boolean, getAdapterFor?: (venue: string) => Promise<ExchangeAdapter>): void;
@@ -1,9 +1,32 @@
1
- import { listJobs, stopJob, removeJob, loadJob, logFile } from "../jobs.js";
2
- import { printJson, jsonOk } from "../utils.js";
1
+ import { listJobs, stopJob, removeJob, loadJob, logFile, assertTmuxSessionName, JOB_STOP_TIMEOUT_MS } from "../jobs.js";
2
+ import { printJson, jsonOk, symbolMatch } from "../utils.js";
3
+ import { PerpError } from "../errors.js";
3
4
  import chalk from "chalk";
4
5
  import { existsSync, readFileSync } from "node:fs";
5
- import { execSync } from "node:child_process";
6
- export function registerBackgroundCommands(program, isJson) {
6
+ import { execFileSync } from "node:child_process";
7
+ import { scrubSecretEnv } from "../secret-env.js";
8
+ /**
9
+ * What a stopped job left on its venue(s): open orders and positions on its symbol (every symbol
10
+ * when the job has none). One entry per venue; a failed read is reported, never "nothing left".
11
+ */
12
+ async function leftoversOf(job, getAdapterFor) {
13
+ const sym = typeof job.params.symbol === "string" && job.params.symbol !== "ALL" ? job.params.symbol.toUpperCase() : null;
14
+ const venues = job.exchange === "multi" ? [] : job.exchange.split(/[+,]/).map(v => v.trim()).filter(Boolean);
15
+ const out = [];
16
+ for (const venue of venues) {
17
+ try {
18
+ const a = await getAdapterFor(venue);
19
+ const [orders, positions] = await Promise.all([a.getOpenOrders(), a.getPositions()]);
20
+ const mine = (xs) => sym === null ? xs : xs.filter(x => symbolMatch(x.symbol, sym));
21
+ out.push({ venue, orders: mine(orders), positions: mine(positions).filter(p => Number(p.size) !== 0), error: null });
22
+ }
23
+ catch (e) {
24
+ out.push({ venue, orders: [], positions: [], error: e instanceof Error ? e.message : String(e) });
25
+ }
26
+ }
27
+ return out;
28
+ }
29
+ export function registerBackgroundCommands(program, isJson, getAdapterFor) {
7
30
  const jobs = program.command("background").description("Background process supervisor (tmux sessions for strategies, alerts, etc.)");
8
31
  jobs
9
32
  .command("list", { isDefault: true })
@@ -41,23 +64,60 @@ export function registerBackgroundCommands(program, isJson) {
41
64
  });
42
65
  jobs
43
66
  .command("stop <id>")
44
- .description("Stop a running job")
45
- .action((id) => {
67
+ .description("Stop a running job gracefully: SIGINT (the job cancels its orders / saves state), wait up to --timeout, only then kill; reports what is left on the venue")
68
+ .option("--timeout <sec>", "Seconds to wait for the job's own cleanup before killing it", String(JOB_STOP_TIMEOUT_MS / 1000))
69
+ .action(async (id, opts) => {
46
70
  const job = loadJob(id);
47
71
  if (!job) {
48
72
  console.log(chalk.red(`\n Job ${id} not found.\n`));
49
73
  return;
50
74
  }
51
- stopJob(id);
75
+ const timeoutSec = Number(opts.timeout);
76
+ if (!(timeoutSec > 0))
77
+ throw new PerpError("INVALID_PARAMS", `--timeout must be a positive number of seconds (got ${opts.timeout})`);
78
+ if (!isJson())
79
+ console.log(chalk.gray(`\n Stopping job ${id} (SIGINT, waiting up to ${timeoutSec}s for its cleanup)...`));
80
+ const r = await stopJob(id, timeoutSec * 1000);
81
+ if (r === null)
82
+ return;
83
+ const log = logFile(id);
84
+ const tail = existsSync(log) ? readFileSync(log, "utf-8").split("\n").filter(l => l.trim() !== "").slice(-12) : [];
85
+ const left = getAdapterFor !== undefined ? await leftoversOf(job, getAdapterFor) : [];
86
+ const leftCount = left.reduce((n, v) => n + v.orders.length + v.positions.length, 0);
52
87
  if (isJson())
53
- return printJson(jsonOk({ id, status: "stopped" }));
54
- console.log(chalk.green(`\n Job ${id} stopped.\n`));
88
+ return printJson(jsonOk({ id, status: "stopped", stop: r, leftovers: left, logTail: tail }));
89
+ if (r.outcome === "forced")
90
+ console.log(chalk.red.bold(` Job ${id} was FORCE-KILLED: ${r.note}. Its own cleanup (cancel orders / save state) did NOT run.`));
91
+ else if (r.outcome === "graceful")
92
+ console.log(chalk.green(` Job ${id} exited after SIGINT (${(r.waitedMs / 1000).toFixed(1)}s).`));
93
+ else
94
+ console.log(chalk.gray(` Job ${id} was not running.`));
95
+ if (tail.length > 0) {
96
+ console.log(chalk.gray(" Last log lines:"));
97
+ for (const l of tail)
98
+ console.log(chalk.gray(` ${l}`));
99
+ }
100
+ for (const v of left) {
101
+ if (v.error !== null) {
102
+ console.log(chalk.yellow(` ${v.venue}: could not read what is left (${v.error}) — check: perp -e ${v.venue} account orders / account positions`));
103
+ continue;
104
+ }
105
+ for (const o of v.orders)
106
+ console.log(chalk.yellow(` LEFT OPEN on ${v.venue}: order ${o.side} ${o.size} ${o.symbol} @ ${o.price} (${o.orderId}) — cancel: perp -e ${v.venue} trade cancel ${o.symbol} ${o.orderId}`));
107
+ for (const p of v.positions)
108
+ console.log(chalk.yellow(` LEFT OPEN on ${v.venue}: position ${p.side} ${p.size.replace(/^-/, "")} ${p.symbol} — close: perp -e ${v.venue} trade close ${p.symbol}`));
109
+ }
110
+ if (left.length > 0 && leftCount === 0 && left.every(v => v.error === null))
111
+ console.log(chalk.green(` Nothing left open on ${left.map(v => v.venue).join(", ")}${typeof job.params.symbol === "string" ? ` for ${job.params.symbol}` : ""}.`));
112
+ if (job.exchange === "multi")
113
+ console.log(chalk.gray(" Multi-venue job: check every venue: perp arb status"));
114
+ console.log();
55
115
  });
56
116
  jobs
57
117
  .command("logs <id>")
58
118
  .description("Show logs for a job")
59
119
  .option("-f, --follow", "Follow log output (tail -f)")
60
- .option("-n, --lines <n>", "Number of lines to show", "50")
120
+ .option("--lines <n>", "Number of lines to show", "50")
61
121
  .action((id, opts) => {
62
122
  const log = logFile(id);
63
123
  if (!existsSync(log)) {
@@ -69,13 +129,13 @@ export function registerBackgroundCommands(program, isJson) {
69
129
  const job = loadJob(id);
70
130
  if (job?.status === "running") {
71
131
  try {
72
- execSync(`tmux attach-session -t ${job.tmuxSession}`, { stdio: "inherit" });
132
+ execFileSync("tmux", ["attach-session", "-t", assertTmuxSessionName(job.tmuxSession)], { stdio: "inherit", env: scrubSecretEnv(process.env) });
73
133
  return;
74
134
  }
75
135
  catch { /* fall through to tail */ }
76
136
  }
77
137
  try {
78
- execSync(`tail -f ${log}`, { stdio: "inherit" });
138
+ execFileSync("tail", ["-f", log], { stdio: "inherit", env: scrubSecretEnv(process.env) });
79
139
  }
80
140
  catch { /* ctrl-c */ }
81
141
  }
@@ -89,15 +149,17 @@ export function registerBackgroundCommands(program, isJson) {
89
149
  });
90
150
  jobs
91
151
  .command("remove <id>")
92
- .description("Remove a job entry and its logs")
93
- .action((id) => {
152
+ .description("Remove a job entry and its logs (a running job is stopped gracefully first)")
153
+ .action(async (id) => {
94
154
  const job = loadJob(id);
95
155
  if (!job) {
96
156
  console.log(chalk.red(`\n Job ${id} not found.\n`));
97
157
  return;
98
158
  }
99
159
  if (job.status === "running") {
100
- stopJob(id);
160
+ const r = await stopJob(id);
161
+ if (r?.outcome === "forced" && !isJson())
162
+ console.log(chalk.red(` Job ${id} was force-killed (${r.note}); its cleanup did not run — check its venue for open orders / positions.`));
101
163
  }
102
164
  removeJob(id);
103
165
  if (isJson())
@@ -1,13 +1,18 @@
1
- import { printJson, jsonOk, jsonError } from "../utils.js";
1
+ import { isDryRun, networkOf, supportsDryRun } from "../cli-flags.js";
2
+ import { printJson, jsonOk, jsonError, withJsonErrors } from "../utils.js";
2
3
  import chalk from "chalk";
3
4
  import { hasPacificaSdk } from "../exchanges/capabilities.js";
4
- import { PerpError } from "../errors.js";
5
- import { loadSettings } from "../settings.js";
5
+ import { PerpError, classifyError, parsePositiveDecimal } from "../errors.js";
6
+ import { unsupported } from "../exchanges/trading-v2.js";
7
+ import { loadSettings, saveSettings } from "../settings.js";
6
8
  import { resolvePassphrase } from "../agent-wallet/passphrase.js";
7
9
  import { runHlSetAbstractionFlow } from "./agent.js";
8
- import { HyperliquidAdapter } from "../exchanges/hyperliquid.js";
10
+ import { HyperliquidAdapter, hlAbstractionCachePrefix } from "../exchanges/hyperliquid.js";
11
+ import { invalidateCache } from "../cache.js";
12
+ import { LighterAdapter } from "../exchanges/lighter.js";
13
+ import { resolveVenueId } from "../exchanges/venue.js";
9
14
  export function registerWalletManageCommands(parent, getAdapter, isJson, getPacificaAdapter) {
10
- const manage = parent.command("manage").description("Exchange account settings (margin, subaccount, API keys, builder, account-mode) — Pacifica + Lighter + Hyperliquid");
15
+ const manage = parent.command("manage").description("Exchange account settings (margin, subaccount, API keys, builder-owner tools, account-mode) — Pacifica + Lighter + Hyperliquid");
11
16
  // Ensure adapter is initialized before accessing PacificaAdapter
12
17
  async function pac() {
13
18
  const adapter = await getAdapter();
@@ -45,15 +50,15 @@ export function registerWalletManageCommands(parent, getAdapter, isJson, getPaci
45
50
  withdrawCmd._hidden = true;
46
51
  // Subaccounts
47
52
  const sub = manage.command("sub").description("Subaccount management");
53
+ // Gated. Docs (rest-api/subaccounts/create-subaccount): "Both the main account and subaccount
54
+ // must sign to establish the relationship." perp-cli has no keypair source for the subaccount
55
+ // signature, so the request cannot be built.
48
56
  sub
49
57
  .command("create <name>")
50
- .description("Create a subaccount")
51
- .action(async (name) => {
52
- const a = await pac();
53
- const result = await a.sdk.createSubaccount(name, a.publicKey, a.signer);
54
- if (isJson())
55
- return printJson(jsonOk(result));
56
- console.log(chalk.green(`\n Subaccount "${name}" created.\n`));
58
+ .description("Create a subaccount (not supported: the subaccount wallet must co-sign — use the Pacifica web app)")
59
+ .action(async (_name) => {
60
+ await pac();
61
+ throw unsupported("Pacifica", "subaccountCreate", "creating a subaccount needs a signature from the subaccount wallet as well as the main account, and perp-cli only holds the main account signer", "Create the subaccount in the Pacifica web app, then use: perp wallet manage sub list / sub transfer");
57
62
  });
58
63
  sub
59
64
  .command("list")
@@ -67,10 +72,17 @@ export function registerWalletManageCommands(parent, getAdapter, isJson, getPaci
67
72
  });
68
73
  sub
69
74
  .command("transfer <from> <to> <amount>")
70
- .description("Transfer funds between accounts")
75
+ .description("Transfer USDC between a main account and its subaccounts (<from> must be the signing account)")
71
76
  .action(async (from, to, amount) => {
72
77
  const a = await pac();
73
- const result = await a.sdk.transferFunds({ from_account: from, to_account: to, amount }, a.publicKey, a.signer);
78
+ // Docs (subaccount-fund-transfer): the sender is the signing "account"; there is no from field.
79
+ if (from !== a.publicKey) {
80
+ throw new PerpError("INVALID_PARAMS", `Pacifica transfers are sent by the signing account (${a.publicKey}); <from> was ${from}`, {
81
+ exchange: "pacifica",
82
+ remediation: "Pass the active account as <from>, or run the command with the sender's wallet",
83
+ });
84
+ }
85
+ const result = await a.sdk.transferFunds({ to_account: to, amount: parsePositiveDecimal(amount, "transfer amount") }, a.publicKey, a.signer);
74
86
  if (isJson())
75
87
  return printJson(jsonOk(result));
76
88
  console.log(chalk.green(`\n Transferred $${amount}.\n`));
@@ -81,77 +93,48 @@ export function registerWalletManageCommands(parent, getAdapter, isJson, getPaci
81
93
  // and OWS at-rest encryption. See `wallet agent approve pacifica --help`.
82
94
  // Lake (liquidity vaults)
83
95
  const lake = manage.command("lake").description("Lake (liquidity vault) management");
96
+ // Field names per docs.pacifica.fi rest-api/vaults (create-vault / deposit-to-vault / withdraw-from-vault).
84
97
  lake
85
- .command("create <symbol> <amount>")
86
- .description("Create a new lake")
87
- .action(async (symbol, amount) => {
98
+ .command("create")
99
+ .description("Create a new vault (lake)")
100
+ .option("--manager <address>", "Manager address (omit to create a fresh vault with no manager)")
101
+ .option("--nickname <name>", "Display name for the vault")
102
+ .option("--initial-deposit <amount>", "Seed deposit in USDC from the creator")
103
+ .action(async (opts) => {
88
104
  const a = await pac();
89
- const result = await a.sdk.createLake({ symbol: symbol.toUpperCase(), amount }, a.publicKey, a.signer);
105
+ const result = await a.sdk.createLake({
106
+ manager: opts.manager === undefined ? null : opts.manager,
107
+ nickname: opts.nickname === undefined ? null : opts.nickname,
108
+ initial_deposit: opts.initialDeposit === undefined ? null : parsePositiveDecimal(opts.initialDeposit, "--initial-deposit"),
109
+ }, a.publicKey, a.signer);
90
110
  if (isJson())
91
111
  return printJson(jsonOk(result));
92
- console.log(chalk.green(`\n Lake created for ${symbol.toUpperCase()} with $${amount}.\n`));
112
+ console.log(chalk.green(`\n Vault created.\n`));
113
+ console.log(JSON.stringify(result, null, 2));
93
114
  });
94
115
  lake
95
- .command("deposit <lakeId> <amount>")
96
- .description("Deposit to a lake")
97
- .action(async (lakeId, amount) => {
116
+ .command("deposit <lake> <amount>")
117
+ .description("Deposit USDC into a vault (<lake> = vault address)")
118
+ .action(async (lakeAddress, amount) => {
98
119
  const a = await pac();
99
- const result = await a.sdk.depositToLake({ lake_id: lakeId, amount }, a.publicKey, a.signer);
120
+ const result = await a.sdk.depositToLake({ lake: lakeAddress, amount: parsePositiveDecimal(amount, "deposit amount") }, a.publicKey, a.signer);
100
121
  if (isJson())
101
122
  return printJson(jsonOk(result));
102
- console.log(chalk.green(`\n Deposited $${amount} to lake ${lakeId}.\n`));
123
+ console.log(chalk.green(`\n Deposited $${amount} to vault ${lakeAddress}.\n`));
103
124
  });
104
125
  lake
105
- .command("withdraw <lakeId> <amount>")
106
- .description("Withdraw from a lake")
107
- .action(async (lakeId, amount) => {
108
- const a = await pac();
109
- const result = await a.sdk.withdrawFromLake({ lake_id: lakeId, amount }, a.publicKey, a.signer);
110
- if (isJson())
111
- return printJson(jsonOk(result));
112
- console.log(chalk.green(`\n Withdrew $${amount} from lake ${lakeId}.\n`));
113
- });
114
- // Builder Codes
115
- const builder = manage.command("builder").description("Builder code management (Pacifica)");
116
- builder
117
- .command("approve <code> <maxFeeRate>")
118
- .description("Approve a builder code (e.g. approve MYCODE 0.001)")
119
- .action(async (code, maxFeeRate) => {
126
+ .command("withdraw <lake> <shares>")
127
+ .description("Redeem vault shares (<lake> = vault address, <shares> = number of shares, not USDC)")
128
+ .action(async (lakeAddress, shares) => {
120
129
  const a = await pac();
121
- const result = await a.sdk.approveBuilderCode({ builder_code: code, max_fee_rate: maxFeeRate }, a.publicKey, a.signer);
130
+ const result = await a.sdk.withdrawFromLake({ lake: lakeAddress, shares: parsePositiveDecimal(shares, "shares") }, a.publicKey, a.signer);
122
131
  if (isJson())
123
132
  return printJson(jsonOk(result));
124
- console.log(chalk.green(`\n Builder code "${code}" approved (max fee: ${maxFeeRate}).\n`));
125
- });
126
- builder
127
- .command("revoke <code>")
128
- .description("Revoke a builder code")
129
- .action(async (code) => {
130
- const a = await pac();
131
- const result = await a.sdk.revokeBuilderCode({ builder_code: code }, a.publicKey, a.signer);
132
- if (isJson())
133
- return printJson(jsonOk(result));
134
- console.log(chalk.green(`\n Builder code "${code}" revoked.\n`));
135
- });
136
- builder
137
- .command("list")
138
- .description("List approved builder codes")
139
- .action(async () => {
140
- const a = await pac();
141
- const result = await a.sdk.getBuilderApprovals(a.publicKey);
142
- if (isJson())
143
- return printJson(jsonOk(result));
144
- const approvals = result;
145
- if (!Array.isArray(approvals) || approvals.length === 0) {
146
- console.log(chalk.gray("\n No builder codes approved.\n"));
147
- return;
148
- }
149
- console.log(chalk.cyan.bold("\n Approved Builder Codes\n"));
150
- for (const b of approvals) {
151
- console.log(` ${chalk.white(b.builder_code.padEnd(16))} max_fee: ${b.max_fee_rate} ${chalk.gray(b.description || "")}`);
152
- }
153
- console.log();
133
+ console.log(chalk.green(`\n Redeemed ${shares} shares from vault ${lakeAddress}.\n`));
154
134
  });
135
+ // Pacifica builder-OWNER tools. A user's builder approval / revoke / status is
136
+ // `perp wallet builder approve|revoke|status <venue>` (settings builder.<venue>).
137
+ const builder = manage.command("builder").description("Pacifica builder-owner tools (overview, trades, leaderboard, update-fee). Approve / revoke / status: perp wallet builder");
155
138
  builder
156
139
  .command("overview")
157
140
  .description("Show your builder code overview (if you are a builder)")
@@ -193,7 +176,7 @@ export function registerWalletManageCommands(parent, getAdapter, isJson, getPaci
193
176
  console.log(chalk.green(`\n Builder code "${code}" fee rate updated to ${feeRate}.\n`));
194
177
  });
195
178
  // Referral
196
- const referral = manage.command("referral").description("Referral code management (Pacifica)");
179
+ const referral = manage.command("referral").description("Referral codes (Pacifica claim; Hyperliquid/Lighter explicit set)");
197
180
  referral
198
181
  .command("claim <code>")
199
182
  .description("Claim a referral code")
@@ -204,17 +187,74 @@ export function registerWalletManageCommands(parent, getAdapter, isJson, getPaci
204
187
  return printJson(jsonOk(result));
205
188
  console.log(chalk.green(`\n Referral code "${code}" claimed!\n`));
206
189
  });
190
+ // Explicit referral apply (HL / Lighter). Never done on adapter creation: this
191
+ // signs and sends one referral action with the active signer. Supports --dry-run.
192
+ supportsDryRun(referral
193
+ .command("set [code]")
194
+ .description("Hyperliquid / Lighter: sign + send a one-time referral action (default code: settings referralCodes.<venue>; supports --dry-run)")
195
+ .action(async (codeArg, _opts, cmd) => {
196
+ await withJsonErrors(isJson(), async () => {
197
+ const venue = resolveVenueId(String(cmd.optsWithGlobals().exchange));
198
+ if (venue !== "hyperliquid" && venue !== "lighter") {
199
+ throw new PerpError("CAPABILITY_UNSUPPORTED", `referral set supports hyperliquid and lighter (got ${venue})`, {
200
+ remediation: "Run with -e hyperliquid or -e lighter (Pacifica: perp -e pacifica wallet manage referral claim <code>)",
201
+ });
202
+ }
203
+ const code = codeArg ?? loadSettings().referralCodes[venue];
204
+ if (!code) {
205
+ throw new PerpError("INVALID_PARAMS", `No ${venue} referral code given and settings referralCodes.${venue} is empty`, {
206
+ remediation: `Pass a code: perp -e ${venue} wallet manage referral set <code>`,
207
+ });
208
+ }
209
+ const network = networkOf(cmd);
210
+ const plan = { venue, network, code, action: venue === "hyperliquid" ? "setReferrer" : "referral/use" };
211
+ if (isDryRun(cmd)) {
212
+ if (isJson())
213
+ return printJson(jsonOk({ dryRun: true, sent: false, ...plan }));
214
+ console.log(chalk.yellow(`\n [dry-run] Would sign + send ${plan.action} with code "${code}" on ${venue} (${network}). Nothing sent.\n`));
215
+ return;
216
+ }
217
+ // The referral is the account owner's action — never signed with an imported agent / API key.
218
+ const { cliCredentialOptsFrom, requireMasterWallet } = await import("../config.js");
219
+ await requireMasterWallet(venue, cliCredentialOptsFrom(cmd.optsWithGlobals()), `${venue} referral set`);
220
+ const adapter = await getAdapter();
221
+ let result;
222
+ if (venue === "hyperliquid") {
223
+ if (!(adapter instanceof HyperliquidAdapter))
224
+ throw new Error("hyperliquid venue did not produce a HyperliquidAdapter");
225
+ result = await adapter.setReferrer(code);
226
+ }
227
+ else {
228
+ if (!(adapter instanceof LighterAdapter))
229
+ throw new Error("lighter venue did not produce a LighterAdapter");
230
+ if (adapter.isReadOnly) {
231
+ throw new PerpError("NO_SIGNER_AVAILABLE", "Lighter referral needs a signer (agent API key)", {
232
+ remediation: "Register one first: perp wallet agent approve lighter",
233
+ });
234
+ }
235
+ result = await adapter.useReferralCode(code);
236
+ }
237
+ const s = loadSettings();
238
+ s.referralApplied[venue] = true;
239
+ saveSettings(s);
240
+ if (isJson())
241
+ return printJson(jsonOk({ sent: true, ...plan, result }));
242
+ console.log(chalk.green(`\n ${venue} referral code "${code}" applied.\n`));
243
+ });
244
+ }));
207
245
  // API Keys
208
246
  const apikey = manage.command("apikey").description("API key management");
209
247
  apikey
210
- .command("create <name> <maxFeeRate>")
211
- .description("Create an API key")
212
- .action(async (name, maxFeeRate) => {
248
+ .command("create")
249
+ .description("Create an API Config Key (rate-limit key; up to 5 per account)")
250
+ .action(async () => {
213
251
  const a = await pac();
214
- const result = await a.sdk.createApiKey(name, maxFeeRate, a.publicKey, a.signer);
252
+ // Docs (api/rate-limits/api-config-keys): no name / fee-rate field — the signed payload is empty.
253
+ const result = await a.sdk.createApiKey(a.publicKey, a.signer);
215
254
  if (isJson())
216
255
  return printJson(jsonOk(result));
217
- console.log(chalk.green(`\n API key "${name}" created.\n`));
256
+ console.log(chalk.green(`\n API Config Key created.\n`));
257
+ console.log(JSON.stringify(result, null, 2));
218
258
  });
219
259
  apikey
220
260
  .command("list")
@@ -258,7 +298,7 @@ export function registerWalletManageCommands(parent, getAdapter, isJson, getPaci
258
298
  // Merge parent + subcommand opts so --passphrase works on either side.
259
299
  const mergedOpts = command.optsWithGlobals();
260
300
  const passphraseFlag = mergedOpts.passphrase ?? opts.passphrase;
261
- const network = mergedOpts.network ?? "mainnet";
301
+ const network = networkOf(command);
262
302
  const isTestnet = network === "testnet";
263
303
  // ── Show branch (no positional arg) ─────────────────────────────────
264
304
  if (!mode) {
@@ -285,7 +325,8 @@ export function registerWalletManageCommands(parent, getAdapter, isJson, getPaci
285
325
  });
286
326
  }
287
327
  adapter.setAddress(evmAccount.address);
288
- const current = await adapter._getAbstractionMode();
328
+ // The command that reports the mode never answers from the disk cache.
329
+ const current = await adapter._getAbstractionMode({ fresh: true });
289
330
  if (useJson) {
290
331
  return printJson(jsonOk({ mode: current, master: masterName, address: evmAccount.address, network: isTestnet ? "testnet" : "mainnet" }));
291
332
  }
@@ -298,9 +339,7 @@ export function registerWalletManageCommands(parent, getAdapter, isJson, getPaci
298
339
  }
299
340
  catch (err) {
300
341
  if (useJson) {
301
- const e = err instanceof PerpError
302
- ? err.structured
303
- : { code: "UNKNOWN", message: err instanceof Error ? err.message : String(err) };
342
+ const e = classifyError(err);
304
343
  console.error(JSON.stringify(jsonError(e.code, e.message)));
305
344
  process.exit(1);
306
345
  }
@@ -345,7 +384,7 @@ export function registerWalletManageCommands(parent, getAdapter, isJson, getPaci
345
384
  const evmAccount = wallet.accounts.find((a) => a.chainId.startsWith("eip155:"));
346
385
  if (evmAccount) {
347
386
  readAdapter.setAddress(evmAccount.address);
348
- prevMode = await readAdapter._getAbstractionMode();
387
+ prevMode = await readAdapter._getAbstractionMode({ fresh: true });
349
388
  }
350
389
  }
351
390
  catch {
@@ -354,12 +393,20 @@ export function registerWalletManageCommands(parent, getAdapter, isJson, getPaci
354
393
  // informational metadata — its absence does NOT mask the write.
355
394
  prevMode = null;
356
395
  }
357
- const result = await runHlSetAbstractionFlow({
358
- masterName,
359
- passphrase: passphrase ?? "",
360
- mode: newMode,
361
- isTestnet,
362
- });
396
+ // The cached mode (HL_ABSTRACTION_TTL_MS) is dropped whatever the outcome: a sent action
397
+ // whose answer was lost may have changed the mode.
398
+ let result;
399
+ try {
400
+ result = await runHlSetAbstractionFlow({
401
+ masterName,
402
+ passphrase: passphrase ?? "",
403
+ mode: newMode,
404
+ isTestnet,
405
+ });
406
+ }
407
+ finally {
408
+ invalidateCache(hlAbstractionCachePrefix(network));
409
+ }
363
410
  if (useJson) {
364
411
  return printJson(jsonOk({
365
412
  mode: result.mode,
@@ -380,9 +427,7 @@ export function registerWalletManageCommands(parent, getAdapter, isJson, getPaci
380
427
  }
381
428
  catch (err) {
382
429
  if (useJson) {
383
- const e = err instanceof PerpError
384
- ? err.structured
385
- : { code: "UNKNOWN", message: err instanceof Error ? err.message : String(err) };
430
+ const e = classifyError(err);
386
431
  console.error(JSON.stringify(jsonError(e.code, e.message)));
387
432
  process.exit(1);
388
433
  }
@@ -1,3 +1,6 @@
1
1
  import { Command } from "commander";
2
2
  import type { ExchangeAdapter } from "../exchanges/index.js";
3
- export declare function registerMarketCommands(program: Command, getAdapter: () => Promise<ExchangeAdapter>, isJson: () => boolean, getAdapterForExchange?: (exchange: string) => Promise<ExchangeAdapter>): void;
3
+ import "../exchanges/venues/index.js";
4
+ export declare function registerMarketCommands(program: Command, getAdapter: () => Promise<ExchangeAdapter>, isJson: () => boolean, getAdapterForExchange?: (exchange: string) => Promise<ExchangeAdapter>,
5
+ /** Credentialed adapter of the selected venue — for market data the venue serves only to an authenticated account. */
6
+ getAccountAdapter?: () => Promise<ExchangeAdapter>): void;
@@ -1,8 +1,17 @@
1
1
  import { makeTable, formatUsd, formatPercent, printJson, jsonOk, jsonError, withJsonErrors } from "../utils.js";
2
2
  import chalk from "chalk";
3
- import { hasPacificaSdk, isDexCapable } from "../exchanges/capabilities.js";
4
- const EXCHANGES = ["pacifica", "hyperliquid", "lighter", "aster"];
5
- export function registerMarketCommands(program, getAdapter, isJson, getAdapterForExchange) {
3
+ import { PerpError } from "../errors.js";
4
+ import { midDecimal } from "../exchanges/precision.js";
5
+ import { hasPacificaSdk, isDexCapable, klinesNeedAuth } from "../exchanges/capabilities.js";
6
+ import { listVenuesWithRole } from "../exchanges/venue.js";
7
+ import "../exchanges/venues/index.js";
8
+ /** Venues with an adapter (market-data-only venues have none — see `perp arb scan`). */
9
+ const EXCHANGES = listVenuesWithRole("account").map(v => v.id);
10
+ /** null = venue does not publish the value → "-" (never rendered as $0). */
11
+ const usdOrDash = (v) => (v === null ? chalk.gray("-") : `$${formatUsd(v)}`);
12
+ export function registerMarketCommands(program, getAdapter, isJson, getAdapterForExchange,
13
+ /** Credentialed adapter of the selected venue — for market data the venue serves only to an authenticated account. */
14
+ getAccountAdapter) {
6
15
  const market = program.command("market").description("Market data commands");
7
16
  market
8
17
  .command("list")
@@ -14,10 +23,10 @@ export function registerMarketCommands(program, getAdapter, isJson, getAdapterFo
14
23
  const marketRow = (m) => [
15
24
  chalk.white.bold(m.symbol),
16
25
  `$${formatUsd(m.markPrice)}`,
17
- `$${formatUsd(m.indexPrice)}`,
26
+ usdOrDash(m.indexPrice),
18
27
  m.fundingRate != null ? formatPercent(m.fundingRate) : chalk.gray("N/A"),
19
- `$${formatUsd(m.volume24h)}`,
20
- `$${formatUsd(m.openInterest)}`,
28
+ usdOrDash(m.volume24h),
29
+ usdOrDash(m.openInterest),
21
30
  String(m.maxLeverage) + "x",
22
31
  ];
23
32
  // Multi-exchange mode
@@ -97,7 +106,7 @@ export function registerMarketCommands(program, getAdapter, isJson, getAdapterFo
97
106
  const priceRow = (m) => [
98
107
  chalk.white.bold(m.symbol),
99
108
  `$${formatUsd(m.markPrice)}`,
100
- `$${formatUsd(m.indexPrice)}`,
109
+ usdOrDash(m.indexPrice),
101
110
  m.fundingRate != null ? formatPercent(m.fundingRate) : chalk.gray("N/A"),
102
111
  ];
103
112
  if (!explicitExchange && getAdapterForExchange) {
@@ -169,10 +178,10 @@ export function registerMarketCommands(program, getAdapter, isJson, getAdapterFo
169
178
  return printJson(jsonOk(m));
170
179
  console.log(chalk.cyan.bold(`\n ${m.symbol} on ${adapter.name}\n`));
171
180
  console.log(` Mark Price: $${formatUsd(m.markPrice)}`);
172
- console.log(` Index Price: $${formatUsd(m.indexPrice)}`);
181
+ console.log(` Index Price: ${usdOrDash(m.indexPrice)}`);
173
182
  console.log(` Funding Rate: ${m.fundingRate != null ? formatPercent(m.fundingRate) : chalk.gray("N/A")}`);
174
- console.log(` 24h Volume: $${formatUsd(m.volume24h)}`);
175
- console.log(` Open Interest: $${formatUsd(m.openInterest)}`);
183
+ console.log(` 24h Volume: ${usdOrDash(m.volume24h)}`);
184
+ console.log(` Open Interest: ${usdOrDash(m.openInterest)}`);
176
185
  console.log(` Max Leverage: ${m.maxLeverage}x`);
177
186
  console.log();
178
187
  });
@@ -279,17 +288,19 @@ export function registerMarketCommands(program, getAdapter, isJson, getAdapterFo
279
288
  .description("Get mid price for a single symbol (fast)")
280
289
  .action(async (symbol) => {
281
290
  const sym = symbol.toUpperCase();
282
- try {
291
+ // withJsonErrors: every failure (incl. an empty book) is an error envelope AND exit code 1.
292
+ await withJsonErrors(isJson(), async () => {
283
293
  const adapter = await getAdapter();
284
- // Try orderbook mid (best bid + best ask) / 2
294
+ // Orderbook mid (best bid + best ask) / 2
285
295
  const book = await adapter.getOrderbook(sym);
286
296
  const bestBid = book.bids[0]?.[0];
287
297
  const bestAsk = book.asks[0]?.[0];
288
298
  if (!bestBid && !bestAsk) {
289
- if (isJson())
290
- return printJson(jsonError("SYMBOL_NOT_FOUND", `No orderbook data for ${sym}`));
291
- console.log(chalk.red(`\n No orderbook data for ${sym}.\n`));
292
- return;
299
+ // The market exists (getOrderbook resolved it) but nobody quotes it: no price, not "unknown symbol".
300
+ throw new PerpError("PRICE_STALE", `${sym} orderbook on ${adapter.name} is empty (no bids and no asks) — no mid price`, {
301
+ exchange: adapter.name,
302
+ remediation: `The market has no liquidity right now; check: perp -e ${adapter.name} market book ${sym}, or pick a market with a two-sided book`,
303
+ });
293
304
  }
294
305
  const bid = Number(bestBid ?? 0);
295
306
  const ask = Number(bestAsk ?? 0);
@@ -298,7 +309,8 @@ export function registerMarketCommands(program, getAdapter, isJson, getAdapterFo
298
309
  if (isJson())
299
310
  return printJson(jsonOk({
300
311
  symbol: sym,
301
- mid: mid.toString(),
312
+ // Exact decimal mid of the two quoted strings (float (bid + ask) / 2 prints artifacts).
313
+ mid: bestBid && bestAsk ? midDecimal(bestBid, bestAsk) : mid.toString(),
302
314
  bid: bestBid ?? null,
303
315
  ask: bestAsk ?? null,
304
316
  spread: spread.toFixed(6),
@@ -311,19 +323,7 @@ export function registerMarketCommands(program, getAdapter, isJson, getAdapterFo
311
323
  console.log(` Ask: $${formatUsd(bestAsk)}`);
312
324
  console.log(` Spread: ${spread.toFixed(4)}%`);
313
325
  console.log();
314
- }
315
- catch (err) {
316
- const msg = err instanceof Error ? err.message : String(err);
317
- if (isJson()) {
318
- const { classifyError } = await import("../errors.js");
319
- const classified = classifyError(err);
320
- return printJson(jsonError(classified.code, classified.message, {
321
- status: classified.status,
322
- retryable: classified.retryable,
323
- }));
324
- }
325
- console.error(chalk.red(`Error: ${msg}`));
326
- }
326
+ });
327
327
  });
328
328
  market
329
329
  .command("kline <symbol> <interval>")
@@ -331,7 +331,9 @@ export function registerMarketCommands(program, getAdapter, isJson, getAdapterFo
331
331
  .option("--start <ts>", "Start time (unix ms)")
332
332
  .option("--end <ts>", "End time (unix ms)")
333
333
  .action(async (symbol, interval, opts) => {
334
- const adapter = await getAdapter();
334
+ const publicAdapter = await getAdapter();
335
+ // A venue whose candles endpoint is authenticated (Ondo) is read with the registered key.
336
+ const adapter = getAccountAdapter !== undefined && klinesNeedAuth(publicAdapter) ? await getAccountAdapter() : publicAdapter;
335
337
  const now = Date.now();
336
338
  const startTime = opts.start ? parseInt(opts.start) : now - 24 * 60 * 60 * 1000;
337
339
  const endTime = opts.end ? parseInt(opts.end) : now;
@@ -362,8 +364,9 @@ export function registerMarketCommands(program, getAdapter, isJson, getAdapterFo
362
364
  const adapter = await getAdapter();
363
365
  if (!(isDexCapable(adapter))) {
364
366
  if (isJson())
365
- return printJson(jsonError("INVALID_EXCHANGE", "HIP-3 dexes are only available on Hyperliquid. Use -e hyperliquid."));
367
+ return printJson(jsonError("INVALID_PARAMS", "HIP-3 dexes are only available on Hyperliquid.", { remediation: "Re-run with -e hyperliquid." }));
366
368
  console.error(chalk.red("\n HIP-3 dexes are only available on Hyperliquid. Use -e hyperliquid.\n"));
369
+ process.exitCode = 1;
367
370
  return;
368
371
  }
369
372
  const dexes = await adapter.listDeployedDexes();
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `perp outcome` — Hyperliquid Outcome markets (HIP-4).
3
+ *
4
+ * Currently HL-only. Asset class is fully collateralized binary/range
5
+ * contracts, quoted per outcome (outcomeMeta quoteToken, e.g. USDC). No leverage, no liquidation. Settlement is
6
+ * dated per-outcome.
7
+ */
8
+ import { Command } from "commander";
9
+ import type { ExchangeAdapter } from "../exchanges/index.js";
10
+ export declare function registerOutcomeCommands(program: Command, getAdapterForExchange: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean): void;