perp-cli 0.14.3 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (363) hide show
  1. package/README.ja.md +243 -0
  2. package/README.ko.md +243 -0
  3. package/README.md +138 -405
  4. package/README.zh.md +243 -0
  5. package/dist/agent-wallet/store.d.ts +3 -2
  6. package/dist/agent-wallet/store.js +2 -2
  7. package/dist/api/public/hyperliquid.js +4 -0
  8. package/dist/api/public/lighter.d.ts +5 -0
  9. package/dist/api/public/lighter.js +5 -0
  10. package/dist/api/public/pacifica.js +85 -15
  11. package/dist/api/public/urls.d.ts +2 -0
  12. package/dist/api/public/urls.js +3 -0
  13. package/dist/arb/sizing.js +4 -2
  14. package/dist/arb/state.d.ts +6 -0
  15. package/dist/arb/utils.d.ts +1 -2
  16. package/dist/arb/utils.js +1 -2
  17. package/dist/balance-by-address.d.ts +39 -0
  18. package/dist/balance-by-address.js +44 -0
  19. package/dist/bot/apex/pulse.js +3 -0
  20. package/dist/bot/apex/radar.js +2 -1
  21. package/dist/bot/engine.d.ts +10 -1
  22. package/dist/bot/engine.js +14 -6
  23. package/dist/bot/indicators.d.ts +7 -0
  24. package/dist/bot/indicators.js +16 -0
  25. package/dist/bot/perp-order.d.ts +4 -4
  26. package/dist/bot/perp-order.js +5 -5
  27. package/dist/bot/quarantine.d.ts +1 -1
  28. package/dist/bot/quarantine.js +1 -1
  29. package/dist/bot/quote-rules.js +1 -1
  30. package/dist/bot/strategies/apex-strategy.js +3 -2
  31. package/dist/bot/strategies/engine-mm.js +6 -5
  32. package/dist/bot/strategies/funding-arb-strategy.js +12 -5
  33. package/dist/bot/strategies/funding-arb-utils.d.ts +15 -3
  34. package/dist/bot/strategies/funding-arb-utils.js +53 -20
  35. package/dist/bot/strategies/funding-auto.js +2 -2
  36. package/dist/bot/strategies/liquidation-mm.d.ts +1 -0
  37. package/dist/bot/strategies/liquidation-mm.js +8 -2
  38. package/dist/bot/strategies/momentum-breakout.d.ts +1 -0
  39. package/dist/bot/strategies/momentum-breakout.js +11 -3
  40. package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +1 -1
  41. package/dist/bot/strategies/spot-perp-arb-strategy.js +6 -4
  42. package/dist/bot/strategies/spot-perp-executor.d.ts +1 -1
  43. package/dist/bot/strategies/spot-perp-executor.js +3 -3
  44. package/dist/bot/strategy-types.d.ts +2 -1
  45. package/dist/bot/tui/BotDashboard.d.ts +1 -1
  46. package/dist/bot/tui/BotDashboard.js +1 -1
  47. package/dist/cache.js +12 -6
  48. package/dist/cli-program.d.ts +15 -0
  49. package/dist/cli-program.js +126 -6
  50. package/dist/commands/account.js +168 -24
  51. package/dist/commands/agent-import.d.ts +7 -0
  52. package/dist/commands/agent-import.js +30 -1
  53. package/dist/commands/agent.d.ts +6 -3
  54. package/dist/commands/agent.js +160 -12
  55. package/dist/commands/arb/index.js +23 -81
  56. package/dist/commands/arb-auto.d.ts +24 -0
  57. package/dist/commands/arb-auto.js +175 -97
  58. package/dist/commands/arb.d.ts +20 -0
  59. package/dist/commands/arb.js +107 -163
  60. package/dist/commands/backtest.js +23 -21
  61. package/dist/commands/bot.js +1 -1
  62. package/dist/commands/builder.js +1 -1
  63. package/dist/commands/funds.js +113 -3
  64. package/dist/commands/init.d.ts +1 -1
  65. package/dist/commands/init.js +27 -7
  66. package/dist/commands/manage.js +23 -21
  67. package/dist/commands/market.d.ts +48 -0
  68. package/dist/commands/market.js +395 -100
  69. package/dist/commands/options.d.ts +14 -0
  70. package/dist/commands/options.js +151 -0
  71. package/dist/commands/pacifica-earn.d.ts +5 -0
  72. package/dist/commands/pacifica-earn.js +455 -0
  73. package/dist/commands/pacifica-orders.d.ts +14 -0
  74. package/dist/commands/pacifica-orders.js +142 -0
  75. package/dist/commands/portfolio.d.ts +16 -0
  76. package/dist/commands/portfolio.js +83 -55
  77. package/dist/commands/rebalance.d.ts +1 -0
  78. package/dist/commands/rebalance.js +15 -0
  79. package/dist/commands/risk.d.ts +11 -2
  80. package/dist/commands/risk.js +39 -44
  81. package/dist/commands/trade.d.ts +1 -0
  82. package/dist/commands/trade.js +67 -62
  83. package/dist/commands/wallet.js +1 -1
  84. package/dist/config.d.ts +2 -2
  85. package/dist/config.js +15 -4
  86. package/dist/constants.js +6 -0
  87. package/dist/cross-chain-margin.d.ts +3 -1
  88. package/dist/cross-chain-margin.js +4 -2
  89. package/dist/dashboard/server.js +3 -3
  90. package/dist/dashboard/ui.js +9 -6
  91. package/dist/errors.d.ts +29 -1
  92. package/dist/errors.js +35 -3
  93. package/dist/exchanges/address.d.ts +8 -0
  94. package/dist/exchanges/address.js +47 -0
  95. package/dist/exchanges/arcus/adapter.d.ts +16 -12
  96. package/dist/exchanges/arcus/adapter.js +45 -18
  97. package/dist/exchanges/arcus/agent.d.ts +1 -1
  98. package/dist/exchanges/arcus/agent.js +1 -1
  99. package/dist/exchanges/arcus/client.js +1 -1
  100. package/dist/exchanges/arcus/public.js +3 -2
  101. package/dist/exchanges/arcus/signing.d.ts +2 -3
  102. package/dist/exchanges/arcus/signing.js +1 -1
  103. package/dist/exchanges/arcus/spot-chain.d.ts +1 -1
  104. package/dist/exchanges/arcus/spot-chain.js +1 -1
  105. package/dist/exchanges/arcus/spot-router.d.ts +1 -1
  106. package/dist/exchanges/arcus/spot-router.js +2 -2
  107. package/dist/exchanges/arcus/spot.d.ts +2 -3
  108. package/dist/exchanges/arcus/spot.js +2 -3
  109. package/dist/exchanges/aster-spot.d.ts +3 -3
  110. package/dist/exchanges/aster-spot.js +6 -9
  111. package/dist/exchanges/aster-typed-data.d.ts +1 -1
  112. package/dist/exchanges/aster-typed-data.js +1 -1
  113. package/dist/exchanges/aster.d.ts +30 -10
  114. package/dist/exchanges/aster.js +153 -53
  115. package/dist/exchanges/builder-fee.d.ts +1 -1
  116. package/dist/exchanges/builder-fee.js +13 -2
  117. package/dist/exchanges/canonical-symbols.d.ts +8 -2
  118. package/dist/exchanges/canonical-symbols.js +46 -2
  119. package/dist/exchanges/capabilities.d.ts +2 -0
  120. package/dist/exchanges/capabilities.js +2 -1
  121. package/dist/exchanges/derive/client.d.ts +37 -0
  122. package/dist/exchanges/derive/client.js +135 -0
  123. package/dist/exchanges/derive/options.d.ts +73 -0
  124. package/dist/exchanges/derive/options.js +379 -0
  125. package/dist/exchanges/extended/adapter.d.ts +96 -0
  126. package/dist/exchanges/extended/adapter.js +241 -0
  127. package/dist/exchanges/extended/client.d.ts +38 -0
  128. package/dist/exchanges/extended/client.js +88 -0
  129. package/dist/exchanges/extended/public.d.ts +133 -0
  130. package/dist/exchanges/extended/public.js +436 -0
  131. package/dist/exchanges/grvt/adapter.d.ts +100 -0
  132. package/dist/exchanges/grvt/adapter.js +283 -0
  133. package/dist/exchanges/grvt/client.d.ts +25 -0
  134. package/dist/exchanges/grvt/client.js +86 -0
  135. package/dist/exchanges/grvt/public.d.ts +151 -0
  136. package/dist/exchanges/grvt/public.js +438 -0
  137. package/dist/exchanges/hyperliquid-outcome.d.ts +2 -0
  138. package/dist/exchanges/hyperliquid-outcome.js +29 -1
  139. package/dist/exchanges/hyperliquid-spot.d.ts +9 -1
  140. package/dist/exchanges/hyperliquid-spot.js +24 -16
  141. package/dist/exchanges/hyperliquid.d.ts +199 -22
  142. package/dist/exchanges/hyperliquid.js +550 -202
  143. package/dist/exchanges/index.d.ts +1 -1
  144. package/dist/exchanges/index.js +1 -1
  145. package/dist/exchanges/interface.d.ts +157 -13
  146. package/dist/exchanges/interface.js +47 -0
  147. package/dist/exchanges/ioc-outcome.d.ts +1 -1
  148. package/dist/exchanges/ioc-outcome.js +1 -1
  149. package/dist/exchanges/legacy-trading-v2.d.ts +2 -2
  150. package/dist/exchanges/legacy-trading-v2.js +7 -2
  151. package/dist/exchanges/lighter-public.d.ts +67 -12
  152. package/dist/exchanges/lighter-public.js +250 -82
  153. package/dist/exchanges/lighter-signer.d.ts +1 -1
  154. package/dist/exchanges/lighter-signer.js +1 -1
  155. package/dist/exchanges/lighter-spot.d.ts +10 -5
  156. package/dist/exchanges/lighter-spot.js +105 -97
  157. package/dist/exchanges/lighter.d.ts +80 -21
  158. package/dist/exchanges/lighter.js +312 -105
  159. package/dist/exchanges/ondo/adapter.d.ts +38 -13
  160. package/dist/exchanges/ondo/adapter.js +86 -27
  161. package/dist/exchanges/ondo/agent.d.ts +1 -1
  162. package/dist/exchanges/ondo/agent.js +1 -1
  163. package/dist/exchanges/ondo/client.d.ts +1 -1
  164. package/dist/exchanges/ondo/client.js +2 -2
  165. package/dist/exchanges/ondo/public.d.ts +35 -3
  166. package/dist/exchanges/ondo/public.js +75 -32
  167. package/dist/exchanges/ondo/spot.d.ts +2 -2
  168. package/dist/exchanges/ondo/spot.js +3 -3
  169. package/dist/exchanges/options-interface.d.ts +158 -0
  170. package/dist/exchanges/options-interface.js +17 -0
  171. package/dist/exchanges/options-venues.d.ts +12 -0
  172. package/dist/exchanges/options-venues.js +35 -0
  173. package/dist/exchanges/outcome-interface.d.ts +4 -0
  174. package/dist/exchanges/pacifica-atomic.d.ts +211 -0
  175. package/dist/exchanges/pacifica-atomic.js +440 -0
  176. package/dist/exchanges/pacifica-decimal.d.ts +10 -0
  177. package/dist/exchanges/pacifica-decimal.js +51 -0
  178. package/dist/exchanges/pacifica-presigned.d.ts +53 -0
  179. package/dist/exchanges/pacifica-presigned.js +141 -0
  180. package/dist/exchanges/pacifica-print.d.ts +200 -0
  181. package/dist/exchanges/pacifica-print.js +417 -0
  182. package/dist/exchanges/pacifica-spot.d.ts +8 -2
  183. package/dist/exchanges/pacifica-spot.js +26 -18
  184. package/dist/exchanges/pacifica-typed-data.d.ts +1 -1
  185. package/dist/exchanges/pacifica-typed-data.js +1 -1
  186. package/dist/exchanges/pacifica-vaults.d.ts +130 -0
  187. package/dist/exchanges/pacifica-vaults.js +277 -0
  188. package/dist/exchanges/pacifica.d.ts +108 -10
  189. package/dist/exchanges/pacifica.js +428 -171
  190. package/dist/exchanges/precision.d.ts +3 -1
  191. package/dist/exchanges/precision.js +9 -2
  192. package/dist/exchanges/public-balances.d.ts +25 -0
  193. package/dist/exchanges/public-balances.js +158 -0
  194. package/dist/exchanges/public-positions.d.ts +43 -0
  195. package/dist/exchanges/public-positions.js +154 -0
  196. package/dist/exchanges/qfex/adapter.d.ts +69 -13
  197. package/dist/exchanges/qfex/adapter.js +102 -17
  198. package/dist/exchanges/qfex/agent.d.ts +1 -1
  199. package/dist/exchanges/qfex/agent.js +1 -1
  200. package/dist/exchanges/qfex/client.d.ts +1 -1
  201. package/dist/exchanges/qfex/client.js +1 -1
  202. package/dist/exchanges/qfex/errors.d.ts +1 -1
  203. package/dist/exchanges/qfex/errors.js +2 -2
  204. package/dist/exchanges/qfex/public.d.ts +65 -12
  205. package/dist/exchanges/qfex/public.js +131 -25
  206. package/dist/exchanges/qfex/signing.d.ts +2 -2
  207. package/dist/exchanges/qfex/signing.js +4 -5
  208. package/dist/exchanges/qfex/ws-transport.d.ts +3 -3
  209. package/dist/exchanges/qfex/ws-transport.js +3 -3
  210. package/dist/exchanges/rate-limiter.d.ts +1 -1
  211. package/dist/exchanges/rate-limiter.js +5 -2
  212. package/dist/exchanges/registry.d.ts +1 -1
  213. package/dist/exchanges/registry.js +1 -1
  214. package/dist/exchanges/risex/adapter.d.ts +160 -0
  215. package/dist/exchanges/risex/adapter.js +842 -0
  216. package/dist/exchanges/risex/agent.d.ts +154 -0
  217. package/dist/exchanges/risex/agent.js +384 -0
  218. package/dist/exchanges/risex/client.d.ts +47 -0
  219. package/dist/exchanges/risex/client.js +174 -0
  220. package/dist/exchanges/risex/nonce.d.ts +43 -0
  221. package/dist/exchanges/risex/nonce.js +212 -0
  222. package/dist/exchanges/risex/public.d.ts +64 -0
  223. package/dist/exchanges/risex/public.js +150 -0
  224. package/dist/exchanges/risex/session.d.ts +71 -0
  225. package/dist/exchanges/risex/session.js +171 -0
  226. package/dist/exchanges/risex/typed-data.d.ts +173 -0
  227. package/dist/exchanges/risex/typed-data.js +240 -0
  228. package/dist/exchanges/risex/units.d.ts +29 -0
  229. package/dist/exchanges/risex/units.js +104 -0
  230. package/dist/exchanges/slippage.d.ts +1 -1
  231. package/dist/exchanges/slippage.js +1 -1
  232. package/dist/exchanges/spot-factory.d.ts +3 -3
  233. package/dist/exchanges/spot-factory.js +8 -8
  234. package/dist/exchanges/spot-interface.d.ts +5 -5
  235. package/dist/exchanges/spot-interface.js +2 -2
  236. package/dist/exchanges/spot-result.d.ts +3 -3
  237. package/dist/exchanges/spot-result.js +3 -3
  238. package/dist/exchanges/trading-v2.d.ts +3 -3
  239. package/dist/exchanges/trading-v2.js +9 -6
  240. package/dist/exchanges/txflow/adapter.d.ts +61 -0
  241. package/dist/exchanges/txflow/adapter.js +150 -0
  242. package/dist/exchanges/txflow/client.d.ts +47 -0
  243. package/dist/exchanges/txflow/client.js +155 -0
  244. package/dist/exchanges/txflow/public.d.ts +87 -0
  245. package/dist/exchanges/txflow/public.js +355 -0
  246. package/dist/exchanges/unknown-symbol.d.ts +34 -0
  247. package/dist/exchanges/unknown-symbol.js +56 -0
  248. package/dist/exchanges/variational/adapter.d.ts +67 -0
  249. package/dist/exchanges/variational/adapter.js +137 -0
  250. package/dist/exchanges/variational/client.d.ts +21 -0
  251. package/dist/exchanges/variational/client.js +66 -0
  252. package/dist/exchanges/variational/public.d.ts +126 -0
  253. package/dist/exchanges/variational/public.js +430 -0
  254. package/dist/exchanges/venue-types.d.ts +52 -10
  255. package/dist/exchanges/venue-types.js +82 -11
  256. package/dist/exchanges/venue.d.ts +150 -11
  257. package/dist/exchanges/venue.js +126 -4
  258. package/dist/exchanges/venues/arcus.d.ts +1 -2
  259. package/dist/exchanges/venues/arcus.js +5 -4
  260. package/dist/exchanges/venues/aster.d.ts +2 -2
  261. package/dist/exchanges/venues/aster.js +6 -4
  262. package/dist/exchanges/venues/create-helpers.d.ts +1 -1
  263. package/dist/exchanges/venues/create-helpers.js +3 -3
  264. package/dist/exchanges/venues/extended.d.ts +21 -0
  265. package/dist/exchanges/venues/extended.js +96 -0
  266. package/dist/exchanges/venues/grvt.d.ts +30 -0
  267. package/dist/exchanges/venues/grvt.js +98 -0
  268. package/dist/exchanges/venues/hyperliquid.d.ts +2 -2
  269. package/dist/exchanges/venues/hyperliquid.js +19 -8
  270. package/dist/exchanges/venues/index.d.ts +7 -2
  271. package/dist/exchanges/venues/index.js +8 -2
  272. package/dist/exchanges/venues/lighter-family.d.ts +7 -9
  273. package/dist/exchanges/venues/lighter-family.js +9 -11
  274. package/dist/exchanges/venues/lighter-rh.d.ts +2 -2
  275. package/dist/exchanges/venues/lighter-rh.js +20 -8
  276. package/dist/exchanges/venues/lighter.d.ts +1 -1
  277. package/dist/exchanges/venues/lighter.js +18 -6
  278. package/dist/exchanges/venues/ondo.d.ts +1 -1
  279. package/dist/exchanges/venues/ondo.js +5 -3
  280. package/dist/exchanges/venues/pacifica.d.ts +2 -2
  281. package/dist/exchanges/venues/pacifica.js +15 -4
  282. package/dist/exchanges/venues/qfex.d.ts +1 -1
  283. package/dist/exchanges/venues/qfex.js +4 -2
  284. package/dist/exchanges/venues/risex-release.d.ts +14 -0
  285. package/dist/exchanges/venues/risex-release.js +14 -0
  286. package/dist/exchanges/venues/risex.d.ts +31 -0
  287. package/dist/exchanges/venues/risex.js +131 -0
  288. package/dist/exchanges/venues/txflow.d.ts +18 -0
  289. package/dist/exchanges/venues/txflow.js +77 -0
  290. package/dist/exchanges/venues/variational.d.ts +21 -0
  291. package/dist/exchanges/venues/variational.js +85 -0
  292. package/dist/execution-log.d.ts +1 -1
  293. package/dist/funding/legacy-public.d.ts +6 -4
  294. package/dist/funding/legacy-public.js +42 -41
  295. package/dist/funding/normalize.d.ts +1 -1
  296. package/dist/funding/normalize.js +1 -1
  297. package/dist/funding/rates.d.ts +95 -6
  298. package/dist/funding/rates.js +99 -19
  299. package/dist/index.js +7 -6
  300. package/dist/jobs.d.ts +1 -0
  301. package/dist/jobs.js +25 -1
  302. package/dist/landing.d.ts +2 -0
  303. package/dist/landing.js +1 -1
  304. package/dist/liquidity.d.ts +3 -1
  305. package/dist/liquidity.js +8 -4
  306. package/dist/mcp-pacifica-earn.d.ts +9 -0
  307. package/dist/mcp-pacifica-earn.js +64 -0
  308. package/dist/mcp-server.js +211 -68
  309. package/dist/pacifica/client.d.ts +69 -5
  310. package/dist/pacifica/client.js +181 -8
  311. package/dist/pacifica/rate-limit.d.ts +144 -0
  312. package/dist/pacifica/rate-limit.js +267 -0
  313. package/dist/pacifica/types/order.d.ts +2 -1
  314. package/dist/plan-executor.js +7 -2
  315. package/dist/positions-by-address.d.ts +34 -0
  316. package/dist/positions-by-address.js +46 -0
  317. package/dist/rebalance.js +1 -1
  318. package/dist/risk.d.ts +2 -1
  319. package/dist/risk.js +2 -4
  320. package/dist/settings.d.ts +32 -3
  321. package/dist/signer/ows-evm.d.ts +1 -1
  322. package/dist/signer/ows-evm.js +1 -1
  323. package/dist/smart-order.d.ts +1 -1
  324. package/dist/smart-order.js +1 -1
  325. package/dist/strategies/funding-arb.d.ts +1 -1
  326. package/dist/strategies/funding-arb.js +36 -63
  327. package/dist/trade-validator.js +2 -2
  328. package/dist/utils.d.ts +22 -3
  329. package/dist/utils.js +11 -3
  330. package/dist/venue-health.d.ts +40 -0
  331. package/dist/venue-health.js +75 -0
  332. package/dist/withdraw-gate.d.ts +1 -1
  333. package/dist/withdraw-gate.js +1 -1
  334. package/dist/ws/arcus-market.d.ts +9 -0
  335. package/dist/ws/arcus-market.js +9 -0
  336. package/dist/ws/aster-market.d.ts +33 -0
  337. package/dist/ws/aster-market.js +30 -0
  338. package/dist/ws/index.d.ts +980 -0
  339. package/dist/ws/index.js +4460 -0
  340. package/dist/ws/lighter-market.d.ts +14 -0
  341. package/dist/ws/lighter-market.js +13 -0
  342. package/dist/ws/node.d.ts +996 -0
  343. package/dist/ws/node.js +4554 -0
  344. package/dist/ws/pacifica-instrument.d.ts +8 -0
  345. package/dist/ws/pacifica-instrument.js +8 -0
  346. package/dist/ws/unclassified.d.ts +16 -0
  347. package/dist/ws/unclassified.js +43 -0
  348. package/dist/ws-cli/credentials.d.ts +62 -0
  349. package/dist/ws-cli/credentials.js +126 -0
  350. package/package.json +71 -10
  351. package/skills/perp-cli/SKILL.md +153 -36
  352. package/skills/perp-cli/package.json +15 -2
  353. package/skills/perp-cli/references/agent-operations.md +22 -12
  354. package/skills/perp-cli/references/commands.md +143 -17
  355. package/skills/perp-cli/references/strategies.md +38 -3
  356. package/skills/perp-cli/references/ws-library.md +523 -0
  357. package/skills/perp-cli/scripts/arb-monitor.sh +13 -10
  358. package/skills/perp-cli/scripts/funding-analysis.sh +57 -29
  359. package/skills/perp-cli/scripts/preflight.sh +27 -19
  360. package/skills/perp-cli/scripts/spot-perp-scan.sh +58 -38
  361. package/skills/perp-cli/scripts/validate-arb.sh +56 -39
  362. package/dist/cli-spec.d.ts +0 -46
  363. package/dist/cli-spec.js +0 -81
