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
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Withdraw gate for venues whose withdrawals are authorized by an API key alone — no owner
3
+ * signature (settings.withdraw.<venue>; today: qfex). Design §14.12 #8.
4
+ *
5
+ * Before such a withdrawal is sent it must pass, locally:
6
+ * 1. a gate is configured at all (absent → refused);
7
+ * 2. the destination is on the allowlist;
8
+ * 3. amount ≤ maxPerTx;
9
+ * 4. today's total (UTC day, from this machine's ledger) + amount ≤ maxPerDay.
10
+ * The adapter enforces it inside withdraw(), so no caller can skip it.
11
+ *
12
+ * What it is NOT: a defence against key theft. Someone holding the key file does not use this
13
+ * CLI. That defence is a trading key WITHOUT the withdraw permission (a separate key for
14
+ * funding). The gate limits mistakes and misdirected automation: its allowlist can only be
15
+ * changed with an interactive terminal confirmation (confirmWithdrawGateChange), never under
16
+ * --json, a pipe, the MCP server or a job.
17
+ *
18
+ * Ledger: ~/.perp/withdraw-ledger.json — amounts and addresses only (no secrets, Rule #3).
19
+ * An attempt is recorded BEFORE it is sent and removed only on a definite refusal, so an
20
+ * unknown outcome counts against the day.
21
+ */
22
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
23
+ import { resolve } from "node:path";
24
+ import { randomUUID } from "node:crypto";
25
+ import { writeKeystoreAtomic } from "./agent-wallet/venue-keystore.js";
26
+ import { PerpError } from "./errors.js";
27
+ function ledgerPath() {
28
+ const home = process.env["HOME"];
29
+ if (!home)
30
+ throw new PerpError("INVALID_PARAMS", "HOME is not set; cannot locate ~/.perp/withdraw-ledger.json");
31
+ return resolve(home, ".perp", "withdraw-ledger.json");
32
+ }
33
+ function readLedger() {
34
+ const path = ledgerPath();
35
+ if (!existsSync(path))
36
+ return []; // nothing withdrawn from this machine yet
37
+ let parsed;
38
+ try {
39
+ parsed = JSON.parse(readFileSync(path, "utf-8"));
40
+ }
41
+ catch (e) {
42
+ throw new PerpError("INVALID_PARAMS", `Invalid ${path}: not valid JSON (${e instanceof Error ? e.message : String(e)})`, {
43
+ remediation: `The daily withdraw total cannot be computed, so withdrawals are refused. Inspect ${path}; move it aside only after checking today's withdrawals on the venue`,
44
+ });
45
+ }
46
+ if (!Array.isArray(parsed) || parsed.some(e => e === null || typeof e !== "object" || typeof e.id !== "string" || typeof e.venue !== "string"
47
+ || typeof e.network !== "string" || typeof e.amount !== "string" || !Number.isFinite(Number(e.amount)) || typeof e.to !== "string" || !Number.isFinite(Date.parse(e.at)))) {
48
+ throw new PerpError("INVALID_PARAMS", `Invalid ${path}: not a list of withdraw entries`, {
49
+ remediation: `The daily withdraw total cannot be computed, so withdrawals are refused. Inspect ${path}`,
50
+ });
51
+ }
52
+ return parsed;
53
+ }
54
+ function writeLedger(entries) {
55
+ const path = ledgerPath();
56
+ const dir = resolve(path, "..");
57
+ if (!existsSync(dir))
58
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
59
+ writeKeystoreAtomic(path, JSON.stringify(entries, null, 2));
60
+ }
61
+ function utcDay(ms) {
62
+ return new Date(ms).toISOString().slice(0, 10);
63
+ }
64
+ /** Sum of the amounts attempted for venue + network on the UTC day of `nowMs`. */
65
+ export function withdrawnOnDay(venue, network, nowMs) {
66
+ const day = utcDay(nowMs);
67
+ return readLedger()
68
+ .filter(e => e.venue === venue && e.network === network && e.at.slice(0, 10) === day)
69
+ .reduce((sum, e) => sum + Number(e.amount), 0);
70
+ }
71
+ function denied(venue, why, remediation, details) {
72
+ return new PerpError("POLICY_DENIED", `${venue} withdraw refused by the local withdraw gate: ${why}`, { exchange: venue, ...details, remediation });
73
+ }
74
+ /**
75
+ * Throws POLICY_DENIED unless the withdrawal passes `gate`. Nothing is sent before this returns.
76
+ * `gate` null = settings.withdraw.<venue> is not configured.
77
+ */
78
+ export function assertWithdrawAllowed(venue, network, amount, to, gate, nowMs) {
79
+ const setHint = `Configure it in an interactive terminal: perp settings set withdraw.${venue} <address[+address…]>,<maxPerTx>,<maxPerDay>`;
80
+ if (gate === null) {
81
+ throw denied(venue, `settings.withdraw.${venue} is not configured — API-key withdrawals are off until you set an allowlist and limits`, setHint, {});
82
+ }
83
+ const n = Number(amount);
84
+ if (!Number.isFinite(n) || n <= 0)
85
+ throw new PerpError("INVALID_PARAMS", `Withdraw amount must be a positive decimal (got "${amount}")`);
86
+ if (!gate.allowlist.includes(to.toLowerCase())) {
87
+ throw denied(venue, `destination ${to} is not on the allowlist`, `Withdraw to an allowlisted address, or change the allowlist yourself. ${setHint}`, { allowlist: gate.allowlist });
88
+ }
89
+ if (n > gate.maxPerTx) {
90
+ throw denied(venue, `amount ${amount} exceeds maxPerTx ${gate.maxPerTx}`, `Withdraw at most ${gate.maxPerTx} USDC per transaction, or raise the limit. ${setHint}`, { maxPerTx: gate.maxPerTx });
91
+ }
92
+ const today = withdrawnOnDay(venue, network, nowMs);
93
+ if (today + n > gate.maxPerDay) {
94
+ throw denied(venue, `amount ${amount} plus ${today} already attempted today (UTC) exceeds maxPerDay ${gate.maxPerDay}`, `Wait for the next UTC day or raise the limit. ${setHint}`, { maxPerDay: gate.maxPerDay, withdrawnToday: today });
95
+ }
96
+ }
97
+ /** Record an attempt (call BEFORE sending). Returns its id for forgetWithdrawAttempt. */
98
+ export function recordWithdrawAttempt(venue, network, amount, to, nowMs) {
99
+ const entries = readLedger();
100
+ const id = randomUUID();
101
+ entries.push({ id, venue, network, amount, to: to.toLowerCase(), at: new Date(nowMs).toISOString() });
102
+ writeLedger(entries);
103
+ return id;
104
+ }
105
+ /** Remove an attempt the venue DEFINITELY refused (nothing left the account). */
106
+ export function forgetWithdrawAttempt(id) {
107
+ writeLedger(readLedger().filter(e => e.id !== id));
108
+ }
109
+ /**
110
+ * Confirmation for creating or changing settings.withdraw.<venue>. Only an interactive
111
+ * terminal can confirm: --json, a non-TTY stdin/stdout (pipes, the MCP server, jobs) are
112
+ * refused, and there is no --yes.
113
+ */
114
+ export async function confirmWithdrawGateChange(venue, gate, json) {
115
+ const what = `withdraw.${venue}: allow withdrawals to ${gate.allowlist.join(", ")} — up to ${gate.maxPerTx} USDC per transaction, ${gate.maxPerDay} USDC per UTC day`;
116
+ if (json || process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
117
+ throw new PerpError("POLICY_DENIED", `Changing the withdraw gate needs an interactive terminal confirmation (${what})`, {
118
+ remediation: `Run it yourself in a terminal, without --json: perp settings set withdraw.${venue} <address[+address…]>,<maxPerTx>,<maxPerDay>`,
119
+ });
120
+ }
121
+ const { createInterface } = await import("node:readline/promises");
122
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
123
+ try {
124
+ const a = (await rl.question(` ${what}.\n Anyone who can run perp on this machine can then withdraw to these addresses. Type the venue name (${venue}) to confirm: `)).trim().toLowerCase();
125
+ if (a !== venue)
126
+ throw new PerpError("INVALID_PARAMS", `withdraw.${venue} not changed — cancelled, nothing was saved`);
127
+ }
128
+ finally {
129
+ rl.close();
130
+ }
131
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "perp-cli",
3
- "version": "0.12.17",
4
- "description": "Multi-DEX Perpetual Futures CLI - Pacifica, Hyperliquid, Lighter, Aster",
3
+ "version": "0.14.1",
4
+ "description": "Multi-DEX Perpetual Futures CLI - Pacifica, Hyperliquid, Lighter, Aster, Lighter RH, Ondo Perps, Arcus",
5
5
  "bin": {
6
6
  "perp": "dist/index.js",
7
7
  "perp-mcp": "dist/mcp-server.js",
@@ -9,7 +9,8 @@
9
9
  },
10
10
  "files": [
11
11
  "dist",
12
- "skills"
12
+ "skills",
13
+ "vendor/lighter-signer"
13
14
  ],
14
15
  "type": "module",
15
16
  "scripts": {
@@ -18,6 +19,9 @@
18
19
  "test": "vitest run --exclude '**/integration/**'",
19
20
  "test:integration": "vitest run -c vitest.integration.config.ts",
20
21
  "test:all": "vitest run",
22
+ "e2e": "tsx scripts/e2e/run.ts",
23
+ "e2e:typecheck": "tsc -p scripts/e2e/tsconfig.json",
24
+ "typecheck:test": "tsc -p tsconfig.test.json",
21
25
  "sync-skill-version": "node scripts/sync-skill-version.mjs",
22
26
  "prepublishOnly": "node scripts/sync-skill-version.mjs && pnpm run build"
23
27
  },
@@ -25,6 +29,12 @@
25
29
  "pacifica",
26
30
  "hyperliquid",
27
31
  "lighter",
32
+ "aster",
33
+ "lighter-rh",
34
+ "robinhood-chain",
35
+ "ondo",
36
+ "ondo-perps",
37
+ "arcus",
28
38
  "solana",
29
39
  "perps",
30
40
  "dex",
@@ -56,11 +66,9 @@
56
66
  "commander": "^13.1.0",
57
67
  "dotenv": "^16.5.0",
58
68
  "ethers": "^6.13.2",
59
- "hyperliquid": "^1.7.7",
60
69
  "ink": "^6.8.0",
61
70
  "ink-spinner": "^5.0.0",
62
71
  "ink-table": "^3.1.0",
63
- "lighter-ts-sdk": "1.0.10",
64
72
  "react": "^19.2.4",
65
73
  "tweetnacl": "^1.0.3",
66
74
  "ws": "^8.19.0",
@@ -70,10 +78,11 @@
70
78
  "devDependencies": {
71
79
  "@types/node": "^25.3.3",
72
80
  "@types/ws": "^8.18.1",
81
+ "@vitest/coverage-v8": "^4.1.6",
73
82
  "ts-morph": "^27.0.2",
74
83
  "tsup": "^8.4.0",
75
84
  "tsx": "^4.21.0",
76
85
  "typescript": "^5.7.0",
77
- "vitest": "^4.0.18"
86
+ "vitest": "^4.1.6"
78
87
  }
79
88
  }
@@ -1,17 +1,17 @@
1
1
  ---
2
2
  name: perp-cli
3
- description: "Multi-DEX perpetual futures trading via the perp-cli command-line tool. Use when user asks to: trade perps, check funding rates, scan/execute arbitrage (perp-perp or spot-perp), run delta-neutral strategies, bridge USDC across chains, manage positions/orders/leverage, deposit/withdraw, hedge spot+perp, trade HIP-3 DEXes, or mentions perp-cli, hypurrquant, Pacifica, Hyperliquid, Lighter, Aster, HyperEVM, funding arb, or U-tokens (UBTC/UETH/USOL). Covers Pacifica (Solana), Hyperliquid (HyperEVM), Lighter (Ethereum), and Aster (BNB Chain)."
3
+ description: "Multi-DEX perpetual futures trading via the perp-cli command-line tool. Use when user asks to: trade perps, check funding rates, scan/execute arbitrage (perp-perp or spot-perp), run delta-neutral strategies, bridge USDC across chains, manage positions/orders/leverage, deposit/withdraw, hedge spot+perp, trade HIP-3 DEXes, or mentions perp-cli, hypurrquant, Pacifica, Hyperliquid, Lighter, Aster, Lighter RH, Ondo Perps, Arcus, HyperEVM, funding arb, or U-tokens (UBTC/UETH/USOL). Covers Pacifica (Solana), Hyperliquid (HyperEVM), Lighter (Ethereum), Aster (BNB Chain), Lighter RH and Arcus (Robinhood chain) and Ondo Perps (off-chain)."
4
4
  license: MIT
5
- allowed-tools: "Bash(perp:*), Bash(npx perp-cli:*), Bash(npx -y perp-cli:*)"
6
- compatibility: "Requires Node.js 20 or newer and the perp-cli npm package (global install or npx). Network access required for exchange APIs and bridge providers. Mutating commands need wallet keys configured via 'perp wallet set EXCHANGE KEY' (Solana base58 / EVM hex / Aster API key). Skill is portable across Claude Code, Claude.ai, Cursor, Codex, and Gemini CLI — no platform-specific features."
5
+ allowed-tools: "Bash(perp:*), Bash(npx -y perp-cli:*), Bash(bash scripts/preflight.sh:*), Bash(bash scripts/arb-monitor.sh:*), Bash(bash scripts/funding-analysis.sh:*), Bash(bash scripts/spot-perp-scan.sh:*), Bash(bash scripts/validate-arb.sh:*)"
6
+ compatibility: "Requires Node.js 20 or newer and the perp-cli npm package (global install or npx). Network access required for exchange APIs and bridge providers. Mutating commands need a wallet key imported into the OWS vault via 'perp wallet set EXCHANGE' (key on stdin or hidden prompt — never argv). Skill is portable across Claude Code, Claude.ai, Cursor, Codex, and Gemini CLI — no platform-specific features."
7
7
  metadata:
8
8
  author: hypurrquant
9
- version: "0.12.17"
9
+ version: "0.14.1"
10
10
  ---
11
11
 
12
12
  # perp-cli
13
13
 
14
- Multi-DEX perpetual futures CLI + MCP server for Pacifica (Solana), Hyperliquid (HyperEVM), Lighter (Ethereum), and Aster (BNB Chain).
14
+ Multi-DEX perpetual futures CLI + MCP server for Pacifica (Solana), Hyperliquid (HyperEVM), Lighter (Ethereum), Aster (BNB Chain), Lighter RH (`lighter-rh`, Robinhood chain, USDG), Ondo Perps (`ondo`), QFEX (`qfex`, custodial TradFi perps — import-only API key; resting reduce-only limits and automation are refused for now, see "QFEX" below), Arcus (`arcus`, Robinhood chain, USDG). v0.13.0+ also supports Hyperliquid Outcome markets (HIP-4) — fully-collateralized binary/range contracts; each outcome's quote token comes from `outcomeMeta.quoteToken` (USDC on every listed outcome as of 2026-10-12), minimum order value 10 quote tokens.
15
15
 
16
16
  ## Instructions
17
17
 
@@ -25,7 +25,7 @@ Activate whenever the user wants to do anything against perp-cli, including: sca
25
25
 
26
26
  This skill uses progressive disclosure. Read these on demand:
27
27
 
28
- - `references/commands.md` — full command tree for every group (market, account, trade, arb, funds, wallet, risk, history, strategy, background, alerts, settings, backtest)
28
+ - `references/commands.md` — full command tree for every group (market, account, trade, outcome, arb, funds, wallet, risk, history, strategy, background, alerts, settings, backtest)
29
29
  - `references/agent-operations.md` — non-interactive setup flows, idempotency rules, error codes, common mistakes
30
30
  - `references/strategies.md` — funding-arb decision framework + risk-management playbook (read before designing arb strategy)
31
31
 
@@ -33,40 +33,70 @@ Also bundled under `scripts/`: `preflight.sh`, `arb-monitor.sh`, `funding-analys
33
33
 
34
34
  ### Critical rules
35
35
 
36
- 1. **Always pass `--json`.** Without it, output is human-formatted and hard to parse.
37
- 2. **Always run `--dry-run` first** for any mutating trade (open / close / bridge / withdraw). Show the result, get user confirmation, then execute without `--dry-run`.
38
- 3. **Always use `--fields`** when you only need specific data — saves tokens.
39
- 4. **NEVER run `perp setup`, `perp init`, or `perp wallet setup`.** All three are interactive and will hang the agent. Use `perp wallet set <exchange> <key>` instead.
40
- 5. **NEVER trade without explicit user confirmation.** A dry-run is not consent.
41
- 6. **NEVER read `~/.perp/.env` or other key files.** The CLI reads them — you should not.
42
- 7. **NEVER manually add `-PERP` suffix** to symbols. Use bare names (`BTC`, `SOL`, `ICP`); the CLI resolves exchange-specific naming.
36
+ 1. **Always pass `--json`.** Without it, output is human-formatted and hard to parse. `ok:false` also exits 1.
37
+ 2. **Always pass `-e <venue>`** on venue-scoped commands. The default is `settings.defaultExchange`, else `pacifica` — never rely on it.
38
+ 3. **Always run `--dry-run` first** for any mutating command that supports it (`--help` says "supports --dry-run"). Show the result, get user confirmation, then execute without `--dry-run`. Commands without dry-run support refuse `--dry-run` with `INVALID_PARAMS` (see "Safety rules").
39
+ 4. **Always use `--fields`** when you only need specific data — saves tokens.
40
+ 5. **Avoid bare `perp setup` / `perp init` / `perp wallet setup`** — they prompt and will hang an agent. Use `printf '%s' "$KEY" | OWS_PASSPHRASE=<pp> perp --json wallet set <exchange>` for key registration (key on stdin; a key on argv or `-k` is rejected), or `perp --json setup --non-interactive --wallet <ows-wallet> --venues <list> [--default-venue <v>] [--slippage-bps <n>] [--slippage <venue>=<bps>]` (new wallet: `--wallet-name <name>` with the passphrase in `OWS_PASSPHRASE`; agent registration only with `--approve-agents --yes`, after user confirmation) (and `OWS_PASSPHRASE=<pp> perp wallet setup --name <n> --max-tx-usd <usd> --max-daily-usd <usd>`) when the wizard is required. The passphrase goes in `OWS_PASSPHRASE` or stdin — the root `--passphrase` flag is deprecated (visible in `ps`).
41
+ 6. **NEVER trade without explicit user confirmation.** A dry-run is not consent.
42
+ 7. **NEVER read `~/.perp/.env` or other key files.** The CLI reads them — you should not.
43
+ 8. **NEVER manually add `-PERP` suffix** to symbols. Use bare names (`BTC`, `SOL`, `ICP`); the CLI resolves exchange-specific naming.
44
+ 9. **NEVER resubmit after `ORDER_PENDING`** (or any unknown outcome). See "Safety rules".
43
45
 
44
46
  ### Step 1: Verify install + version
45
47
 
46
48
  ```bash
47
- perp --version 2>/dev/null # must be >= 0.12.17
48
- npm install -g perp-cli@latest 2>/dev/null || npx -y perp-cli@latest --json --version
49
+ perp --version 2>/dev/null || npx -y perp-cli@latest --version # must be >= 0.14.1
49
50
  ```
50
51
 
51
- If `perp` is not on PATH, prefix every command with `npx -y perp-cli@latest`.
52
+ If it is missing or older, ask the user before installing/upgrading (`npm install -g perp-cli@latest`) — never install on your own. If `perp` is not on PATH, prefix every command with `npx -y perp-cli@latest`.
52
53
 
53
54
  ### Step 2: Verify wallet + balance
54
55
 
55
56
  ```bash
56
57
  perp --json wallet show # confirms configured exchanges
57
- perp --json -e <ex> account balance # per-exchange USDC available
58
+ perp --json portfolio -e <venue-id> # per-exchange USDC available; full id (hyperliquid, not hl) — portfolio rejects aliases
58
59
  ```
59
60
 
60
61
  If any required exchange is missing, register it (user supplies the key):
61
62
 
62
63
  ```bash
63
- perp --json wallet set pac <SOLANA_BASE58_KEY> # Pacifica
64
- perp --json wallet set hl <EVM_HEX_KEY> # Hyperliquid
65
- perp --json wallet set lt <EVM_HEX_KEY> # Lighter (auto-generates API key, default index 4)
66
- perp --json wallet set aster <ASTER_API_KEY> # Aster (BNB Chain)
64
+ printf '%s' "$EVM_HEX_KEY" | OWS_PASSPHRASE=<pp> perp --json wallet set hl # Hyperliquid / Lighter / Aster (EVM)
65
+ printf '%s' "$SOLANA_BASE58_KEY" | OWS_PASSPHRASE=<pp> perp --json wallet set pac # Pacifica (Solana)
67
66
  ```
68
67
 
69
- The same EVM key works for both Hyperliquid and Lighter.
68
+ The key is read from stdin (never argv — an argv key is rejected) and imported into the OWS vault; the imported wallet becomes `owsActiveWallet`, which serves every venue. The same EVM key works for Hyperliquid, Lighter and Aster. Lighter trading additionally needs `perp wallet agent approve lighter`.
69
+
70
+ Lighter RH / Ondo Perps / Arcus have no `wallet set` path — register an agent / API key (user runs it; Ondo reads key id + secret from stdin):
71
+
72
+ ```bash
73
+ perp --json wallet agent approve lighter-rh
74
+ printf '%s\n%s\n' "$KEY_ID" "$SECRET" | perp --json wallet agent approve ondo
75
+ perp --json wallet agent approve arcus --master <ows-wallet> # address must be on the Arcus access whitelist
76
+ ```
77
+
78
+ **No master wallet on this computer?** The user can create the agent / API key on the exchange website and import it — key on stdin only, verified against the venue before it is stored (`verification.status` is `"unverified"` only for Pacifica, which lists bindings master-signed only):
79
+
80
+ ```bash
81
+ printf '%s' "$AGENT_KEY" | perp --json wallet agent import hyperliquid # master read from HL
82
+ printf '%s' "$AGENT_KEY" | perp --json wallet agent import aster --master-address 0x… # also Aster testnet
83
+ printf '%s' "$AGENT_KEY" | perp --json wallet agent import pacifica --master-address <SOL>
84
+ printf '%s' "$API_KEY" | perp --json wallet agent import lighter --account-index <n> --api-key-index <slot>
85
+ printf '%s' "$ED25519_KEY" | perp --json wallet agent import arcus --master-address 0x…
86
+ ```
87
+
88
+ Imported keys trade; `funds deposit|withdraw|transfer|bridge`, `wallet manage referral set` and `wallet agent approve` need the master → `NO_SIGNER_AVAILABLE` (tell the user to do it on the website or `perp wallet import` the master). `wallet agent revoke` of an imported key needs `--local-only` and does NOT revoke it on the exchange; `rotate` is refused (new key on the website → `import --replace`).
89
+
90
+ **QFEX** (`-e qfex`, testnet = QFEX UAT): keys are created on the QFEX website and imported by the USER (`perp wallet agent import qfex [--account-id <uuid>]` — the resolved account UUID must be confirmed). Rules an agent must follow:
91
+ - Works: market / IOC reduce-only closes (`trade close/close-all/flatten/reduce`), reduce-only stop-market orders (`trade stop`, `trade tpsl` TP/SL legs, `--tp/--sl` as separate stops after the verified fill; cancel with `trade cancel <S> <id>` or `trade cancel-stop`), `trade edit` of a plain LIMIT / ALO (the venue assigns a new order id; the client id is kept).
92
+ - Refused with `CAPABILITY_UNSUPPORTED` (do not work around): resting reduce-only limits (GTC / ALO / FOK, `scale-tp`), stop-limit and opening stops, amend of a reduce-only order, `--client-id`, non-USD-quoted markets, unattended automation (bots / plans / TWAP / DCA / grid / background jobs), QFEX as a funding-arb / funding-bot leg.
93
+ - After a close, reduce-only / stop orders left on a now-flat symbol are listed as a WARNING (the venue does not cancel them) — cancel them, or use `trade flatten`, which cancels them.
94
+ - Limit GTC / ALO returns `status: accepted, finality: pending` after the ACK; `open` only via `perp --json trade status c:<id> --symbol <S> -e qfex`.
95
+ - `ORDER_PENDING` = outcome unknown: NEVER resubmit; check with the `c:<id>` in the remediation.
96
+ - Funding rates on QFEX are unverified: excluded everywhere unless `perp --json arb scan --rates --include-unverified` (display only).
97
+ - Withdrawals are disabled in perp-cli for now: `funds withdraw qfex` → `CAPABILITY_UNSUPPORTED` (also under `--dry-run`). Withdraw in the QFEX web app. Ondo Perps has no API withdrawals either.
98
+
99
+ Venue rules for these: `trade limit` defaults to `--tif GTC` on every venue (arcus maps it to GTT; ondo offers GTC|IOC|ALO; FOK is never emulated → `CAPABILITY_UNSUPPORTED` where not offered); market orders on every venue are bounded by `--slippage-bps <n>` → `settings.slippageBps.<venue>` → `settings.defaultSlippageBps` (shipped default 50). Ondo (US, PA) and Arcus (US, CA, GB) print a one-time restricted-region notice on stderr when used with credentials (warn only, never blocks).
70
100
 
71
101
  ### Step 3: Discover (read-only)
72
102
 
@@ -86,13 +116,30 @@ perp --json arb scan --live --interval 60 # continuous monitoring
86
116
 
87
117
  Common knobs: `--min <pct>`, `--top <n>`, `--hold-days <n>`, `--bridge-cost <usd>`, `--size <usd>`.
88
118
 
119
+ #### HIP-3 dexes (part of Hyperliquid, not a separate venue)
120
+
121
+ HIP-3 dexes are selected on the Hyperliquid venue with the root `--dex <name>` flag; every market / account / trade command works the same way. Same account and wallet as native Hyperliquid perps.
122
+
123
+ ```bash
124
+ perp --json -e hyperliquid market hip3 # list deployed dexes (name, markets)
125
+ perp --json -e hyperliquid --dex xyz market list # markets on dex "xyz"
126
+ perp --json -e hyperliquid --dex xyz market info GOLD # symbols WITHOUT the "xyz:" prefix
127
+ perp --json -e hyperliquid --dex xyz account positions # positions on that dex only
128
+ perp --json -e hyperliquid --dex xyz --dry-run trade market GOLD buy 0.01
129
+ ```
130
+
131
+ - `--dex` with any other `-e` venue → `INVALID_PARAMS` (never ignored).
132
+ - A dex symbol without `--dex` (e.g. `-e hyperliquid market mid xyz:GOLD`) → `SYMBOL_NOT_FOUND` with remediation `--dex xyz`; it is never read from the native dex.
133
+ - `portfolio --dexes xyz,…` adds HIP-3 dex balances; builder fees are never attached on HIP-3.
134
+ - MCP tools cover native Hyperliquid perps only — use the CLI for HIP-3.
135
+
89
136
  ### Step 4: Validate before mutating
90
137
 
91
138
  For arb:
92
139
 
93
140
  ```bash
94
- perp --json -e <longEx> account balance # check available USDC
95
- perp --json -e <shortEx> account balance # check available USDC
141
+ perp --json portfolio -e <longEx> # check available USDC (long leg)
142
+ perp --json portfolio -e <shortEx> # check available USDC (short leg)
96
143
  perp --json --dry-run arb exec <SYM> <longEx> <shortEx> <USD>
97
144
  ```
98
145
 
@@ -104,6 +151,8 @@ perp --json --dry-run -e <ex> trade buy <SYM> <SIZE> --smart
104
151
  ```
105
152
 
106
153
  > `trade check` does NOT read exchange-set leverage. Always pass `--leverage` explicitly.
154
+ >
155
+ > `--smart` (IOC at best bid/ask ± 1 tick) exists only on pacifica / hyperliquid / lighter / aster, and is refused (`CAPABILITY_UNSUPPORTED`) together with `--tp`/`--sl` or `--client-id`/`--auto-id`.
107
156
 
108
157
  Show the dry-run output to the user. Wait for confirmation.
109
158
 
@@ -112,10 +161,12 @@ Show the dry-run output to the user. Wait for confirmation.
112
161
  After explicit user confirmation:
113
162
 
114
163
  ```bash
115
- # Perp-perp arb (auto-matches both legs)
116
- perp --json arb exec <SYM> <longEx> <shortEx> <USD> --smart --reconcile
164
+ # Perp-perp arb: legs are SEQUENTIAL — long first, then the short sized to the verified long fill;
165
+ # reconcile is always on (the long is trimmed reduce-only to the short's verified fill).
166
+ # An unconfirmed leg quarantines the symbol (see "Safety rules").
167
+ perp --json arb exec <SYM> <longEx> <shortEx> <USD> --smart
117
168
 
118
- # Spot+perp arb (spot has 0 funding cost; spot exchanges: hl, lt)
169
+ # Spot+perp arb (spot has 0 funding cost; spot:<venue> = hl, lt, lrh, aster, ondo)
119
170
  perp --json arb exec <SYM> spot:hl hl <USD>
120
171
  perp --json arb exec <SYM> spot:lt pac <USD>
121
172
 
@@ -147,6 +198,8 @@ perp --json arb close <SYM> # perp-perp arb
147
198
  perp --json arb close <SYM> --pair <longEx>:<shortEx> # disambiguate
148
199
  perp --json arb close <SYM> --spot-exch hl --perp-exch hl # spot+perp
149
200
  perp --json arb close <SYM> --smart # IOC limits
201
+ perp --json arb close <SYM> --force # quarantined symbol — only after the user checked positions
202
+ perp --json arb close <SYM> --state-only --yes # drop a stale state entry (arb status staleStateEntries); sends no order
150
203
 
151
204
  # Single trade
152
205
  perp --json -e <ex> trade close <SYM> # close one symbol
@@ -164,27 +217,37 @@ perp --json funds deposit hyperliquid 100
164
217
  perp --json funds deposit pacifica 100
165
218
  perp --json funds deposit lighter ethereum 100 # L1 direct
166
219
  perp --json funds deposit lighter cctp arbitrum 100 # via CCTP
167
- perp --json funds withdraw hyperliquid 100
168
- perp --json funds transfer 100 <ADDRESS> # HL internal (instant)
220
+ perp --json funds withdraw hyperliquid 100 [--chain arbitrum|ethereum|base] # CCTP to your own wallet
221
+ perp --json funds transfer 100 <ADDRESS> # HL internal (instant) — allowlisted destinations only
169
222
 
170
223
  # Cross-chain bridge (auto-cheapest cctp/relay/debridge)
171
224
  perp --json funds bridge quote --from solana --to arbitrum --amount 100
172
225
  perp --json funds bridge send --from solana --to arbitrum --amount 100
173
226
  perp --json funds bridge status <orderId> # poll until success
174
227
 
175
- # Inter-exchange rebalance (orchestrated withdraw → bridge → deposit)
228
+ # Inter-exchange rebalance
176
229
  perp --json funds rebalance check
177
230
  perp --json funds rebalance plan
178
- perp --json funds rebalance execute --auto-bridge
231
+ perp --json --dry-run funds rebalance execute --auto-bridge # status "simulated" (moves[].status "dry_run"), or "balanced" (nothing to move)
232
+ perp --json funds rebalance execute --auto-bridge # withdraw → bridge ONLY; no deposit
179
233
  ```
180
234
 
181
235
  Always run `funds bridge quote` before `funds bridge send` so the user sees fees + ETA.
182
236
 
237
+ - `funds rebalance execute` returns `ok:true` even when a move failed: check EVERY `moves[].status` (`withdrawn`, `bridged`, `same_chain`, `needs_bridge`, `withdraw_failed`, `bridge_failed`). It never deposits — after the bridge arrives, deposit manually (`funds deposit <venue> <amt>`).
238
+ - `funds transfer <amt> <addr>` (HL) and `funds withdraw hyperliquid --to <other address>` require the destination on the `settings withdraw.hyperliquid` allowlist (`POLICY_DENIED` otherwise). Only the user can set it, in an interactive terminal (`--json` / piped → `POLICY_DENIED`). NEVER try to set or change `withdraw.<venue>` yourself.
239
+ - HL withdraw goes over CCTP (native USDC); on Arbitrum a 0.2 USDC forwarding fee is deducted (an amount ≤ the fee → `SIZE_TOO_SMALL`).
240
+ - Bridges: exactly `--amount` leaves the wallet (fees come out of it); a deBridge `nativeFee` is shown separately and is paid in the source chain's native token. Solana-origin deBridge is refused (`NO_BRIDGE_ROUTE`) → `--provider cctp`. `--fast` = fast CCTP finality (higher fee).
241
+ - `funds bridge exchange --from <ex> --to <ex>` bridges the master wallet's OWN USDC between the venues' chains (pacifica → solana, lighter → arbitrum); it does NOT withdraw from / deposit into Pacifica or Lighter. Exception hyperliquid: `--from hyperliquid` spends HyperCore spot USDC, `--to hyperliquid` credits HyperCore perps. HyperCore → Solana is refused (`NO_BRIDGE_ROUTE`).
242
+
243
+ Relay swaps between assets (`--from-asset` / `--to-asset`, chains `robinhood`, `arbitrum`, `bsc`, `base`, destination `solana`): run `perp --json --dry-run funds bridge send ...` first — it prints the locally verified quote (amount in, expected out, protocol-guaranteed minimum, fees, ETA) and the min-out verdict without loading a key. Stable → native (e.g. USDG → SOL / ETH) needs `--min-out`. The recipient is always the master wallet's own address; an `ORDER_PENDING` result carries the Relay request id — never bridge again before checking it.
244
+
183
245
  ### Strategy engine (20 algorithms + scripted plans)
184
246
 
185
247
  ```bash
186
248
  perp strategy list-strategies # 20 registered
187
- perp strategy run <strategy> [symbol] # run any strategy
249
+ perp strategy run <strategy> [symbol] # run any strategy (multi-venue: --exchanges must include the -e venue)
250
+ perp --json --dry-run strategy run <strategy> [symbol] # simulate: actions logged, never sent
188
251
  perp strategy apex [symbol] # APEX orchestrator
189
252
  perp strategy reflect # performance analysis
190
253
  perp --json strategy plan validate <FILE> # validate scripted plan
@@ -196,17 +259,22 @@ perp --json strategy plan execute <FILE> --dry-run # dry-run scripted
196
259
  Background daemon (tmux):
197
260
 
198
261
  ```bash
262
+ perp --json --dry-run arb auto --min-spread 30 --size 100 # simulate first (--background inherits --dry-run)
199
263
  perp --json arb auto --min-spread 30 --size 100 --background
200
264
  perp --json background list
201
265
  perp --json background logs <id> -f
202
- perp --json background stop <id>
266
+ perp --json background stop <id> [--timeout 90] # graceful: SIGINT, job cancels its orders, waits, reports what is left on the venue
203
267
  ```
204
268
 
269
+ `strategy funding-arb` is monitor-only unless `--auto-execute`. Bots: `equity` is `null` when a balance read fails — arb strategies then skip new entries and the bot stops after 10 consecutive unknown ticks; `ordersPlaced` counts orders sent, `fills` only venue-reported fills — a resting order is placed, not filled.
270
+
271
+ Background jobs inherit the parent's `-n`, `--dry-run`, `-e`, `--ows`, `-w`, `--no-agent`, `--dex`, and never receive key material (argv/env): they resolve credentials from the OWS vault / agent keystores / `~/.perp/.env` themselves. `--background` with `-k`, `--passphrase`, `--ows-key` or a shell-exported key / `OWS_PASSPHRASE` → `INVALID_PARAMS` — register an agent (`perp wallet agent approve <venue>`), which signs without the master passphrase.
272
+
205
273
  ### Position sizing & safety
206
274
 
207
275
  - Single position notional < **80%** of that exchange's available balance.
208
276
  - Leverage 1–3x for arb; never above 5x for arb positions.
209
- - `arb exec` auto-matches both legs to the same size; pass `--reconcile` to trim mismatches.
277
+ - `arb exec` sizes the short to the verified long fill and always reconciles; still check `arb status` (`unconfirmedPairs`, `staleStateEntries`) afterwards.
210
278
  - ALWAYS follow `longExch`/`shortExch` from `arb scan` — NEVER reverse direction.
211
279
  - NEVER enter if `netSpread <= 0` or `netSpread < estimated round-trip slippage`.
212
280
  - Capital that's mid-bridge is "at risk" and unhedged — factor it in.
@@ -222,8 +290,8 @@ For deeper risk-management framework, read `references/strategies.md`.
222
290
  perp --json arb scan --min 10 --top 10
223
291
  # 2. Show top 3 to user; ask which to evaluate
224
292
  # 3. Check balances on the two exchanges
225
- perp --json -e <longEx> account balance
226
- perp --json -e <shortEx> account balance
293
+ perp --json portfolio -e <longEx>
294
+ perp --json portfolio -e <shortEx>
227
295
  # 4. Recommend size + dry-run
228
296
  perp --json --dry-run arb exec BTC <longEx> <shortEx> 100
229
297
  # 5. After user confirmation:
@@ -265,16 +333,13 @@ perp --json funds bridge status <orderId> # poll until completed
265
333
  perp --json funds deposit hyperliquid 500
266
334
  ```
267
335
 
268
- Or use the orchestrated path:
269
-
270
- ```bash
271
- perp --json funds rebalance plan
272
- perp --json funds rebalance execute --auto-bridge
273
- ```
336
+ `funds rebalance execute --auto-bridge` only withdraws and bridges — check every `moves[].status`, then deposit by hand (`funds deposit hyperliquid <amt>`) after the bridge arrives.
274
337
 
275
338
  ### Example 5: "Run a delta-neutral funding bot in background"
276
339
 
277
340
  ```bash
341
+ perp --json --dry-run arb auto --min-spread 30 --close-spread 5 --size 100 --max-positions 5
342
+ # → show the simulation, ask confirmation
278
343
  perp --json arb auto --min-spread 30 --close-spread 5 --size 100 --max-positions 5 --background
279
344
  perp --json background list
280
345
  perp --json background logs <id> -f
@@ -284,17 +349,40 @@ perp --json background stop <id>
284
349
 
285
350
  ## Troubleshooting
286
351
 
287
- Every response is `{ "ok": true, "data": ... }` or `{ "ok": false, "error": { "code": "...", "retryable": true|false } }`. Always check `ok` first.
352
+ Every response is `{ "ok": true, "data": ... }` or `{ "ok": false, "error": { "code": "...", "retryable": true|false, "remediation"?: "..." } }`; `ok:false` exits 1. Always check `ok` first and follow `error.remediation`. `retryable: true` → wait and retry ONCE, and only for read-only commands — a write is never blindly retried.
288
353
 
289
354
  | Error code | Meaning | Action |
290
355
  |---|---|---|
291
- | `RATE_LIMITED` | Exchange throttled the request | Wait 5s, retry up to 3 times. For Lighter, space calls 3s+ apart. |
292
- | `EXCHANGE_UNREACHABLE` | API down or network issue | Wait 10s, retry. After 3 failures, skip that exchange and continue. |
293
- | `INSUFFICIENT_BALANCE` | Not enough USDC on that exchange | Reduce size or `funds bridge`/`funds rebalance` first. |
294
- | `SYMBOL_NOT_FOUND` | Symbol not listed on this exchange | Run `market list` to verify. Don't add `-PERP` suffix manually. |
295
- | `SIZE_TOO_SMALL` | Below exchange minimum (HL is $10 notional) | Compute `size × price` before submitting; raise size. |
296
- | `DUPLICATE_ORDER` | Same `--client-id` already submitted | Already executed — check `account positions` instead of retrying. |
297
- | `INVALID_SIGNATURE` (Lighter) | API key drift / not approved | `perp wallet agent approve lighter --master <wallet>` to regenerate. |
356
+ | `ORDER_PENDING` | A write was sent, outcome unknown | NEVER resubmit. See "Safety rules". |
357
+ | `ORDER_PARTIAL` | Part of a multi-leg / close succeeded | Don't retry; check positions per venue, close residuals by hand (may be quarantined). |
358
+ | `ORDER_REJECTED` / `ACTION_REJECTED` | Definitely not placed / not executed | Fix the cause from `error.message`; a new attempt is a new decision (ask the user). |
359
+ | `RISK_VIOLATION` | Local risk limit breached, or symbol quarantined | Report to the user. `--force` only with explicit user consent, and never bypasses quarantine. |
360
+ | `CAPABILITY_UNSUPPORTED` | Venue/feature combination not offered | Do NOT work around it (no emulation via other orders/venues); tell the user. |
361
+ | `INVALID_TICK` | Limit/trigger price off the venue grid (HL: ≤ 5 significant figures) | Refused, never rounded — re-price on the grid and confirm with the user. |
362
+ | `INVALID_PARAMS` | Bad argument (incl. strict sizes: `1,000` / `1e3` / `10abc` refused; `--dry-run` on a command without dry-run) | Fix the argument. |
363
+ | `CLI_ERROR` | Command parse error (unknown command / missing option) | Check `<cmd> --help`. |
364
+ | `NO_SIGNER_AVAILABLE` | No key that may sign this (e.g. imported agent key for a master-only action) | User does it on the website, or `perp wallet import`s the master. |
365
+ | `AGENT_NOT_REGISTERED` / `AGENT_EXPIRED` | Agent key missing, on the other network, or expired | Follow `remediation`: master on this computer → `perp wallet agent approve <venue> [--rotate]` (master-signed: user consent); imported key → new key on the website, `wallet agent import <venue> --replace`. |
366
+ | `POLICY_DENIED` | Local withdraw gate / OWS policy refused | Never change `withdraw.<venue>` — only the user can, in a terminal. |
367
+ | `PASSPHRASE_REQUIRED` | Vault passphrase needed | Ask the user to provide `OWS_PASSPHRASE` (or stdin); never put it on argv. |
368
+ | `LOCK_HELD` | Another perp process holds a state lock | Wait ~5s and retry once; check `background list`. |
369
+ | `NO_BRIDGE_ROUTE` | No supported bridge route (e.g. deBridge from Solana, HyperCore → Solana) | Use the route in `remediation` (e.g. `--provider cctp`). |
370
+ | `RATE_LIMITED` / `EXCHANGE_UNREACHABLE` | Throttled / API down (reads) | Wait 5–10s, retry once; then skip that venue. |
371
+ | `INSUFFICIENT_BALANCE` | Not enough collateral on that venue | Reduce size or move funds first (user-confirmed). |
372
+ | `SYMBOL_NOT_FOUND` | Not listed on this venue | `market list`. Don't add `-PERP`. |
373
+ | `SIZE_TOO_SMALL` | Below venue minimum (HL TWAP: $100 total) | Compute `size × price`; raise size. |
374
+
375
+ `--client-id` (`trade market/buy/sell/limit`): an id already recorded in `~/.perp/client-ids.jsonl` (and not marked failed) returns `ok:true` with `data.duplicate: true` and sends nothing — local tracking only, not a venue check (it is NOT a `DUPLICATE_ORDER` error). Formats: Pacifica a full UUID, Hyperliquid `0x` + 32 hex; QFEX refuses `--client-id` (use `--auto-id`); `--smart` + client id → `CAPABILITY_UNSUPPORTED`.
376
+
377
+ ## Safety rules
378
+
379
+ - **Unknown outcome (`ORDER_PENDING`) on ANY write** — orders, cancels, edits, leverage, withdraw / deposit / bridge / transfer: do NOT resubmit. Check `perp --json -e <ex> trade status c:<id> --symbol <S>` (the id in `remediation`), `account orders` / `account positions` / `portfolio`, and the tx hash / request id in `error.details` (`funds bridge status <id>` for bridges). Report to the user.
380
+ - **Quarantine**: an unknown or unhedged outcome (arb, bots, `trade close`, `trade multi`) quarantines the symbol — global per symbol, scoped by network. It blocks opening orders, `arb exec`, `arb auto` and bots (`RISK_VIOLATION`); reduce-only orders pass. List: `perp --json strategy quarantine`. Clear only after the USER has verified the positions on the venues: `perp --json strategy quarantine-clear <SYM>`. `arb close` on a quarantined symbol needs `--force`.
381
+ - **Risk gate**: `trade market / buy / sell / limit / split / market --split / scale-in / twap / multi / stop --no-reduce-only` check `~/.perp/risk.json` (`perp risk limits ...`); reduce-only is exempt. `--force` bypasses the limits (never quarantine) — only with explicit user consent.
382
+ - **Stops** (`trade stop`, `tpsl`) are reduce-only by default; `--no-reduce-only` is refused on ondo and qfex. `trade tpsl <SYM> long|short` = the OPEN position's side; legs are sized to the live position.
383
+ - **Dry-run is default-deny**: commands that cannot simulate refuse `--dry-run` with `INVALID_PARAMS` (e.g. `trade twap`, `trade multi`, `trade cancel-stop`, `trade cancel-twap`, `strategy start / preset / grid / dca / quick-* / funding-arb`, `wallet agent approve`, `wallet rotate`). Simulate bots with `perp --json --dry-run strategy run <strategy>` / `perp --json --dry-run arb auto`.
384
+ - **A dry-run is not a full venue check**: it validates arguments, the risk gate and quarantine, but some venue rules are enforced only on the live send — e.g. an off-grid limit price (`INVALID_TICK`) or `--no-reduce-only` on ondo / qfex (`CAPABILITY_UNSUPPORTED`). Those refusals happen before anything is sent.
385
+ - **`wallet rotate`** creates a new wallet and transfers assets, with no dry-run — only on the user's explicit request.
298
386
 
299
387
  ### Common mistakes
300
388
 
@@ -311,16 +399,32 @@ For a full troubleshooting + non-interactive flow guide, read `references/agent-
311
399
  ## Referrals (optional, ask once per setup)
312
400
 
313
401
  ```bash
314
- perp --json settings referrals on # opt-in (no extra fees — exchange rebates only)
315
- perp --json settings referrals off
402
+ perp --json --dry-run -e hyperliquid wallet manage referral set # preview (nothing signed)
403
+ perp --json -e hyperliquid wallet manage referral set [code] # signs + sends ONE referral action
404
+ perp --json -e lighter wallet manage referral set [code] # Lighter (needs an agent key)
405
+ ```
406
+
407
+ Referral codes are never applied implicitly (adapter creation signs nothing). When the user first sets up a wallet, ask:
408
+ > "perp-cli development is supported by optional referrals. Apply the referral code? It signs one referral action on the venue; no extra fees — only exchange rebates are used."
409
+
410
+ ## Builder fees (OFF by default — never enable on your own)
411
+
412
+ Builder fees are extra fees on the USER's perp orders, paid to a third-party builder. perp-cli attaches none unless the user explicitly set `settings builder.<venue>` AND the venue approval is confirmed. Do not configure or approve a builder unless the user asks for it.
413
+
414
+ ```bash
415
+ perp --json wallet builder status [venue] # support, config, approval, attaches?
416
+ perp --json --dry-run wallet builder approve <venue> # plan only — show it to the user
417
+ perp --json wallet builder approve <venue> --yes # only after explicit user consent (master-signed)
418
+ perp --json --no-builder -e <ex> trade market BTC buy 0.1 # one run without the fee
316
419
  ```
317
420
 
318
- When the user first sets up a wallet, ask:
319
- > "perp-cli development is supported by optional referrals. Enable? No extra fees — only exchange rebates are used."
421
+ qfex: `builder.qfex <uuid>` is a fee share (no extra fee, no approval; mainnet only). Unsupported: lighter, lighter-rh, arcus (`CAPABILITY_UNSUPPORTED`). An order whose approval is missing or unknown is sent without the fee (`execution.notes`); `execution.builderFee` records what was attached.
320
422
 
321
423
  ## MCP Server
322
424
 
323
- The package also ships a 18-tool MCP server (no API keys required for read-only market data):
425
+ The package also ships a 22-tool MCP server (no API keys required for read-only market data — `get_markets`, `get_orderbook`, `get_funding_rates`, `get_prices`, `get_outcome_markets`, `get_outcome_view`, `get_outcome_book`, plus 15 account/advisor tools):
426
+
427
+ MCP network: `PERP_NETWORK=mainnet|testnet` (validated; unset = mainnet); every result's `meta.network` says which. The only write tools, `trade_execute` / `trade_close`, run the same local risk gate as `perp trade` (no override over MCP), refuse non-reduce-only orders on quarantined symbols, and log every order.
324
428
 
325
429
  ```json
326
430
  {