perp-cli 0.14.2 → 0.15.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 (313) hide show
  1. package/README.md +152 -13
  2. package/dist/agent-wallet/store.d.ts +3 -2
  3. package/dist/agent-wallet/store.js +2 -2
  4. package/dist/api/public/hyperliquid.js +4 -0
  5. package/dist/api/public/pacifica.js +83 -13
  6. package/dist/api/public/urls.d.ts +2 -0
  7. package/dist/api/public/urls.js +3 -0
  8. package/dist/arb/utils.d.ts +1 -2
  9. package/dist/arb/utils.js +1 -2
  10. package/dist/bot/apex/pulse.js +3 -0
  11. package/dist/bot/apex/radar.js +2 -1
  12. package/dist/bot/engine.d.ts +10 -1
  13. package/dist/bot/engine.js +14 -6
  14. package/dist/bot/indicators.d.ts +7 -0
  15. package/dist/bot/indicators.js +16 -0
  16. package/dist/bot/perp-order.d.ts +4 -4
  17. package/dist/bot/perp-order.js +5 -5
  18. package/dist/bot/quarantine.d.ts +1 -1
  19. package/dist/bot/quarantine.js +1 -1
  20. package/dist/bot/strategies/apex-strategy.js +3 -2
  21. package/dist/bot/strategies/engine-mm.js +6 -5
  22. package/dist/bot/strategies/funding-arb-strategy.js +9 -9
  23. package/dist/bot/strategies/funding-arb-utils.d.ts +15 -2
  24. package/dist/bot/strategies/funding-arb-utils.js +43 -24
  25. package/dist/bot/strategies/funding-auto.js +2 -2
  26. package/dist/bot/strategies/liquidation-mm.d.ts +1 -0
  27. package/dist/bot/strategies/liquidation-mm.js +8 -2
  28. package/dist/bot/strategies/momentum-breakout.d.ts +1 -0
  29. package/dist/bot/strategies/momentum-breakout.js +11 -3
  30. package/dist/bot/strategies/spot-perp-arb-strategy.js +23 -12
  31. package/dist/bot/strategies/spot-perp-executor.d.ts +1 -1
  32. package/dist/bot/strategies/spot-perp-executor.js +1 -1
  33. package/dist/bot/strategy-types.d.ts +2 -1
  34. package/dist/bot/tui/BotDashboard.d.ts +1 -1
  35. package/dist/bot/tui/BotDashboard.js +1 -1
  36. package/dist/cache.js +12 -6
  37. package/dist/cli-program.d.ts +15 -0
  38. package/dist/cli-program.js +125 -5
  39. package/dist/commands/agent-import.d.ts +7 -0
  40. package/dist/commands/agent-import.js +30 -1
  41. package/dist/commands/agent.d.ts +6 -3
  42. package/dist/commands/agent.js +160 -12
  43. package/dist/commands/arb/index.js +4 -4
  44. package/dist/commands/arb-auto.js +21 -17
  45. package/dist/commands/arb.d.ts +12 -0
  46. package/dist/commands/arb.js +61 -143
  47. package/dist/commands/bot.js +1 -1
  48. package/dist/commands/builder.js +1 -1
  49. package/dist/commands/funds.js +113 -3
  50. package/dist/commands/init.d.ts +1 -1
  51. package/dist/commands/init.js +27 -7
  52. package/dist/commands/manage.js +23 -21
  53. package/dist/commands/market.js +168 -36
  54. package/dist/commands/options.d.ts +9 -0
  55. package/dist/commands/options.js +134 -0
  56. package/dist/commands/pacifica-earn.d.ts +5 -0
  57. package/dist/commands/pacifica-earn.js +468 -0
  58. package/dist/commands/pacifica-orders.d.ts +14 -0
  59. package/dist/commands/pacifica-orders.js +142 -0
  60. package/dist/commands/portfolio.js +32 -47
  61. package/dist/commands/rebalance.d.ts +1 -0
  62. package/dist/commands/rebalance.js +15 -0
  63. package/dist/commands/risk.d.ts +11 -2
  64. package/dist/commands/risk.js +39 -44
  65. package/dist/commands/trade.d.ts +1 -0
  66. package/dist/commands/trade.js +63 -58
  67. package/dist/commands/wallet.js +1 -1
  68. package/dist/config.d.ts +2 -2
  69. package/dist/config.js +15 -4
  70. package/dist/constants.js +6 -0
  71. package/dist/dashboard/server.js +1 -1
  72. package/dist/dashboard/ui.js +1 -1
  73. package/dist/errors.d.ts +24 -1
  74. package/dist/errors.js +25 -3
  75. package/dist/exchanges/arcus/adapter.d.ts +7 -7
  76. package/dist/exchanges/arcus/adapter.js +12 -12
  77. package/dist/exchanges/arcus/agent.d.ts +1 -1
  78. package/dist/exchanges/arcus/agent.js +1 -1
  79. package/dist/exchanges/arcus/client.js +1 -1
  80. package/dist/exchanges/arcus/signing.d.ts +2 -3
  81. package/dist/exchanges/arcus/signing.js +1 -1
  82. package/dist/exchanges/arcus/spot-chain.d.ts +1 -1
  83. package/dist/exchanges/arcus/spot-chain.js +1 -1
  84. package/dist/exchanges/arcus/spot-router.d.ts +1 -1
  85. package/dist/exchanges/arcus/spot-router.js +2 -2
  86. package/dist/exchanges/arcus/spot.d.ts +2 -3
  87. package/dist/exchanges/arcus/spot.js +2 -3
  88. package/dist/exchanges/aster-spot.d.ts +3 -3
  89. package/dist/exchanges/aster-spot.js +6 -9
  90. package/dist/exchanges/aster-typed-data.d.ts +1 -1
  91. package/dist/exchanges/aster-typed-data.js +1 -1
  92. package/dist/exchanges/aster.d.ts +9 -4
  93. package/dist/exchanges/aster.js +125 -55
  94. package/dist/exchanges/builder-fee.d.ts +1 -1
  95. package/dist/exchanges/builder-fee.js +13 -2
  96. package/dist/exchanges/canonical-symbols.d.ts +1 -2
  97. package/dist/exchanges/canonical-symbols.js +26 -2
  98. package/dist/exchanges/derive/client.d.ts +37 -0
  99. package/dist/exchanges/derive/client.js +135 -0
  100. package/dist/exchanges/derive/options.d.ts +68 -0
  101. package/dist/exchanges/derive/options.js +360 -0
  102. package/dist/exchanges/extended/adapter.d.ts +82 -0
  103. package/dist/exchanges/extended/adapter.js +179 -0
  104. package/dist/exchanges/extended/client.d.ts +38 -0
  105. package/dist/exchanges/extended/client.js +88 -0
  106. package/dist/exchanges/extended/public.d.ts +130 -0
  107. package/dist/exchanges/extended/public.js +429 -0
  108. package/dist/exchanges/grvt/adapter.d.ts +86 -0
  109. package/dist/exchanges/grvt/adapter.js +206 -0
  110. package/dist/exchanges/grvt/client.d.ts +25 -0
  111. package/dist/exchanges/grvt/client.js +86 -0
  112. package/dist/exchanges/grvt/public.d.ts +151 -0
  113. package/dist/exchanges/grvt/public.js +438 -0
  114. package/dist/exchanges/hyperliquid-spot.d.ts +1 -1
  115. package/dist/exchanges/hyperliquid-spot.js +5 -5
  116. package/dist/exchanges/hyperliquid.d.ts +22 -16
  117. package/dist/exchanges/hyperliquid.js +80 -55
  118. package/dist/exchanges/index.d.ts +1 -1
  119. package/dist/exchanges/index.js +1 -1
  120. package/dist/exchanges/interface.d.ts +97 -8
  121. package/dist/exchanges/interface.js +19 -2
  122. package/dist/exchanges/ioc-outcome.d.ts +1 -1
  123. package/dist/exchanges/ioc-outcome.js +1 -1
  124. package/dist/exchanges/legacy-trading-v2.d.ts +2 -2
  125. package/dist/exchanges/legacy-trading-v2.js +7 -2
  126. package/dist/exchanges/lighter-public.d.ts +2 -2
  127. package/dist/exchanges/lighter-public.js +2 -2
  128. package/dist/exchanges/lighter-signer.d.ts +1 -1
  129. package/dist/exchanges/lighter-signer.js +1 -1
  130. package/dist/exchanges/lighter-spot.d.ts +3 -4
  131. package/dist/exchanges/lighter-spot.js +6 -7
  132. package/dist/exchanges/lighter.d.ts +11 -11
  133. package/dist/exchanges/lighter.js +28 -14
  134. package/dist/exchanges/ondo/adapter.d.ts +5 -6
  135. package/dist/exchanges/ondo/adapter.js +16 -14
  136. package/dist/exchanges/ondo/agent.d.ts +1 -1
  137. package/dist/exchanges/ondo/agent.js +1 -1
  138. package/dist/exchanges/ondo/client.d.ts +1 -1
  139. package/dist/exchanges/ondo/client.js +2 -2
  140. package/dist/exchanges/ondo/public.d.ts +8 -1
  141. package/dist/exchanges/ondo/public.js +13 -2
  142. package/dist/exchanges/ondo/spot.d.ts +2 -2
  143. package/dist/exchanges/ondo/spot.js +3 -3
  144. package/dist/exchanges/options-interface.d.ts +158 -0
  145. package/dist/exchanges/options-interface.js +17 -0
  146. package/dist/exchanges/options-venues.d.ts +12 -0
  147. package/dist/exchanges/options-venues.js +35 -0
  148. package/dist/exchanges/pacifica-atomic.d.ts +211 -0
  149. package/dist/exchanges/pacifica-atomic.js +440 -0
  150. package/dist/exchanges/pacifica-decimal.d.ts +10 -0
  151. package/dist/exchanges/pacifica-decimal.js +51 -0
  152. package/dist/exchanges/pacifica-presigned.d.ts +53 -0
  153. package/dist/exchanges/pacifica-presigned.js +141 -0
  154. package/dist/exchanges/pacifica-print.d.ts +200 -0
  155. package/dist/exchanges/pacifica-print.js +417 -0
  156. package/dist/exchanges/pacifica-spot.d.ts +2 -2
  157. package/dist/exchanges/pacifica-spot.js +3 -3
  158. package/dist/exchanges/pacifica-typed-data.d.ts +1 -1
  159. package/dist/exchanges/pacifica-typed-data.js +1 -1
  160. package/dist/exchanges/pacifica-vaults.d.ts +121 -0
  161. package/dist/exchanges/pacifica-vaults.js +264 -0
  162. package/dist/exchanges/pacifica.d.ts +32 -5
  163. package/dist/exchanges/pacifica.js +114 -48
  164. package/dist/exchanges/precision.d.ts +3 -1
  165. package/dist/exchanges/precision.js +9 -2
  166. package/dist/exchanges/qfex/adapter.d.ts +18 -5
  167. package/dist/exchanges/qfex/adapter.js +32 -12
  168. package/dist/exchanges/qfex/agent.d.ts +1 -1
  169. package/dist/exchanges/qfex/agent.js +1 -1
  170. package/dist/exchanges/qfex/client.d.ts +1 -1
  171. package/dist/exchanges/qfex/client.js +1 -1
  172. package/dist/exchanges/qfex/errors.d.ts +1 -1
  173. package/dist/exchanges/qfex/errors.js +2 -2
  174. package/dist/exchanges/qfex/public.d.ts +3 -3
  175. package/dist/exchanges/qfex/public.js +3 -3
  176. package/dist/exchanges/qfex/signing.d.ts +2 -2
  177. package/dist/exchanges/qfex/signing.js +4 -5
  178. package/dist/exchanges/qfex/ws-transport.d.ts +2 -2
  179. package/dist/exchanges/qfex/ws-transport.js +3 -3
  180. package/dist/exchanges/rate-limiter.d.ts +1 -1
  181. package/dist/exchanges/rate-limiter.js +5 -2
  182. package/dist/exchanges/registry.d.ts +1 -1
  183. package/dist/exchanges/registry.js +1 -1
  184. package/dist/exchanges/risex/adapter.d.ts +148 -0
  185. package/dist/exchanges/risex/adapter.js +796 -0
  186. package/dist/exchanges/risex/agent.d.ts +154 -0
  187. package/dist/exchanges/risex/agent.js +384 -0
  188. package/dist/exchanges/risex/client.d.ts +47 -0
  189. package/dist/exchanges/risex/client.js +174 -0
  190. package/dist/exchanges/risex/nonce.d.ts +43 -0
  191. package/dist/exchanges/risex/nonce.js +212 -0
  192. package/dist/exchanges/risex/public.d.ts +64 -0
  193. package/dist/exchanges/risex/public.js +150 -0
  194. package/dist/exchanges/risex/session.d.ts +71 -0
  195. package/dist/exchanges/risex/session.js +171 -0
  196. package/dist/exchanges/risex/typed-data.d.ts +173 -0
  197. package/dist/exchanges/risex/typed-data.js +240 -0
  198. package/dist/exchanges/risex/units.d.ts +29 -0
  199. package/dist/exchanges/risex/units.js +104 -0
  200. package/dist/exchanges/slippage.d.ts +1 -1
  201. package/dist/exchanges/slippage.js +1 -1
  202. package/dist/exchanges/spot-factory.d.ts +3 -3
  203. package/dist/exchanges/spot-factory.js +7 -7
  204. package/dist/exchanges/spot-interface.d.ts +5 -5
  205. package/dist/exchanges/spot-interface.js +2 -2
  206. package/dist/exchanges/spot-result.d.ts +3 -3
  207. package/dist/exchanges/spot-result.js +3 -3
  208. package/dist/exchanges/trading-v2.d.ts +3 -3
  209. package/dist/exchanges/trading-v2.js +9 -6
  210. package/dist/exchanges/txflow/adapter.d.ts +54 -0
  211. package/dist/exchanges/txflow/adapter.js +118 -0
  212. package/dist/exchanges/txflow/client.d.ts +47 -0
  213. package/dist/exchanges/txflow/client.js +153 -0
  214. package/dist/exchanges/txflow/public.d.ts +76 -0
  215. package/dist/exchanges/txflow/public.js +284 -0
  216. package/dist/exchanges/unknown-symbol.d.ts +29 -0
  217. package/dist/exchanges/unknown-symbol.js +51 -0
  218. package/dist/exchanges/variational/adapter.d.ts +71 -0
  219. package/dist/exchanges/variational/adapter.js +137 -0
  220. package/dist/exchanges/variational/client.d.ts +21 -0
  221. package/dist/exchanges/variational/client.js +66 -0
  222. package/dist/exchanges/variational/public.d.ts +126 -0
  223. package/dist/exchanges/variational/public.js +430 -0
  224. package/dist/exchanges/venue-types.d.ts +6 -7
  225. package/dist/exchanges/venue-types.js +2 -2
  226. package/dist/exchanges/venue.d.ts +51 -8
  227. package/dist/exchanges/venue.js +54 -4
  228. package/dist/exchanges/venues/arcus.d.ts +1 -2
  229. package/dist/exchanges/venues/arcus.js +5 -4
  230. package/dist/exchanges/venues/aster.d.ts +2 -2
  231. package/dist/exchanges/venues/aster.js +6 -4
  232. package/dist/exchanges/venues/create-helpers.d.ts +1 -1
  233. package/dist/exchanges/venues/create-helpers.js +3 -3
  234. package/dist/exchanges/venues/extended.d.ts +21 -0
  235. package/dist/exchanges/venues/extended.js +96 -0
  236. package/dist/exchanges/venues/grvt.d.ts +30 -0
  237. package/dist/exchanges/venues/grvt.js +98 -0
  238. package/dist/exchanges/venues/hyperliquid.d.ts +2 -2
  239. package/dist/exchanges/venues/hyperliquid.js +10 -8
  240. package/dist/exchanges/venues/index.d.ts +7 -2
  241. package/dist/exchanges/venues/index.js +8 -2
  242. package/dist/exchanges/venues/lighter-family.d.ts +7 -9
  243. package/dist/exchanges/venues/lighter-family.js +9 -11
  244. package/dist/exchanges/venues/lighter-rh.d.ts +2 -2
  245. package/dist/exchanges/venues/lighter-rh.js +7 -5
  246. package/dist/exchanges/venues/lighter.d.ts +1 -1
  247. package/dist/exchanges/venues/lighter.js +5 -3
  248. package/dist/exchanges/venues/ondo.d.ts +1 -1
  249. package/dist/exchanges/venues/ondo.js +5 -3
  250. package/dist/exchanges/venues/pacifica.d.ts +2 -2
  251. package/dist/exchanges/venues/pacifica.js +6 -4
  252. package/dist/exchanges/venues/qfex.d.ts +1 -1
  253. package/dist/exchanges/venues/qfex.js +4 -2
  254. package/dist/exchanges/venues/risex-release.d.ts +14 -0
  255. package/dist/exchanges/venues/risex-release.js +14 -0
  256. package/dist/exchanges/venues/risex.d.ts +31 -0
  257. package/dist/exchanges/venues/risex.js +116 -0
  258. package/dist/exchanges/venues/txflow.d.ts +18 -0
  259. package/dist/exchanges/venues/txflow.js +77 -0
  260. package/dist/exchanges/venues/variational.d.ts +21 -0
  261. package/dist/exchanges/venues/variational.js +85 -0
  262. package/dist/execution-log.d.ts +1 -1
  263. package/dist/funding/legacy-public.d.ts +1 -1
  264. package/dist/funding/legacy-public.js +9 -4
  265. package/dist/funding/normalize.d.ts +1 -1
  266. package/dist/funding/normalize.js +1 -1
  267. package/dist/funding/rates.d.ts +44 -4
  268. package/dist/funding/rates.js +56 -12
  269. package/dist/index.js +7 -6
  270. package/dist/jobs.d.ts +1 -0
  271. package/dist/jobs.js +25 -1
  272. package/dist/landing.d.ts +2 -0
  273. package/dist/landing.js +1 -1
  274. package/dist/mcp-pacifica-earn.d.ts +9 -0
  275. package/dist/mcp-pacifica-earn.js +58 -0
  276. package/dist/mcp-server.js +113 -59
  277. package/dist/pacifica/client.d.ts +67 -4
  278. package/dist/pacifica/client.js +170 -8
  279. package/dist/pacifica/rate-limit.d.ts +144 -0
  280. package/dist/pacifica/rate-limit.js +267 -0
  281. package/dist/pacifica/types/order.d.ts +2 -1
  282. package/dist/plan-executor.js +7 -2
  283. package/dist/rebalance.js +1 -1
  284. package/dist/risk.d.ts +2 -1
  285. package/dist/risk.js +2 -4
  286. package/dist/settings.d.ts +32 -3
  287. package/dist/signer/ows-evm.d.ts +1 -1
  288. package/dist/signer/ows-evm.js +1 -1
  289. package/dist/smart-order.d.ts +1 -1
  290. package/dist/smart-order.js +1 -1
  291. package/dist/strategies/funding-arb.d.ts +1 -1
  292. package/dist/strategies/funding-arb.js +1 -1
  293. package/dist/trade-validator.d.ts +1 -1
  294. package/dist/trade-validator.js +9 -4
  295. package/dist/utils.d.ts +12 -3
  296. package/dist/utils.js +11 -3
  297. package/dist/venue-health.d.ts +40 -0
  298. package/dist/venue-health.js +75 -0
  299. package/dist/withdraw-gate.d.ts +1 -1
  300. package/dist/withdraw-gate.js +1 -1
  301. package/dist/ws/index.d.ts +959 -0
  302. package/dist/ws/index.js +4295 -0
  303. package/dist/ws/node.d.ts +975 -0
  304. package/dist/ws/node.js +4366 -0
  305. package/dist/ws/pacifica-instrument.d.ts +8 -0
  306. package/dist/ws/pacifica-instrument.js +8 -0
  307. package/dist/ws-cli/credentials.d.ts +62 -0
  308. package/dist/ws-cli/credentials.js +126 -0
  309. package/package.json +58 -10
  310. package/skills/perp-cli/SKILL.md +62 -9
  311. package/skills/perp-cli/references/agent-operations.md +7 -0
  312. package/skills/perp-cli/references/commands.md +76 -5
  313. package/skills/perp-cli/references/ws-library.md +492 -0
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Variational (Omni) venue definition — MARKET DATA ONLY, through the OFFICIAL read-only API.
3
+ *
4
+ * Variational Omni is an RFQ venue: the Omni Liquidity Provider (OLP) is the only maker and quotes
5
+ * every trade — there is no order book (docs.variational.io/variational-protocol/key-concepts/trading-via-rfq).
6
+ * Its documented public API (docs.variational.io/technical-documentation/api) is one endpoint,
7
+ * `GET /metadata/v2/stats`, serving every market at once. The trading API "is still in
8
+ * development, and is not yet available to any users", so:
9
+ * - roles are ["marketData"]: every account / trading / funds / agent / builder / bot path refuses
10
+ * with CAPABILITY_UNSUPPORTED before any request (venue.ts roleUnsupported());
11
+ * - `market book` shows the OLP's indicative RFQ quotes at fixed notionals, labelled as such
12
+ * (never as order-book levels).
13
+ * No WebSocket is documented, so there is no ws codec.
14
+ * Not registered here; the registry module owns registerVenue().
15
+ */
16
+ import type { VenueDefinition } from "../venue.js";
17
+ export declare const VARIATIONAL_REST_URL = "https://omni-client-api.prod.ap-northeast-1.variational.io";
18
+ export declare const VARIATIONAL_API_DOCS = "https://docs.variational.io/technical-documentation/api";
19
+ /** Every role refusal's remediation (VenueDefinition.readOnlyRemediation, read by roleUnsupported). */
20
+ export declare const VARIATIONAL_READ_ONLY_REMEDIATION = "Variational is read-only (market data) in perp-cli; Variational has no public trading API yet. Trade on omni.variational.io; perp-cli shows Variational market data (perp -e variational market list|prices|info|funding|book, perp arb scan --rates --include-unverified)";
21
+ export declare const variationalVenue: VenueDefinition;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Variational (Omni) venue definition — MARKET DATA ONLY, through the OFFICIAL read-only API.
3
+ *
4
+ * Variational Omni is an RFQ venue: the Omni Liquidity Provider (OLP) is the only maker and quotes
5
+ * every trade — there is no order book (docs.variational.io/variational-protocol/key-concepts/trading-via-rfq).
6
+ * Its documented public API (docs.variational.io/technical-documentation/api) is one endpoint,
7
+ * `GET /metadata/v2/stats`, serving every market at once. The trading API "is still in
8
+ * development, and is not yet available to any users", so:
9
+ * - roles are ["marketData"]: every account / trading / funds / agent / builder / bot path refuses
10
+ * with CAPABILITY_UNSUPPORTED before any request (venue.ts roleUnsupported());
11
+ * - `market book` shows the OLP's indicative RFQ quotes at fixed notionals, labelled as such
12
+ * (never as order-book levels).
13
+ * No WebSocket is documented, so there is no ws codec.
14
+ * Not registered here; the registry module owns registerVenue().
15
+ */
16
+ export const VARIATIONAL_REST_URL = "https://omni-client-api.prod.ap-northeast-1.variational.io";
17
+ export const VARIATIONAL_API_DOCS = "https://docs.variational.io/technical-documentation/api";
18
+ /** Every role refusal's remediation (VenueDefinition.readOnlyRemediation, read by roleUnsupported). */
19
+ export const VARIATIONAL_READ_ONLY_REMEDIATION = "Variational is read-only (market data) in perp-cli; Variational has no public trading API yet. Trade on omni.variational.io; perp-cli shows Variational market data (perp -e variational market list|prices|info|funding|book, perp arb scan --rates --include-unverified)";
20
+ /** Lazy: public.ts is only loaded when market data is actually requested. */
21
+ const publicData = {
22
+ async fetchFundingRates(net) {
23
+ return (await import("../variational/public.js")).variationalPublicData.fetchFundingRates(net);
24
+ },
25
+ async fetchMarkets(net) {
26
+ return (await import("../variational/public.js")).variationalPublicData.fetchMarkets(net);
27
+ },
28
+ };
29
+ export const variationalVenue = {
30
+ id: "variational",
31
+ aliases: ["var", "omni"],
32
+ displayName: "Variational",
33
+ family: "variational",
34
+ masterKeyKind: "none",
35
+ agentKeyKind: "none",
36
+ secretStorage: { kind: "none", dir: null },
37
+ networks: {
38
+ mainnet: {
39
+ restUrl: VARIATIONAL_REST_URL,
40
+ // No WebSocket is documented.
41
+ wsUrl: null,
42
+ explorerUrl: null,
43
+ signingChainId: null,
44
+ // Settlement pools live on Arbitrum; perp-cli never touches them (read-only).
45
+ settlementChain: "offchain",
46
+ collateral: ["USDC"],
47
+ collateralAssetIndex: null,
48
+ rpcUrl: null,
49
+ depositPins: null,
50
+ },
51
+ // No public testnet API.
52
+ testnet: null,
53
+ },
54
+ // funding.interval_seconds is per market (1h / 4h / 8h perps; 24h swaps).
55
+ fundingRateBasis: "perMarket",
56
+ settlementIntervalHours: "perMarket",
57
+ // UNVERIFIED (design §14.12 #6: verified = checked against settled payments). The unit is
58
+ // documented (funding.last.interval_rate = fraction per interval_seconds, positive = longs pay)
59
+ // and interval_rate = annualized_rate × interval / 365 d holds on every live perp (2026-10-19),
60
+ // but that only shows the published fields agree: the rate is a moving prediction, there is no
61
+ // public account API to check settled payments, and the accrual hours of equity / ETF perps are
62
+ // not documented. Shown only on request (arb scan --rates --include-unverified).
63
+ fundingVerified: false,
64
+ roles: ["marketData"],
65
+ gatedRoles: null,
66
+ capabilities: null,
67
+ // Documented: 10 requests / 10 s per IP (shared with the legacy v1 endpoint). One call serves
68
+ // every market and is cached briefly (variational/public.ts), so commands rarely hit this.
69
+ rateLimit: { capacity: 10, refillPerSec: 1, weights: null },
70
+ // docs.variational.io/legal/restricted-persons (last updated 2026-04-20). Warn-only (never enforced).
71
+ restrictedRegions: ["US", "CA", "CU", "IR", "KP", "SD", "SY", "TW", "PR", "GU", "VI", "AS", "MP", "UM", "FM", "MH", "PW"],
72
+ symbolIdentity: "baseTicker",
73
+ publicData,
74
+ spot: null,
75
+ // Unused (no trading role); the field is required by the definition shape.
76
+ maxSlippageBps: 1,
77
+ builderFee: null,
78
+ apiStatus: { kind: "official" },
79
+ readOnlyRemediation: VARIATIONAL_READ_ONLY_REMEDIATION,
80
+ // A public-only adapter: market data works, every account / trading method refuses.
81
+ async create(ctx) {
82
+ const { VariationalPublicAdapter } = await import("../variational/adapter.js");
83
+ return new VariationalPublicAdapter(variationalVenue, ctx.network);
84
+ },
85
+ };
@@ -1,7 +1,7 @@
1
1
  export interface ExecutionRecord {
2
2
  id: string;
3
3
  timestamp: string;
4
- type: "market_order" | "limit_order" | "cancel" | "stop_order" | "edit_order" | "rebalance" | "arb_entry" | "arb_close" | "bridge" | "multi_leg" | "multi_leg_rollback" | "split_order";
4
+ type: "market_order" | "limit_order" | "cancel" | "stop_order" | "edit_order" | "rebalance" | "arb_entry" | "arb_close" | "bridge" | "multi_leg" | "multi_leg_rollback" | "split_order" | "print_deposit" | "print_end" | "vault_deposit" | "vault_withdraw";
5
5
  exchange: string;
6
6
  symbol: string;
7
7
  side: string;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Public market data for the legacy venues without a native `getVenueMarkets`
3
- * (pacifica, hyperliquid core, aster) — design §10.6. Lighter uses
3
+ * (pacifica, hyperliquid core, aster). Lighter uses
4
4
  * exchanges/lighter-public.ts.
5
5
  *
6
6
  * Network-aware: the REST base comes from `legacyPublicBaseUrl(venue, net)`,
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Public market data for the legacy venues without a native `getVenueMarkets`
3
- * (pacifica, hyperliquid core, aster) — design §10.6. Lighter uses
3
+ * (pacifica, hyperliquid core, aster). Lighter uses
4
4
  * exchanges/lighter-public.ts.
5
5
  *
6
6
  * Network-aware: the REST base comes from `legacyPublicBaseUrl(venue, net)`,
@@ -27,6 +27,7 @@
27
27
  * size; Aster leverage brackets need a signed call; Aster OI is per-symbol only).