@@ -9,7 +9,8 @@ import chalk from "chalk";
9
9
  import bs58 from "bs58";
10
10
  import { loadSettings } from "../settings.js";
11
11
  import { getAgent, setAgent, deleteAgent, listAgents, acquireLock, releaseLock, getVenueAgent, getVenueAgentForNetwork, setVenueAgent, deleteVenueAgent, listVenueAgents, agentNetworkOf, recordedAgentNetworkOf, assertAgentNetwork, getAgentForNetwork, agentSourceOf, } from "../agent-wallet/store.js";
12
- import { getVenue, networkConfig, resolveVenueId } from "../exchanges/venue.js";
12
+ import { getVenue, networkConfig, resolveVenueId, roleUnsupported } from "../exchanges/venue.js";
13
+ import { RISEX_APP_URL } from "../exchanges/venues/risex.js";
13
14
  import { listConfiguredAccountVenues } from "../config.js";
14
15
  import "../exchanges/venues/index.js";
15
16
  import { LIGHTER_RH_RESERVED_KEY_INDEXES } from "../exchanges/venues/lighter-family.js";
@@ -114,7 +115,7 @@ function readPipedStdin() {
114
115
  });
115
116
  }
116
117
  /**
117
- * Ondo API key id + secret, from stdin only (design §10.4 — never flags).
118
+ * Ondo API key id + secret, from stdin only (never flags).
118
119
  * TTY: key id prompt, then hidden secret prompt. Piped: line 1 = key id, line 2 = secret.
119
120
  */
