perp-cli 0.12.15 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (361) hide show
  1. package/README.md +278 -31
  2. package/dist/_stderr-filter.js +0 -1
  3. package/dist/agent-wallet/lighter-keystore.d.ts +11 -0
  4. package/dist/agent-wallet/lighter-keystore.js +92 -10
  5. package/dist/agent-wallet/passphrase.d.ts +1 -1
  6. package/dist/agent-wallet/passphrase.js +16 -3
  7. package/dist/agent-wallet/store.d.ts +37 -4
  8. package/dist/agent-wallet/store.js +132 -0
  9. package/dist/agent-wallet/venue-keystore.d.ts +14 -0
  10. package/dist/agent-wallet/venue-keystore.js +121 -0
  11. package/dist/api/public/hyperliquid.js +4 -4
  12. package/dist/api/public/lighter.js +5 -5
  13. package/dist/api/public/pacifica.d.ts +0 -1
  14. package/dist/api/public/pacifica.js +4 -13
  15. package/dist/api/public/urls.d.ts +9 -1
  16. package/dist/api/public/urls.js +35 -13
  17. package/dist/arb/file-lock.d.ts +11 -0
  18. package/dist/arb/file-lock.js +74 -0
  19. package/dist/arb/index.d.ts +2 -2
  20. package/dist/arb/index.js +2 -2
  21. package/dist/arb/pending-entry.d.ts +32 -0
  22. package/dist/arb/pending-entry.js +77 -0
  23. package/dist/arb/sizing.d.ts +6 -13
  24. package/dist/arb/sizing.js +12 -51
  25. package/dist/arb/state.d.ts +45 -12
  26. package/dist/arb/state.js +214 -42
  27. package/dist/arb/utils.d.ts +20 -6
  28. package/dist/arb/utils.js +43 -39
  29. package/dist/bot/conditions.d.ts +4 -4
  30. package/dist/bot/conditions.js +11 -6
  31. package/dist/bot/config.d.ts +17 -1
  32. package/dist/bot/config.js +48 -12
  33. package/dist/bot/engine.d.ts +61 -2
  34. package/dist/bot/engine.js +610 -144
  35. package/dist/bot/fill-size.d.ts +7 -0
  36. package/dist/bot/fill-size.js +18 -0
  37. package/dist/bot/index.d.ts +1 -1
  38. package/dist/bot/index.js +1 -1
  39. package/dist/bot/perp-order.d.ts +99 -0
  40. package/dist/bot/perp-order.js +299 -0
  41. package/dist/bot/presets.d.ts +7 -0
  42. package/dist/bot/presets.js +17 -0
  43. package/dist/bot/quarantine.d.ts +75 -0
  44. package/dist/bot/quarantine.js +190 -0
  45. package/dist/bot/quote-rules.d.ts +23 -0
  46. package/dist/bot/quote-rules.js +69 -0
  47. package/dist/bot/strategies/avellaneda-mm.d.ts +32 -2
  48. package/dist/bot/strategies/avellaneda-mm.js +72 -70
  49. package/dist/bot/strategies/basis-arb.js +2 -0
  50. package/dist/bot/strategies/dca-strategy.d.ts +2 -0
  51. package/dist/bot/strategies/dca-strategy.js +5 -0
  52. package/dist/bot/strategies/funding-arb-strategy.d.ts +43 -3
  53. package/dist/bot/strategies/funding-arb-strategy.js +571 -380
  54. package/dist/bot/strategies/funding-arb-utils.d.ts +48 -23
  55. package/dist/bot/strategies/funding-arb-utils.js +159 -195
  56. package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
  57. package/dist/bot/strategies/funding-arb-v2.js +258 -47
  58. package/dist/bot/strategies/funding-auto.js +215 -194
  59. package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +14 -0
  60. package/dist/bot/strategies/spot-perp-arb-strategy.js +217 -50
  61. package/dist/bot/strategies/spot-perp-executor.d.ts +73 -19
  62. package/dist/bot/strategies/spot-perp-executor.js +319 -120
  63. package/dist/bot/strategies/spot-perp-state.d.ts +43 -0
  64. package/dist/bot/strategies/spot-perp-state.js +70 -0
  65. package/dist/bot/strategy-types.d.ts +6 -0
  66. package/dist/bot/tui/BotDashboard.d.ts +7 -2
  67. package/dist/bot/tui/BotDashboard.js +6 -3
  68. package/dist/bridge-engine.d.ts +120 -19
  69. package/dist/bridge-engine.js +710 -422
  70. package/dist/bridge-signers.d.ts +54 -0
  71. package/dist/bridge-signers.js +161 -0
  72. package/dist/cache.d.ts +4 -1
  73. package/dist/cache.js +11 -0
  74. package/dist/cli-flags.d.ts +50 -0
  75. package/dist/cli-flags.js +104 -0
  76. package/dist/cli-program.d.ts +35 -0
  77. package/dist/cli-program.js +286 -0
  78. package/dist/cli-spec.js +1 -1
  79. package/dist/client-id-tracker.js +11 -2
  80. package/dist/commands/account.d.ts +7 -1
  81. package/dist/commands/account.js +74 -84
  82. package/dist/commands/agent-import.d.ts +114 -0
  83. package/dist/commands/agent-import.js +794 -0
  84. package/dist/commands/agent.d.ts +232 -2
  85. package/dist/commands/agent.js +1858 -955
  86. package/dist/commands/alerts.d.ts +1 -0
  87. package/dist/commands/alerts.js +31 -28
  88. package/dist/commands/arb/index.d.ts +60 -1
  89. package/dist/commands/arb/index.js +381 -384
  90. package/dist/commands/arb-auto.d.ts +132 -4
  91. package/dist/commands/arb-auto.js +1474 -967
  92. package/dist/commands/arb.d.ts +3 -1
  93. package/dist/commands/arb.js +62 -30
  94. package/dist/commands/bot.js +127 -47
  95. package/dist/commands/bridge.d.ts +2 -1
  96. package/dist/commands/bridge.js +339 -192
  97. package/dist/commands/builder.d.ts +55 -0
  98. package/dist/commands/builder.js +387 -0
  99. package/dist/commands/funds-onchain.d.ts +32 -0
  100. package/dist/commands/funds-onchain.js +286 -0
  101. package/dist/commands/funds.d.ts +25 -0
  102. package/dist/commands/funds.js +939 -245
  103. package/dist/commands/history.js +72 -45
  104. package/dist/commands/init.d.ts +107 -15
  105. package/dist/commands/init.js +1116 -249
  106. package/dist/commands/jobs.d.ts +2 -1
  107. package/dist/commands/jobs.js +77 -15
  108. package/dist/commands/manage.js +136 -91
  109. package/dist/commands/market.d.ts +4 -1
  110. package/dist/commands/market.js +35 -32
  111. package/dist/commands/outcome.d.ts +10 -0
  112. package/dist/commands/outcome.js +396 -0
  113. package/dist/commands/plan.js +52 -12
  114. package/dist/commands/portfolio.d.ts +1 -0
  115. package/dist/commands/portfolio.js +60 -72
  116. package/dist/commands/rebalance.d.ts +2 -1
  117. package/dist/commands/rebalance.js +33 -43
  118. package/dist/commands/risk.js +46 -19
  119. package/dist/commands/settings.d.ts +1 -0
  120. package/dist/commands/settings.js +246 -51
  121. package/dist/commands/trade.d.ts +50 -0
  122. package/dist/commands/trade.js +1943 -695
  123. package/dist/commands/wallet.d.ts +30 -1
  124. package/dist/commands/wallet.js +316 -337
  125. package/dist/config.d.ts +60 -9
  126. package/dist/config.js +252 -82
  127. package/dist/constants.d.ts +29 -4
  128. package/dist/constants.js +62 -20
  129. package/dist/dashboard/server.d.ts +2 -0
  130. package/dist/dashboard/server.js +9 -6
  131. package/dist/dashboard/ui.js +6 -4
  132. package/dist/dashboard/ws-feeds.js +20 -8
  133. package/dist/dex-asset-map.js +5 -3
  134. package/dist/errors.d.ts +122 -0
  135. package/dist/errors.js +194 -0
  136. package/dist/event-stream.js +75 -19
  137. package/dist/evm-master-tx.d.ts +59 -0
  138. package/dist/evm-master-tx.js +154 -0
  139. package/dist/exchanges/adapter-network.d.ts +10 -0
  140. package/dist/exchanges/adapter-network.js +10 -0
  141. package/dist/exchanges/arcus/adapter.d.ts +196 -0
  142. package/dist/exchanges/arcus/adapter.js +1092 -0
  143. package/dist/exchanges/arcus/agent.d.ts +55 -0
  144. package/dist/exchanges/arcus/agent.js +188 -0
  145. package/dist/exchanges/arcus/client.d.ts +63 -0
  146. package/dist/exchanges/arcus/client.js +285 -0
  147. package/dist/exchanges/arcus/public.d.ts +65 -0
  148. package/dist/exchanges/arcus/public.js +138 -0
  149. package/dist/exchanges/arcus/signing.d.ts +176 -0
  150. package/dist/exchanges/arcus/signing.js +288 -0
  151. package/dist/exchanges/arcus/spot-chain.d.ts +60 -0
  152. package/dist/exchanges/arcus/spot-chain.js +241 -0
  153. package/dist/exchanges/arcus/spot-router.d.ts +177 -0
  154. package/dist/exchanges/arcus/spot-router.js +183 -0
  155. package/dist/exchanges/arcus/spot.d.ts +109 -0
  156. package/dist/exchanges/arcus/spot.js +645 -0
  157. package/dist/exchanges/aster-funds.d.ts +127 -0
  158. package/dist/exchanges/aster-funds.js +252 -0
  159. package/dist/exchanges/aster-nonce.d.ts +2 -0
  160. package/dist/exchanges/aster-nonce.js +107 -0
  161. package/dist/exchanges/aster-spot.d.ts +117 -0
  162. package/dist/exchanges/aster-spot.js +686 -0
  163. package/dist/exchanges/aster-typed-data.d.ts +133 -7
  164. package/dist/exchanges/aster-typed-data.js +121 -7
  165. package/dist/exchanges/aster.d.ts +219 -6
  166. package/dist/exchanges/aster.js +1023 -153
  167. package/dist/exchanges/builder-fee.d.ts +254 -0
  168. package/dist/exchanges/builder-fee.js +421 -0
  169. package/dist/exchanges/canonical-symbols.d.ts +32 -0
  170. package/dist/exchanges/canonical-symbols.js +96 -0
  171. package/dist/exchanges/capabilities.d.ts +7 -1
  172. package/dist/exchanges/capabilities.js +7 -0
  173. package/dist/exchanges/hl-signing.d.ts +165 -0
  174. package/dist/exchanges/hl-signing.js +158 -0
  175. package/dist/exchanges/hyperliquid-outcome.d.ts +211 -0
  176. package/dist/exchanges/hyperliquid-outcome.js +646 -0
  177. package/dist/exchanges/hyperliquid-spot.d.ts +44 -5
  178. package/dist/exchanges/hyperliquid-spot.js +308 -117
  179. package/dist/exchanges/hyperliquid.d.ts +462 -57
  180. package/dist/exchanges/hyperliquid.js +1575 -449
  181. package/dist/exchanges/index.d.ts +6 -0
  182. package/dist/exchanges/index.js +4 -0
  183. package/dist/exchanges/interface.d.ts +63 -10
  184. package/dist/exchanges/interface.js +18 -3
  185. package/dist/exchanges/ioc-outcome.d.ts +44 -0
  186. package/dist/exchanges/ioc-outcome.js +57 -0
  187. package/dist/exchanges/legacy-trading-v2.d.ts +39 -0
  188. package/dist/exchanges/legacy-trading-v2.js +940 -0
  189. package/dist/exchanges/lighter-public.d.ts +43 -0
  190. package/dist/exchanges/lighter-public.js +276 -0
  191. package/dist/exchanges/lighter-rh-deposit.d.ts +62 -0
  192. package/dist/exchanges/lighter-rh-deposit.js +185 -0
  193. package/dist/exchanges/lighter-signer.d.ts +123 -0
  194. package/dist/exchanges/lighter-signer.js +215 -0
  195. package/dist/exchanges/lighter-spot.d.ts +91 -13
  196. package/dist/exchanges/lighter-spot.js +595 -233
  197. package/dist/exchanges/lighter.d.ts +611 -39
  198. package/dist/exchanges/lighter.js +1890 -649
  199. package/dist/exchanges/ondo/adapter.d.ts +166 -0
  200. package/dist/exchanges/ondo/adapter.js +835 -0
  201. package/dist/exchanges/ondo/agent.d.ts +31 -0
  202. package/dist/exchanges/ondo/agent.js +101 -0
  203. package/dist/exchanges/ondo/client.d.ts +60 -0
  204. package/dist/exchanges/ondo/client.js +297 -0
  205. package/dist/exchanges/ondo/funds.d.ts +122 -0
  206. package/dist/exchanges/ondo/funds.js +290 -0
  207. package/dist/exchanges/ondo/public.d.ts +95 -0
  208. package/dist/exchanges/ondo/public.js +214 -0
  209. package/dist/exchanges/ondo/spot.d.ts +127 -0
  210. package/dist/exchanges/ondo/spot.js +392 -0
  211. package/dist/exchanges/outcome-interface.d.ts +140 -0
  212. package/dist/exchanges/outcome-interface.js +16 -0
  213. package/dist/exchanges/pacifica-spot.d.ts +83 -0
  214. package/dist/exchanges/pacifica-spot.js +324 -0
  215. package/dist/exchanges/pacifica-typed-data.d.ts +30 -13
  216. package/dist/exchanges/pacifica-typed-data.js +57 -9
  217. package/dist/exchanges/pacifica.d.ts +288 -4
  218. package/dist/exchanges/pacifica.js +1013 -76
  219. package/dist/exchanges/precision.d.ts +47 -0
  220. package/dist/exchanges/precision.js +209 -0
  221. package/dist/exchanges/qfex/adapter.d.ts +341 -0
  222. package/dist/exchanges/qfex/adapter.js +1383 -0
  223. package/dist/exchanges/qfex/agent.d.ts +58 -0
  224. package/dist/exchanges/qfex/agent.js +188 -0
  225. package/dist/exchanges/qfex/client.d.ts +54 -0
  226. package/dist/exchanges/qfex/client.js +138 -0
  227. package/dist/exchanges/qfex/errors.d.ts +70 -0
  228. package/dist/exchanges/qfex/errors.js +236 -0
  229. package/dist/exchanges/qfex/public.d.ts +122 -0
  230. package/dist/exchanges/qfex/public.js +268 -0
  231. package/dist/exchanges/qfex/signing.d.ts +160 -0
  232. package/dist/exchanges/qfex/signing.js +283 -0
  233. package/dist/exchanges/qfex/ws-transport.d.ts +294 -0
  234. package/dist/exchanges/qfex/ws-transport.js +798 -0
  235. package/dist/exchanges/rate-limiter.d.ts +12 -0
  236. package/dist/exchanges/rate-limiter.js +64 -0
  237. package/dist/exchanges/registry.d.ts +14 -2
  238. package/dist/exchanges/registry.js +20 -32
  239. package/dist/exchanges/slippage.d.ts +37 -0
  240. package/dist/exchanges/slippage.js +70 -0
  241. package/dist/exchanges/spot-factory.d.ts +166 -0
  242. package/dist/exchanges/spot-factory.js +431 -0
  243. package/dist/exchanges/spot-interface.d.ts +98 -4
  244. package/dist/exchanges/spot-interface.js +27 -1
  245. package/dist/exchanges/spot-result.d.ts +79 -0
  246. package/dist/exchanges/spot-result.js +273 -0
  247. package/dist/exchanges/trading-v2.d.ts +117 -0
  248. package/dist/exchanges/trading-v2.js +301 -0
  249. package/dist/exchanges/venue-types.d.ts +231 -0
  250. package/dist/exchanges/venue-types.js +87 -0
  251. package/dist/exchanges/venue.d.ts +211 -0
  252. package/dist/exchanges/venue.js +132 -0
  253. package/dist/exchanges/venues/arcus.d.ts +8 -0
  254. package/dist/exchanges/venues/arcus.js +109 -0
  255. package/dist/exchanges/venues/aster.d.ts +49 -0
  256. package/dist/exchanges/venues/aster.js +176 -0
  257. package/dist/exchanges/venues/create-helpers.d.ts +18 -0
  258. package/dist/exchanges/venues/create-helpers.js +199 -0
  259. package/dist/exchanges/venues/hyperliquid.d.ts +23 -0
  260. package/dist/exchanges/venues/hyperliquid.js +126 -0
  261. package/dist/exchanges/venues/index.d.ts +16 -0
  262. package/dist/exchanges/venues/index.js +26 -0
  263. package/dist/exchanges/venues/lighter-family.d.ts +92 -0
  264. package/dist/exchanges/venues/lighter-family.js +166 -0
  265. package/dist/exchanges/venues/lighter-rh.d.ts +7 -0
  266. package/dist/exchanges/venues/lighter-rh.js +58 -0
  267. package/dist/exchanges/venues/lighter.d.ts +6 -0
  268. package/dist/exchanges/venues/lighter.js +55 -0
  269. package/dist/exchanges/venues/ondo.d.ts +34 -0
  270. package/dist/exchanges/venues/ondo.js +140 -0
  271. package/dist/exchanges/venues/pacifica.d.ts +8 -0
  272. package/dist/exchanges/venues/pacifica.js +95 -0
  273. package/dist/exchanges/venues/qfex.d.ts +28 -0
  274. package/dist/exchanges/venues/qfex.js +132 -0
  275. package/dist/execution-log.js +10 -2
  276. package/dist/funding/legacy-public.d.ts +31 -0
  277. package/dist/funding/legacy-public.js +369 -0
  278. package/dist/funding/normalize.d.ts +28 -16
  279. package/dist/funding/normalize.js +61 -29
  280. package/dist/funding/rates.d.ts +46 -11
  281. package/dist/funding/rates.js +228 -277
  282. package/dist/guardrail/perp-guardrail.js +0 -0
  283. package/dist/index.js +157 -682
  284. package/dist/jobs.d.ts +84 -6
  285. package/dist/jobs.js +239 -41
  286. package/dist/landing.d.ts +14 -0
  287. package/dist/landing.js +36 -0
  288. package/dist/mcp-server.d.ts +14 -4
  289. package/dist/mcp-server.js +820 -402
  290. package/dist/mcp-trade-guard.d.ts +16 -0
  291. package/dist/mcp-trade-guard.js +59 -0
  292. package/dist/pacifica/client.d.ts +41 -10
  293. package/dist/pacifica/client.js +167 -63
  294. package/dist/pacifica/constants.d.ts +0 -1
  295. package/dist/pacifica/constants.js +0 -5
  296. package/dist/pacifica/deposit.d.ts +30 -3
  297. package/dist/pacifica/deposit.js +119 -2
  298. package/dist/pacifica/faucet.d.ts +29 -0
  299. package/dist/pacifica/faucet.js +100 -0
  300. package/dist/pacifica/sign-tx.d.ts +15 -0
  301. package/dist/pacifica/sign-tx.js +41 -0
  302. package/dist/pacifica/types/account.d.ts +28 -4
  303. package/dist/pacifica/types/lake.d.ts +20 -5
  304. package/dist/pacifica/types/lake.js +4 -0
  305. package/dist/pacifica/types/market.d.ts +5 -0
  306. package/dist/pacifica/types/order.d.ts +4 -1
  307. package/dist/pacifica/types/position.d.ts +9 -1
  308. package/dist/plan-executor.d.ts +10 -5
  309. package/dist/plan-executor.js +165 -54
  310. package/dist/position-history.js +13 -0
  311. package/dist/rebalance.d.ts +26 -2
  312. package/dist/rebalance.js +86 -7
  313. package/dist/relay-swap.d.ts +232 -0
  314. package/dist/relay-swap.js +630 -0
  315. package/dist/risk.js +5 -1
  316. package/dist/secret-env.d.ts +15 -0
  317. package/dist/secret-env.js +36 -0
  318. package/dist/settings.d.ts +169 -6
  319. package/dist/settings.js +219 -38
  320. package/dist/signer/ows-evm.d.ts +13 -0
  321. package/dist/signer/ows-evm.js +22 -0
  322. package/dist/smart-order.d.ts +9 -0
  323. package/dist/smart-order.js +53 -8
  324. package/dist/strategies/dca.d.ts +6 -2
  325. package/dist/strategies/dca.js +66 -26
  326. package/dist/strategies/funding-arb.d.ts +11 -1
  327. package/dist/strategies/funding-arb.js +202 -89
  328. package/dist/strategies/grid.d.ts +31 -0
  329. package/dist/strategies/grid.js +89 -11
  330. package/dist/strategies/pending-stop.d.ts +8 -0
  331. package/dist/strategies/pending-stop.js +12 -0
  332. package/dist/strategies/split-order.d.ts +45 -17
  333. package/dist/strategies/split-order.js +157 -91
  334. package/dist/strategies/trailing-stop.d.ts +2 -1
  335. package/dist/strategies/trailing-stop.js +52 -6
  336. package/dist/strategies/twap.d.ts +6 -1
  337. package/dist/strategies/twap.js +39 -11
  338. package/dist/trade-validator.d.ts +22 -0
  339. package/dist/trade-validator.js +119 -15
  340. package/dist/utils/numeric.d.ts +46 -0
  341. package/dist/utils/numeric.js +79 -0
  342. package/dist/utils.d.ts +19 -0
  343. package/dist/utils.js +96 -19
  344. package/dist/withdraw-gate.d.ts +49 -0
  345. package/dist/withdraw-gate.js +131 -0
  346. package/package.json +15 -6
  347. package/skills/perp-cli/SKILL.md +338 -134
  348. package/skills/perp-cli/references/agent-operations.md +181 -92
  349. package/skills/perp-cli/references/commands.md +475 -132
  350. package/skills/perp-cli/references/strategies.md +44 -38
  351. package/skills/perp-cli/scripts/arb-monitor.sh +22 -17
  352. package/skills/perp-cli/scripts/funding-analysis.sh +20 -10
  353. package/skills/perp-cli/scripts/preflight.sh +21 -14
  354. package/skills/perp-cli/scripts/spot-perp-scan.sh +12 -7
  355. package/skills/perp-cli/scripts/validate-arb.sh +33 -16
  356. package/vendor/lighter-signer/SHA256SUMS +2 -0
  357. package/vendor/lighter-signer/VERSION +8 -0
  358. package/vendor/lighter-signer/lighter-signer.wasm +0 -0
  359. package/vendor/lighter-signer/wasm_exec.js +575 -0
  360. package/dist/bot/strategies/funding-arb-v2-strategy.d.ts +0 -42
  361. package/dist/bot/strategies/funding-arb-v2-strategy.js +0 -864
