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
@@ -1,12 +1,12 @@
1
1
  /**
2
- * Venue-level data contracts (docs/DESIGN-venue-abstraction.md §10).
2
+ * Venue-level data contracts.
3
3
  *
4
4
  * SSOT Rule #2: every "unknown" value is `T | null`, never optional, never a
5
5
  * silently substituted default.
6
6
  */
7
7
  export const VENUE_IDS = [
8
8
  "pacifica", "hyperliquid", "lighter", "aster",
9
- "lighter-rh", "ondo", "arcus", "qfex",
9
+ "lighter-rh", "ondo", "arcus", "qfex", "risex", "txflow", "variational", "grvt", "extended",
10
10
  ];
11
11
  export const LEGACY_VENUE_IDS = ["pacifica", "hyperliquid", "lighter", "aster"];
12
12
  export function isVenueId(s) {
@@ -21,10 +21,17 @@ export function rateToHourly(rate, basis) {
21
21
  return rate / basis.hours;
22
22
  return rate / (24 * 365);
23
23
  }
24
- function decimalsOf(step) {
25
- const s = step.includes("e") || step.includes("E") ? Number(step).toFixed(20).replace(/0+$/, "") : step;
26
- const dot = s.indexOf(".");
27
- return dot === -1 ? 0 : s.length - dot - 1;
24
+ /**
25
+ * Quantity decimals of a lot step, or null when the step is not a power of ten ≤ 1 ("0.001" → 3,
26
+ * "1" → 0; TxFlow "10" / "100" and a "0.5" step → null): a decimals count cannot express them, and
27
+ * 0 next to stepSize "10" read as "any integer". `stepSize` is the authoritative grid.
28
+ */
29
+ export function decimalsOf(step) {
30
+ const s = step.includes("e") || step.includes("E") ? Number(step).toFixed(20) : step;
31
+ const t = s.includes(".") ? s.replace(/0+$/, "").replace(/\.$/, "") : s;
32
+ if (!/^(1|0\.0*1)$/.test(t))
33
+ return null;
34
+ return t === "1" ? 0 : t.length - 2;
28
35
  }
29
36
  /** Pure mapping used by new-venue adapters to serve the legacy getMarkets(). */
30
37
  /** Tick/step/leverage required for order entry. Throws when the venue did not publish them. */
@@ -34,9 +41,54 @@ export function requireTradingPrecision(m) {
34
41
  }
35
42
  return { tickSize: m.tickSize, stepSize: m.stepSize, maxLeverage: m.maxLeverage };
36
43
  }
37
- export function toLegacyMarketInfo(m) {
38
- const p = requireTradingPrecision(m);
44
+ /**
45
+ * What a VenueMarket.fundingRate is per venue (REST source), consistent with the WS `rateKind` where
46
+ * both read the same field. null = not verified. Venues with per-row kinds (GRVT, Extended,
47
+ * Variational) set their own on top.
48
+ * - arcus: /v1/markets fundingRate = the last hourly settlement (WS markets channel: lastSettled).
49
+ * - risex: current_funding_rate equals the last settled print (venues/risex.ts, verified 2026-10-14).
50
+ * - txflow: allAssetCtxs `funding` moves every tick — the running period's accruing estimate.
51
+ * - hyperliquid: metaAndAssetCtxs `funding` (WS allDexsAssetCtxs) — accruing.
52
+ * - aster: premiumIndex lastFundingRate moves during the period (WS markPrice@arr `r`) — accruing.
53
+ * - pacifica: /info/prices `funding` = the last settled hourly rate (WS prices `funding`).
54
+ * - lighter, lighter-rh: WS market_stats funding_rate = the newest /fundings print — lastSettled.
55
+ * - grvt: the ticker's funding rate is the next settlement's (predicted) — as the adapter labels it.
56
+ * - extended: /info/markets fundingRate is the current hour's accruing rate.
57
+ * - variational: the quoted rate is the predicted next payment.
58
+ * - ondo, qfex: not verified against settled prints → null.
59
+ */
60
+ const VENUE_MARKET_RATE_KIND = {
61
+ arcus: "lastSettled",
62
+ risex: "lastSettled",
63
+ txflow: "accruing",
64
+ hyperliquid: "accruing",
65
+ aster: "accruing",
66
+ pacifica: "lastSettled",
67
+ lighter: "lastSettled",
68
+ "lighter-rh": "lastSettled",
69
+ grvt: "predicted",
70
+ extended: "accruing",
71
+ variational: "predicted",
72
+ // Ondo /v1/perps/contracts `fundingRate` is the last settled print: live 2026-10-19 21:17 UTC it
73
+ // equalled the 21:00 funding_rate_history row on every market whose rate moves (CXMT 0.0001139,
74
+ // BB 0.00003, PURR 0.0000182, WTI 0.0000038, USDJPY 0.000001 — each different from the 20:00 print).
75
+ ondo: "lastSettled",
76
+ };
77
+ export function fundingRateKindOf(venue, fundingRate) {
78
+ if (fundingRate === null)
79
+ return null;
80
+ const k = VENUE_MARKET_RATE_KIND[venue];
81
+ return k === undefined ? null : k;
82
+ }
83
+ /**
84
+ * THE market-row shape (`perp market list` / `info` / `prices`, ExchangeMarketInfo) of a
85
+ * VenueMarket — the one mapper for the CLI adapters of VenueMarket venues and the keyless MCP
86
+ * `get_markets` public directory rows. It does not require trading precision: a public row
87
+ * without a published tick / lot / leverage maps those to null.
88
+ */
89
+ export function venueMarketRow(m) {
39
90
  const hours = m.fundingRateBasis.kind === "perHours" ? m.fundingRateBasis.hours : null;
91
+ const status = m.status;
40
92
  return {
41
93
  symbol: m.symbol,
42
94
  markPrice: m.markPrice,
@@ -44,13 +96,22 @@ export function toLegacyMarketInfo(m) {
44
96
  fundingRate: m.fundingRate,
45
97
  volume24h: m.volume24h,
46
98
  openInterest: m.openInterest,
47
- maxLeverage: p.maxLeverage,
48
- sizeDecimals: decimalsOf(p.stepSize),
49
- stepSize: p.stepSize,
99
+ maxLeverage: m.maxLeverage,
100
+ sizeDecimals: m.stepSize === null ? null : decimalsOf(m.stepSize),
101
+ stepSize: m.stepSize,
50
102
  fundingHours: hours,
51
103
  marginTiers: m.marginTiers,
104
+ tradable: m.tradable,
105
+ fundingRateKind: fundingRateKindOf(m.venue, m.fundingRate),
106
+ quoteAsset: m.quoteAsset,
107
+ status: typeof status === "string" ? status : null,
52
108
  };
53
109
  }
110
+ /** venueMarketRow for an adapter that trades the market: tick / lot / leverage must be published. */
111
+ export function toLegacyMarketInfo(m) {
112
+ requireTradingPrecision(m);
113
+ return venueMarketRow(m);
114
+ }
54
115
  /**
55
116
  * Attach an execution record to a result built without one. Fields of `partial`
56
117
  * that are absent are recorded as null (unknown), never defaulted to a value.
@@ -85,3 +146,13 @@ export function parseOrderRef(s) {
85
146
  return { kind: "clientOrderId", value: s.slice(2) };
86
147
  return { kind: "orderId", value: s };
87
148
  }
149
+ /**
150
+ * Next funding settlement of a venue that documents hourly settlement on the hour (UTC): the next
151
+ * top of the hour after `now`. Used only where the venue's docs say so — Hyperliquid ("paid every
152
+ * hour"), Lighter ("at each hour mark"), Pacifica ("at the end of each 1-hour interval") — and
153
+ * their settled history is stamped on the hour (live 2026-10-19: HL 18:00:00.139 … 21:00:00.032,
154
+ * Pacifica 18:00:02.674 … 21:00:02.574). Other venues keep null unless they publish the time.
155
+ */
156
+ export function nextTopOfHourMs(now = Date.now()) {
157
+ return (Math.floor(now / 3_600_000) + 1) * 3_600_000;
158
+ }
@@ -2,14 +2,16 @@
2
2
  * Venue definitions — the single source of truth for "which venue, on which
3
3
  * network, signed how, funded how, supporting what".
4
4
  *
5
- * See docs/DESIGN-venue-abstraction.md §10. One adapter class may serve several
5
+ * One adapter class may serve several
6
6
  * venues (LighterAdapter serves `lighter` and `lighter-rh`).
7
7
  *
8
8
  * Venue definition modules must load adapter code lazily (`import()` inside
9
9
  * `create`) so that public-data paths never pull in heavy SDKs (lighter WASM).
10
10
  */
11
- import type { ExchangeAdapter } from "./interface.js";
12
- import type { SpotAdapter } from "./spot-interface.js";
11
+ import { PerpError } from "../errors.js";
12
+ import type { ExchangeAdapter, ExchangeBalance, ExchangePosition } from "./interface.js";
13
+ import { type AddressKind } from "./address.js";
14
+ import type { SpotAdapter, SpotBalance } from "./spot-interface.js";
13
15
  import type { FundingRateBasis, LegacyVenueId, Network, TimeInForce, VenueFundingRate, VenueId, VenueMarket } from "./venue-types.js";
14
16
  import { isVenueId } from "./venue-types.js";
15
17
  import type { AnyAgentMeta } from "../settings.js";
@@ -103,7 +105,75 @@ export interface RateLimitPolicy {
103
105
  }
104
106
  export interface VenuePublicData {
105
107
  fetchFundingRates(net: Network): Promise<VenueFundingRate[]>;
106
- fetchMarkets(net: Network): Promise<VenueMarket[]>;
108
+ /**
109
+ * `opts.funding: false` = the caller needs marks / precision only: a venue whose funding rate
110
+ * costs an extra read (Lighter: the WS market_stats snapshot) skips it (rows fundingRate null).
111
+ */
112
+ fetchMarkets(net: Network, opts?: {
113
+ funding?: boolean;
114
+ }): Promise<VenueMarket[]>;
115
+ /**
116
+ * Keyless open positions of ANY account by its public address (`account positions --address`,
117
+ * MCP get_positions_by_address). Present = the venue supports it; `addressKind` is then the
118
+ * address format it takes (checked before any request). Rows are the keyed `getPositions`
119
+ * mapping (the same mapper) with plain symbols.
120
+ */
121
+ addressKind?: AddressKind;
122
+ fetchPositionsByAddress?(net: Network, address: string, opts: PublicPositionsOptions): Promise<PublicPositionsResult>;
123
+ /**
124
+ * Keyless balances of ANY account by its public address (`account balance --address`, MCP
125
+ * get_balance_by_address). Present = the venue supports it (takes `addressKind`, like
126
+ * fetchPositionsByAddress). The balance is the keyed `getBalance` math (the same function).
127
+ */
128
+ fetchBalanceByAddress?(net: Network, address: string, opts: PublicPositionsOptions): Promise<PublicBalanceResult>;
129
+ }
130
+ /** One Lighter account (sub-account) of an address: its balance and spot assets. */
131
+ export interface PublicAccountBalance {
132
+ accountIndex: number;
133
+ balance: ExchangeBalance;
134
+ spot: SpotBalance[];
135
+ }
136
+ export interface PublicBalanceResult {
137
+ /** The account's balance (the keyed getBalance fields + the venue's extras); null on Lighter (per account in `accounts`). */
138
+ balance: ExchangeBalance | null;
139
+ /** Spot token balances where the venue publishes them by address; null = not published (nothing invented). */
140
+ spot: SpotBalance[] | null;
141
+ /** Lighter / Lighter RH: every account of the address (or the one of accountIndex); null elsewhere. */
142
+ accounts: PublicAccountBalance[] | null;
143
+ /** Hyperliquid: HIP-3 dexes with a non-zero account value (the dex's own perp clearinghouse); null elsewhere. */
144
+ dexes: Record<string, ExchangeBalance> | null;
145
+ /** Hyperliquid: the account abstraction mode the balance was read under; null elsewhere. */
146
+ accountMode: string | null;
147
+ /** Sub-reads that failed (Hyperliquid: one HIP-3 dex, "hip3:<dex>") — unknown, never zero. */
148
+ errors: Record<string, {
149
+ code: string;
150
+ message: string;
151
+ }>;
152
+ notes: string[];
153
+ }
154
+ export interface PublicPositionsOptions {
155
+ /** Lighter: one account of the address (sub-accounts); default every account. */
156
+ accountIndex?: number;
157
+ }
158
+ /** A position read by public address: the keyed row plus where it lives. */
159
+ export interface PublicPosition extends ExchangePosition {
160
+ /** Hyperliquid: the HIP-3 dex of the row; null = the native perp dex. */
161
+ dex?: string | null;
162
+ /** Lighter: the account (sub-account) index the row belongs to. */
163
+ accountIndex?: number;
164
+ }
165
+ export interface PublicPositionsResult {
166
+ positions: PublicPosition[];
167
+ /**
168
+ * Sub-reads that failed while the others succeeded (Hyperliquid: one HIP-3 dex), keyed by
169
+ * source ("hip3:xyz"). Their rows are unknown — never silently absent: callers surface them.
170
+ */
171
+ errors: Record<string, {
172
+ code: string;
173
+ message: string;
174
+ }>;
175
+ /** Human notes on an empty answer ("no Lighter account for this address"). */
176
+ notes: string[];
107
177
  }
108
178
  export interface VenueCredentials {
109
179
  /** Raw private key from --private-key or (legacy venues only) env. */
@@ -127,11 +197,29 @@ export interface VenueCreateContext {
127
197
  export declare const READ_ONLY_CREDENTIALS: VenueCredentials;
128
198
  export type VenueRole = "marketData" | "account" | "trading";
129
199
  export type SymbolIdentity = "baseTicker" | "explicit";
200
+ /**
201
+ * How perp-cli reaches the venue. "official" = a documented public API. "unofficial" = an
202
+ * undocumented API (e.g. the one the venue's own web app uses) that may change or be blocked
203
+ * without notice; every user-facing surface (JSON `venueApi` / `apiStatus`, human footnotes,
204
+ * MCP output, the landing screen, the skill docs) labels data read through it.
205
+ */
206
+ export type VenueApiStatus = {
207
+ kind: "official";
208
+ } | {
209
+ kind: "unofficial";
210
+ note: string;
211
+ docs: string | null;
212
+ };
213
+ export declare const OFFICIAL_API: VenueApiStatus;
214
+ /** "official" | "unofficial" — the per-row label for mixed-venue tables and JSON. */
215
+ export declare function apiStatusOf(venue: VenueId): VenueApiStatus["kind"];
216
+ /** Human footnote for an unofficial-API venue; null for an official one. */
217
+ export declare function unofficialApiNote(def: VenueDefinition): string | null;
130
218
  export interface VenueDefinition {
131
219
  id: VenueId;
132
220
  aliases: readonly string[];
133
221
  displayName: string;
134
- family: "pacifica" | "hyperliquid" | "lighter" | "aster" | "ondo" | "arcus" | "qfex";
222
+ family: "pacifica" | "hyperliquid" | "lighter" | "aster" | "ondo" | "arcus" | "qfex" | "risex" | "txflow" | "variational" | "grvt" | "extended";
135
223
  masterKeyKind: MasterKeyKind;
136
224
  agentKeyKind: AgentKeyKind;
137
225
  secretStorage: SecretStorage;
@@ -143,12 +231,22 @@ export interface VenueDefinition {
143
231
  settlementIntervalHours: number | "perMarket";
144
232
  /**
145
233
  * false = the unit / meaning of the venue's published funding rate is not verified against
146
- * settled funding payments yet (design §14.12 #6). Such a venue is left out of cross-venue
234
+ * settled funding payments yet. Such a venue is left out of cross-venue
147
235
  * funding comparison (fetchVenuesRates drops it unless includeUnverified is passed), its
148
236
  * adapter reports fundingRate null to bots, and funding-arb strategies refuse it as a leg.
149
237
  */
150
238
  fundingVerified: boolean;
151
239
  roles: readonly VenueRole[];
240
+ /**
241
+ * Roles that are implemented but switched off in this release (phased venue rollout, e.g.
242
+ * RISEx trading in 0.15.0). They are NOT in `roles`; roleUnsupported() uses `reason` /
243
+ * `remediation` for them. null = nothing held back.
244
+ */
245
+ gatedRoles: {
246
+ roles: readonly VenueRole[];
247
+ reason: string;
248
+ remediation: string;
249
+ } | null;
152
250
  /** null when the venue has no trading role. */
153
251
  capabilities: VenueCapabilities | null;
154
252
  rateLimit: RateLimitPolicy;
@@ -159,11 +257,10 @@ export interface VenueDefinition {
159
257
  publicData: VenuePublicData;
160
258
  /** null when the venue cannot produce an adapter (public data only). */
161
259
  create: ((ctx: VenueCreateContext) => Promise<ExchangeAdapter>) | null;
162
- /** null = venue has no spot trading (§12). */
260
+ /** null = venue has no spot trading. */
163
261
  spot: VenueSpotDefinition | null;
164
262
  /**
165
- * Largest slippage bound (bps) a market-type order may carry on this venue
166
- * (design §13.10 #1). resolveSlippage() rejects anything above it with
263
+ * Largest slippage bound (bps) a market-type order may carry on this venue. resolveSlippage() rejects anything above it with
167
264
  * INVALID_PARAMS before sending — never clamps.
168
265
  */
169
266
  maxSlippageBps: number;
@@ -173,6 +270,13 @@ export interface VenueDefinition {
173
270
  * settings.builder.<venue> and the approval is confirmed.
174
271
  */
175
272
  builderFee: BuilderFeeCapability | null;
273
+ /** Official documented API, or an unofficial one that is labelled everywhere (VenueApiStatus). */
274
+ apiStatus: VenueApiStatus;
275
+ /**
276
+ * Remediation of every role refusal (roleUnsupported) on a venue that is read-only in this
277
+ * version (Variational, GRVT, Extended). Absent = the generic market-data-only remediation.
278
+ */
279
+ readOnlyRemediation?: string;
176
280
  }
177
281
  export declare function registerVenue(def: VenueDefinition): void;
178
282
  /** Resolve a venue id or alias. Throws UNKNOWN_VENUE. */
@@ -181,7 +285,33 @@ export declare function tryResolveVenueId(nameOrAlias: string): VenueId | null;
181
285
  export declare function getVenue(nameOrAlias: string): VenueDefinition;
182
286
  export declare function listVenues(): VenueDefinition[];
183
287
  export declare function listVenuesWithRole(role: VenueRole): VenueDefinition[];
288
+ /** Venues that read positions by public address (VenuePublicData.fetchPositionsByAddress). */
289
+ export declare function listPublicPositionVenues(): VenueDefinition[];
290
+ /** CAPABILITY_UNSUPPORTED for a venue without public-address position reads (registry-derived). */
291
+ export declare function publicPositionsUnsupported(def: VenueDefinition): PerpError;
292
+ /**
293
+ * Open positions of `address` on `venue`, keyless: unsupported venue → CAPABILITY_UNSUPPORTED,
294
+ * wrong address format → INVALID_PARAMS, both before any request.
295
+ */
296
+ export declare function fetchPositionsByAddress(venue: string, net: Network, address: string, opts?: PublicPositionsOptions): Promise<PublicPositionsResult>;
297
+ /** Venues that read balances by public address (VenuePublicData.fetchBalanceByAddress). */
298
+ export declare function listPublicBalanceVenues(): VenueDefinition[];
299
+ /** CAPABILITY_UNSUPPORTED for a venue without public-address balance reads (registry-derived). */
300
+ export declare function publicBalanceUnsupported(def: VenueDefinition): PerpError;
301
+ /**
302
+ * Balances of `address` on `venue`, keyless: unsupported venue → CAPABILITY_UNSUPPORTED, wrong
303
+ * address format → INVALID_PARAMS, both before any request.
304
+ */
305
+ export declare function fetchBalanceByAddress(venue: string, net: Network, address: string, opts?: PublicPositionsOptions): Promise<PublicBalanceResult>;
184
306
  export declare function networkConfig(def: VenueDefinition, net: Network): VenueNetworkConfig;
307
+ /**
308
+ * The uniform refusal for any account / trading / funds / agent / builder / bot action on a venue
309
+ * whose roles do not include `role` (a market-data-only venue such as TxFlow). Raised before any
310
+ * credential is resolved or any request is sent.
311
+ */
312
+ export declare function roleUnsupported(def: VenueDefinition, feature: string, role?: VenueRole): PerpError;
313
+ /** Throws roleUnsupported() unless the venue has `role`. */
314
+ export declare function requireRole(def: VenueDefinition, role: VenueRole, feature: string): void;
185
315
  export declare function requireCapabilities(def: VenueDefinition): VenueCapabilities;
186
316
  /**
187
317
  * A venue reference as it appears in stored data and funding keys:
@@ -194,7 +324,7 @@ export interface VenueRef {
194
324
  export declare function parseVenueRef(ref: string): VenueRef;
195
325
  /**
196
326
  * Funding basis for a venue ref. HIP-3 dexes use the Hyperliquid core basis
197
- * (current behavior preserved; see design §10.1 — verify against live data).
327
+ * (current behavior preserved; verify against live data).
198
328
  * Throws for venues whose basis is per-market; callers must use the market's value.
199
329
  */
200
330
  export declare function fundingBasisOf(ref: string): FundingRateBasis;
@@ -202,9 +332,18 @@ export declare function settlementIntervalOf(ref: string): number;
202
332
  /**
203
333
  * Refuse a venue whose funding rate is unverified (VenueDefinition.fundingVerified false) as a
204
334
  * leg of a funding strategy (arb scan ranking, arb exec / auto, funding bots). Uniform
205
- * CAPABILITY_UNSUPPORTED (design §13.6, §14.12 #6). Unknown venue refs throw UNKNOWN_VENUE.
335
+ * CAPABILITY_UNSUPPORTED. Unknown venue refs throw UNKNOWN_VENUE.
206
336
  */
207
337
  export declare function assertFundingArbVenue(ref: string): void;
338
+ /**
339
+ * Market rows (`market list|prices|info`, MCP get_markets) state whether their venue's funding rate
340
+ * is verified — on every venue, true or false (VenueDefinition.fundingVerified), so a consumer
341
+ * never has to read a missing field as "verified". A venue ref ("hip3:xyz" → hyperliquid) is
342
+ * resolved; an unknown ref leaves the rows as they are.
343
+ */
344
+ export declare function withFundingVerified<T extends object>(venue: string, rows: T[]): Array<T & {
345
+ fundingVerified: boolean;
346
+ }> | T[];
208
347
  export declare function isLegacyVenue(def: VenueDefinition): def is VenueDefinition & {
209
348
  id: LegacyVenueId;
210
349
  };
@@ -2,17 +2,30 @@
2
2
  * Venue definitions — the single source of truth for "which venue, on which
3
3
  * network, signed how, funded how, supporting what".
4
4
  *
5
- * See docs/DESIGN-venue-abstraction.md §10. One adapter class may serve several
5
+ * One adapter class may serve several
6
6
  * venues (LighterAdapter serves `lighter` and `lighter-rh`).
7
7
  *
8
8
  * Venue definition modules must load adapter code lazily (`import()` inside
9
9
  * `create`) so that public-data paths never pull in heavy SDKs (lighter WASM).
10
10
  */
11
11
  import { PerpError } from "../errors.js";
12
+ import { requireAddressOfKind } from "./address.js";
12
13
  import { isVenueId } from "./venue-types.js";
14
+ import { optionsVenueWords } from "./options-venues.js";
13
15
  export const READ_ONLY_CREDENTIALS = {
14
16
  pk: null, owsWallet: null, owsPassphrase: null, owsKeyToken: null, agent: null,
15
17
  };
18
+ export const OFFICIAL_API = { kind: "official" };
19
+ /** "official" | "unofficial" — the per-row label for mixed-venue tables and JSON. */
20
+ export function apiStatusOf(venue) {
21
+ return getVenue(venue).apiStatus.kind;
22
+ }
23
+ /** Human footnote for an unofficial-API venue; null for an official one. */
24
+ export function unofficialApiNote(def) {
25
+ return def.apiStatus.kind === "unofficial"
26
+ ? `${def.displayName}: UNOFFICIAL API — ${def.apiStatus.note}`
27
+ : null;
28
+ }
16
29
  // ── Registry ──
17
30
  const _venues = new Map();
18
31
  const _aliases = new Map();
@@ -29,8 +42,12 @@ export function registerVenue(def) {
29
42
  }
30
43
  }
31
44
  function unknownVenue(input) {
45
+ // An options venue (e.g. derive) is not a perp venue: say where it works instead.
46
+ const optionsOnly = optionsVenueWords().includes(input.toLowerCase())
47
+ ? ` — ${input} is options-only: perp -e ${input} options …`
48
+ : "";
32
49
  return new PerpError("UNKNOWN_VENUE", `Unknown venue: "${input}"`, {
33
- remediation: `Use one of: ${[..._venues.keys()].join(", ")}`,
50
+ remediation: `Use one of: ${[..._venues.keys()].join(", ")}${optionsOnly}`,
34
51
  });
35
52
  }
36
53
  /** Resolve a venue id or alias. Throws UNKNOWN_VENUE. */
@@ -56,6 +73,64 @@ export function listVenues() {
56
73
  export function listVenuesWithRole(role) {
57
74
  return listVenues().filter(v => v.roles.includes(role));
58
75
  }
76
+ /** Venues that read positions by public address (VenuePublicData.fetchPositionsByAddress). */
77
+ export function listPublicPositionVenues() {
78
+ return listVenues().filter(v => typeof v.publicData.fetchPositionsByAddress === "function" && v.publicData.addressKind !== undefined);
79
+ }
80
+ /** CAPABILITY_UNSUPPORTED for a venue without public-address position reads (registry-derived). */
81
+ export function publicPositionsUnsupported(def) {
82
+ const reason = !def.roles.includes("account")
83
+ ? `${def.displayName} is available for market data only (no account reads)`
84
+ : `${def.displayName} publishes positions only to the account itself (signed or API-key reads)`;
85
+ const supported = listPublicPositionVenues().map(v => v.id).join(", ");
86
+ return new PerpError("CAPABILITY_UNSUPPORTED", `${def.id} does not support publicPositions — ${reason}`, {
87
+ venue: def.id, feature: "publicPositions", reason,
88
+ remediation: `Public-address reads: ${supported}; for your own account: perp -e ${def.id} account positions`,
89
+ });
90
+ }
91
+ /**
92
+ * Open positions of `address` on `venue`, keyless: unsupported venue → CAPABILITY_UNSUPPORTED,
93
+ * wrong address format → INVALID_PARAMS, both before any request.
94
+ */
95
+ export async function fetchPositionsByAddress(venue, net, address, opts = {}) {
96
+ const def = getVenue(venue);
97
+ const pd = def.publicData;
98
+ if (pd.fetchPositionsByAddress === undefined || pd.addressKind === undefined)
99
+ throw publicPositionsUnsupported(def);
100
+ if (opts.accountIndex !== undefined && def.id !== "lighter" && def.id !== "lighter-rh") {
101
+ throw new PerpError("INVALID_PARAMS", `--account-index applies to Lighter only (${def.id} has one account per address)`, { exchange: def.id, remediation: "Drop --account-index" });
102
+ }
103
+ return pd.fetchPositionsByAddress(net, requireAddressOfKind(def.id, pd.addressKind, address), opts);
104
+ }
105
+ /** Venues that read balances by public address (VenuePublicData.fetchBalanceByAddress). */
106
+ export function listPublicBalanceVenues() {
107
+ return listVenues().filter(v => typeof v.publicData.fetchBalanceByAddress === "function" && v.publicData.addressKind !== undefined);
108
+ }
109
+ /** CAPABILITY_UNSUPPORTED for a venue without public-address balance reads (registry-derived). */
110
+ export function publicBalanceUnsupported(def) {
111
+ const reason = !def.roles.includes("account")
112
+ ? `${def.displayName} is available for market data only (no account reads)`
113
+ : `${def.displayName} publishes balances only to the account itself (signed or API-key reads)`;
114
+ const supported = listPublicBalanceVenues().map(v => v.id).join(", ");
115
+ return new PerpError("CAPABILITY_UNSUPPORTED", `${def.id} does not support publicBalance — ${reason}`, {
116
+ venue: def.id, feature: "publicBalance", reason,
117
+ remediation: `Public-address reads: ${supported}; for your own account: perp -e ${def.id} portfolio`,
118
+ });
119
+ }
120
+ /**
121
+ * Balances of `address` on `venue`, keyless: unsupported venue → CAPABILITY_UNSUPPORTED, wrong
122
+ * address format → INVALID_PARAMS, both before any request.
123
+ */
124
+ export async function fetchBalanceByAddress(venue, net, address, opts = {}) {
125
+ const def = getVenue(venue);
126
+ const pd = def.publicData;
127
+ if (pd.fetchBalanceByAddress === undefined || pd.addressKind === undefined)
128
+ throw publicBalanceUnsupported(def);
129
+ if (opts.accountIndex !== undefined && def.id !== "lighter" && def.id !== "lighter-rh") {
130
+ throw new PerpError("INVALID_PARAMS", `--account-index applies to Lighter only (${def.id} has one account per address)`, { exchange: def.id, remediation: "Drop --account-index" });
131
+ }
132
+ return pd.fetchBalanceByAddress(net, requireAddressOfKind(def.id, pd.addressKind, address), opts);
133
+ }
59
134
  export function networkConfig(def, net) {
60
135
  const cfg = def.networks[net];
61
136
  if (!cfg) {
@@ -65,7 +140,41 @@ export function networkConfig(def, net) {
65
140
  }
66
141
  return cfg;
67
142
  }
143
+ /**
144
+ * The uniform refusal for any account / trading / funds / agent / builder / bot action on a venue
145
+ * whose roles do not include `role` (a market-data-only venue such as TxFlow). Raised before any
146
+ * credential is resolved or any request is sent.
147
+ */
148
+ export function roleUnsupported(def, feature, role = "trading") {
149
+ if (def.gatedRoles !== null && def.gatedRoles.roles.includes(role)) {
150
+ const { reason, remediation } = def.gatedRoles;
151
+ return new PerpError("CAPABILITY_UNSUPPORTED", `${def.id} does not support ${feature} — ${reason}`, {
152
+ venue: def.id, feature, reason, remediation,
153
+ });
154
+ }
155
+ const reason = def.apiStatus.kind === "unofficial"
156
+ ? `${def.displayName} is integrated through an unofficial read-only API (market data only)`
157
+ : `${def.displayName} is available for market data only (no ${role} role)`;
158
+ // Precedence: gatedRoles (above: a role switched off this release, e.g. RISEx) → the venue's own
159
+ // readOnlyRemediation (official read-only venues, e.g. Variational) → the generic text (TxFlow).
160
+ const remediation = def.readOnlyRemediation !== undefined
161
+ ? def.readOnlyRemediation
162
+ : def.apiStatus.kind === "unofficial"
163
+ ? `Trade on the ${def.displayName} website; perp-cli shows ${def.displayName} market data only (perp -e ${def.id} market list|info|funding|book|trades|kline)`
164
+ : `Use the market-data commands for ${def.id} (perp -e ${def.id} market list|funding)`;
165
+ return new PerpError("CAPABILITY_UNSUPPORTED", `${def.id} does not support ${feature} — ${reason}`, {
166
+ venue: def.id, feature, reason, remediation,
167
+ });
168
+ }
169
+ /** Throws roleUnsupported() unless the venue has `role`. */
170
+ export function requireRole(def, role, feature) {
171
+ if (!def.roles.includes(role))
172
+ throw roleUnsupported(def, feature, role);
173
+ }
68
174
  export function requireCapabilities(def) {
175
+ // A venue without the trading role is a definite capability refusal, not "not implemented yet".
176
+ if (!def.roles.includes("trading"))
177
+ throw roleUnsupported(def, "trading");
69
178
  if (!def.capabilities) {
70
179
  throw new PerpError("NOT_IMPLEMENTED", `${def.displayName} does not support trading via API`, {
71
180
  remediation: `${def.id} is available for market data only (funding/market commands)`,
@@ -88,7 +197,7 @@ export function parseVenueRef(ref) {
88
197
  }
89
198
  /**
90
199
  * Funding basis for a venue ref. HIP-3 dexes use the Hyperliquid core basis
91
- * (current behavior preserved; see design §10.1 — verify against live data).
200
+ * (current behavior preserved; verify against live data).
92
201
  * Throws for venues whose basis is per-market; callers must use the market's value.
93
202
  */
94
203
  export function fundingBasisOf(ref) {
@@ -114,7 +223,7 @@ export function settlementIntervalOf(ref) {
114
223
  /**
115
224
  * Refuse a venue whose funding rate is unverified (VenueDefinition.fundingVerified false) as a
116
225
  * leg of a funding strategy (arb scan ranking, arb exec / auto, funding bots). Uniform
117
- * CAPABILITY_UNSUPPORTED (design §13.6, §14.12 #6). Unknown venue refs throw UNKNOWN_VENUE.
226
+ * CAPABILITY_UNSUPPORTED. Unknown venue refs throw UNKNOWN_VENUE.
118
227
  */
119
228
  export function assertFundingArbVenue(ref) {
120
229
  const def = getVenue(parseVenueRef(ref).venue);
@@ -126,6 +235,19 @@ export function assertFundingArbVenue(ref) {
126
235
  remediation: `Use venues with verified funding (${listVenues().filter(v => v.fundingVerified && v.roles.includes("trading")).map(v => v.id).join(", ")}). ${def.displayName} rates can still be viewed: perp arb scan --rates --include-unverified`,
127
236
  });
128
237
  }
238
+ /**
239
+ * Market rows (`market list|prices|info`, MCP get_markets) state whether their venue's funding rate
240
+ * is verified — on every venue, true or false (VenueDefinition.fundingVerified), so a consumer
241
+ * never has to read a missing field as "verified". A venue ref ("hip3:xyz" → hyperliquid) is
242
+ * resolved; an unknown ref leaves the rows as they are.
243
+ */
244
+ export function withFundingVerified(venue, rows) {
245
+ const id = tryResolveVenueId(venue.startsWith("hip3:") ? "hyperliquid" : venue);
246
+ const def = id === null ? undefined : _venues.get(id);
247
+ if (def === undefined)
248
+ return rows;
249
+ return rows.map(r => ({ ...r, fundingVerified: def.fundingVerified }));
250
+ }
129
251
  export function isLegacyVenue(def) {
130
252
  return def.id === "pacifica" || def.id === "hyperliquid" || def.id === "lighter" || def.id === "aster";
131
253
  }
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * Arcus — perps on the Robinhood chain rootchain, USDG collateral
3
- * (design §4 Arcus column, §10.4, §10.10).
4
3
  * Not registered here; the registry module owns registerVenue().
5
- * Adapter and public-data code are loaded lazily (§10.10).
4
+ * Adapter and public-data code are loaded lazily.
6
5
  */
7
6
  import type { VenueDefinition } from "../venue.js";
8
7
  export declare const arcusVenue: VenueDefinition;
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * Arcus — perps on the Robinhood chain rootchain, USDG collateral
3
- * (design §4 Arcus column, §10.4, §10.10).
4
3
  * Not registered here; the registry module owns registerVenue().
5
- * Adapter and public-data code are loaded lazily (§10.10).
4
+ * Adapter and public-data code are loaded lazily.
6
5
  */
7
6
  import { BUILDER_FEE_CAPABILITIES } from "../builder-fee.js";
8
7
  const publicData = {
@@ -50,6 +49,7 @@ export const arcusVenue = {
50
49
  settlementIntervalHours: 1,
51
50
  fundingVerified: true,
52
51
  roles: ["marketData", "account", "trading"],
52
+ gatedRoles: null,
53
53
  capabilities: {
54
54
  marketOrder: "slippageBounded",
55
55
  // GTC = GTT at goodTil now + 40 days (ARCUS_GOOD_TIL_HORIZON_MS); GTT kept as the venue alias.
@@ -83,7 +83,7 @@ export const arcusVenue = {
83
83
  restrictedRegions: ["US", "CA", "GB"],
84
84
  symbolIdentity: "baseTicker",
85
85
  publicData,
86
- // §12: market swaps via router.spot.arcus.xyz, settled on-chain from the master EVM wallet.
86
+ // market swaps via router.spot.arcus.xyz, settled on-chain from the master EVM wallet.
87
87
  spot: {
88
88
  accountModel: "wallet",
89
89
  quoteAssets: { mainnet: ["USDG"], testnet: ["USDG"] },
@@ -99,9 +99,10 @@ export const arcusVenue = {
99
99
  return spot;
100
100
  },
101
101
  },
102
- // Arcus venue limit per design §13.10 #1.
102
+ // Arcus venue slippage limit.
103
103
  maxSlippageBps: 1000,
104
104
  builderFee: BUILDER_FEE_CAPABILITIES["arcus"],
105
+ apiStatus: { kind: "official" },
105
106
  async create(ctx) {
106
107
  const { ArcusAdapter } = await import("../arcus/adapter.js");
107
108
  return new ArcusAdapter(arcusVenue, ctx);
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Aster venue definition (design §10.1, §10.8, §10.13 #4).
3
- * Adapter + public-data code are loaded lazily (§10.10).
2
+ * Aster venue definition.
3
+ * Adapter + public-data code are loaded lazily.
4
4
  */
5
5
  import type { VenueDefinition } from "../venue.js";
6
6
  /** Aster client order id (V3 futures "New Order" `newClientOrderId` rule). */
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Aster venue definition (design §10.1, §10.8, §10.13 #4).
3
- * Adapter + public-data code are loaded lazily (§10.10).
2
+ * Aster venue definition.
3
+ * Adapter + public-data code are loaded lazily.
4
4
  */
5
5
  import { BUILDER_FEE_CAPABILITIES } from "../builder-fee.js";
6
6
  /** Aster client order id (V3 futures "New Order" `newClientOrderId` rule). */
@@ -122,6 +122,7 @@ export const asterVenue = {
122
122
  settlementIntervalHours: "perMarket",
123
123
  fundingVerified: true,
124
124
  roles: ["marketData", "account", "trading"],
125
+ gatedRoles: null,
125
126
  capabilities: {
126
127
  // Market = IOC LIMIT at mark ± slippage (adapter marketOrder), never native MARKET.
127
128
  marketOrder: "slippageBounded",
@@ -146,12 +147,12 @@ export const asterVenue = {
146
147
  },
147
148
  // GET /fapi/v1/exchangeInfo rateLimits: REQUEST_WEIGHT 2400/min
148
149
  // (ORDERS 1200/min and 300/10s are separate order-count limits, not modeled).
149
- // Declared only — per §10.9 the limiter is applied to new venues + lighter-rh.
150
+ // Declared only — per the limiter is applied to new venues + lighter-rh.
150
151
  rateLimit: { capacity: 2400, refillPerSec: 40, weights: null },
151
152
  restrictedRegions: [],
152
153
  symbolIdentity: "baseTicker",
153
154
  publicData,
154
- // §12: separate spot account (sapi /api/v3) + /asset/wallet/transfer. Agent signer needs canSpotTrade.
155
+ // separate spot account (sapi /api/v3) + /asset/wallet/transfer. Agent signer needs canSpotTrade.
155
156
  spot: {
156
157
  accountModel: "separate",
157
158
  quoteAssets: { mainnet: ["USDT", "USD1"], testnet: ["USDT", "USD1"] },
@@ -172,5 +173,6 @@ export const asterVenue = {
172
173
  // (INVALID_PARAMS) at order time; this is only the outer venue-wide bound.
173
174
  maxSlippageBps: 5000,
174
175
  builderFee: BUILDER_FEE_CAPABILITIES["aster"],
176
+ apiStatus: { kind: "official" },
175
177
  create: async (ctx) => (await import("./create-helpers.js")).createAster(ctx),
176
178
  };