120
121
  /** Lighter ChangePubKey settlement window when confirming a slot (docs example: 30 s, 1 s apart; kept under the 60 req/min budget). */
@@ -445,7 +446,7 @@ async function verifyQfex(network, opts) {
445
446
  };
446
447
  }
447
448
  /**
448
- * Confirmation of the account a QFEX key was resolved to (design §14.12 #9). In a terminal: a
449
+ * Confirmation of the account a QFEX key was resolved to. In a terminal: a
449
450
  * y/N prompt showing the UUID. Without one (piped stdin, --json): only an explicit --account-id
450
451
  * that equals the resolved account counts — the resolved UUID is printed on stderr either way.
451
452
  */
@@ -471,6 +472,40 @@ export function confirmImportAccount(explicitAccountId, json) {
471
472
  }
472
473
  };
473
474
  }
475
+ /** RISEx: session-key status on the venue and the on-chain permission bitmap (read-only). */
476
+ async function verifyRisex(network, opts) {
477
+ const meta = opts.agentName !== undefined ? getVenueAgent("risex", opts.agentName) : getVenueAgentForNetwork("risex", network);
478
+ // Read-only release (trading role gated): approve / revoke are refused, so point to the RISEx app.
479
+ const tradingGated = !getVenue("risex").roles.includes("trading");
480
+ if (!meta) {
481
+ throw new PerpError("AGENT_NOT_REGISTERED", `No RISEx signer registered for ${network}`, {
482
+ remediation: tradingGated
483
+ ? `RISEx is read-only in this perp-cli release (trading in a later release): manage signers in the RISEx app (${RISEX_APP_URL})`
484
+ : `perp${network === "testnet" ? " -n testnet" : ""} wallet agent approve risex --master <ows-wallet>`,
485
+ });
486
+ }
487
+ const { verifyRisexAgent } = await import("../exchanges/risex/agent.js");
488
+ const v = await verifyRisexAgent(meta);
489
+ const warnings = [];
490
+ if (!v.active)
491
+ warnings.push(`RISEx signer ${meta.signerAddress} status is ${String(v.status)} (1 = Active)`);
492
+ if (v.expired)
493
+ warnings.push(`RISEx signer ${meta.signerAddress} expired at ${meta.expiresAt}`);
494
+ if (v.active && (!v.tradingOnly || meta.permissions.tradingOnly !== true)) {
495
+ // An active signer that can do more than trade is a failure, never ok:true.
496
+ throw new PerpError("POLICY_DENIED", `RISEx signer ${meta.signerAddress} ("${meta.agentName}") is NOT restricted to trading-only (on-chain ${JSON.stringify(v.permissions)}, bitmap ${String(v.bitmap)}; recorded tradingOnly=${String(meta.permissions.tradingOnly)})${v.reason !== null ? ` — ${v.reason}` : ""}`, {
497
+ exchange: "risex", signer: meta.signerAddress, permissions: v.permissions, bitmap: v.bitmap,
498
+ remediation: tradingGated
499
+ ? `Revoke it in the RISEx app (${RISEX_APP_URL}); then remove the local record: perp${network === "testnet" ? " -n testnet" : ""} wallet agent revoke risex ${meta.agentName} --force`
500
+ : `Revoke it: perp${network === "testnet" ? " -n testnet" : ""} wallet agent revoke risex ${meta.agentName}, then approve a new one`,
501
+ });
502
+ }
503
+ return {
504
+ registered: v.active, count: 1,
505
+ items: [{ agentName: meta.agentName, signer: meta.signerAddress, account: meta.accountAddress, network: meta.network, status: v.status, permissions: v.permissions, tradingOnly: v.tradingOnly, expiresAt: meta.expiresAt }],
506
+ warnings: warnings.length > 0 ? warnings : undefined,
507
+ };
508
+ }
474
509
  /** Dispatch a verify to the venue-specific implementation. */