@@ -6,10 +6,11 @@ You are not expected to follow rigid rules — use this as a decision framework
6
6
  ## Core Concepts
7
7
 
8
8
  ### Funding Rate Mechanics
9
- - Funding rates settle **every 1 hour** on all supported exchanges
9
+ - Funding settles **every 1 hour** on every venue except Aster, whose interval is **per symbol** (from its fundingInfo `fundingIntervalHours`)
10
+ - QFEX funding rates are unverified: excluded from scans / arb; shown only with `arb scan --rates --include-unverified` (display only)
10
11
  - Positive rate = longs pay shorts, negative rate = shorts pay longs
11
- - Rates are annualized in display but applied hourly: `hourly = annual / 8760`
12
- - Scan opportunities: `perp --json arb scan --min 5` (shows spread, longExch, shortExch, netSpread)
12
+ - Rates are displayed annualized: per hour ≈ `annual / 8760`
13
+ - Scan opportunities: `perp --json arb scan --min 5` (shows grossSpread, netSpread, longExch, shortExch)
13
14
 
14
15
  ### Opportunity Cost Awareness
15
16
 
@@ -26,9 +27,9 @@ Switch Cost =
26
27
 
27
28
  Query each component:
28
29
  ```bash
29
- perp --json -e <FROM> trade check <SYM> <SIDE> <SIZE> # close cost estimate
30
- perp --json bridge quote --from <CHAIN> --to <CHAIN> --amount <AMT> # bridge cost
31
- perp --json -e <TO> trade check <SYM> <SIDE> <SIZE> # open cost estimate
30
+ perp --json -e <FROM> trade check <SYM> <SIDE> <SIZE> --leverage <L> # close cost estimate
31
+ perp --json funds bridge quote --from <CHAIN> --to <CHAIN> --amount <AMT> # bridge cost
32
+ perp --json -e <TO> trade check <SYM> <SIDE> <SIZE> --leverage <L> # open cost estimate
32
33
  ```
