perp-cli 0.3.3

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 (325) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +293 -0
  3. package/dist/__tests__/alert-logic.test.d.ts +1 -0
  4. package/dist/__tests__/alert-logic.test.js +107 -0
  5. package/dist/__tests__/arb-auto-3dex.test.d.ts +1 -0
  6. package/dist/__tests__/arb-auto-3dex.test.js +397 -0
  7. package/dist/__tests__/arb-history-stats.test.d.ts +1 -0
  8. package/dist/__tests__/arb-history-stats.test.js +176 -0
  9. package/dist/__tests__/arb-logic.test.d.ts +1 -0
  10. package/dist/__tests__/arb-logic.test.js +84 -0
  11. package/dist/__tests__/arb-manage.test.d.ts +1 -0
  12. package/dist/__tests__/arb-manage.test.js +253 -0
  13. package/dist/__tests__/arb-new-features.test.d.ts +1 -0
  14. package/dist/__tests__/arb-new-features.test.js +457 -0
  15. package/dist/__tests__/arb-sizing.test.d.ts +1 -0
  16. package/dist/__tests__/arb-sizing.test.js +48 -0
  17. package/dist/__tests__/arb-state.test.d.ts +1 -0
  18. package/dist/__tests__/arb-state.test.js +284 -0
  19. package/dist/__tests__/arb-userflow.test.d.ts +1 -0
  20. package/dist/__tests__/arb-userflow.test.js +945 -0
  21. package/dist/__tests__/arb-utils.test.d.ts +1 -0
  22. package/dist/__tests__/arb-utils.test.js +264 -0
  23. package/dist/__tests__/bot-conditions.test.d.ts +1 -0
  24. package/dist/__tests__/bot-conditions.test.js +341 -0
  25. package/dist/__tests__/client-id-tracker.test.d.ts +1 -0
  26. package/dist/__tests__/client-id-tracker.test.js +137 -0
  27. package/dist/__tests__/commands/new-atomic-commands.test.d.ts +1 -0
  28. package/dist/__tests__/commands/new-atomic-commands.test.js +502 -0
  29. package/dist/__tests__/commands/order-intent.test.d.ts +1 -0
  30. package/dist/__tests__/commands/order-intent.test.js +600 -0
  31. package/dist/__tests__/commands/trade-commands.test.d.ts +1 -0
  32. package/dist/__tests__/commands/trade-commands.test.js +821 -0
  33. package/dist/__tests__/config.test.d.ts +1 -0
  34. package/dist/__tests__/config.test.js +86 -0
  35. package/dist/__tests__/cross-chain-margin.test.d.ts +1 -0
  36. package/dist/__tests__/cross-chain-margin.test.js +287 -0
  37. package/dist/__tests__/dex-asset-map.test.d.ts +1 -0
  38. package/dist/__tests__/dex-asset-map.test.js +191 -0
  39. package/dist/__tests__/errors.test.d.ts +1 -0
  40. package/dist/__tests__/errors.test.js +110 -0
  41. package/dist/__tests__/event-stream.test.d.ts +1 -0
  42. package/dist/__tests__/event-stream.test.js +276 -0
  43. package/dist/__tests__/exchanges/interface.test.d.ts +1 -0
  44. package/dist/__tests__/exchanges/interface.test.js +132 -0
  45. package/dist/__tests__/exchanges/mock-adapter.d.ts +69 -0
  46. package/dist/__tests__/exchanges/mock-adapter.js +137 -0
  47. package/dist/__tests__/execution-log.test.d.ts +1 -0
  48. package/dist/__tests__/execution-log.test.js +106 -0
  49. package/dist/__tests__/funding-calc.test.d.ts +1 -0
  50. package/dist/__tests__/funding-calc.test.js +71 -0
  51. package/dist/__tests__/funding-history.test.d.ts +1 -0
  52. package/dist/__tests__/funding-history.test.js +343 -0
  53. package/dist/__tests__/funding-rates.test.d.ts +1 -0
  54. package/dist/__tests__/funding-rates.test.js +342 -0
  55. package/dist/__tests__/funding.test.d.ts +1 -0
  56. package/dist/__tests__/funding.test.js +173 -0
  57. package/dist/__tests__/gap-logic.test.d.ts +1 -0
  58. package/dist/__tests__/gap-logic.test.js +43 -0
  59. package/dist/__tests__/hip3-dex.test.d.ts +1 -0
  60. package/dist/__tests__/hip3-dex.test.js +234 -0
  61. package/dist/__tests__/integration/agent-features.integration.test.d.ts +1 -0
  62. package/dist/__tests__/integration/agent-features.integration.test.js +553 -0
  63. package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +13 -0
  64. package/dist/__tests__/integration/atomic-commands.integration.test.js +246 -0
  65. package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +1 -0
  66. package/dist/__tests__/integration/bridge-simulation.integration.test.js +453 -0
  67. package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +1 -0
  68. package/dist/__tests__/integration/bridge-strict.integration.test.js +812 -0
  69. package/dist/__tests__/integration/bridge.integration.test.d.ts +1 -0
  70. package/dist/__tests__/integration/bridge.integration.test.js +309 -0
  71. package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +1 -0
  72. package/dist/__tests__/integration/cli-e2e.integration.test.js +202 -0
  73. package/dist/__tests__/integration/dex-arb.integration.test.d.ts +1 -0
  74. package/dist/__tests__/integration/dex-arb.integration.test.js +116 -0
  75. package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +13 -0
  76. package/dist/__tests__/integration/envelope-consistency.integration.test.js +205 -0
  77. package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +1 -0
  78. package/dist/__tests__/integration/hip3-dex.integration.test.js +147 -0
  79. package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +1 -0
  80. package/dist/__tests__/integration/hyperliquid.integration.test.js +79 -0
  81. package/dist/__tests__/integration/lighter.integration.test.d.ts +1 -0
  82. package/dist/__tests__/integration/lighter.integration.test.js +53 -0
  83. package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +9 -0
  84. package/dist/__tests__/integration/new-commands-e2e.integration.test.js +236 -0
  85. package/dist/__tests__/integration/order-verification.integration.test.d.ts +1 -0
  86. package/dist/__tests__/integration/order-verification.integration.test.js +321 -0
  87. package/dist/__tests__/integration/pacifica.integration.test.d.ts +1 -0
  88. package/dist/__tests__/integration/pacifica.integration.test.js +75 -0
  89. package/dist/__tests__/integration/response-shapes.integration.test.d.ts +1 -0
  90. package/dist/__tests__/integration/response-shapes.integration.test.js +278 -0
  91. package/dist/__tests__/liquidity.test.d.ts +1 -0
  92. package/dist/__tests__/liquidity.test.js +225 -0
  93. package/dist/__tests__/plan-executor.test.d.ts +1 -0
  94. package/dist/__tests__/plan-executor.test.js +314 -0
  95. package/dist/__tests__/position-history.test.d.ts +1 -0
  96. package/dist/__tests__/position-history.test.js +367 -0
  97. package/dist/__tests__/retry.test.d.ts +1 -0
  98. package/dist/__tests__/retry.test.js +310 -0
  99. package/dist/__tests__/risk-assessment.test.d.ts +1 -0
  100. package/dist/__tests__/risk-assessment.test.js +145 -0
  101. package/dist/__tests__/security-adversarial.test.d.ts +1 -0
  102. package/dist/__tests__/security-adversarial.test.js +574 -0
  103. package/dist/__tests__/strategies.test.d.ts +1 -0
  104. package/dist/__tests__/strategies.test.js +539 -0
  105. package/dist/__tests__/trade-execution.test.d.ts +1 -0
  106. package/dist/__tests__/trade-execution.test.js +129 -0
  107. package/dist/__tests__/trade-validator.test.d.ts +1 -0
  108. package/dist/__tests__/trade-validator.test.js +655 -0
  109. package/dist/__tests__/utils.test.d.ts +1 -0
  110. package/dist/__tests__/utils.test.js +76 -0
  111. package/dist/api/public/hyperliquid.d.ts +18 -0
  112. package/dist/api/public/hyperliquid.js +82 -0
  113. package/dist/api/public/index.d.ts +8 -0
  114. package/dist/api/public/index.js +8 -0
  115. package/dist/api/public/lighter.d.ts +24 -0
  116. package/dist/api/public/lighter.js +100 -0
  117. package/dist/api/public/pacifica.d.ts +17 -0
  118. package/dist/api/public/pacifica.js +54 -0
  119. package/dist/api/public/urls.d.ts +12 -0
  120. package/dist/api/public/urls.js +33 -0
  121. package/dist/arb/history-stats.d.ts +44 -0
  122. package/dist/arb/history-stats.js +135 -0
  123. package/dist/arb/index.d.ts +4 -0
  124. package/dist/arb/index.js +4 -0
  125. package/dist/arb/sizing.d.ts +23 -0
  126. package/dist/arb/sizing.js +96 -0
  127. package/dist/arb/state.d.ts +51 -0
  128. package/dist/arb/state.js +112 -0
  129. package/dist/arb/utils.d.ts +81 -0
  130. package/dist/arb/utils.js +267 -0
  131. package/dist/arb-history-stats.d.ts +5 -0
  132. package/dist/arb-history-stats.js +5 -0
  133. package/dist/arb-sizing.d.ts +5 -0
  134. package/dist/arb-sizing.js +5 -0
  135. package/dist/arb-state.d.ts +5 -0
  136. package/dist/arb-state.js +5 -0
  137. package/dist/arb-utils.d.ts +5 -0
  138. package/dist/arb-utils.js +5 -0
  139. package/dist/bot/conditions.d.ts +32 -0
  140. package/dist/bot/conditions.js +141 -0
  141. package/dist/bot/config.d.ts +76 -0
  142. package/dist/bot/config.js +160 -0
  143. package/dist/bot/engine.d.ts +8 -0
  144. package/dist/bot/engine.js +519 -0
  145. package/dist/bot/presets.d.ts +11 -0
  146. package/dist/bot/presets.js +296 -0
  147. package/dist/bridge-engine.d.ts +133 -0
  148. package/dist/bridge-engine.js +1487 -0
  149. package/dist/cache.d.ts +25 -0
  150. package/dist/cache.js +99 -0
  151. package/dist/cli-spec.d.ts +50 -0
  152. package/dist/cli-spec.js +75 -0
  153. package/dist/client-id-tracker.d.ts +25 -0
  154. package/dist/client-id-tracker.js +76 -0
  155. package/dist/commands/account.d.ts +3 -0
  156. package/dist/commands/account.js +425 -0
  157. package/dist/commands/agent.d.ts +3 -0
  158. package/dist/commands/agent.js +386 -0
  159. package/dist/commands/alert.d.ts +2 -0
  160. package/dist/commands/alert.js +421 -0
  161. package/dist/commands/analytics.d.ts +3 -0
  162. package/dist/commands/analytics.js +311 -0
  163. package/dist/commands/arb/index.d.ts +3 -0
  164. package/dist/commands/arb/index.js +921 -0
  165. package/dist/commands/arb-auto.d.ts +54 -0
  166. package/dist/commands/arb-auto.js +1328 -0
  167. package/dist/commands/arb-manage.d.ts +5 -0
  168. package/dist/commands/arb-manage.js +5 -0
  169. package/dist/commands/arb.d.ts +2 -0
  170. package/dist/commands/arb.js +347 -0
  171. package/dist/commands/backtest.d.ts +2 -0
  172. package/dist/commands/backtest.js +327 -0
  173. package/dist/commands/bot.d.ts +3 -0
  174. package/dist/commands/bot.js +412 -0
  175. package/dist/commands/bridge.d.ts +2 -0
  176. package/dist/commands/bridge.js +396 -0
  177. package/dist/commands/dashboard.d.ts +3 -0
  178. package/dist/commands/dashboard.js +176 -0
  179. package/dist/commands/deposit.d.ts +4 -0
  180. package/dist/commands/deposit.js +573 -0
  181. package/dist/commands/dex.d.ts +3 -0
  182. package/dist/commands/dex.js +114 -0
  183. package/dist/commands/env.d.ts +2 -0
  184. package/dist/commands/env.js +136 -0
  185. package/dist/commands/funding.d.ts +2 -0
  186. package/dist/commands/funding.js +347 -0
  187. package/dist/commands/gap.d.ts +2 -0
  188. package/dist/commands/gap.js +305 -0
  189. package/dist/commands/health.d.ts +2 -0
  190. package/dist/commands/health.js +67 -0
  191. package/dist/commands/history.d.ts +2 -0
  192. package/dist/commands/history.js +235 -0
  193. package/dist/commands/init.d.ts +15 -0
  194. package/dist/commands/init.js +266 -0
  195. package/dist/commands/jobs.d.ts +2 -0
  196. package/dist/commands/jobs.js +133 -0
  197. package/dist/commands/manage.d.ts +4 -0
  198. package/dist/commands/manage.js +309 -0
  199. package/dist/commands/market.d.ts +3 -0
  200. package/dist/commands/market.js +225 -0
  201. package/dist/commands/plan.d.ts +3 -0
  202. package/dist/commands/plan.js +95 -0
  203. package/dist/commands/portfolio.d.ts +3 -0
  204. package/dist/commands/portfolio.js +169 -0
  205. package/dist/commands/rebalance.d.ts +3 -0
  206. package/dist/commands/rebalance.js +293 -0
  207. package/dist/commands/risk.d.ts +3 -0
  208. package/dist/commands/risk.js +169 -0
  209. package/dist/commands/run.d.ts +3 -0
  210. package/dist/commands/run.js +202 -0
  211. package/dist/commands/settings.d.ts +2 -0
  212. package/dist/commands/settings.js +102 -0
  213. package/dist/commands/stream.d.ts +5 -0
  214. package/dist/commands/stream.js +123 -0
  215. package/dist/commands/trade.d.ts +3 -0
  216. package/dist/commands/trade.js +1273 -0
  217. package/dist/commands/wallet.d.ts +14 -0
  218. package/dist/commands/wallet.js +602 -0
  219. package/dist/commands/withdraw.d.ts +3 -0
  220. package/dist/commands/withdraw.js +187 -0
  221. package/dist/config.d.ts +5 -0
  222. package/dist/config.js +68 -0
  223. package/dist/cross-chain-margin.d.ts +46 -0
  224. package/dist/cross-chain-margin.js +107 -0
  225. package/dist/dashboard/server.d.ts +80 -0
  226. package/dist/dashboard/server.js +340 -0
  227. package/dist/dashboard/ui.d.ts +4 -0
  228. package/dist/dashboard/ui.js +538 -0
  229. package/dist/dashboard/ws-feeds.d.ts +29 -0
  230. package/dist/dashboard/ws-feeds.js +660 -0
  231. package/dist/dex-asset-map.d.ts +80 -0
  232. package/dist/dex-asset-map.js +201 -0
  233. package/dist/errors.d.ts +109 -0
  234. package/dist/errors.js +84 -0
  235. package/dist/event-stream.d.ts +25 -0
  236. package/dist/event-stream.js +168 -0
  237. package/dist/exchanges/hyperliquid.d.ts +212 -0
  238. package/dist/exchanges/hyperliquid.js +931 -0
  239. package/dist/exchanges/interface.d.ts +95 -0
  240. package/dist/exchanges/interface.js +5 -0
  241. package/dist/exchanges/lighter.d.ts +159 -0
  242. package/dist/exchanges/lighter.js +793 -0
  243. package/dist/exchanges/pacifica.d.ts +51 -0
  244. package/dist/exchanges/pacifica.js +248 -0
  245. package/dist/execution-log.d.ts +36 -0
  246. package/dist/execution-log.js +102 -0
  247. package/dist/funding/history.d.ts +63 -0
  248. package/dist/funding/history.js +266 -0
  249. package/dist/funding/index.d.ts +3 -0
  250. package/dist/funding/index.js +3 -0
  251. package/dist/funding/normalize.d.ts +39 -0
  252. package/dist/funding/normalize.js +66 -0
  253. package/dist/funding/rates.d.ts +45 -0
  254. package/dist/funding/rates.js +172 -0
  255. package/dist/funding-history.d.ts +5 -0
  256. package/dist/funding-history.js +5 -0
  257. package/dist/funding-rates.d.ts +5 -0
  258. package/dist/funding-rates.js +5 -0
  259. package/dist/funding.d.ts +5 -0
  260. package/dist/funding.js +5 -0
  261. package/dist/index.d.ts +2 -0
  262. package/dist/index.js +458 -0
  263. package/dist/jobs.d.ts +37 -0
  264. package/dist/jobs.js +152 -0
  265. package/dist/liquidity.d.ts +34 -0
  266. package/dist/liquidity.js +100 -0
  267. package/dist/mcp-server.d.ts +9 -0
  268. package/dist/mcp-server.js +1206 -0
  269. package/dist/pacifica/client.d.ts +111 -0
  270. package/dist/pacifica/client.js +310 -0
  271. package/dist/pacifica/constants.d.ts +27 -0
  272. package/dist/pacifica/constants.js +47 -0
  273. package/dist/pacifica/deposit.d.ts +14 -0
  274. package/dist/pacifica/deposit.js +78 -0
  275. package/dist/pacifica/index.d.ts +6 -0
  276. package/dist/pacifica/index.js +11 -0
  277. package/dist/pacifica/signing.d.ts +49 -0
  278. package/dist/pacifica/signing.js +97 -0
  279. package/dist/pacifica/types/account.d.ts +42 -0
  280. package/dist/pacifica/types/account.js +1 -0
  281. package/dist/pacifica/types/index.d.ts +6 -0
  282. package/dist/pacifica/types/index.js +6 -0
  283. package/dist/pacifica/types/lake.d.ts +18 -0
  284. package/dist/pacifica/types/lake.js +1 -0
  285. package/dist/pacifica/types/market.d.ts +64 -0
  286. package/dist/pacifica/types/market.js +1 -0
  287. package/dist/pacifica/types/order.d.ts +92 -0
  288. package/dist/pacifica/types/order.js +1 -0
  289. package/dist/pacifica/types/position.d.ts +25 -0
  290. package/dist/pacifica/types/position.js +1 -0
  291. package/dist/pacifica/types/ws.d.ts +34 -0
  292. package/dist/pacifica/types/ws.js +41 -0
  293. package/dist/pacifica/ws-client.d.ts +42 -0
  294. package/dist/pacifica/ws-client.js +180 -0
  295. package/dist/plan-executor.d.ts +48 -0
  296. package/dist/plan-executor.js +280 -0
  297. package/dist/position-history.d.ts +68 -0
  298. package/dist/position-history.js +222 -0
  299. package/dist/rebalance.d.ts +64 -0
  300. package/dist/rebalance.js +142 -0
  301. package/dist/retry.d.ts +74 -0
  302. package/dist/retry.js +129 -0
  303. package/dist/risk.d.ts +48 -0
  304. package/dist/risk.js +156 -0
  305. package/dist/settings.d.ts +19 -0
  306. package/dist/settings.js +45 -0
  307. package/dist/shared-api.d.ts +5 -0
  308. package/dist/shared-api.js +5 -0
  309. package/dist/strategies/dca.d.ts +25 -0
  310. package/dist/strategies/dca.js +114 -0
  311. package/dist/strategies/funding-arb.d.ts +15 -0
  312. package/dist/strategies/funding-arb.js +281 -0
  313. package/dist/strategies/grid.d.ts +34 -0
  314. package/dist/strategies/grid.js +185 -0
  315. package/dist/strategies/trailing-stop.d.ts +17 -0
  316. package/dist/strategies/trailing-stop.js +121 -0
  317. package/dist/strategies/twap.d.ts +20 -0
  318. package/dist/strategies/twap.js +78 -0
  319. package/dist/trade-validator.d.ts +39 -0
  320. package/dist/trade-validator.js +154 -0
  321. package/dist/utils.d.ts +38 -0
  322. package/dist/utils.js +110 -0
  323. package/package.json +63 -0
  324. package/skills/perp-cli/SKILL.md +149 -0
  325. package/skills/perp-cli/references/commands.md +143 -0
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Integration tests verifying JSON envelope consistency across all CLI commands.
3
+ *
4
+ * Every --json output must:
5
+ * 1. Be valid JSON (single object, no extra text)
6
+ * 2. Have ok: boolean
7
+ * 3. If ok=true: have data and meta.timestamp
8
+ * 4. If ok=false: have error.code, error.message, and meta.timestamp
9
+ *
10
+ * These tests spawn the real CLI process to catch any console.log leaks,
11
+ * chalk output in JSON mode, or missing envelope wrappers.
12
+ */
13
+ import "dotenv/config";
14
+ import { execSync } from "child_process";
15
+ import { describe, it, expect } from "vitest";
16
+ const CLI_CWD = "/Users/hik/Documents/GitHub/pacifica/packages/cli";
17
+ const CLI_CMD = "npx tsx src/index.ts";
18
+ function runCliSafe(args) {
19
+ try {
20
+ const stdout = execSync(`${CLI_CMD} ${args}`, {
21
+ encoding: "utf-8",
22
+ cwd: CLI_CWD,
23
+ timeout: 25000,
24
+ env: { ...process.env, NODE_NO_WARNINGS: "1" },
25
+ stdio: ["pipe", "pipe", "pipe"],
26
+ });
27
+ return { stdout, stderr: "", exitCode: 0 };
28
+ }
29
+ catch (err) {
30
+ const e = err;
31
+ return {
32
+ stdout: e.stdout ?? "",
33
+ stderr: e.stderr ?? "",
34
+ exitCode: e.status ?? 1,
35
+ };
36
+ }
37
+ }
38
+ function validateEnvelope(raw, label) {
39
+ // 1. Must be valid JSON
40
+ let parsed;
41
+ try {
42
+ parsed = JSON.parse(raw);
43
+ }
44
+ catch {
45
+ throw new Error(`[${label}] stdout is not valid JSON:\n${raw.slice(0, 500)}`);
46
+ }
47
+ // 2. Must have ok: boolean
48
+ expect(typeof parsed.ok).toBe("boolean");
49
+ // 3. Must have meta.timestamp
50
+ expect(parsed.meta).toBeDefined();
51
+ expect(typeof parsed.meta.timestamp).toBe("string");
52
+ expect(parsed.meta.timestamp.length).toBeGreaterThan(0);
53
+ // Verify ISO 8601
54
+ const ts = new Date(parsed.meta.timestamp);
55
+ expect(ts.getTime()).toBeGreaterThan(0);
56
+ if (parsed.ok) {
57
+ // 4a. Success: must have data
58
+ expect(parsed.data).toBeDefined();
59
+ }
60
+ else {
61
+ // 4b. Error: must have error.code and error.message
62
+ expect(parsed.error).toBeDefined();
63
+ expect(typeof parsed.error.code).toBe("string");
64
+ expect(parsed.error.code.length).toBeGreaterThan(0);
65
+ expect(typeof parsed.error.message).toBe("string");
66
+ }
67
+ return parsed;
68
+ }
69
+ describe("JSON Envelope Consistency", { timeout: 30000 }, () => {
70
+ // ── Commands that require no adapter (always work) ──
71
+ describe("no-adapter commands", () => {
72
+ it("api-spec: valid success envelope", () => {
73
+ const { stdout } = runCliSafe("--json api-spec");
74
+ const env = validateEnvelope(stdout, "api-spec");
75
+ expect(env.ok).toBe(true);
76
+ });
77
+ it("plan example: valid JSON output", () => {
78
+ const { stdout } = runCliSafe("--json plan example");
79
+ const parsed = JSON.parse(stdout);
80
+ // plan example wraps in envelope in --json mode
81
+ if (parsed.ok !== undefined) {
82
+ // Envelope mode
83
+ expect(parsed.ok).toBe(true);
84
+ expect(parsed.data).toBeDefined();
85
+ }
86
+ else {
87
+ // Raw plan JSON (legacy)
88
+ expect(parsed.steps || parsed.version).toBeDefined();
89
+ }
90
+ });
91
+ });
92
+ // ── Error paths ──
93
+ describe("error envelopes", () => {
94
+ it("unknown command: CLI_ERROR envelope", () => {
95
+ const { stdout } = runCliSafe("--json nonexistentcommand999");
96
+ const env = validateEnvelope(stdout, "unknown command");
97
+ expect(env.ok).toBe(false);
98
+ expect(env.error.code).toBe("CLI_ERROR");
99
+ });
100
+ it("plan validate with bad file: error envelope", () => {
101
+ const { stdout } = runCliSafe("--json plan validate /tmp/__no_file_here_99.json");
102
+ const env = validateEnvelope(stdout, "plan validate bad file");
103
+ expect(env.ok).toBe(false);
104
+ expect(env.error.message).toBeTruthy();
105
+ });
106
+ it("stdout has no extra text before/after JSON", () => {
107
+ const { stdout } = runCliSafe("--json api-spec");
108
+ // Trim whitespace, should start with { and end with }
109
+ const trimmed = stdout.trim();
110
+ expect(trimmed.startsWith("{")).toBe(true);
111
+ expect(trimmed.endsWith("}")).toBe(true);
112
+ // No extra lines
113
+ const lines = trimmed.split("\n").filter((l) => l.trim().length > 0);
114
+ // All lines should be part of the JSON (indented)
115
+ const reparsed = JSON.parse(trimmed);
116
+ expect(reparsed.ok).toBeDefined();
117
+ });
118
+ it("stderr is empty in --json mode for error paths", () => {
119
+ const { stderr } = runCliSafe("--json nonexistentcommand999");
120
+ // In JSON mode, errors go to stdout as JSON, not stderr
121
+ // stderr should be empty or only contain warnings
122
+ // (Commander may still write to stderr in some cases)
123
+ // We mainly verify that stdout has the JSON envelope
124
+ });
125
+ });
126
+ // ── Commands that need HL adapter (read-only) ──
127
+ const HAS_KEY = !!(process.env.HYPERLIQUID_PRIVATE_KEY || process.env.HL_PRIVATE_KEY);
128
+ describe.skipIf(!HAS_KEY)("HL adapter commands — envelope validation", () => {
129
+ it("market list: success envelope with array data", () => {
130
+ const { stdout } = runCliSafe("--json -e hyperliquid market list");
131
+ const env = validateEnvelope(stdout, "market list");
132
+ expect(env.ok).toBe(true);
133
+ expect(Array.isArray(env.data)).toBe(true);
134
+ expect(env.data.length).toBeGreaterThan(0);
135
+ });
136
+ it("market mid BTC: success envelope with mid price", () => {
137
+ const { stdout } = runCliSafe("--json -e hyperliquid market mid BTC");
138
+ const env = validateEnvelope(stdout, "market mid BTC");
139
+ expect(env.ok).toBe(true);
140
+ const data = env.data;
141
+ expect(data.symbol).toBe("BTC");
142
+ expect(data.mid).toBeDefined();
143
+ });
144
+ it("market info BTC: success envelope with market info", () => {
145
+ const { stdout } = runCliSafe("--json -e hyperliquid market info BTC");
146
+ const env = validateEnvelope(stdout, "market info BTC");
147
+ expect(env.ok).toBe(true);
148
+ });
149
+ it("market book BTC: success envelope with orderbook", () => {
150
+ const { stdout } = runCliSafe("--json -e hyperliquid market book BTC");
151
+ const env = validateEnvelope(stdout, "market book BTC");
152
+ expect(env.ok).toBe(true);
153
+ const data = env.data;
154
+ expect(data.bids).toBeDefined();
155
+ expect(data.asks).toBeDefined();
156
+ });
157
+ it("account info: success envelope with balance", () => {
158
+ const { stdout } = runCliSafe("--json -e hyperliquid account info");
159
+ const env = validateEnvelope(stdout, "account info");
160
+ expect(env.ok).toBe(true);
161
+ const data = env.data;
162
+ expect(data.equity).toBeDefined();
163
+ expect(data.available).toBeDefined();
164
+ });
165
+ it("account positions: success envelope with array", () => {
166
+ const { stdout } = runCliSafe("--json -e hyperliquid account positions");
167
+ const env = validateEnvelope(stdout, "account positions");
168
+ expect(env.ok).toBe(true);
169
+ expect(Array.isArray(env.data)).toBe(true);
170
+ });
171
+ it("account orders: success envelope with array", () => {
172
+ const { stdout } = runCliSafe("--json -e hyperliquid account orders");
173
+ const env = validateEnvelope(stdout, "account orders");
174
+ expect(env.ok).toBe(true);
175
+ expect(Array.isArray(env.data)).toBe(true);
176
+ });
177
+ it("status: success envelope with exchange, balance, positions, orders", () => {
178
+ const { stdout } = runCliSafe("--json -e hyperliquid status");
179
+ const env = validateEnvelope(stdout, "status");
180
+ expect(env.ok).toBe(true);
181
+ const data = env.data;
182
+ expect(data.exchange).toBe("hyperliquid");
183
+ expect(data.balance).toBeDefined();
184
+ expect(data.positions).toBeDefined();
185
+ expect(data.orders).toBeDefined();
186
+ });
187
+ it("account margin XYZFAKE: POSITION_NOT_FOUND error envelope", () => {
188
+ const { stdout } = runCliSafe("--json -e hyperliquid account margin XYZFAKE");
189
+ const env = validateEnvelope(stdout, "account margin XYZFAKE");
190
+ expect(env.ok).toBe(false);
191
+ expect(env.error.code).toBe("POSITION_NOT_FOUND");
192
+ });
193
+ it("trade fills: success envelope with array", () => {
194
+ const { stdout } = runCliSafe("--json -e hyperliquid trade fills");
195
+ const env = validateEnvelope(stdout, "trade fills");
196
+ expect(env.ok).toBe(true);
197
+ expect(Array.isArray(env.data)).toBe(true);
198
+ });
199
+ it("health: success envelope with healthy flag", () => {
200
+ const { stdout } = runCliSafe("--json -e hyperliquid health");
201
+ const env = validateEnvelope(stdout, "health");
202
+ expect(env.ok).toBe(true);
203
+ });
204
+ });
205
+ });
@@ -0,0 +1,147 @@
1
+ import { describe, it, expect } from "vitest";
2
+ /**
3
+ * Integration tests for HIP-3 deployed perp dex support.
4
+ *
5
+ * These tests hit the real Hyperliquid mainnet API (read-only)
6
+ * to verify our parsing of allPerpMetas and dex-specific queries.
7
+ *
8
+ * No private key needed — all calls are public info endpoints.
9
+ *
10
+ * Run: pnpm --filter perp-cli test -- --testPathPattern hip3-dex.integration
11
+ */
12
+ const HL_INFO_URL = "https://api.hyperliquid.xyz/info";
13
+ async function infoPost(body) {
14
+ const res = await fetch(HL_INFO_URL, {
15
+ method: "POST",
16
+ headers: { "Content-Type": "application/json" },
17
+ body: JSON.stringify(body),
18
+ });
19
+ if (!res.ok)
20
+ throw new Error(`HTTP ${res.status}`);
21
+ return res.json();
22
+ }
23
+ describe("HIP-3 Integration — allPerpMetas", () => {
24
+ it("returns an array with native perps as first entry", async () => {
25
+ const data = await infoPost({ type: "allPerpMetas" });
26
+ expect(Array.isArray(data)).toBe(true);
27
+ expect(data.length).toBeGreaterThanOrEqual(1);
28
+ // First entry: native perps (BTC, ETH, etc.)
29
+ const native = data[0];
30
+ expect(native).toHaveProperty("universe");
31
+ expect(Array.isArray(native.universe)).toBe(true);
32
+ expect(native.universe.length).toBeGreaterThan(100); // 200+ native perps
33
+ const btc = native.universe.find((a) => a.name === "BTC");
34
+ expect(btc).toBeTruthy();
35
+ expect(btc.maxLeverage).toBeGreaterThanOrEqual(40);
36
+ }, 15000);
37
+ it("has deployed dexes after index 0", async () => {
38
+ const data = await infoPost({ type: "allPerpMetas" });
39
+ expect(data.length).toBeGreaterThan(1); // at least 1 deployed dex
40
+ // Each deployed dex entry has universe with prefixed assets
41
+ for (let i = 1; i < data.length; i++) {
42
+ const entry = data[i];
43
+ expect(entry).toHaveProperty("universe");
44
+ expect(entry).toHaveProperty("collateralToken");
45
+ if (entry.universe.length === 0)
46
+ continue; // skip empty
47
+ // Assets should have a prefix with colon
48
+ const firstName = entry.universe[0].name;
49
+ expect(firstName).toContain(":");
50
+ }
51
+ }, 15000);
52
+ it("extracts known dex names (xyz, vntl, etc.)", async () => {
53
+ const data = await infoPost({ type: "allPerpMetas" });
54
+ const dexNames = [];
55
+ for (let i = 1; i < data.length; i++) {
56
+ const universe = data[i]?.universe ?? [];
57
+ if (universe.length === 0)
58
+ continue;
59
+ const first = universe[0].name;
60
+ const colon = first.indexOf(":");
61
+ if (colon > 0)
62
+ dexNames.push(first.slice(0, colon));
63
+ }
64
+ expect(dexNames.length).toBeGreaterThan(0);
65
+ // xyz is one of the most established deployed dexes
66
+ expect(dexNames).toContain("xyz");
67
+ console.log("Discovered deployed dexes:", dexNames.join(", "));
68
+ }, 15000);
69
+ });
70
+ describe("HIP-3 Integration — dex-specific metaAndAssetCtxs", () => {
71
+ it("fetches xyz dex markets with mark prices", async () => {
72
+ const data = await infoPost({ type: "metaAndAssetCtxs", dex: "xyz" });
73
+ expect(Array.isArray(data)).toBe(true);
74
+ expect(data.length).toBe(2); // [meta, ctxs]
75
+ const universe = data[0]?.universe ?? [];
76
+ const ctxs = data[1] ?? [];
77
+ expect(universe.length).toBeGreaterThan(0);
78
+ expect(ctxs.length).toBe(universe.length);
79
+ // All assets should have xyz: prefix
80
+ for (const asset of universe) {
81
+ expect(asset.name.startsWith("xyz:")).toBe(true);
82
+ }
83
+ // Check TSLA exists and has valid price
84
+ const tslaIdx = universe.findIndex((a) => a.name === "xyz:TSLA");
85
+ expect(tslaIdx).toBeGreaterThanOrEqual(0);
86
+ const tslaCtx = ctxs[tslaIdx];
87
+ expect(Number(tslaCtx.markPx)).toBeGreaterThan(0);
88
+ expect(tslaCtx).toHaveProperty("funding");
89
+ expect(tslaCtx).toHaveProperty("openInterest");
90
+ console.log(`xyz:TSLA — mark: $${tslaCtx.markPx}, funding: ${tslaCtx.funding}, OI: ${tslaCtx.openInterest}`);
91
+ }, 15000);
92
+ it("fetches dex-specific meta (used for asset map)", async () => {
93
+ const data = await infoPost({ type: "meta", dex: "xyz" });
94
+ expect(data).toHaveProperty("universe");
95
+ expect(data.universe.length).toBeGreaterThan(0);
96
+ expect(data.universe[0].name).toContain("xyz:");
97
+ // Verify assets can be mapped to indices
98
+ const assetMap = new Map();
99
+ data.universe.forEach((a, i) => {
100
+ assetMap.set(a.name, i);
101
+ });
102
+ expect(assetMap.has("xyz:TSLA")).toBe(true);
103
+ expect(assetMap.has("xyz:NVDA")).toBe(true);
104
+ console.log(`xyz dex has ${assetMap.size} assets`);
105
+ }, 15000);
106
+ });
107
+ describe("HIP-3 Integration — dex-specific clearinghouseState", () => {
108
+ it("returns valid structure for a random address", async () => {
109
+ // Use a zero address — no positions, but structure should be valid
110
+ const address = "0x0000000000000000000000000000000000000001";
111
+ const data = await infoPost({
112
+ type: "clearinghouseState",
113
+ user: address,
114
+ dex: "xyz",
115
+ });
116
+ // Should return clearinghouse state structure
117
+ expect(data).toBeTruthy();
118
+ expect(data).toHaveProperty("assetPositions");
119
+ expect(Array.isArray(data.assetPositions)).toBe(true);
120
+ // Zero address should have no positions
121
+ expect(data.assetPositions.length).toBe(0);
122
+ // Should have margin info
123
+ const hasMoney = data.marginSummary || data.crossMarginSummary;
124
+ expect(hasMoney).toBeTruthy();
125
+ }, 15000);
126
+ });
127
+ describe("HIP-3 Integration — native vs dex asset separation", () => {
128
+ it("native perps do not have prefixed names", async () => {
129
+ const data = await infoPost({ type: "metaAndAssetCtxs" });
130
+ const universe = data[0]?.universe ?? [];
131
+ // Native assets should NOT have colon prefix
132
+ const btc = universe.find((a) => a.name === "BTC");
133
+ expect(btc).toBeTruthy();
134
+ expect(btc.name).not.toContain(":");
135
+ // None of the native assets should be prefixed
136
+ const prefixed = universe.filter((a) => a.name.includes(":"));
137
+ expect(prefixed.length).toBe(0);
138
+ }, 15000);
139
+ it("dex perps all have matching prefix", async () => {
140
+ const data = await infoPost({ type: "metaAndAssetCtxs", dex: "xyz" });
141
+ const universe = data[0]?.universe ?? [];
142
+ // ALL xyz dex assets should have "xyz:" prefix
143
+ for (const asset of universe) {
144
+ expect(asset.name.startsWith("xyz:")).toBe(true);
145
+ }
146
+ }, 15000);
147
+ });
@@ -0,0 +1,79 @@
1
+ import { describe, it, expect, beforeAll } from "vitest";
2
+ import { HyperliquidAdapter } from "../../exchanges/hyperliquid.js";
3
+ /**
4
+ * Integration tests for Hyperliquid exchange adapter (Testnet).
5
+ *
6
+ * Prerequisites:
7
+ * 1. Set HYPERLIQUID_PRIVATE_KEY env var (0x-prefixed EVM private key, 66 chars)
8
+ * 2. Have testnet ETH on Arbitrum Sepolia (for gas)
9
+ * 3. Have testnet USDC deposited into Hyperliquid testnet
10
+ *
11
+ * Run: HYPERLIQUID_PRIVATE_KEY=<key> pnpm --filter perp-cli test -- --testPathPattern integration/hyperliquid
12
+ */
13
+ const SKIP = !process.env.HYPERLIQUID_PRIVATE_KEY;
14
+ describe.skipIf(SKIP)("Hyperliquid Integration (Testnet)", () => {
15
+ let adapter;
16
+ beforeAll(async () => {
17
+ adapter = new HyperliquidAdapter(process.env.HYPERLIQUID_PRIVATE_KEY, true);
18
+ await adapter.init();
19
+ }, 30000);
20
+ describe("Read-only operations", () => {
21
+ it("initializes and has address", () => {
22
+ expect(adapter.address).toBeTruthy();
23
+ expect(adapter.address.startsWith("0x")).toBe(true);
24
+ });
25
+ it("fetches markets", async () => {
26
+ const markets = await adapter.getMarkets();
27
+ expect(markets.length).toBeGreaterThan(0);
28
+ const btc = markets.find((m) => m.symbol === "BTC");
29
+ expect(btc).toBeTruthy();
30
+ expect(Number(btc.markPrice)).toBeGreaterThan(0);
31
+ });
32
+ it("resolves asset index for BTC", () => {
33
+ const idx = adapter.getAssetIndex("BTC");
34
+ expect(typeof idx).toBe("number");
35
+ expect(idx).toBeGreaterThanOrEqual(0);
36
+ });
37
+ it("fetches orderbook for BTC", async () => {
38
+ const book = await adapter.getOrderbook("BTC");
39
+ expect(book.bids.length).toBeGreaterThan(0);
40
+ expect(book.asks.length).toBeGreaterThan(0);
41
+ });
42
+ it("fetches balance", async () => {
43
+ const balance = await adapter.getBalance();
44
+ expect(balance).toHaveProperty("equity");
45
+ expect(Number(balance.equity)).toBeGreaterThanOrEqual(0);
46
+ });
47
+ it("fetches positions", async () => {
48
+ const positions = await adapter.getPositions();
49
+ expect(Array.isArray(positions)).toBe(true);
50
+ });
51
+ it("fetches open orders", async () => {
52
+ const orders = await adapter.getOpenOrders();
53
+ expect(Array.isArray(orders)).toBe(true);
54
+ });
55
+ });
56
+ describe("Trading operations", () => {
57
+ const TEST_SYMBOL = "BTC";
58
+ let limitOrderId;
59
+ it("places a limit buy order (far from market)", async () => {
60
+ const result = await adapter.limitOrder(TEST_SYMBOL, "buy", "10000", "0.001");
61
+ expect(result).toBeTruthy();
62
+ const orders = await adapter.getOpenOrders();
63
+ const myOrder = orders.find((o) => o.symbol === TEST_SYMBOL && o.side === "buy");
64
+ if (myOrder) {
65
+ limitOrderId = myOrder.orderId;
66
+ }
67
+ });
68
+ it("cancels the limit order", async () => {
69
+ if (!limitOrderId)
70
+ return;
71
+ const result = await adapter.cancelOrder(TEST_SYMBOL, limitOrderId);
72
+ expect(result).toBeTruthy();
73
+ });
74
+ it("cancel all orders succeeds", async () => {
75
+ const result = await adapter.cancelAllOrders();
76
+ expect(result).toBeTruthy();
77
+ });
78
+ });
79
+ });
@@ -0,0 +1,53 @@
1
+ import { describe, it, expect, beforeAll } from "vitest";
2
+ import { LighterAdapter } from "../../exchanges/lighter.js";
3
+ /**
4
+ * Integration tests for Lighter exchange adapter (Mainnet ONLY — no testnet available).
5
+ *
6
+ * ⚠️ WARNING: Lighter has NO testnet. These tests run on MAINNET with REAL funds.
7
+ * Only run if you understand the risks and have funded an account.
8
+ *
9
+ * Prerequisites:
10
+ * 1. Set LIGHTER_PRIVATE_KEY env var (0x-prefixed EVM private key, 66 chars)
11
+ * 2. Have ETH on Ethereum L1 for gas (~$3-10 per tx)
12
+ * 3. Have USDC deposited into Lighter (mainnet)
13
+ *
14
+ * Run: LIGHTER_PRIVATE_KEY=<key> LIGHTER_INTEGRATION=1 pnpm --filter perp-cli test -- --testPathPattern integration/lighter
15
+ */
16
+ // Require BOTH the key AND explicit opt-in flag (since this is mainnet)
17
+ const SKIP = !process.env.LIGHTER_PRIVATE_KEY || !process.env.LIGHTER_INTEGRATION;
18
+ describe.skipIf(SKIP)("Lighter Integration (Mainnet)", () => {
19
+ let adapter;
20
+ beforeAll(async () => {
21
+ adapter = new LighterAdapter(process.env.LIGHTER_PRIVATE_KEY);
22
+ await adapter.init();
23
+ }, 30000);
24
+ describe("Read-only operations (safe)", () => {
25
+ it("initializes with account index", () => {
26
+ expect(adapter.accountIndex).toBeGreaterThanOrEqual(0);
27
+ });
28
+ it("fetches markets", async () => {
29
+ const markets = await adapter.getMarkets();
30
+ expect(Array.isArray(markets)).toBe(true);
31
+ });
32
+ it("fetches orderbook", async () => {
33
+ const book = await adapter.getOrderbook("BTC");
34
+ expect(book).toHaveProperty("bids");
35
+ expect(book).toHaveProperty("asks");
36
+ });
37
+ it("fetches balance", async () => {
38
+ const balance = await adapter.getBalance();
39
+ expect(balance).toHaveProperty("equity");
40
+ expect(Number(balance.equity)).toBeGreaterThanOrEqual(0);
41
+ });
42
+ it("fetches positions", async () => {
43
+ const positions = await adapter.getPositions();
44
+ expect(Array.isArray(positions)).toBe(true);
45
+ });
46
+ it("fetches open orders", async () => {
47
+ const orders = await adapter.getOpenOrders();
48
+ expect(Array.isArray(orders)).toBe(true);
49
+ });
50
+ });
51
+ // Trading tests intentionally omitted for Lighter (mainnet = real money)
52
+ // Use the CLI manually for trade testing: perp trade limit BTC buy 10000 0.001 --exchange lighter
53
+ });
@@ -0,0 +1,9 @@
1
+ /**
2
+ * E2E integration tests for new atomic commands and api-spec.
3
+ * These tests spawn the actual CLI process and verify JSON output.
4
+ *
5
+ * - api-spec: no adapter needed, always works
6
+ * - market mid: needs HL mainnet (read-only, no key needed for public data)
7
+ * - error envelopes: verify structured errors across commands
8
+ */
9
+ import "dotenv/config";