475
510
  async function verifyVenue(ex, network, opts) {
476
511
  if (ex === "aster")
@@ -487,8 +522,10 @@ async function verifyVenue(ex, network, opts) {
487
522
  return verifyArcus(network, opts);
488
523
  if (ex === "qfex")
489
524
  return verifyQfex(network, opts);
525
+ if (ex === "risex")
526
+ return verifyRisex(network, opts);
490
527
  throw new PerpError("NOT_IMPLEMENTED", `${ex} has no agents to verify`, {
491
- remediation: "perp wallet agent verify <aster|hyperliquid|pacifica|lighter|lighter-rh|ondo|arcus|qfex> [agentName]",
528
+ remediation: "perp wallet agent verify <aster|hyperliquid|pacifica|lighter|lighter-rh|ondo|arcus|qfex|risex> [agentName]",
492
529
  });
493
530
  }
494
531
  /**
@@ -509,6 +546,9 @@ export function agentApproveUnsupportedReason(venue, network, op) {
509
546
  }
510
547
  /** Throws the uniform CAPABILITY_UNSUPPORTED error when agentApproveUnsupportedReason() is non-null. */
511
548
  export function assertAgentApproveSupported(venue, network, op) {
549
+ const def = getVenue(venue);
550
+ if (!def.roles.includes("trading"))
551
+ throw roleUnsupported(def, `agent ${op}`);
512
552
  if (venue === "qfex") {
513
553
  // Programmatic key creation needs a website session (SIWE + MFA) — not a CLI path.
514
554
  throw unsupported("qfex", "agentApprove", "API keys are created (and revoked) on the QFEX website, behind 2FA", `Create the key in QFEX Developer Settings, then import it: perp${network === "testnet" ? " -n testnet" : ""} wallet agent import qfex`);
@@ -540,6 +580,11 @@ export function defaultAgentName(venue, network) {
540
580
  case "ondo": return `perp-cli-ondo${suffix}`;
541
581
  case "arcus": return `perp-cli-arcus${suffix}`;
542
582
  case "qfex": return `perp-cli-qfex${suffix}`;
583
+ case "risex": return `perp-cli-risex${suffix}`;
584
+ case "txflow": return `perp-cli-txflow${suffix}`;
585
+ case "variational": return `perp-cli-variational${suffix}`;
586
+ case "grvt": return `perp-cli-grvt${suffix}`;
587
+ case "extended": return `perp-cli-extended${suffix}`;
543
588
  }
544
589
  }
545
590
  /**
@@ -622,7 +667,7 @@ function requirePassphrase(passphrase) {
622
667
  export async function approveVenueAgent(p) {
623
668
  assertAgentApproveSupported(p.venue, p.network, "approve");
624
669
  const agentName = p.agentName ?? defaultAgentName(p.venue, p.network);
625
- // Ondo: HMAC key entered on stdin only (design §10.4)
670
+ // Ondo: HMAC key entered on stdin only
626
671
  if (p.venue === "ondo") {
627
672
  assertVenueAgentNameFree("ondo", agentName, p.network);
628
673
  if (getVenueAgent("ondo", agentName) && !p.rotate) {
@@ -636,7 +681,7 @@ export async function approveVenueAgent(p) {
636
681
  setVenueAgent("ondo", meta);
637
682
  return { venue: "ondo", meta };
638
683
  }
639
- // Arcus: OWS Ed25519 agent + EIP-712 createApiKey (design §10.4)
684
+ // Arcus: OWS Ed25519 agent + EIP-712 createApiKey
640
685
  if (p.venue === "arcus") {
641
686
  const masterName = requireMaster(p.master);
642
687
  assertVenueAgentNameFree("arcus", agentName, p.network);
@@ -660,6 +705,53 @@ export async function approveVenueAgent(p) {
660
705
  releaseLock("arcus");
661
706
  }
662
707
  }
708
+ // RISEx: master-signed RegisterSigner, then an on-chain restriction to trading-only.
709
+ if (p.venue === "risex") {
710
+ const masterName = requireMaster(p.master);
711
+ assertVenueAgentNameFree("risex", agentName, p.network);
712
+ if (p.canWithdraw || p.canSpot) {
713
+ throw new PerpError("INVALID_PARAMS", "RISEx signers registered by perp-cli are always restricted to trading only (no fund movement, no spot)", {
714
+ remediation: "Drop --can-withdraw / --can-spot",
715
+ });
716
+ }
717
+ const expiresAt = parseExpiresInParam(p.expiresIn);
718
+ const { approveRisexAgent, revokeRisexAgent, risexAgentWalletName, risexRotatePreflight, risexSignerStatus } = await import("../exchanges/risex/agent.js");
719
+ acquireLock("risex");
720
+ try {
721
+ const existing = getVenueAgent("risex", agentName);
722
+ if (existing !== null) {
723
+ if (!p.rotate) {
724
+ throw new PerpError("INVALID_PARAMS", `RISEx signer "${agentName}" already exists`, {
725
+ remediation: `Replace it (revoke the old signer, register a fresh one): perp${p.network === "testnet" ? " -n testnet" : ""} wallet agent approve risex --rotate — or choose another --agent-name`,
726
+ });
727
+ }
728
+ // Rotate: the new signer's checks (pins, maintenance, master gas) pass first, then a
729
+ // venue-confirmed revoke of the old signer, then drop its key, then approve.
730
+ await risexRotatePreflight(masterName, p.passphrase, p.network);
731
+ // A kept record whose signer never registered (or is already revoked) has nothing to
732
+ // revoke on the venue: it is only forgotten locally. An Active one is revoked first.
733
+ const status = await risexSignerStatus(existing);
734
+ if (status === 1)
735
+ await revokeRisexAgent({ meta: existing, masterOwsWallet: masterName, passphrase: p.passphrase });
736
+ try {
737
+ loadOws().deleteWallet(existing.agentWalletName);
738
+ }
739
+ catch { /* already gone */ }
740
+ deleteVenueAgent("risex", agentName);
741
+ }
742
+ else if (p.rotate && loadOws().listWallets().some((w) => w.name === risexAgentWalletName(masterName, p.network))) {
743
+ throw new PerpError("INVALID_PARAMS", `OWS wallet "${risexAgentWalletName(masterName, p.network)}" exists but no RISEx signer record uses it`, {
744
+ remediation: `If no RISEx signer uses its key, delete it (ows wallet delete ${risexAgentWalletName(masterName, p.network)}) and re-run the approve`,
745
+ });
746
+ }
747
+ const meta = await approveRisexAgent({ agentName, masterOwsWallet: masterName, passphrase: p.passphrase, network: p.network, expiresAt });
748
+ setVenueAgent("risex", meta);
749
+ return { venue: "risex", meta };
750
+ }
751
+ finally {
752
+ releaseLock("risex");
753
+ }
754
+ }
663
755
  const expiresAt = parseExpiresInParam(p.expiresIn);