33
34
 
34
35
  **Only switch if:**
@@ -56,8 +57,10 @@ During transition, you are **unhedged**. Price can move against you. Factor this
56
57
 
57
58
  ### Discovery Loop
58
59
  ```bash
59
- perp --json arb scan --min 5 # find spreads > 5 bps (shows longExch/shortExch/netSpread)
60
- # NOTE: 'arb rates' is deprecated — use 'arb scan' instead
60
+ perp --json arb scan --min 5 # find spreads > 5% annual (shows longExch/shortExch/netSpread)
61
+ perp --json arb scan --rates # raw funding rates across all exchanges
62
+ perp --json arb scan --history <SYMBOL> # historical funding for a symbol
63
+ # NOTE: legacy 'arb rates'/'arb prices'/'arb dex' are removed — all routes go through 'arb scan'
61
64
  ```
62
65
 
63
66
  ### CRITICAL: Reading arb scan Results
@@ -67,12 +70,14 @@ The `arb scan` output tells you EXACTLY what to do:
67
70
  ```
68
71
  longExch: "hyperliquid" → open LONG on this exchange
69
72
  shortExch: "pacifica" → open SHORT on this exchange
70
- netSpread: 12.87 → profit after fees (bps/hour)
73
+ grossSpread: 15.2 → funding spread, annualized %
74
+ netSpread: 12.87 → annualized % after round-trip trading costs amortized over --hold-days (default 7)
75
+ (and --bridge-cost if given)
71
76
  ```