28
28
  */
29
29
  import { HL_MIN_ORDER_VALUE_USD } from "../constants.js";
30
+ import { isPacificaPerpetual } from "../ws/pacifica-instrument.js";
30
31
  import { PerpError } from "../errors.js";
31
32
  import { venueRateString } from "../utils/numeric.js";
32
33
  import { assertOk } from "../api/public/_http.js";
@@ -127,11 +128,11 @@ async function pacificaRows(net) {
127
128
  const unclassified = [];
128
129
  const unpriced = [];
129
130
  for (const m of info) {
130
- // /info lists spot markets too (e.g. SOL-USDC). A row without the field cannot be classified.
131
+ // /info lists spot markets too (e.g. SOL-USDC): perps only. A row without the field cannot be classified.
131
132
  if (typeof m.instrument_type !== "string") {
132
133
  throw new PerpError("EXCHANGE_ERROR", `Pacifica /info row ${String(m.symbol)} has no instrument_type`, { exchange: "pacifica" });
133
134
  }
134
- if (m.instrument_type === "spot")
135
+ if (!isPacificaPerpetual(m))
135
136
  continue;
136
137
  const symbol = String(m.symbol ?? "");
137
138
  const assetClass = classifyPacifica(symbol);
@@ -200,6 +201,10 @@ async function hyperliquidRows(net) {
200
201
  const unclassified = [];
201
202
  const unpriced = [];
202
203
  universe.forEach((a, i) => {
204
+ // Delisted assets stay in meta (isDelisted: true) with zeroed funding / volume / OI
205
+ // (live 2026-10-14: 56 of 234) — not a market: excluded from listings, prices and scans.
206
+ if (a.isDelisted === true)
207
+ return;
203
208
  const ctx = ctxs[i];
204
209
  const symbol = String(a.name ?? "");
205
210
  const assetClass = classifyHyperliquid(symbol);
@@ -237,7 +242,7 @@ async function hyperliquidRows(net) {
237
242
  maxLeverage: Number.isFinite(maxLeverage) && maxLeverage > 0 ? maxLeverage : null,
238
243
  marginTiers: null,
239
244
  session: null,
240
- tradable: a.isDelisted !== true,
245
+ tradable: true, // delisted assets are skipped above
241
246
  },
242
247
  nextFundingTime: null, // not published in metaAndAssetCtxs
243
248
  });
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Funding rate normalization (design §10.1, §10.13 #1).
2
+ * Funding rate normalization.
3
3
  *
4
4
  * The number of hours one quoted rate covers (the funding BASIS) comes from the
5
5
  * venue definition — `VenueDefinition.fundingRateBasis`, read through
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Funding rate normalization (design §10.1, §10.13 #1).
2
+ * Funding rate normalization.
3
3
  *
4
4
  * The number of hours one quoted rate covers (the funding BASIS) comes from the
5
5
  * venue definition — `VenueDefinition.fundingRateBasis`, read through
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Real-time cross-venue funding rate comparison.
3
3
  *
4
- * Fans out over every venue with the "marketData" role (design §10.6) using
4
+ * Fans out over every venue with the "marketData" role using
5
5
  * each venue's `publicData.fetchFundingRates(net)`, normalizes every row to a
6
6
  * per-hour rate with the row's own basis (Lighter
7
7
  * family 8h, Aster per-symbol), and matches markets across venues by
8
- * `canonical` (null → excluded, §10.9).
8
+ * `canonical` (null → excluded).
9
9
  *
10
10
  * A venue that fails is reported in `exchangeStatus` ("error") and
11
11
  * `exchangeErrors` (message) — its rows are absent, never silently empty.
@@ -28,6 +28,8 @@ export interface ExchangeFundingRate {
28
28
  historicalAvg: HistoricalAverages | null;
29
29
  /** false = the venue's funding rate is unverified (VenueDefinition.fundingVerified) — shown only on explicit request. */
30
30
  fundingVerified: boolean;
31
+ /** How the row was read: "unofficial" = an undocumented venue API (VenueDefinition.apiStatus) — may change without notice. */
32
+ apiStatus: "official" | "unofficial";
31
33
  }
32
34
  export interface SymbolFundingComparison {
33
35
  /** Canonical symbol shared by every row. */
@@ -38,12 +40,19 @@ export interface SymbolFundingComparison {
38
40
  shortExchange: string;
39
41
  bestMarkPrice: number;
40
42
  estHourlyIncomeUsd: number;
43
+ /**
44
+ * false = a leg of the long/short pair sits on a venue without the trading role (RISEx while its
45
+ * trading is gated in this release): the spread is data only — `arb exec` refuses it.
46
+ */
47
+ executable: boolean;
48
+ /** Venues of the long/short pair that perp-cli cannot trade (read-only); [] when executable. */
49
+ readOnlyLegs: string[];
41
50
  }
42
51
  export interface FundingRateSnapshot {
43
52
  timestamp: string;
44
53
  symbols: SymbolFundingComparison[];
45
54
  exchangeStatus: Record<string, "ok" | "error">;
46
- /** Error message per venue whose status is "error" (§10.13 #3). */
55
+ /** Error message per venue whose status is "error". */
47
56
  exchangeErrors: Record<string, string>;
48
57
  }
49
58
  export declare const TOP_SYMBOLS: string[];
@@ -59,6 +68,37 @@ export declare function fetchVenuesRates(venues: VenueDefinition[], net: Network
59
68
  exchangeStatus: Record<string, "ok" | "error">;
60
69
  exchangeErrors: Record<string, string>;
61
70
  }>;
71
+ export interface VenuePrice {
72
+ exchange: VenueId;
73
+ /** Venue display symbol. */
74
+ symbol: string;
75
+ canonical: string;
76
+ markPrice: number;
77
+ apiStatus: "official" | "unofficial";
78
+ }
79
+ export interface PriceGroupSnapshot {
80
+ /** canonical → venue prices, after the median price-outlier drop (same guard as the funding scan). */
81
+ groups: Map<string, VenuePrice[]>;
82
+ exchangeStatus: Record<string, "ok" | "error">;
83
+ exchangeErrors: Record<string, string>;
84
+ }
85
+ /**
86
+ * Mark prices of `venues`, grouped by canonical symbol — the grouping and price-outlier guard of
87
+ * the funding comparison, WITHOUT any funding dependency: read from each venue's market directory
88
+ * (publicData.fetchMarkets), so a market with a null funding rate or a venue whose funding is
89
+ * unverified is still priced. Rows without a canonical symbol, not tradable, or without a
90
+ * positive mark are excluded. A venue that fails is reported in exchangeStatus / exchangeErrors.
91
+ */
92
+ export declare function fetchCanonicalPriceGroups(venues: VenueDefinition[], opts?: {
93
+ symbols?: string[];
94
+ network?: Network;
95
+ tag?: string;
96
+ }): Promise<PriceGroupSnapshot>;
97
+ /** Whether perp-cli can trade both legs of a long/short pair (the trading role), and which it cannot. */
98
+ export declare function pairExecutability(longExchange: string, shortExchange: string): {
99
+ executable: boolean;
100
+ readOnlyLegs: string[];
101
+ };
62
102
  /**
63
103
  * Fetch funding rates from every market-data venue in parallel, normalize, and compare.
64
104
  * Returns rates sorted by max spread (descending).
@@ -97,7 +137,7 @@ export interface SpotPerpSpread {
97
137
  /**
98
138
  * Fetch spot+perp funding rate spreads.
99
139
  * Since spot has 0 funding cost, the spread is simply |perp funding rate|.
100
- * Only includes symbols available on at least one bot spot venue (def.spot, §12.4 #3).
140
+ * Only includes symbols available on at least one bot spot venue (def.spot).
101
141
  *
102
142
  * Key safety checks:
103
143
  * - U-token mapping: UBTC→BTC, UETH→ETH, USOL→SOL, UFART→FARTCOIN
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Real-time cross-venue funding rate comparison.
3
3
  *
4
- * Fans out over every venue with the "marketData" role (design §10.6) using
4
+ * Fans out over every venue with the "marketData" role using
5
5
  * each venue's `publicData.fetchFundingRates(net)`, normalizes every row to a
6
6
  * per-hour rate with the row's own basis (Lighter
7
7
  * family 8h, Aster per-symbol), and matches markets across venues by
8
- * `canonical` (null → excluded, §10.9).
8
+ * `canonical` (null → excluded).
9
9
  *
10
10
  * A venue that fails is reported in `exchangeStatus` ("error") and
11
11
  * `exchangeErrors` (message) — its rows are absent, never silently empty.
@@ -26,7 +26,11 @@ export const TOP_SYMBOLS = [
26
26
  ];
27
27
  /** Mark-price source preference for `bestMarkPrice` (most liquid first). */
28
28
  const MARK_PRICE_PREFERENCE = [
29
- "hyperliquid", "pacifica", "lighter", "aster", "lighter-rh", "ondo", "arcus", "qfex",
29
+ "hyperliquid", "pacifica", "lighter", "aster", "lighter-rh", "ondo", "arcus", "risex", "qfex", "grvt", "extended",
30
+ // RFQ venue: the mark is the single maker's (OLP) price.
31
+ "variational",
32
+ // Unofficial API: last resort as a mark-price source.
33
+ "txflow",
30
34
  ];
31
35
  // ── Per-venue fetch ──
32
36
  function errorMessage(e) {
@@ -57,14 +61,14 @@ async function fetchVenueRates(def, net) {
57
61
  unpriced.push(r.symbol);
58
62
  continue;
59
63
  }
60
- out.push(toExchangeRate(r, r.canonical, markPrice, def.fundingVerified));
64
+ out.push(toExchangeRate(r, r.canonical, markPrice, def.fundingVerified, def.apiStatus.kind));
61
65
  }
62
66
  if (unpriced.length > 0) {
63
67
  console.warn(`[${def.id}] excluded ${unpriced.length} funding row(s) without a mark price: ${unpriced.join(", ")}`);
64
68
  }
65
69
  return out;
66
70
  }
67
- function toExchangeRate(r, canonical, markPrice, fundingVerified) {
71
+ function toExchangeRate(r, canonical, markPrice, fundingVerified, apiStatus) {
68
72
  const fundingRate = Number(r.rate);
69
73
  if (!Number.isFinite(fundingRate)) {
70
74
  throw new PerpError("EXCHANGE_ERROR", `${r.venue} ${r.symbol}: non-numeric funding rate "${r.rate}"`);
@@ -82,6 +86,7 @@ function toExchangeRate(r, canonical, markPrice, fundingVerified) {
82
86
  nextFundingTime: r.nextFundingTime,
83
87
  historicalAvg: null,
84
88
  fundingVerified,
89
+ apiStatus,
85
90
  };
86
91
  }
87
92
  /**
@@ -117,7 +122,7 @@ const _warnedPriceMismatch = new Set();
117
122
  * venue whose mark price deviates more than MAX_PRICE_DEVIATION_PCT from the
118
123
  * group median, with a one-time stderr warning per (symbol, venue).
119
124
  */
120
- function dropPriceOutliers(canonical, list) {
125
+ function dropPriceOutliers(canonical, list, tag = "funding") {
121
126
  if (list.length < 2)
122
127
  return list;
123
128
  const sorted = list.map(r => r.markPrice).sort((x, y) => x - y);
@@ -133,12 +138,12 @@ function dropPriceOutliers(canonical, list) {
133
138
  const key = `${canonical}:${r.exchange}`;
134
139
  if (!_warnedPriceMismatch.has(key)) {
135
140
  _warnedPriceMismatch.add(key);
136
- process.stderr.write(`[funding] excluded ${r.exchange} ${canonical}: mark ${r.markPrice} deviates ${devPct.toFixed(1)}% from cross-venue median ${mid} (likely a different asset with the same ticker)\n`);
141
+ process.stderr.write(`[${tag}] excluded ${r.exchange} ${canonical}: mark ${r.markPrice} deviates ${devPct.toFixed(1)}% from cross-venue median ${mid} (likely a different asset with the same ticker)\n`);
137
142
  }
138
143
  return false;
139
144
  });
140
145
  }
141
- function groupByCanonical(rates, symbols) {
146
+ function groupByCanonical(rates, symbols, tag = "funding") {
142
147
  const out = new Map();
143
148
  for (const r of rates) {
144
149
  if (symbols && !symbols.includes(r.canonical))
@@ -150,7 +155,7 @@ function groupByCanonical(rates, symbols) {
150
155
  out.set(r.canonical, [r]);
151
156
  }
152
157
  for (const [canonical, list] of [...out]) {
153
- const kept = dropPriceOutliers(canonical, list);
158
+ const kept = dropPriceOutliers(canonical, list, tag);
154
159
  if (kept.length === 0)
155
160
  out.delete(canonical); // every venue disagreed — not comparable
156
161
  else
@@ -158,6 +163,44 @@ function groupByCanonical(rates, symbols) {
158
163
  }
159
164
  return out;
160
165
  }
166
+ /**
167
+ * Mark prices of `venues`, grouped by canonical symbol — the grouping and price-outlier guard of
168
+ * the funding comparison, WITHOUT any funding dependency: read from each venue's market directory
169
+ * (publicData.fetchMarkets), so a market with a null funding rate or a venue whose funding is
170
+ * unverified is still priced. Rows without a canonical symbol, not tradable, or without a
171
+ * positive mark are excluded. A venue that fails is reported in exchangeStatus / exchangeErrors.
172
+ */
173
+ export async function fetchCanonicalPriceGroups(venues, opts) {
174
+ ensureVenuesRegistered();
175
+ const net = opts?.network ?? sharedApiNetwork();
176
+ const settled = await Promise.allSettled(venues.map(v => v.publicData.fetchMarkets(net)));
177
+ const exchangeStatus = {};
178
+ const exchangeErrors = {};
179
+ const prices = [];
180
+ settled.forEach((res, i) => {
181
+ const def = venues[i];
182
+ if (res.status === "rejected") {
183
+ exchangeStatus[def.id] = "error";
184
+ exchangeErrors[def.id] = errorMessage(res.reason);
185
+ return;
186
+ }
187
+ exchangeStatus[def.id] = "ok";
188
+ for (const m of res.value) {
189
+ if (m.canonical === null || !m.tradable)
190
+ continue;
191
+ const markPrice = Number(m.markPrice);
192
+ if (!Number.isFinite(markPrice) || markPrice <= 0)
193
+ continue;
194
+ prices.push({ exchange: def.id, symbol: m.symbol, canonical: m.canonical, markPrice, apiStatus: def.apiStatus.kind });
195
+ }
196
+ });
197
+ return { groups: groupByCanonical(prices, opts?.symbols, opts?.tag ?? "prices"), exchangeStatus, exchangeErrors };
198
+ }
199
+ /** Whether perp-cli can trade both legs of a long/short pair (the trading role), and which it cannot. */
200
+ export function pairExecutability(longExchange, shortExchange) {
201
+ const readOnlyLegs = [...new Set([longExchange, shortExchange])].filter(v => !getVenue(v).roles.includes("trading"));
202
+ return { executable: readOnlyLegs.length === 0, readOnlyLegs };
203
+ }
161
204
  function bestMarkPrice(rates) {
162
205
  const rank = (id) => MARK_PRICE_PREFERENCE.indexOf(id);
163
206
  return [...rates].sort((a, b) => rank(a.exchange) - rank(b.exchange))[0].markPrice;
@@ -218,6 +261,7 @@ export async function fetchAllFundingRates(opts) {
218
261
  shortExchange: highest.exchange, // short where funding is highest
219
262
  bestMarkPrice: bestMarkPrice(rates),
220
263
  estHourlyIncomeUsd,
264
+ ...pairExecutability(lowest.exchange, highest.exchange),
221
265
  });
222
266
  }
223
267
  // Sort by spread descending
@@ -239,7 +283,7 @@ export async function fetchSymbolFundingRates(symbol, includeUnverified = false)
239
283
  /**
240
284
  * Fetch spot+perp funding rate spreads.
241
285
  * Since spot has 0 funding cost, the spread is simply |perp funding rate|.
242
- * Only includes symbols available on at least one bot spot venue (def.spot, §12.4 #3).
286
+ * Only includes symbols available on at least one bot spot venue (def.spot).
243
287
  *
244
288
  * Key safety checks:
245
289
  * - U-token mapping: UBTC→BTC, UETH→ETH, USOL→SOL, UFART→FARTCOIN
@@ -247,8 +291,8 @@ export async function fetchSymbolFundingRates(symbol, includeUnverified = false)
247
291
  * (filters out same-ticker-different-token issues like HIP-1 TRUMP ≠ perp TRUMP)
248
292
  */
249
293
  export async function fetchSpotPerpSpreads(opts) {
250
- // Spot legs come from the venue definitions (def.spot, §12): only bot/arb spot venues
251
- // (separate/runtime account + transfer, §12.4 #3). Perp legs: those venues + pacifica.
294
+ // Spot legs come from the venue definitions (def.spot): only bot/arb spot venues
295
+ // (separate/runtime account + transfer). Perp legs: those venues + pacifica.
252
296
  ensureVenuesRegistered();
253
297
  const net = opts?.network ?? sharedApiNetwork();
254
298
  const { listBotSpotVenues, requireSpotAdapter, defaultSpotQuote } = await import("../exchanges/spot-factory.js");
package/dist/index.js CHANGED
@@ -17,6 +17,7 @@ import { createRequire } from "node:module";
17
17
  config({ path: resolve(process.env.HOME || "~", ".perp", ".env") });
18
18
  import chalk from "chalk";
19
19
  import { listVenues } from "./exchanges/venue.js";
20
+ import { optionsVenueWords } from "./exchanges/options-venues.js";
20
21
  import { PerpError } from "./errors.js";
21
22
  import { loadSettings } from "./settings.js";
22
23
  import { jsonError, jsonOk, printCliError, printJson } from "./utils.js";
@@ -49,7 +50,7 @@ const _defaultExchange = _settings.defaultExchange || "pacifica";
49
50
  const { program, getAdapterForExchange, isJson } = buildProgram({ defaultExchange: _defaultExchange, version: _pkg.version });
50
51
  // Smart landing page: `perp` with no subcommand
51
52
  const rawArgs = process.argv.slice(2);
52
- const _venueWords = listVenues().flatMap(v => [v.id, ...v.aliases]);
53
+ const _venueWords = [...listVenues().flatMap(v => [v.id, ...v.aliases]), ...optionsVenueWords()];
53
54
  const hasSubcommand = rawArgs.some((a) => !a.startsWith("-") && ![..._venueWords, "mainnet", "testnet"].includes(a));
54
55
  // `history list -n 5` (old record-count flag) → INVALID_PARAMS with the --limit migration, before
55
56
  // Commander turns it into a bare "invalid network" usage error.
@@ -118,18 +119,18 @@ if (rawArgs.length === 0 || (!hasSubcommand && !rawArgs.includes("-h") && !rawAr
118
119
  const landingVenues = [];
119
120
  for (const ex of LANDING_EXCHANGES) {
120
121
  const def = getVenue(ex);
121
- if (def.create === null || await hasCredentials(def.id, NO_CLI_CREDENTIAL_OPTS))
122
+ if (!def.roles.includes("account") || await hasCredentials(def.id, NO_CLI_CREDENTIAL_OPTS))
122
123
  landingVenues.push(ex);
123
124
  }
124
125
  const statusResults = await Promise.allSettled(landingVenues.map(async (ex) => {
125
- if (getVenue(ex).create === null) {
126
- return { exchange: ex, marketDataOnly: true, ok: false, equity: 0, positions: 0 };
126
+ if (!getVenue(ex).roles.includes("account")) {
127
+ return { exchange: ex, marketDataOnly: true, unofficialApi: getVenue(ex).apiStatus.kind === "unofficial", ok: false, equity: 0, positions: 0 };
127
128
  }
128
129
  try {
129
130
  const adapter = await withTimeout(getAdapterForExchange(ex), 5000);
130
131
  const [balance, positions] = await withTimeout(Promise.all([adapter.getBalance(), adapter.getPositions()]), 5000);
131
132
  const posCount = positions.filter(p => Number(p.size) > 0).length;
132
- // Spot value on top of perp equity for venues with def.spot (§12). Shared balances
133
+ // Spot value on top of perp equity for venues with def.spot. Shared balances
133
134
  // (already in perp equity) are skipped; unpriced tokens are excluded + warned.
134
135
  let spotValue = 0;
135
136
  const spotDef = getVenue(ex);
@@ -196,7 +197,7 @@ else {
196
197
  }).catch(async (err) => {
197
198
  // Central printer (utils.printCliError): typed PerpError code / remediation / details
198
199
  // are preserved (Rule #2: stay loud, keep semantic envelope); generic Errors go
199
- // through classifyError; CAPABILITY_UNSUPPORTED prints the uniform §13.6 line.
200
+ // through classifyError; CAPABILITY_UNSUPPORTED prints the uniform line.
200
201
  const { printCliError } = await import("./utils.js");
201
202
  printCliError(err, isJson());
202
203
  _origExit(1);
package/dist/jobs.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { Command } from "commander";
2
2
  import type { Network } from "./exchanges/venue-types.js";
3
+ import "./exchanges/venues/index.js";
3
4
  export interface JobEntry {
4
5
  id: string;
5
6
  strategy: string;
package/dist/jobs.js CHANGED
@@ -4,9 +4,11 @@ import { join } from "node:path";
4
4
  import { homedir } from "node:os";
5
5
  import { randomBytes } from "node:crypto";
6
6
  import { parse as parseDotenv } from "dotenv";
7
+ import { getVenue, roleUnsupported, tryResolveVenueId } from "./exchanges/venue.js";
8
+ import "./exchanges/venues/index.js";
7
9
  import { PerpError } from "./errors.js";
8
10
  import { QFEX_AUTOMATION_VERIFIED, QFEX_REDUCE_ONLY_WIRE, qfexAutomationGate, qfexReduceOnlyGate } from "./exchanges/qfex/signing.js";
9
- import { startRefusal } from "./exchanges/trading-v2.js";
11
+ import { startRefusal, unsupported } from "./exchanges/trading-v2.js";
10
12
  import { scrubSecretEnv, secretEnvNamesIn } from "./secret-env.js";
11
13
  const JOBS_DIR = join(homedir(), ".perp", "jobs");
12
14
  const LOGS_DIR = join(homedir(), ".perp", "logs");
@@ -165,6 +167,28 @@ export function startJob(opts) {
165
167
  if (automation !== null)
166
168
  throw startRefusal(what, "it would trade unattended", automation);
167
169
  }
170
+ for (const v of new Set([opts.venue, opts.exchange])) {
171
+ const id = v?.toLowerCase();
172
+ const what = `Background job "${opts.strategy}"`;
173
+ // RISEx: unattended trading stays off until its trading path is verified on mainnet (no TP/SL yet).
174
+ if (id === "risex" || id === "rise") {
175
+ // Read-only release (trading role gated): the uniform phase refusal.
176
+ const risex = getVenue("risex");
177
+ if (!risex.roles.includes("trading"))
178
+ throw startRefusal(what, "it would trade unattended", roleUnsupported(risex, "automation"));
179
+ throw startRefusal(what, "it would trade unattended", unsupported("RISEx", "automation", "unattended trading on RISEx is not enabled until its trading path is verified on mainnet (and it has no TP/SL yet)", "Trade RISEx manually with perp trade …"));
180
+ }
181
+ // TxFlow: market data only (unofficial read-only API).
182
+ if (id === "txflow" || id === "txf") {
183
+ throw startRefusal(what, "TxFlow has no trading in perp-cli", unsupported("txflow", "trading", "TxFlow is integrated through an unofficial read-only API (market data only)", "Trade on the TxFlow website"));
184
+ }
185
+ // Any other registered venue without the trading role (Variational, GRVT, Extended: read-only in this version).
186
+ const venueId = id === undefined ? null : tryResolveVenueId(id);
187
+ if (venueId !== null && !getVenue(venueId).roles.includes("trading")) {
188
+ const def = getVenue(venueId);
189
+ throw startRefusal(what, `${def.displayName} has no trading in perp-cli`, roleUnsupported(def, "trading"));
190
+ }
191
+ }
168
192
  }
169
193
  if (!hasTmux()) {
170
194
  throw new Error("tmux is required for background jobs. Install with: brew install tmux");
package/dist/landing.d.ts CHANGED
@@ -5,6 +5,8 @@ export type LandingExchangeStatus = {
5
5
  exchange: string;
6
6
  /** Venue has no account API — rendered as market data only. */
7
7
  marketDataOnly?: boolean;
8
+ /** Venue is read through an undocumented API (VenueDefinition.apiStatus) — labelled "unofficial API". */
9
+ unofficialApi?: boolean;
8
10
  ok: boolean;
9
11
  equity: number;
10
12
  positions: number;
package/dist/landing.js CHANGED
@@ -25,7 +25,7 @@ export function renderLandingExchangeLine(status, asterAgentMissing) {
25
25
  return ` ${chalk.yellow("⚙")} ${chalk.cyan(exchangeLabel(status.exchange).padEnd(14))} ${chalk.yellow("agent required")} ${chalk.gray("→ perp wallet agent approve aster")}`;
26
26
  }
27
27
  if (status.marketDataOnly) {
28
- return ` ${chalk.gray("◇")} ${chalk.cyan(exchangeLabel(status.exchange).padEnd(14))} ${chalk.gray("market data only")}`;
28
+ return ` ${chalk.gray("◇")} ${chalk.cyan(exchangeLabel(status.exchange).padEnd(14))} ${chalk.gray("market data only")}${status.unofficialApi ? chalk.yellow(" (unofficial API)") : ""}`;
29
29
  }
30
30
  const icon = status.ok ? chalk.green("●") : chalk.red("○");
31
31
  const equity = status.ok
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Read-only MCP tools for Pacifica Print and vaults (unauthenticated GETs; nothing is signed).
3
+ * Print deposits / ends and vault deposits / withdrawals are NOT exposed over MCP: the server has
4
+ * no money-moving deposit / withdraw tools (funds moves are CLI-only, `perp funds …`), and these
5
+ * follow the same rule — use `perp print deposit` / `perp vault deposit|withdraw` (with --dry-run).
6
+ */
7
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
+ import type { Network } from "./exchanges/venue-types.js";
9
+ export declare function registerPacificaEarnMcpTools(server: McpServer, network: Network, ok: (data: unknown, meta?: Record<string, unknown>) => string, errOf: (e: unknown, meta?: Record<string, unknown>) => string): void;
@@ -0,0 +1,58 @@
1
+ import { z } from "zod";
2
+ import { PacificaClient } from "./pacifica/client.js";
3
+ import { fetchPrintGames, fetchPrintHistory, fetchPrintMarket, fetchPrintPositions, findPrintGame, quotePrintOrder } from "./exchanges/pacifica-print.js";
4
+ import { DEPOSIT_WHITELIST_API_STATUS, fetchDepositWhitelist, fetchVaults, findVault, heldShares, withdrawWindowAt } from "./exchanges/pacifica-vaults.js";
5
+ export function registerPacificaEarnMcpTools(server, network, ok, errOf) {
6
+ const client = () => new PacificaClient({ network });
7
+ const run = async (meta, f) => {
8
+ try {
9
+ return { content: [{ type: "text", text: ok(await f(), meta) }] };
10
+ }
11
+ catch (e) {
12
+ return { content: [{ type: "text", text: errOf(e, meta) }], isError: true };
13
+ }
14
+ };
15
+ const address = z.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/, "base58 Solana address");
16
+ server.tool("pacifica_print_products", "Pacifica Print products (read-only): game id (e.g. BTC_24H, BTC_7D, BTC_1M), cycle length in days, max leverage, deposit caps, allowed target distance from mark. Print = a strike order that earns a premium each cycle; while resting it fills / is liquidated only at a cycle checkpoint, after a fill it is an ordinary perp position (continuous liquidation).", {}, async () => run({ exchange: "pacifica" }, async () => fetchPrintGames(client())));
17
+ server.tool("pacifica_print_quote", "Validate a Pacifica Print order and preview it (read-only, nothing signed): notional, size on fill, docs-formula liquidation price (an outer bound — a liquidation was observed earlier in one live case; venue trigger unpublished), max loss (deposit + premiums), lock period. The premium is not quoted (the venue's preview is a signed request). Placing the order is CLI-only: perp -e pacifica print deposit … (supports --dry-run).", {
18
+ game: z.string().describe("Print product, e.g. BTC_24H"),
19
+ direction: z.enum(["long", "short"]).describe("long = target below mark, short = target above mark"),
20
+ deposit: z.string().describe("USDC deposit, min 10"),
21
+ strike: z.string().describe("Target price (on the market tick)"),
22
+ leverage: z.string().describe("Whole-number leverage 1 … product max"),
23
+ cycles: z.number().int().optional().describe("Cycles 1 … 1000; omit = roll until cancelled"),
24
+ }, async ({ game, direction, deposit, strike, leverage, cycles }) => run({ exchange: "pacifica", game }, async () => {
25
+ const c = client();
26
+ const g = findPrintGame(await fetchPrintGames(c), game);
27
+ return quotePrintOrder(g, { game: g.game, direction, deposit, strikePrice: strike, leverage, cycles: cycles ?? null }, await fetchPrintMarket(c, g.asset));
28
+ }));
29
+ server.tool("pacifica_print_positions", "Pacifica Print orders of an account (read-only): current orders, or with history=true every order (paginated: limit / cursor). Status printing | cancelling | filled | liquidated | cancelled | completed | ended.", {
30
+ account: address.describe("Pacifica account (Solana address)"),
31
+ history: z.boolean().optional().describe("true = every order (history_summary), default false = current orders"),
32
+ limit: z.number().int().positive().optional(),
33
+ cursor: z.string().optional(),
34
+ }, async ({ account, history, limit, cursor }) => run({ exchange: "pacifica", account }, async () => {
35
+ if (history !== true)
36
+ return { account, positions: await fetchPrintPositions(client(), account) };
37
+ const page = await fetchPrintHistory(client(), account, { limit: limit ?? null, cursor: cursor ?? null });
38
+ return { account, positions: page.items, hasMore: page.hasMore, nextCursor: page.nextCursor };
39
+ }));
40
+ server.tool("pacifica_vaults", "Pacifica vaults (API: lakes), read-only. Without vault: every vault (TVL, NAV per share, fee, lock-up, withdrawal window, config); with account: plus that account's shares. With vault (address, or a nickname matching exactly one vault — an ambiguous nickname is INVALID_PARAMS listing the candidates): one vault plus live account equity, positions, deposit whitelist (unofficial endpoint, advisory) and whether withdrawals are open now. APY is not published (null). Deposits / withdrawals are CLI-only: perp -e pacifica vault deposit|withdraw (supports --dry-run).", {
41
+ vault: z.string().optional().describe("Vault address or exact nickname"),
42
+ account: address.optional().describe("Include this account's shares"),
43
+ }, async ({ vault, account }) => run({ exchange: "pacifica" }, async () => {
44
+ const c = client();
45
+ const vaults = await fetchVaults(c, account ?? null);
46
+ if (vault === undefined)
47
+ return vaults;
48
+ const v = findVault(vaults, vault);
49
+ const [acct, positions, whitelist] = await Promise.all([c.getAccount(v.address), c.getPositions(v.address), fetchDepositWhitelist(c, v.address).catch(() => null)]);
50
+ return {
51
+ ...v,
52
+ ...(account !== undefined ? { holding: heldShares(v, account) } : {}),
53
+ live: { accountEquity: acct.account_equity ?? null, availableToWithdraw: acct.available_to_withdraw ?? null, positions: positions.map((p) => ({ symbol: p.symbol, side: p.side, amount: p.amount, entryPrice: p.entry_price })) },
54
+ depositWhitelist: { apiStatus: DEPOSIT_WHITELIST_API_STATUS, addresses: whitelist },
55
+ withdrawWindow: withdrawWindowAt(v.config, Date.now()),
56
+ };
57
+ }));
58
+ }