664
756
  const masterName = requireMaster(p.master);
665
757
  const expiresAtIso = expiresAt.toISOString();
@@ -993,6 +1085,16 @@ export function printApproveOutcome(outcome) {
993
1085
  console.log(` Valid Until: ${chalk.cyan(outcome.meta.validUntil)}`);
994
1086
  console.log();
995
1087
  return;
1088
+ case "risex":
1089
+ console.log(chalk.green.bold("\n RISEx signer registered and restricted to trading-only!\n"));
1090
+ console.log(` Name: ${chalk.cyan(outcome.meta.agentName)}`);
1091
+ console.log(` Signer: ${chalk.cyan(outcome.meta.signerAddress)}`);
1092
+ console.log(` Account: ${chalk.cyan(outcome.meta.accountAddress)}`);
1093
+ console.log(` Expires: ${chalk.cyan(outcome.meta.expiresAt)}`);
1094
+ console.log(` Permissions: ${chalk.cyan("trading only")} ${chalk.gray(`(on-chain; disabled ${outcome.meta.permissions.disabled.join(", ")}; verified ${outcome.meta.permissions.verifiedAt})`)}`);
1095
+ console.log(chalk.gray(" Deposits / withdrawals: RISEx app only (perp-cli never moves RISEx funds)."));
1096
+ console.log();
1097
+ return;
996
1098
  case "lighter":
997
1099
  case "lighter-rh":
998
1100
  console.log(chalk.green.bold(`\n ${getVenue(outcome.venue).displayName} Agent approved successfully!\n`));
@@ -1043,7 +1145,7 @@ export function registerWalletAgentCommands(walletCmd, isJson) {
1043
1145
  // ── agent approve <exchange> ── register an agent wallet ──────────────────
1044
1146
  agent
1045
1147
  .command("approve <exchange>")
1046
- .description("Register an agent wallet / API key for an exchange (aster, hyperliquid, pacifica, lighter, lighter-rh, ondo, arcus)")
1148
+ .description("Register an agent wallet / API key for an exchange (aster, hyperliquid, pacifica, lighter, lighter-rh, ondo, arcus, risex — risex: a trading-only session signer, restricted on-chain)")
1047
1149
  .option("--master <name>", "Master OWS wallet name")
1048
1150
  .option("--agent-name <name>", "Agent name (default: perp-cli-aster)")
1049
1151
  .option("--expires-in <duration>", "Expiry: 30d, 90d, 180d, 1y, or ISO-8601 datetime (default: 90d)")
@@ -1087,7 +1189,8 @@ export function registerWalletAgentCommands(walletCmd, isJson) {
1087
1189
  assertAgentApproveSupported(venue, network, "approve");
1088
1190
  let master = opts.master ?? null;
1089
1191
  let agentName = opts.agentName ?? null;
1090
- let expiresIn = opts.expiresIn ?? "90d";
1192
+ // RISEx signers default to 30 days (owner decision; the venue maximum is not documented).
1193
+ let expiresIn = opts.expiresIn ?? (venue === "risex" ? "30d" : "90d");
1091
1194
  let canPerp = opts.canPerp ?? true;
1092
1195
  let canSpot = opts.canSpot ?? false;
1093
1196
  let canWithdraw = opts.canWithdraw ?? false;
@@ -1218,6 +1321,9 @@ Examples:
1218
1321
  }
1219
1322
  const network = networkOf(command);
1220
1323
  const venue = resolveVenueId(exchange);
1324
+ // TxFlow (market data only) / RISEx (perp-cli-registered, restricted signers only): refused before any stdin read.
1325
+ const { assertImportSupported } = await import("./agent-import.js");
1326
+ assertImportSupported(venue, network);
1221
1327
  const int = (flag, raw) => {
1222
1328
  if (raw === undefined)
1223
1329
  return null;
@@ -1296,14 +1402,14 @@ Examples:
1296
1402
  for (const { exchange: ex, meta } of listVenueAgents()) {
1297
1403
  if (venueFilter !== null && venueFilter !== ex)
1298
1404
  continue;
1299
- const expiresAt = meta.kind === "arcus" ? meta.validUntil : null;
1405
+ const expiresAt = meta.kind === "arcus" ? meta.validUntil : meta.kind === "risex" ? meta.expiresAt : null;
1300
1406
  const status = expiresAt !== null && expiresAt !== "" && new Date(expiresAt).getTime() < now ? "expired" : "active";
1301
1407
  rows.push({
1302
1408
  name: meta.agentName,
1303
1409
  exchange: ex,
1304
1410
  network: meta.network,
1305
1411
  source: agentSourceOf(ex, meta),
1306
- evmAddress: meta.kind === "arcus" ? meta.ownerAddress : "",
1412
+ evmAddress: meta.kind === "arcus" ? meta.ownerAddress : meta.kind === "risex" ? meta.signerAddress : "",
1307
1413
  // Ondo keys carry no expiry; shown as "—".
1308
1414
  expiresAt: expiresAt ?? "",
1309
1415
  status,
@@ -1373,9 +1479,11 @@ Examples:
1373
1479
  network = networkOf(command);
1374
1480
  // An imported key is only ever removed locally, so it needs no venue support for revoke
1375
1481
  // (e.g. an imported Aster testnet key).
1376
- const rec = exchange === "ondo" || exchange === "arcus" || exchange === "qfex" ? getVenueAgent(exchange, agentName) : getAgent(exchange, agentName);
1482
+ const rec = exchange === "ondo" || exchange === "arcus" || exchange === "qfex" || exchange === "risex" ? getVenueAgent(exchange, agentName) : getAgent(exchange, agentName);
1377
1483
  // QFEX keys only exist as imports: a missing record is "already removed", never an approve-capability error.
1378
- if (exchange !== "qfex" && (rec === null || agentSourceOf(exchange, rec) !== "imported"))
1484
+ // A release-gated venue (RISEx in 0.15.0) still lets --force / --local-only clear local state: nothing is signed or sent.
1485
+ const localCleanupOfGated = localOnly && getVenue(exchange).gatedRoles !== null;
1486
+ if (exchange !== "qfex" && !localCleanupOfGated && (rec === null || agentSourceOf(exchange, rec) !== "imported"))
1379
1487
  assertAgentApproveSupported(exchange, network, "revoke");
1380
1488
  }
1381
1489
  catch (e) {
@@ -1425,6 +1533,46 @@ Examples:
1425
1533
  console.log(chalk.green(`Agent "${agentName}" on qfex removed locally.`) + chalk.yellow(`\n ${note}`));
1426
1534
  return;
1427
1535
  }
1536
+ // ── RISEx: RevokeSigner signed by the master wallet (gasless REST); then the key is deleted ──
1537
+ if (exchange === "risex") {
1538
+ const risexMeta = getVenueAgent("risex", agentName);
1539
+ if (!risexMeta) {
1540
+ if (useJson)
1541
+ printJson({ ok: true, data: { alreadyRevoked: true } });
1542
+ else
1543
+ console.log(`Agent "${agentName}" on risex not found locally — already revoked.`);
1544
+ return;
1545
+ }
1546
+ if (risexMeta.network !== network) {
1547
+ throw new PerpError("AGENT_NOT_REGISTERED", `RISEx signer "${agentName}" is registered on ${risexMeta.network}, not ${network}`, {
1548
+ remediation: risexMeta.network === "mainnet" ? "Run without -n testnet to revoke it" : "Run with -n testnet to revoke it",
1549
+ });
1550
+ }
1551
+ let txHash = null;
1552
+ if (!localOnly) {
1553
+ const masterName = opts.master ?? loadSettings().owsActiveWallet;
1554
+ if (!masterName) {
1555
+ throw new PerpError("INVALID_PARAMS", "Master wallet name is required to revoke a RISEx signer", {
1556
+ remediation: "Use --master <name>, or --force to clear local state only (then revoke it in the RISEx app)",
1557
+ });
1558
+ }
1559
+ const passphrase = await resolvePassphrase({ flag: passphraseFlag });
1560
+ const { revokeRisexAgent } = await import("../exchanges/risex/agent.js");
1561
+ txHash = (await revokeRisexAgent({ meta: risexMeta, masterOwsWallet: masterName, passphrase })).txHash;
1562
+ }
1563
+ try {
1564
+ loadOws().deleteWallet(risexMeta.agentWalletName);
1565
+ }
1566
+ catch { /* already gone */ }
1567
+ deleteVenueAgent("risex", agentName);
1568
+ if (useJson)
1569
+ printJson(jsonOk({ revoked: !localOnly, local: localOnly, txHash }));
1570
+ else
1571
+ console.log(localOnly
1572
+ ? chalk.yellow(`Agent "${agentName}" on risex removed locally only — revoke signer ${risexMeta.signerAddress} in the RISEx app.`)
1573
+ : chalk.green(`Agent "${agentName}" on risex revoked successfully.`));
1574
+ return;
1575
+ }
1428
1576
  // ── Arcus: EIP-712 RevokeApiKey signed by the master wallet ──
1429
1577
  if (exchange === "arcus") {
1430
1578
  const arcusMeta = getVenueAgent("arcus", agentName);
@@ -1,10 +1,11 @@
1
1
  import { isDryRun, networkOf, supportsDryRun } from "../../cli-flags.js";
2
2
  import chalk from "chalk";
3
- import { makeTable, formatUsd, formatPnl, printJson, jsonOk, logSettledRejections } from "../../utils.js";
3
+ import { makeTable, formatUsd, formatPnl, printJson, jsonOk } from "../../utils.js";
4
4
  import { SPOT_PERP_TOKEN_MAP } from "../../exchanges/index.js";
5
5
  import { readExecutionLog, logExecution } from "../../execution-log.js";
6
6
  import { toHourlyRate } from "../../funding.js";
7
- import { fetchPacificaPrices, fetchHyperliquidMeta, fetchLighterOrderBookDetails, fetchLighterFundingRates, } from "../../shared-api.js";
7
+ import { fetchVenuesRates } from "../../funding/rates.js";
8
+ import { sharedApiNetwork } from "../../api/public/urls.js";
8
9
  import { computeBasisRisk } from "../../arb-utils.js";
9
10
  import { removePosition as persistRemovePosition, getPositions as getArbStatePositions, loadArbState } from "../../arb-state.js";
10
11
  import { fetchBalancesWithFailures, computeRebalancePlan } from "../../rebalance.js";
@@ -15,7 +16,7 @@ import { listConfiguredAccountVenues } from "../../config.js";
15
16
  import { closePerpPairVerified, handleSpotClose } from "../arb-auto.js";
16
17
  import { quarantineEntryFor } from "../../bot/quarantine.js";
17
18
  import { PerpError, classifyError, parsePositiveDecimal } from "../../errors.js";
18
- import { listVenuesWithRole, resolveVenueId } from "../../exchanges/venue.js";
19
+ import { getVenue, listVenuesWithRole, resolveVenueId } from "../../exchanges/venue.js";
19
20
  // ── Helpers ──
20
21
  import { DEFAULT_TAKER_FEE as TAKER_FEE } from "../../constants.js";
21
22
  /**
@@ -108,81 +109,22 @@ function entryHourly(e) {
108
109
  return e.fundingHours !== null ? e.rate / e.fundingHours : toHourlyRate(e.rate, e.exchange);
109
110
  }
110
111
  async function fetchFundingRatesMap() {
112
+ // The SAME rate rows as `arb scan --rates` (fetchVenuesRates): canonical-keyed (unclassified /
113
+ // ambiguous tickers have no row), each with its own basis (fundingHours) and rateKind. A failed
114
+ // venue is reported on stderr (explicit, never silent).
115
+ const venues = ["pacifica", "hyperliquid", "lighter", "aster"].map(id => getVenue(id));
116
+ const { rates: fetched, exchangeErrors } = await fetchVenuesRates(venues, sharedApiNetwork(), false);
117
+ // Never a leg on an unverified rate (row label; the venue filter already drops them).
118
+ const rates = fetched.filter(r => r.fundingVerified);
119
+ for (const [ex, msg] of Object.entries(exchangeErrors))
120
+ process.stderr.write(`[arb] ${ex}: funding rates unavailable — ${msg}\n`);
111
121
  const rateMap = new Map();
112
- // SSOT rule #2: each DEX is independent. allSettled keeps the comparison
113
- // alive when one DEX errors out, while rejection reasons get logged to
114
- // stderr (explicit failure, never silent .catch(() => [])).
115
- const settled = await Promise.allSettled([
116
- fetchPacificaPrices(),
117
- fetchHyperliquidMeta(),
118
- fetchLighterOrderBookDetails(),
119
- fetchLighterFundingRates(),
120
- fetch("https://fapi.asterdex.com/fapi/v1/premiumIndex").then(r => r.json()),
121
- fetch("https://fapi.asterdex.com/fapi/v3/fundingInfo").then(r => r.json()),
122
- ]);
123
- logSettledRejections(settled, ["pacifica", "hyperliquid", "lighter:orderbook", "lighter:funding", "aster:premiumIndex", "aster:fundingInfo"], "arb");
124
- const pacAssets = settled[0].status === "fulfilled" ? settled[0].value : [];
125
- const hlAssets = settled[1].status === "fulfilled" ? settled[1].value : [];
126
- const ltDetails = settled[2].status === "fulfilled" ? settled[2].value : [];
127
- const ltFunding = settled[3].status === "fulfilled" ? settled[3].value : [];
128
- // Aster funding basis is per symbol: a rate is usable only with its fundingInfo interval.
129
- const asterPremiums = settled[4].status === "fulfilled" && settled[5].status === "fulfilled" ? settled[4].value : [];
130
- const asterInfo = settled[5].status === "fulfilled" ? settled[5].value : [];
131
- const asterHours = new Map();
132
- for (const row of Array.isArray(asterInfo) ? asterInfo : []) {
133
- const h = Number(row.fundingIntervalHours);
134
- if (typeof row.symbol === "string" && Number.isFinite(h) && h > 0)
135
- asterHours.set(row.symbol, h);
136
- }
137
- const addRate = (sym, exchange, rate, markPrice, fundingHours = null) => {
138
- if (!sym)
139
- return;
140
- if (!rateMap.has(sym))
141
- rateMap.set(sym, []);
142
- rateMap.get(sym).push({ exchange, rate, markPrice, fundingHours });
143
- };
144
- for (const p of pacAssets)
145
- addRate(p.symbol, "pacifica", p.funding, p.mark);
146
- for (const h of hlAssets)
147
- addRate(h.symbol, "hyperliquid", h.funding, h.markPx);
148
- // Lighter: join details + funding by marketId
149
- // SSOT rule #2: skip rows whose mark price cannot be resolved (no 0 fallback).
150
- // Prefer fr.markPrice; fall back to orderBook last-trade as documented
151
- // price-source preference (NOT error fallback).
152
- const ltPriceMap = new Map(ltDetails.map(d => [d.marketId, d.lastTradePrice]));
153
- const ltSymMap = new Map(ltDetails.map(d => [d.marketId, d.symbol]));
154
- for (const fr of ltFunding) {
155
- const sym = fr.symbol || ltSymMap.get(fr.marketId) || "";
156
- if (!sym)
157
- continue;
158
- const directMark = fr.markPrice;
159
- const fallbackMark = ltPriceMap.get(fr.marketId);
160
- let mp;
161
- if (directMark !== null && Number.isFinite(directMark) && directMark > 0) {
162
- mp = directMark;
163
- }
164
- else if (fallbackMark !== undefined && Number.isFinite(fallbackMark) && fallbackMark > 0) {
165
- mp = fallbackMark;
166
- }
167
- if (mp === undefined || !Number.isFinite(fr.rate))
168
- continue;
169
- addRate(sym, "lighter", fr.rate, mp);
170
- }
171
- // Aster: premiumIndex
172
- // SSOT rule #2: skip rows missing markPrice / lastFundingRate instead of
173
- // 0-coercing — a 0 markPrice would corrupt downstream sizing.
174
- for (const p of asterPremiums) {
175
- const rawSym = String(p.symbol ?? "");
176
- if (!rawSym.endsWith("USDT"))
177
- continue;
178
- const fundingRate = Number(p.lastFundingRate);
179
- const markPrice = Number(p.markPrice);
180
- if (!Number.isFinite(fundingRate) || !Number.isFinite(markPrice) || markPrice <= 0)
181
- continue;
182
- const hours = asterHours.get(rawSym);
183
- if (hours === undefined)
184
- continue; // not in /fapi/v3/fundingInfo → cannot normalize
185
- addRate(rawSym.replace(/USDT$/, ""), "aster", fundingRate, markPrice, hours);
122
+ for (const r of rates) {
123
+ if (r.basis.kind !== "perHours")
124
+ continue; // these four venues quote per hours
125
+ if (!rateMap.has(r.canonical))
126
+ rateMap.set(r.canonical, []);
127
+ rateMap.get(r.canonical).push({ exchange: r.exchange, rate: r.fundingRate, markPrice: r.markPrice, fundingHours: r.basis.hours });
186
128
  }
187
129
  return rateMap;
188
130
  }
@@ -420,7 +362,7 @@ export function registerArbManageCommands(program, getAdapterForExchange, isJson
420
362
  };
421
363
  // Collect spot balances per exchange
422
364
  const spotBalancesByExchange = new Map();
423
- // Bot/arb spot scope (§12.4 #3): configured venues with a separate spot balance + transfer.
365
+ // Bot/arb spot scope: configured venues with a separate spot balance + transfer.
424
366
  const configuredVenues = new Set(await listConfiguredAccountVenues());
425
367
  for (const def of listBotSpotVenues()) {
426
368
  if (!configuredVenues.has(def.id))
@@ -802,7 +744,7 @@ export function registerArbManageCommands(program, getAdapterForExchange, isJson
802
744
  await dropStaleStateEntry(sym, opts.yes === true, dryRun);
803
745
  return;
804
746
  }
805
- // Global quarantine (design §12.3): an unknown / unhedged outcome was recorded for
747
+ // Global quarantine: an unknown / unhedged outcome was recorded for
806
748
  // this symbol. A human may still close it, but only knowingly (--force).
807
749
  const quarantined = quarantineEntryFor(sym, (m) => console.error(chalk.red(m)));
808
750
  if (quarantined !== null) {
@@ -846,7 +788,7 @@ export function registerArbManageCommands(program, getAdapterForExchange, isJson
846
788
  console.log(chalk.cyan(` [DRY-RUN] Would close spot-perp ${sym}: sell spot on ${spotExch} + buy back the perp short on ${perpExch}`));
847
789
  return;
848
790
  }
849
- // One implementation for spot-perp close (fill-checked legs, §12.4 #5): arb-auto's handler.
791
+ // One implementation for spot-perp close (fill-checked legs): arb-auto's handler.
850
792
  if (handleSpotClose === undefined)
851
793
  throw new Error("spot-perp close handler is not registered (registerArbAutoCommands not called)");
852
794
  await handleSpotClose(sym, { spotExch, perpExch }, getAdapterForExchange, isJson);
@@ -982,7 +924,7 @@ export function registerArbManageCommands(program, getAdapterForExchange, isJson
982
924
  // Close the legs sequentially (long, then short), reduce-only and verified, each
983
925
  // sized from the venue's own position size. No retries: a definite failure of the
984
926
  // long close changes nothing; an unknown outcome or a leg left open after the
985
- // other closed quarantines the symbol (design §12.3).
927
+ // other closed quarantines the symbol.
986
928
  const results = [];
987
929
  const closeRes = await closePerpPairVerified({
988
930
  source: "arb-close", sym,
@@ -1,6 +1,7 @@
1
1
  import type { Network } from "../exchanges/venue-types.js";
2
2
  import { Command } from "commander";
3
3
  import type { ExchangeAdapter } from "../exchanges/index.js";
4
+ import type { FundingRateKind } from "../exchanges/interface.js";
4
5
  interface FundingSnapshot {
5
6
  symbol: string;
6
7
  pacRate: number;
@@ -17,7 +18,24 @@ interface FundingSnapshot {
17
18
  astMarkPrice: number;
18
19
  /** Aster per-symbol funding interval (= basis and settlement interval) from /fapi/v3/fundingInfo; null when Aster has no rate. */
19
20
  astHours: number | null;
21
+ /** Which rate each paired venue's value is (arb scan rateKind): "lastSettled" | "accruing" | "predicted" | null. */
22
+ rateKinds?: Record<string, FundingRateKind | null>;
23
+ /**
24
+ * Each venue's OWN symbol for this canonical row (Hyperliquid kPEPE, Lighter 1000PEPE): orders,
25
+ * position matching and closes use it, never the canonical key. Absent = the venue trades `symbol`.
26
+ */
27
+ venueSymbols?: Record<string, string>;
20
28
  }
29
+ /** The venue symbol a snapshot / position leg trades on `exchange` (the canonical key when the venue uses it). */
30
+ export declare function legSymbol(row: {
31
+ symbol: string;
32
+ venueSymbols?: Record<string, string>;
33
+ }, exchange: string): string;
34
+ /** --symbols matches a row by its canonical key or any venue's own symbol (kPEPE or 1000PEPE). */
35
+ export declare function matchesSymbolFilter(row: {
36
+ symbol: string;
37
+ venueSymbols?: Record<string, string>;
38
+ }, filter: readonly string[]): boolean;
21
39
  export interface LegOrderMinimums {
22
40
  exchange: string;
23
41
  minSize: string | null;
@@ -216,10 +234,16 @@ export declare function dexCloseBlockedBy(pos: {
216
234
  longDex: string;
217
235
  shortDex: string;
218
236
  }, failedDexes: Record<string, string>): string | null;
237
+ export declare function fetchFundingSpreads(): Promise<FundingSnapshot[]>;
219
238
  export declare let handleSpotClose: ((sym: string, opts: {
220
239
  spotExch?: string;
221
240
  perpExch?: string;
222
241
  }, getAdapterForExchange: (exchange: string) => Promise<import("../exchanges/interface.js").ExchangeAdapter>, isJson: () => boolean) => Promise<void>) | undefined;
242
+ /**
243
+ * `arb auto` flag validation: every numeric flag must be a finite number in its range, `--size` a
244
+ * number or "auto", `--settle-strategy` one of block | aggressive | off. INVALID_PARAMS otherwise.
245
+ */
246
+ export declare function assertArbAutoFlags(opts: Record<string, unknown>): void;
223
247
  export declare function registerArbAutoCommands(program: Command, getAdapterForExchange: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean, getHLAdapterForDex?: (dex: string) => Promise<import("../exchanges/hyperliquid.js").HyperliquidAdapter>): void;
224
248
  export declare let handleArbPnlRef: (() => Promise<void>) | undefined;
225
249
  /**