72
77
 
73
78
  **Rules:**
74
79
  1. **ALWAYS follow longExch/shortExch directions exactly.** DO NOT reverse them.
75
- 2. **NEVER enter if netSpread ≤ 0.** Negative netSpread = loss after fees.
80
+ 2. **NEVER enter if netSpread ≤ 0.** Negative netSpread = loss after fees over the assumed hold. A shorter real hold makes costs weigh more — re-scan with your `--hold-days`.
76
81
  3. The direction logic: go LONG where funding is negative (longs receive), go SHORT where funding is positive (shorts receive).
77
82
 
78
83
  **Why these directions?**
@@ -92,7 +97,7 @@ When evaluating an arb opportunity:
92
97
 
93
98
  3. **How long will the spread persist?**
94
99
  - Historical funding tends to mean-revert
95
- - Higher confidence in moderate, stable spreads (20-50 bps) than extreme spikes (>100 bps)
100
+ - Higher confidence in moderate, stable spreads (e.g. 10–50% annualized netSpread) than extreme spikes (hundreds of % annualized)
96
101
 
97
102
  4. **Can I execute both legs atomically?**
98
103
  - Both positions should open near-simultaneously to minimize directional exposure
@@ -103,19 +108,20 @@ When evaluating an arb opportunity:
103
108
  **Before entering any arb position, set a target hold duration.** This is critical for calculating whether the position is worth entering.
104
109
 
105
110
  ```
106
- Expected Profit = hourly_spread × target_hours
107
- Entry/Exit Cost = open_fees + close_fees + slippage (both legs)
108
- Net Profit = Expected Profit - Entry/Exit Cost
111
+ Expected Funding = notional × (grossSpread% / 100) × target_hours / 8760
112
+ Entry/Exit Cost = open_fees + close_fees + slippage (both legs)
113
+ Net Profit = Expected Funding - Entry/Exit Cost
109
114
  ```
115
+ (`netSpread` already does this amortization for `--hold-days`; pass `--hold-days` = your target to read it directly.)
110
116
 
111
117
  **Only enter if Net Profit > 0 with a comfortable margin.**
112
118
 
113
119
  Guidelines for hold duration:
114
- - **Stable spreads (20-50 bps):** target 8-24 hours. These are reliable but low-yield — you need time for the funding to accumulate past your entry/exit costs.
115
- - **Elevated spreads (50-100 bps):** target 4-8 hours. Higher yield, but likely to compress. Take profit earlier.
116
- - **Spike spreads (>100 bps):** target 1-4 hours. These revert quickly. Must cover entry/exit costs within a few funding cycles.
120
+ - **Moderate, stable spreads (≈10–50% annualized):** target days, not hours — the funding must accumulate past entry/exit costs.
121
+ - **Elevated spreads (≈50–100% annualized):** shorter targets; likely to compress. Take profit earlier.
122
+ - **Spikes (well above 100% annualized):** revert quickly — enter only if the funding over a few settlements covers entry/exit costs.
117
123
 
118
- **Re-evaluate at each funding settlement (every hour):**
124
+ **Re-evaluate at each funding settlement (hourly on most venues):**
119
125
  1. Is the spread still above breakeven for the remaining target hours?
120
126
  2. If spread compressed, should I exit early or extend the hold?
121
127
  3. Has a better opportunity appeared? (Remember: switching has its own cost)
@@ -124,18 +130,18 @@ Guidelines for hold duration:
124
130
 
125
131
  Example entry decision log:
126
132
  ```
127
- Entry: BTC HL↔PAC | Spread: 35 bps | Target hold: 12h
128
- Expected: 35 bps × 12h = 420 bps gross
129
- Entry/exit cost: ~80 bps (fees + slippage both legs)
130
- Net expected: ~340 bps → ENTER
131
- Hour 6 check: spread compressed to 15 bps → below breakeven for remaining 6h → EXIT
132
- Actual hold: 6h | Actual net: ~130 bps
133
+ Entry: BTC HL↔PAC | $1000/leg | grossSpread 80% annual | Target hold: 7d
134
+ Expected funding: 1000 × 0.80 × 168 / 8760 ≈ $15.3
135
+ Entry/exit cost: ~$4 (fees + slippage, both legs, open + close)
136
+ Net expected: ≈ $11 → ENTER (arb scan --hold-days 7 netSpread > 0 agrees)
137
+ Day 3 check: grossSpread compressed to 10% → remaining 4d earn ≈ $1.1 → EXIT if it keeps falling / flips, or a better net opportunity exists
138
+ Actual hold: 3d | log actual funding vs estimate
133
139
  ```
134
140
 
135
141
  ### Monitoring Active Positions
136
142
  ```bash
137
143
  perp --json portfolio # unified multi-exchange view
138
- perp --json risk overview # cross-exchange risk assessment
144
+ perp --json risk status # cross-exchange risk assessment (level, violations, canTrade)
139
145
  perp --json -e <EX> account positions # per-exchange positions
140
146
  perp --json arb scan --min 5 # are current rates still favorable?
141
147
  ```
@@ -179,6 +185,8 @@ Round `ORDER_SIZE` to the coarser step size of the two exchanges. If the matched
179
185
 
180
186
  #### Step 2: Execute Legs Sequentially with Same Size
181
187
 
188
+ Prefer `perp --json arb exec <SYM> <LONG_EX> <SHORT_EX> <USD>` — it does exactly this: long first, then the short sized to the verified long fill, reconcile always on, and an unconfirmed leg quarantines the symbol. Manual legs (below) only when `arb exec` cannot be used.
189
+
182
190
  Once you have a single `ORDER_SIZE`, execute both legs using THAT EXACT SIZE:
183
191
 
184
192
  ```bash
@@ -241,7 +249,7 @@ Leg B: perp --json -e <SHORT_EX> trade split <SYM> sell 5000
241
249
  #### Using Limit Orders for Better Execution
242
250
  For non-urgent entries, use limit orders at best bid/ask instead of market:
243
251
  ```bash
244
- perp --json -e <EX> trade buy <SYM> <ORDER_SIZE> -p <PRICE> # limit order
252
+ perp --json -e <EX> trade limit <SYM> buy <PRICE> <ORDER_SIZE> # limit order (GTC default; --tif ALO = post-only)
245
253
  ```
246
254
  This avoids crossing the spread but risks not getting filled. Set a timeout and fall back to market if unfilled. **Both legs must still use the same `ORDER_SIZE`.**
247
255
 
@@ -309,7 +317,7 @@ perp --json -e <EX> trade leverage <SYM> <LEVERAGE>
309
317
  **For funding rate arb, use ISOLATED margin.** Each leg should be independent — if one side gets liquidated, the other side survives.
310
318
 
311
319
  ```bash
312
- perp --json manage margin <SYM> isolated # set isolated margin
320
+ perp --json -e <EX> wallet manage margin <SYM> isolated # set isolated margin
313
321
  perp --json -e <EX> trade leverage <SYM> <LEV> --isolated # set leverage + isolated at once
314
322
  ```
315
323
 
@@ -405,8 +413,8 @@ Rules of thumb:
405
413
 
406
414
  Each exchange holds its own separate balance. Before entering any arb:
407
415
  ```bash
408
- perp --json -e <EX_A> account balance # check available balance on exchange A
409
- perp --json -e <EX_B> account balance # check available balance on exchange B
416
+ perp --json portfolio -e <EX_A> # check available balance on exchange A
417
+ perp --json portfolio -e <EX_B> # check available balance on exchange B
410
418
  ```
411
419
 
412
420
  **Size each leg to fit the available balance on that exchange:**
@@ -426,15 +434,12 @@ Even "market-neutral" arb can lose money if:
426
434
  - Extreme funding spike in the wrong direction
427
435
  - Slippage on entry/exit far exceeds estimates
428
436
 
429
- **Always set stop losses on both legs:**
430
- ```bash
431
- perp --json -e <EX_A> trade sl <SYM> <PRICE> # stop loss on leg A
432
- perp --json -e <EX_B> trade sl <SYM> <PRICE> # stop loss on leg B
433
- ```
434
-
435
- Or use TP/SL together:
437
+ **Always set stop losses on both legs** (`long|short` = the OPEN position's side; legs are reduce-only, sized to the live position):
436
438
  ```bash
437
- perp --json -e <EX> trade tp-sl <SYM> --tp <PRICE> --sl <PRICE>
439
+ perp --json --dry-run -e <EX_A> trade tpsl <SYM> long --sl <PRICE> # leg A (long): SL below mark
440
+ perp --json -e <EX_A> trade tpsl <SYM> long --sl <PRICE>
441
+ perp --json -e <EX_B> trade tpsl <SYM> short --sl <PRICE> # leg B (short): SL above mark
442
+ perp --json -e <EX> trade tpsl <SYM> long --sl <PRICE> --tp <PRICE> # TP + SL together
438
443
  ```
439
444
 
440
445
  ### What to Track Over Time
@@ -453,7 +458,8 @@ This is YOUR operational knowledge. Use it to make better decisions over time.
453
458
  ### Cross-Exchange Capital Allocation
454
459
  Your capital is split across exchanges. Rebalancing has real costs:
455
460
  ```bash
456
- perp --json bridge quote --from solana --to arbitrum --amount 1000
461
+ perp --json funds bridge quote --from solana --to arbitrum --amount 1000
462
+ perp --json funds rebalance plan # auto-compute optimal moves across exchanges
457
463
  ```
458
464
 
459
465
  Before rebalancing, ask:
@@ -1,7 +1,7 @@
1
1
  #!/bin/bash
2
2
  # Monitor open arb positions and evaluate exit conditions
3
3
  # Usage: ./arb-monitor.sh [--json] [--min-spread 5]
4
- # Checks: current spread vs entry, funding earned, PnL, liquidation distance
4
+ # Checks: current spread vs entry, funding earned (from arb status), PnL, liquidation distance
5
5
  # Returns JSON with position health + actionable recommendations
6
6
 
7
7
  set -euo pipefail
@@ -13,6 +13,11 @@ else
13
13
  PERP="npx -y perp-cli@latest"
14
14
  fi
15
15
 
16
+ # perp exits 1 on {"ok":false} and prints exactly one JSON document: capture it as-is
17
+ # (never append a fallback document); empty output (crash / not installed) → {"ok":false}.
18
+ pj() { local out; out=$($PERP "$@" 2>/dev/null) || true; if [[ -n "$out" ]]; then printf '%s\n' "$out"; else echo '{"ok":false}'; fi; }
19
+ isok() { jq -e '.ok == true' >/dev/null 2>&1 <<<"$1"; }
20
+
16
21
  JSON_MODE=false
17
22
  MIN_SPREAD=5
18
23
  while [[ $# -gt 0 ]]; do
@@ -23,9 +28,9 @@ while [[ $# -gt 0 ]]; do
23
28
  esac
24
29
  done
25
30
 
26
- # 1. Get current arb status
27
- ARB_STATUS=$($PERP --json arb status 2>/dev/null || echo '{"ok":false}')
28
- if ! echo "$ARB_STATUS" | grep -q '"ok":true'; then
31
+ # 1. Get current arb status (already includes funding earned per period)
32
+ ARB_STATUS=$(pj --json arb status)
33
+ if ! isok "$ARB_STATUS"; then
29
34
  if $JSON_MODE; then
30
35
  echo '{"ok":false,"error":"No arb positions or arb status unavailable"}'
31
36
  else
@@ -34,14 +39,14 @@ if ! echo "$ARB_STATUS" | grep -q '"ok":true'; then
34
39
  exit 0
35
40
  fi
36
41
 
37
- # 2. Get current spreads
38
- ARB_SCAN=$($PERP --json arb scan --min 0 2>/dev/null || echo '{"ok":false,"data":{"opportunities":[]}}')
42
+ # 2. Get current spreads (all modes)
43
+ ARB_SCAN=$(pj --json arb scan --mode all --min 0)
39
44
 
40
- # 3. Get funding earned
41
- FUNDING=$($PERP --json arb funding-earned 2>/dev/null || echo '{"ok":false}')
45
+ # 3. Get liquidation distances
46
+ LIQ_DIST=$(pj --json risk liquidation-distance)
42
47
 
43
- # 4. Get liquidation distances
44
- LIQ_DIST=$($PERP --json risk liquidation-distance 2>/dev/null || echo '{"ok":false}')
48
+ # 4. Get risk overview
49
+ RISK_STATUS=$(pj --json risk status)
45
50
 
46
51
  # 5. Output combined analysis
47
52
  if $JSON_MODE; then
@@ -51,8 +56,8 @@ if $JSON_MODE; then
51
56
  "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
52
57
  "positions": $ARB_STATUS,
53
58
  "currentSpreads": $ARB_SCAN,
54
- "fundingEarned": $FUNDING,
55
59
  "liquidationDistance": $LIQ_DIST,
60
+ "riskStatus": $RISK_STATUS,
56
61
  "minSpreadThreshold": $MIN_SPREAD
57
62
  }
58
63
  EOF
@@ -60,15 +65,15 @@ else
60
65
  echo "=== Arb Position Monitor ==="
61
66
  echo "Timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
62
67
  echo ""
63
- echo "--- Positions ---"
68
+ echo "--- Positions (with funding earned) ---"
64
69
  echo "$ARB_STATUS" | jq -r '.data // empty'
65
70
  echo ""
66
- echo "--- Funding Earned ---"
67
- echo "$FUNDING" | jq -r '.data // empty'
68
- echo ""
69
71
  echo "--- Liquidation Distance ---"
70
72
  echo "$LIQ_DIST" | jq -r '.data // empty'
71
73
  echo ""
72
- echo "Min spread threshold: ${MIN_SPREAD} bps"
73
- echo "Recommendation: Check if current spreads exceed ${MIN_SPREAD} bps to continue holding."
74
+ echo "--- Risk Status ---"
75
+ echo "$RISK_STATUS" | jq -r '.data // empty'
76
+ echo ""
77
+ echo "Min spread threshold: ${MIN_SPREAD}% annual"
78
+ echo "Recommendation: Check if current spreads exceed ${MIN_SPREAD}% to continue holding."
74
79
  fi
@@ -12,6 +12,11 @@ else
12
12
  PERP="npx -y perp-cli@latest"
13
13
  fi
14
14
 
15
+ # perp exits 1 on {"ok":false} and prints exactly one JSON document: capture it as-is
16
+ # (never append a fallback document); empty output (crash / not installed) → {"ok":false}.
17
+ pj() { local out; out=$($PERP "$@" 2>/dev/null) || true; if [[ -n "$out" ]]; then printf '%s\n' "$out"; else echo '{"ok":false}'; fi; }
18
+ isok() { jq -e '.ok == true' >/dev/null 2>&1 <<<"$1"; }
19
+
15
20
  JSON_MODE=false
16
21
  SYMBOL=""
17
22
  TOP=10
@@ -26,9 +31,10 @@ done
26
31
 
27
32
  if [[ -n "$SYMBOL" ]]; then
28
33
  # Single symbol detailed analysis
29
- FUNDING=$($PERP --json market funding "$SYMBOL" 2>/dev/null || echo '{"ok":false}')
30
- SCAN=$($PERP --json arb scan --min 0 2>/dev/null || echo '{"ok":false}')
31
- SPOT_SCAN=$($PERP --json arb scan --mode spot-perp --min 0 2>/dev/null || echo '{"ok":false}')
34
+ FUNDING=$(pj --json arb scan --history "$SYMBOL")
35
+ COMPARE=$(pj --json arb scan --compare "$SYMBOL")
36
+ SCAN=$(pj --json arb scan --mode perp-perp --min 0)
37
+ SPOT_SCAN=$(pj --json arb scan --mode spot-perp --min 0)
32
38
 
33
39
  if $JSON_MODE; then
34
40
  cat <<EOF
@@ -36,7 +42,8 @@ if [[ -n "$SYMBOL" ]]; then
36
42
  "ok": true,
37
43
  "symbol": "$SYMBOL",
38
44
  "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
39
- "funding": $FUNDING,
45
+ "fundingHistory": $FUNDING,
46
+ "compare": $COMPARE,
40
47
  "perpPerpArb": $SCAN,
41
48
  "spotPerpArb": $SPOT_SCAN
42
49
  }
@@ -44,25 +51,28 @@ EOF
44
51
  else
45
52
  echo "=== Funding Analysis: $SYMBOL ==="
46
53
  echo ""
47
- echo "--- Current Funding Rates ---"
54
+ echo "--- Cross-Exchange Compare ---"
55
+ echo "$COMPARE" | jq -r '.data // empty' 2>/dev/null || echo "(unavailable)"
56
+ echo ""
57
+ echo "--- Funding History ---"
48
58
  echo "$FUNDING" | jq -r '.data // empty' 2>/dev/null || echo "(unavailable)"
49
59
  echo ""
50
60
  echo "--- Perp-Perp Arb ---"
51
61
  echo "$SCAN" | jq -r '
52
62
  .data // [] | map(select(.symbol == "'"$SYMBOL"'")) | .[] |
53
- " \(.longExch // .longExchange)→\(.shortExch // .shortExchange): \(.netSpread // .spread)bps"
63
+ " \(.longExch // .longExchange)→\(.shortExch // .shortExchange): net \(.netSpread)% annual"
54
64
  ' 2>/dev/null || echo " (no opportunity)"
55
65
  echo ""
56
66
  echo "--- Spot+Perp Arb ---"
57
67
  echo "$SPOT_SCAN" | jq -r '
58
68
  .data // [] | map(select(.symbol == "'"$SYMBOL"'")) | .[] |
59
- " \(.direction): \(.annualSpreadPct // .spread)% annual"
69
+ " \(.direction) spot:\((.spotExchanges // []) | join("/"))→\(.perpExchange): net \(.netSpreadPct)% annual"
60
70
  ' 2>/dev/null || echo " (no opportunity)"
61
71
  fi
62
72
  else
63
73
  # All symbols scan
64
- SCAN=$($PERP --json arb scan --mode all --min 0 2>/dev/null || echo '{"ok":false}')
65
- FUNDING_DETAIL=$($PERP --json arb rates 2>/dev/null || echo '{"ok":false}')
74
+ SCAN=$(pj --json arb scan --mode all --min 0)
75
+ FUNDING_DETAIL=$(pj --json arb scan --rates)
66
76
 
67
77
  if $JSON_MODE; then
68
78
  cat <<EOF
@@ -79,7 +89,7 @@ EOF
79
89
  echo ""
80
90
  echo "$SCAN" | jq -r "
81
91
  .data // [] | .[0:$TOP] | .[] |
82
- \" \(.symbol) | \(.mode // \"perp\") | \(.netSpread // .spread)bps | \(.longExch // .longExchange)→\(.shortExch // .shortExchange)\"
92
+ \" \(.symbol) | \(.mode // \"perp\") | net \(.netSpread // .netSpreadPct)%/yr | \(.longExch // (\"spot:\" + ((.spotExchanges // []) | join(\"/\"))))→\(.shortExch // .perpExchange)\"
83
93
  " 2>/dev/null || echo "(no data)"
84
94
  fi
85
95
  fi
@@ -13,6 +13,11 @@ else
13
13
  PERP="npx -y perp-cli@latest"
14
14
  fi
15
15
 
16
+ # perp exits 1 on {"ok":false} and prints exactly one JSON document: capture it as-is
17
+ # (never append a fallback document); empty output (crash / not installed) → {"ok":false}.
18
+ pj() { local out; out=$($PERP "$@" 2>/dev/null) || true; if [[ -n "$out" ]]; then printf '%s\n' "$out"; else echo '{"ok":false}'; fi; }
19
+ isok() { jq -e '.ok == true' >/dev/null 2>&1 <<<"$1"; }
20
+
16
21
  JSON_MODE=false
17
22
  [[ "${1:-}" == "--json" ]] && JSON_MODE=true
18
23
 
@@ -22,7 +27,7 @@ checks=()
22
27
  add_check() {
23
28
  local name="$1" status="$2" detail="$3"
24
29
  checks+=("{\"name\":\"$name\",\"status\":\"$status\",\"detail\":\"$detail\"}")
25
- [[ "$status" == "fail" ]] && issues+=("$name: $detail")
30
+ if [[ "$status" == "fail" ]]; then issues+=("$name: $detail"); fi
26
31
  }
27
32
 
28
33
  # 1. perp-cli installed and version check
@@ -30,38 +35,39 @@ VERSION=$($PERP --version 2>/dev/null || echo "unknown")
30
35
  if [[ "$VERSION" != "unknown" ]]; then
31
36
  add_check "install" "pass" "perp-cli $VERSION ($(command -v perp &>/dev/null && echo 'global' || echo 'npx'))"
32
37
  else
33
- add_check "install" "fail" "perp-cli not found — run: npm i -g perp-cli@latest or use npx"
38
+ add_check "install" "fail" "perp-cli not found — ask the user before installing (npm i -g perp-cli@latest) or use npx"
34
39
  fi
35
40
 
36
41
  # 2. Wallet configured
37
- WALLET_OUT=$($PERP --json wallet show 2>/dev/null || echo '{"ok":false}')
38
- if echo "$WALLET_OUT" | grep -q '"ok":true'; then
42
+ WALLET_OUT=$(pj --json wallet show)
43
+ if isok "$WALLET_OUT"; then
39
44
  add_check "wallet" "pass" "Wallet configured"
40
45
  else
41
- add_check "wallet" "fail" "No wallet configured — run: perp wallet set <exchange> <key>"
46
+ add_check "wallet" "fail" "No wallet configured — run: perp wallet set <exchange> (key via hidden prompt or stdin)"
42
47
  fi
43
48
 
44
49
  # 3. Exchange connectivity (proxy via unauthenticated market prices fetch)
45
- HEALTH_OUT=$($PERP --json market prices 2>/dev/null || echo '{"ok":false}')
46
- if echo "$HEALTH_OUT" | grep -q '"ok":true'; then
50
+ HEALTH_OUT=$(pj --json market prices)
51
+ if isok "$HEALTH_OUT"; then
47
52
  add_check "connectivity" "pass" "Exchange reachable"
48
53
  else
49
54
  add_check "connectivity" "warn" "Exchange unreachable"
50
55
  fi
51
56
 
52
57
  # 4. Portfolio & balances
53
- PORTFOLIO_OUT=$($PERP --json portfolio 2>/dev/null || echo '{"ok":false}')
54
- if echo "$PORTFOLIO_OUT" | grep -q '"ok":true'; then
58
+ PORTFOLIO_OUT=$(pj --json portfolio)
59
+ if isok "$PORTFOLIO_OUT"; then
55
60
  add_check "portfolio" "pass" "Portfolio accessible"
56
61
  else
57
62
  add_check "portfolio" "warn" "Could not fetch portfolio"
58
63
  fi
59
64
 
60
65
  # 5. Risk status
61
- RISK_OUT=$($PERP --json risk status 2>/dev/null || echo '{"ok":false}')
62
- if echo "$RISK_OUT" | grep -q '"canTrade":true'; then
66
+ RISK_OUT=$(pj --json risk status)
67
+ CAN_TRADE=$(jq -r '.data.canTrade // empty' <<<"$RISK_OUT" 2>/dev/null || true)
68
+ if [[ "$CAN_TRADE" == "true" ]]; then
63
69
  add_check "risk" "pass" "Trading allowed (canTrade=true)"
64
- elif echo "$RISK_OUT" | grep -q '"canTrade":false'; then
70
+ elif [[ "$CAN_TRADE" == "false" ]]; then
65
71
  add_check "risk" "fail" "Trading blocked — check risk violations"
66
72
  else
67
73
  add_check "risk" "warn" "Could not check risk status"
@@ -69,11 +75,12 @@ fi
69
75
 
70
76
  # Output
71
77
  READY=true
72
- [[ ${#issues[@]} -gt 0 ]] && READY=false
78
+ if [[ ${#issues[@]} -gt 0 ]]; then READY=false; fi
73
79
 
74
80
  if $JSON_MODE; then
75
81
  CHECKS_JSON=$(IFS=,; echo "${checks[*]}")
76
- ISSUES_JSON=$(printf '%s\n' "${issues[@]}" | jq -R . | jq -s .)
82
+ ISSUES_JSON='[]'
83
+ if [[ ${#issues[@]} -gt 0 ]]; then ISSUES_JSON=$(printf '%s\n' "${issues[@]}" | jq -R . | jq -s .); fi
77
84
  echo "{\"ready\":$READY,\"checks\":[$CHECKS_JSON],\"issues\":$ISSUES_JSON}"
78
85
  else
79
86
  echo "=== perp-cli Pre-flight Check ==="
@@ -12,6 +12,11 @@ else
12
12
  PERP="npx -y perp-cli@latest"
13
13
  fi
14
14
 
15
+ # perp exits 1 on {"ok":false} and prints exactly one JSON document: capture it as-is
16
+ # (never append a fallback document); empty output (crash / not installed) → {"ok":false}.
17
+ pj() { local out; out=$($PERP "$@" 2>/dev/null) || true; if [[ -n "$out" ]]; then printf '%s\n' "$out"; else echo '{"ok":false}'; fi; }
18
+ isok() { jq -e '.ok == true' >/dev/null 2>&1 <<<"$1"; }
19
+
15
20
  JSON_MODE=false
16
21
  MODE="all"
17
22
  MIN_SPREAD=10
@@ -25,8 +30,8 @@ while [[ $# -gt 0 ]]; do
25
30
  done
26
31
 
27
32
  # 1. Portfolio check (ensure we have balances)
28
- PORTFOLIO=$($PERP --json portfolio 2>/dev/null || echo '{"ok":false}')
29
- if ! echo "$PORTFOLIO" | grep -q '"ok":true'; then
33
+ PORTFOLIO=$(pj --json portfolio)
34
+ if ! isok "$PORTFOLIO"; then
30
35
  if $JSON_MODE; then
31
36
  echo '{"ok":false,"error":"Cannot fetch portfolio — check wallet setup"}'
32
37
  else
@@ -36,10 +41,10 @@ if ! echo "$PORTFOLIO" | grep -q '"ok":true'; then
36
41
  fi
37
42
 
38
43
  # 2. Run appropriate scan
39
- SCAN_RESULT=$($PERP --json arb scan --mode "$MODE" --min "$MIN_SPREAD" 2>/dev/null || echo '{"ok":false}')
44
+ SCAN_RESULT=$(pj --json arb scan --mode "$MODE" --min "$MIN_SPREAD")
40
45
 
41
46
  # 3. Get exchange health for context (proxy via market prices fetch)
42
- HEALTH=$($PERP --json market prices 2>/dev/null || echo '{"ok":false}')
47
+ HEALTH=$(pj --json market prices)
43
48
 
44
49
  if $JSON_MODE; then
45
50
  cat <<EOF
@@ -55,14 +60,14 @@ if $JSON_MODE; then
55
60
  EOF
56
61
  else
57
62
  echo "=== Spot+Perp Arb Scanner ==="
58
- echo "Mode: $MODE | Min spread: ${MIN_SPREAD} bps"
63
+ echo "Mode: $MODE | Min spread: ${MIN_SPREAD}% annual"
59
64
  echo ""
60
65
 
61
66
  # Show balances
62
67
  echo "--- Balances ---"
63
68
  echo "$PORTFOLIO" | jq -r '
64
69
  .data.exchanges // [] | .[] |
65
- " \(.exchange): $\(.balance // .equity // "N/A")"
70
+ " \(.name): equity $\(.perp.equity // "N/A")"
66
71
  ' 2>/dev/null || echo " (unable to parse)"
67
72
  echo ""
68
73
 
@@ -70,7 +75,7 @@ else
70
75
  echo "--- Opportunities ---"
71
76
  echo "$SCAN_RESULT" | jq -r '
72
77
  .data // [] | .[] |
73
- " \(.symbol) | \(.mode // "perp-perp") | spread: \(.netSpread // .spread)bps | \(.longExch // .longExchange)→\(.shortExch // .shortExchange)"
78
+ " \(.symbol) | \(.mode // "perp-perp") | net: \(.netSpread // .netSpreadPct)%/yr | \(.longExch // ("spot:" + ((.spotExchanges // []) | join("/"))))→\(.shortExch // .perpExchange)"
74
79
  ' 2>/dev/null || echo " (no opportunities or parse error)"
75
80
 
76
81
  # Show exchange health
@@ -1,8 +1,9 @@
1
1
  #!/bin/bash
2
2
  # Validate an arb opportunity before execution
3
3
  # Usage: ./validate-arb.sh <SYMBOL> <LONG_EX> <SHORT_EX> <SIZE_USD> [--leverage N]
4
- # Performs: price check, liquidity check, balance check, risk check, slippage estimate
4
+ # Performs: price check, liquidity check, balance check, risk check, dry-run via 'arb exec'
5
5
  # Returns JSON validation report
6
+ # NOTE: For spot+perp arb, pass spot:<exch> as LONG_EX (e.g. spot:hl).
6
7
 
7
8
  set -euo pipefail
8
9
 
@@ -13,6 +14,11 @@ else
13
14
  PERP="npx -y perp-cli@latest"
14
15
  fi
15
16
 
17
+ # perp exits 1 on {"ok":false} and prints exactly one JSON document: capture it as-is
18
+ # (never append a fallback document); empty output (crash / not installed) → {"ok":false}.
19
+ pj() { local out; out=$($PERP "$@" 2>/dev/null) || true; if [[ -n "$out" ]]; then printf '%s\n' "$out"; else echo '{"ok":false}'; fi; }
20
+ isok() { jq -e '.ok == true' >/dev/null 2>&1 <<<"$1"; }
21
+
16
22
  SYM="${1:?Usage: validate-arb.sh <SYMBOL> <LONG_EX> <SHORT_EX> <SIZE_USD> [--leverage N]}"
17
23
  LONG_EX="${2:?Usage: validate-arb.sh <SYMBOL> <LONG_EX> <SHORT_EX> <SIZE_USD>}"
18
24
  SHORT_EX="${3:?Usage: validate-arb.sh <SYMBOL> <LONG_EX> <SHORT_EX> <SIZE_USD>}"
@@ -33,12 +39,22 @@ pass=true
33
39
  add_result() {
34
40
  local name="$1" ok="$2" detail="$3"
35
41
  checks+=("{\"check\":\"$name\",\"ok\":$ok,\"detail\":\"$detail\"}")
36
- [[ "$ok" == "false" ]] && pass=false
42
+ if [[ "$ok" == "false" ]]; then pass=false; fi
43
+ }
44
+
45
+ # Venue of the long leg (spot:<venue> → <venue>) for the per-venue reads below
46
+ LONG_VENUE="${LONG_EX#spot:}"
47
+ # portfolio -e accepts only full venue ids (no aliases)
48
+ venue_id() {
49
+ case "$1" in
50
+ pac) echo pacifica ;; hl) echo hyperliquid ;; lt) echo lighter ;; lrh|rh) echo lighter-rh ;;
51
+ ast) echo aster ;; ondo-perps) echo ondo ;; qf) echo qfex ;; *) echo "$1" ;;
52
+ esac
37
53
  }
38
54
 
39
55
  # 1. Price comparison — both exchanges must report similar prices
40
- LONG_PRICE=$($PERP --json -e "$LONG_EX" market mid "$SYM" 2>/dev/null | jq -r '.data.mid // "0"')
41
- SHORT_PRICE=$($PERP --json -e "$SHORT_EX" market mid "$SYM" 2>/dev/null | jq -r '.data.mid // "0"')
56
+ LONG_PRICE=$(pj --json -e "$LONG_VENUE" market mid "$SYM" | jq -r '.data.mid // "0"' 2>/dev/null || echo 0)
57
+ SHORT_PRICE=$(pj --json -e "$SHORT_EX" market mid "$SYM" | jq -r '.data.mid // "0"' 2>/dev/null || echo 0)
42
58
 
43
59
  if [[ "$LONG_PRICE" == "0" || "$SHORT_PRICE" == "0" ]]; then
44
60
  add_result "price" "false" "Cannot fetch price: long=$LONG_PRICE short=$SHORT_PRICE"
@@ -53,8 +69,8 @@ else
53
69
  fi
54
70
 
55
71
  # 2. Balance check on both exchanges
56
- LONG_BAL=$($PERP --json -e "$LONG_EX" account balance 2>/dev/null | jq -r '.data.available // .data.equity // "0"')
57
- SHORT_BAL=$($PERP --json -e "$SHORT_EX" account balance 2>/dev/null | jq -r '.data.available // .data.equity // "0"')
72
+ LONG_BAL=$(pj --json portfolio -e "$(venue_id "$LONG_VENUE")" | jq -r '.data.exchanges[0].perp.available // .data.exchanges[0].perp.equity // "0"' 2>/dev/null || echo 0)
73
+ SHORT_BAL=$(pj --json portfolio -e "$(venue_id "$SHORT_EX")" | jq -r '.data.exchanges[0].perp.available // .data.exchanges[0].perp.equity // "0"' 2>/dev/null || echo 0)
58
74
 
59
75
  NEEDED=$(echo "$SIZE" | awk '{print $1}')
60
76
  if (( $(echo "$LONG_BAL < $NEEDED" | bc -l 2>/dev/null || echo 1) )); then
@@ -69,31 +85,32 @@ else
69
85
  fi
70
86
 
71
87
  # 3. Market info (symbol availability)
72
- LONG_INFO=$($PERP --json -e "$LONG_EX" market info "$SYM" 2>/dev/null || echo '{"ok":false}')
73
- SHORT_INFO=$($PERP --json -e "$SHORT_EX" market info "$SYM" 2>/dev/null || echo '{"ok":false}')
88
+ LONG_INFO=$(pj --json -e "$LONG_VENUE" market info "$SYM")
89
+ SHORT_INFO=$(pj --json -e "$SHORT_EX" market info "$SYM")
74
90
 
75
- if echo "$LONG_INFO" | grep -q '"ok":true' && echo "$SHORT_INFO" | grep -q '"ok":true'; then
91
+ if isok "$LONG_INFO" && isok "$SHORT_INFO"; then
76
92
  add_result "market_info" "true" "Both exchanges support $SYM"
77
93
  else
78
94
  add_result "market_info" "false" "Symbol $SYM not available on one or both exchanges"
79
95
  fi
80
96
 
81
- # 4. Risk pre-check
97
+ # 4. Risk pre-check (risk check requires --leverage; default 1x when not given)
82
98
  LEV_FLAG=""
83
99
  [[ -n "$LEVERAGE" ]] && LEV_FLAG="--leverage $LEVERAGE"
84
- RISK_CHECK=$($PERP --json risk check --notional "$SIZE" $LEV_FLAG 2>/dev/null || echo '{"ok":false}')
85
- if echo "$RISK_CHECK" | grep -q '"ok":true'; then
100
+ RISK_CHECK=$(pj --json risk check --notional "$SIZE" --leverage "${LEVERAGE:-1}")
101
+ if isok "$RISK_CHECK" && [[ "$(jq -r '.data.allowed' <<<"$RISK_CHECK" 2>/dev/null || true)" == "true" ]]; then
86
102
  add_result "risk" "true" "Within risk limits"
87
103
  else
88
- add_result "risk" "false" "Risk check failed — review risk limits"
104
+ RISK_WHY=$(jq -r '.data.reason // .error.code // "unknown"' <<<"$RISK_CHECK" 2>/dev/null || echo unknown)
105
+ add_result "risk" "false" "Risk check failed: $RISK_WHY"
89
106
  fi
90
107
 
91
108
  # 5. Dry-run
92
- DRY_RUN=$($PERP --json --dry-run arb exec "$SYM" "$LONG_EX" "$SHORT_EX" "$SIZE" $LEV_FLAG 2>/dev/null || echo '{"ok":false}')
93
- if echo "$DRY_RUN" | grep -q '"ok":true'; then
109
+ DRY_RUN=$(pj --json --dry-run arb exec "$SYM" "$LONG_EX" "$SHORT_EX" "$SIZE" $LEV_FLAG)
110
+ if isok "$DRY_RUN"; then
94
111
  add_result "dry_run" "true" "Dry-run succeeded"
95
112
  else
96
- DRY_ERR=$(echo "$DRY_RUN" | jq -r '.error.message // .error // "unknown"' 2>/dev/null)
113
+ DRY_ERR=$(jq -r '.error.code // "unknown"' <<<"$DRY_RUN" 2>/dev/null || echo unknown)
97
114
  add_result "dry_run" "false" "Dry-run failed: $DRY_ERR"
98
115
  fi
99
116
 
@@ -0,0 +1,2 @@
1
+ 781ba28b5e7fca1ea816f516f28fe2adbe704b734828e0c941025f8133bd7b4b lighter-signer.wasm
2
+ 0c949f4996f9a89698e4b5c586de32249c3b69b7baadb64d220073cc04acba14 wasm_exec.js
@@ -0,0 +1,8 @@
1
+ lighter-signer.wasm: elliottech/lighter-go release v1.0.9 (2026-09-14), asset "lighter-signer.wasm"
2
+ https://github.com/elliottech/lighter-go/releases/download/v1.0.9/lighter-signer.wasm
3
+ built from lighter-go 8854554 ("allow markets gr 4095 (#89)") with go1.25.6 (GOOS=js GOARCH=wasm)
4
+ sha256 matches the release asset digest published by GitHub.
5
+ wasm_exec.js: Go go1.25.6 lib/wasm/wasm_exec.js (the runtime glue matching the toolchain the wasm was built with)
6
+ https://github.com/golang/go/blob/go1.25.6/lib/wasm/wasm_exec.js
7
+ Functions/arguments: https://apidocs.lighter.xyz/docs/wasm-signer
8
+ Both files are verified against SHA256SUMS (also pinned in src/exchanges/lighter-signer.ts) before load; a mismatch refuses to sign.