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,236 @@
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";
10
+ import { execSync } from "child_process";
11
+ import { describe, it, expect } from "vitest";
12
+ const CLI_CWD = "/Users/hik/Documents/GitHub/pacifica/packages/cli";
13
+ const CLI_CMD = "npx tsx src/index.ts";
14
+ function runCli(args) {
15
+ return execSync(`${CLI_CMD} ${args}`, {
16
+ encoding: "utf-8",
17
+ cwd: CLI_CWD,
18
+ timeout: 25000,
19
+ env: { ...process.env, NODE_NO_WARNINGS: "1" },
20
+ });
21
+ }
22
+ function runCliSafe(args) {
23
+ try {
24
+ const stdout = execSync(`${CLI_CMD} ${args}`, {
25
+ encoding: "utf-8",
26
+ cwd: CLI_CWD,
27
+ timeout: 25000,
28
+ env: { ...process.env, NODE_NO_WARNINGS: "1" },
29
+ stdio: ["pipe", "pipe", "pipe"],
30
+ });
31
+ return { stdout, stderr: "", exitCode: 0 };
32
+ }
33
+ catch (err) {
34
+ const e = err;
35
+ return {
36
+ stdout: e.stdout ?? "",
37
+ stderr: e.stderr ?? "",
38
+ exitCode: e.status ?? 1,
39
+ };
40
+ }
41
+ }
42
+ describe("New Commands E2E Integration", { timeout: 30000 }, () => {
43
+ // ══════════════════════════════════════════════════════════
44
+ // api-spec — no adapter needed
45
+ // ══════════════════════════════════════════════════════════
46
+ describe("perp api-spec", () => {
47
+ let spec;
48
+ it("outputs valid JSON envelope with ok:true", () => {
49
+ const output = runCli("api-spec");
50
+ spec = JSON.parse(output);
51
+ expect(spec.ok).toBe(true);
52
+ expect(spec.data).toBeDefined();
53
+ expect(spec.meta).toBeDefined();
54
+ expect(spec.meta.timestamp).toBeDefined();
55
+ });
56
+ it("data contains name, version, commands, errorCodes", () => {
57
+ const output = runCli("api-spec");
58
+ spec = JSON.parse(output);
59
+ const data = spec.data;
60
+ expect(data.name).toBe("perp");
61
+ expect(data.version).toBeDefined();
62
+ expect(data.description).toBeDefined();
63
+ expect(Array.isArray(data.commands)).toBe(true);
64
+ expect(typeof data.errorCodes).toBe("object");
65
+ expect(Array.isArray(data.exchanges)).toBe(true);
66
+ expect(Array.isArray(data.tips)).toBe(true);
67
+ });
68
+ it("commands include all major command groups with subcommands", () => {
69
+ const output = runCli("api-spec");
70
+ spec = JSON.parse(output);
71
+ const data = spec.data;
72
+ const commands = data.commands;
73
+ const names = commands.map((c) => c.name);
74
+ expect(names).toContain("market");
75
+ expect(names).toContain("account");
76
+ expect(names).toContain("trade");
77
+ expect(names).toContain("arb");
78
+ expect(names).toContain("status");
79
+ expect(names).toContain("health");
80
+ expect(names).toContain("portfolio");
81
+ expect(names).toContain("risk");
82
+ expect(names).toContain("api-spec");
83
+ // market should have subcommands including mid
84
+ const market = commands.find((c) => c.name === "market");
85
+ expect(market?.subcommands).toBeDefined();
86
+ const marketSubs = market.subcommands.map((s) => s.name);
87
+ expect(marketSubs).toContain("mid");
88
+ expect(marketSubs).toContain("list");
89
+ expect(marketSubs).toContain("info");
90
+ expect(marketSubs).toContain("book");
91
+ // account should have margin subcommand
92
+ const account = commands.find((c) => c.name === "account");
93
+ const accountSubs = account.subcommands.map((s) => s.name);
94
+ expect(accountSubs).toContain("margin");
95
+ expect(accountSubs).toContain("info");
96
+ expect(accountSubs).toContain("positions");
97
+ // trade should have status and fills subcommands
98
+ const trade = commands.find((c) => c.name === "trade");
99
+ const tradeSubs = trade.subcommands.map((s) => s.name);
100
+ expect(tradeSubs).toContain("status");
101
+ expect(tradeSubs).toContain("fills");
102
+ });
103
+ it("errorCodes have consistent structure", () => {
104
+ const output = runCli("api-spec");
105
+ spec = JSON.parse(output);
106
+ const data = spec.data;
107
+ const errorCodes = data.errorCodes;
108
+ const codes = Object.keys(errorCodes);
109
+ expect(codes.length).toBeGreaterThanOrEqual(15);
110
+ for (const [code, info] of Object.entries(errorCodes)) {
111
+ expect(typeof info.status).toBe("number");
112
+ expect(typeof info.retryable).toBe("boolean");
113
+ expect(typeof info.description).toBe("string");
114
+ expect(info.description.length).toBeGreaterThan(0);
115
+ // HTTP status codes should be in valid range
116
+ expect(info.status).toBeGreaterThanOrEqual(400);
117
+ expect(info.status).toBeLessThanOrEqual(599);
118
+ }
119
+ // Retryable codes should have 5xx status
120
+ expect(errorCodes.EXCHANGE_UNREACHABLE.retryable).toBe(true);
121
+ expect(errorCodes.RATE_LIMITED.retryable).toBe(true);
122
+ expect(errorCodes.TIMEOUT.retryable).toBe(true);
123
+ // Non-retryable codes
124
+ expect(errorCodes.INVALID_PARAMS.retryable).toBe(false);
125
+ expect(errorCodes.INSUFFICIENT_BALANCE.retryable).toBe(false);
126
+ });
127
+ it("globalOptions include --json, --exchange, --dry-run", () => {
128
+ const output = runCli("api-spec");
129
+ spec = JSON.parse(output);
130
+ const data = spec.data;
131
+ const opts = data.globalOptions;
132
+ const allFlags = opts.map((o) => o.flags).join(" ");
133
+ expect(allFlags).toContain("--json");
134
+ expect(allFlags).toContain("--exchange");
135
+ expect(allFlags).toContain("--dry-run");
136
+ expect(allFlags).toContain("--dex");
137
+ });
138
+ it("tips array includes referral nudge", () => {
139
+ const output = runCli("api-spec");
140
+ spec = JSON.parse(output);
141
+ const data = spec.data;
142
+ const tips = data.tips;
143
+ expect(tips.length).toBeGreaterThanOrEqual(5);
144
+ const joined = tips.join("\n");
145
+ expect(joined).toContain("referrals");
146
+ });
147
+ });
148
+ // ══════════════════════════════════════════════════════════
149
+ // market mid — uses HL mainnet read-only (public data)
150
+ // ══════════════════════════════════════════════════════════
151
+ const HAS_KEY = !!(process.env.HYPERLIQUID_PRIVATE_KEY || process.env.HL_PRIVATE_KEY);
152
+ describe.skipIf(!HAS_KEY)("perp --json -e hyperliquid market mid BTC", () => {
153
+ it("returns valid mid price envelope", () => {
154
+ const output = runCli("--json -e hyperliquid market mid BTC");
155
+ const parsed = JSON.parse(output);
156
+ expect(parsed.ok).toBe(true);
157
+ expect(parsed.meta?.timestamp).toBeDefined();
158
+ const data = parsed.data;
159
+ expect(data.symbol).toBe("BTC");
160
+ expect(typeof data.mid).toBe("string");
161
+ expect(typeof data.bid).toBe("string");
162
+ expect(typeof data.ask).toBe("string");
163
+ expect(typeof data.spread).toBe("string");
164
+ // Price sanity
165
+ const mid = parseFloat(data.mid);
166
+ expect(mid).toBeGreaterThan(100);
167
+ const bid = parseFloat(data.bid);
168
+ const ask = parseFloat(data.ask);
169
+ expect(bid).toBeLessThan(ask);
170
+ expect(bid).toBeGreaterThan(0);
171
+ // Spread should be tiny for BTC
172
+ const spread = parseFloat(data.spread);
173
+ expect(spread).toBeGreaterThanOrEqual(0);
174
+ expect(spread).toBeLessThan(1); // < 1%
175
+ });
176
+ it("returns ETH mid price with reasonable values", () => {
177
+ const output = runCli("--json -e hyperliquid market mid ETH");
178
+ const parsed = JSON.parse(output);
179
+ expect(parsed.ok).toBe(true);
180
+ const mid = parseFloat(parsed.data.mid);
181
+ expect(mid).toBeGreaterThan(10);
182
+ });
183
+ });
184
+ // ══════════════════════════════════════════════════════════
185
+ // Error envelope consistency
186
+ // ══════════════════════════════════════════════════════════
187
+ describe("--json error envelope consistency", () => {
188
+ it("unknown command returns CLI_ERROR with meta.timestamp", () => {
189
+ const { stdout } = runCliSafe("--json fakecmd123");
190
+ const parsed = JSON.parse(stdout);
191
+ expect(parsed.ok).toBe(false);
192
+ expect(parsed.error).toBeDefined();
193
+ expect(parsed.error.code).toBe("CLI_ERROR");
194
+ expect(typeof parsed.error.message).toBe("string");
195
+ expect(parsed.meta).toBeDefined();
196
+ expect(parsed.meta.timestamp).toBeDefined();
197
+ // Timestamp should be valid ISO 8601
198
+ expect(new Date(parsed.meta.timestamp).toISOString()).toBe(parsed.meta.timestamp);
199
+ });
200
+ it("plan validate with nonexistent file returns structured error", () => {
201
+ const { stdout } = runCliSafe("--json plan validate /tmp/__nonexistent_99999.json");
202
+ const parsed = JSON.parse(stdout);
203
+ expect(parsed.ok).toBe(false);
204
+ expect(parsed.error.code).toBeDefined();
205
+ expect(parsed.error.message).toContain("ENOENT");
206
+ expect(parsed.meta.timestamp).toBeDefined();
207
+ });
208
+ it("api-spec always returns ok:true even without --json flag", () => {
209
+ const output = runCli("api-spec");
210
+ const parsed = JSON.parse(output);
211
+ expect(parsed.ok).toBe(true);
212
+ });
213
+ });
214
+ // ══════════════════════════════════════════════════════════
215
+ // help output validation
216
+ // ══════════════════════════════════════════════════════════
217
+ describe("help output includes new commands", () => {
218
+ it("market --help lists mid subcommand", () => {
219
+ const { stdout } = runCliSafe("market --help");
220
+ expect(stdout).toContain("mid");
221
+ });
222
+ it("account --help lists margin subcommand", () => {
223
+ const { stdout } = runCliSafe("account --help");
224
+ expect(stdout).toContain("margin");
225
+ });
226
+ it("trade --help lists status and fills subcommands", () => {
227
+ const { stdout } = runCliSafe("trade --help");
228
+ expect(stdout).toContain("status");
229
+ expect(stdout).toContain("fills");
230
+ });
231
+ it("top-level --help lists api-spec", () => {
232
+ const { stdout } = runCliSafe("--help");
233
+ expect(stdout).toContain("api-spec");
234
+ });
235
+ });
236
+ });
@@ -0,0 +1,321 @@
1
+ /**
2
+ * Integration tests: Order Verification against Hyperliquid Mainnet
3
+ *
4
+ * READ-ONLY tests — no orders are placed, no funds are touched.
5
+ * Uses a dummy private key to instantiate the adapter for market data queries.
6
+ * All assertions use reasonable ranges to accommodate live price fluctuations.
7
+ */
8
+ import { describe, it, expect, beforeAll } from "vitest";
9
+ import { HyperliquidAdapter } from "../../exchanges/hyperliquid.js";
10
+ import { validateTrade } from "../../trade-validator.js";
11
+ // Dummy key — only used for read-only info queries, never signs a transaction
12
+ const DUMMY_KEY = "0x" + "1".repeat(64);
13
+ /** Find a market by base symbol, tolerating a -PERP suffix. */
14
+ function findMarket(markets, base) {
15
+ const b = base.toUpperCase();
16
+ return markets.find((m) => m.symbol.toUpperCase() === b ||
17
+ m.symbol.toUpperCase() === `${b}-PERP` ||
18
+ m.symbol.toUpperCase().replace(/-PERP$/, "") === b);
19
+ }
20
+ describe("Hyperliquid mainnet — order verification (read-only)", () => {
21
+ let adapter;
22
+ let markets;
23
+ beforeAll(async () => {
24
+ adapter = new HyperliquidAdapter(DUMMY_KEY, false);
25
+ await adapter.init();
26
+ markets = await adapter.getMarkets();
27
+ // Sanity: the exchange must expose at least a handful of markets
28
+ expect(markets.length).toBeGreaterThan(10);
29
+ }, 30_000);
30
+ // ────────────────────────────────────────────────────────────
31
+ // 1. "Buy 0.001 BTC" — validation with real data
32
+ // ────────────────────────────────────────────────────────────
33
+ it("validates a 0.001 BTC market buy with sane estimates", async () => {
34
+ const result = await validateTrade(adapter, {
35
+ symbol: "BTC",
36
+ side: "buy",
37
+ size: 0.001,
38
+ type: "market",
39
+ });
40
+ // Symbol must be found
41
+ const symCheck = result.checks.find((c) => c.check === "symbol_valid");
42
+ expect(symCheck).toBeDefined();
43
+ expect(symCheck.passed).toBe(true);
44
+ // Mark price: BTC should be well above $10 000
45
+ expect(result.marketInfo).toBeDefined();
46
+ expect(result.marketInfo.markPrice).toBeGreaterThan(10_000);
47
+ // Estimated cost: 0.001 BTC at ~$60-120k → notional $60-$120
48
+ // Margin at max leverage (~50x) ≈ $1.2-$2.4
49
+ // Fee at 0.05% ≈ $0.03-$0.06
50
+ expect(result.estimatedCost).toBeDefined();
51
+ expect(result.estimatedCost.margin).toBeGreaterThan(0.5);
52
+ expect(result.estimatedCost.margin).toBeLessThan(20);
53
+ expect(result.estimatedCost.fee).toBeGreaterThan(0.01);
54
+ expect(result.estimatedCost.fee).toBeLessThan(1);
55
+ }, 30_000);
56
+ // ────────────────────────────────────────────────────────────
57
+ // 2. "Buy 1 ETH" — cost estimation accuracy
58
+ // ────────────────────────────────────────────────────────────
59
+ it("validates a 1 ETH market buy with accurate cost estimation", async () => {
60
+ const result = await validateTrade(adapter, {
61
+ symbol: "ETH",
62
+ side: "buy",
63
+ size: 1.0,
64
+ type: "market",
65
+ });
66
+ expect(result.marketInfo).toBeDefined();
67
+ const ethPrice = result.marketInfo.markPrice;
68
+ // ETH price should be in a broad but reasonable range
69
+ expect(ethPrice).toBeGreaterThan(500);
70
+ expect(ethPrice).toBeLessThan(20_000);
71
+ // Margin at max leverage: notional / maxLev
72
+ // At 50x: margin ~$36-$400 depending on price
73
+ expect(result.estimatedCost).toBeDefined();
74
+ expect(result.estimatedCost.margin).toBeGreaterThan(5);
75
+ expect(result.estimatedCost.margin).toBeLessThan(500);
76
+ // Fee: 0.05% of notional ($500-$20k) → $0.25 - $10
77
+ expect(result.estimatedCost.fee).toBeGreaterThan(0.1);
78
+ expect(result.estimatedCost.fee).toBeLessThan(15);
79
+ }, 30_000);
80
+ // ────────────────────────────────────────────────────────────
81
+ // 3. "Buy 0.1 SOL" — smaller asset
82
+ // ────────────────────────────────────────────────────────────
83
+ it("validates a 0.1 SOL buy — smaller asset sanity", async () => {
84
+ const result = await validateTrade(adapter, {
85
+ symbol: "SOL",
86
+ side: "buy",
87
+ size: 0.1,
88
+ type: "market",
89
+ });
90
+ const symCheck = result.checks.find((c) => c.check === "symbol_valid");
91
+ expect(symCheck).toBeDefined();
92
+ expect(symCheck.passed).toBe(true);
93
+ expect(result.marketInfo).toBeDefined();
94
+ expect(result.marketInfo.markPrice).toBeGreaterThan(10);
95
+ // Notional ~$1-$100 at current prices — cost should be small but positive
96
+ expect(result.estimatedCost).toBeDefined();
97
+ expect(result.estimatedCost.margin).toBeGreaterThan(0);
98
+ expect(result.estimatedCost.fee).toBeGreaterThan(0);
99
+ }, 30_000);
100
+ // ────────────────────────────────────────────────────────────
101
+ // 4. "Sell 100 BTC" — liquidity / slippage check
102
+ // ────────────────────────────────────────────────────────────
103
+ it("detects liquidity/slippage concern for a 100 BTC sell", async () => {
104
+ const result = await validateTrade(adapter, {
105
+ symbol: "BTC",
106
+ side: "sell",
107
+ size: 100,
108
+ type: "market",
109
+ });
110
+ // 100 BTC ≈ $6-12M notional — the validator should either:
111
+ // a) flag insufficient liquidity in the visible orderbook, or
112
+ // b) flag high slippage, or
113
+ // c) flag a risk-limits violation (max position size $5k default)
114
+ const liqCheck = result.checks.find((c) => c.check === "liquidity_ok");
115
+ const riskCheck = result.checks.find((c) => c.check === "risk_limits");
116
+ // At least one of these should flag the absurd size
117
+ const anyFlagged = (liqCheck && !liqCheck.passed) || (riskCheck && !riskCheck.passed);
118
+ expect(anyFlagged).toBe(true);
119
+ }, 30_000);
120
+ // ────────────────────────────────────────────────────────────
121
+ // 5. Orderbook consistency — bid < ask
122
+ // ────────────────────────────────────────────────────────────
123
+ it("verifies orderbook bid < ask and spread near mark for BTC, ETH, SOL", async () => {
124
+ const symbols = ["BTC", "ETH", "SOL"];
125
+ for (const sym of symbols) {
126
+ const book = await adapter.getOrderbook(sym);
127
+ // Must have at least one level on each side
128
+ expect(book.bids.length).toBeGreaterThan(0);
129
+ expect(book.asks.length).toBeGreaterThan(0);
130
+ const bestBid = Number(book.bids[0][0]);
131
+ const bestAsk = Number(book.asks[0][0]);
132
+ // Best bid must be strictly less than best ask (positive spread)
133
+ expect(bestBid).toBeLessThan(bestAsk);
134
+ // Spread should be tiny relative to price (< 0.1%)
135
+ const spread = (bestAsk - bestBid) / bestBid;
136
+ expect(spread).toBeLessThan(0.001); // 0.1%
137
+ // Both sides should be near the mark price (within 0.1%)
138
+ const market = findMarket(markets, sym);
139
+ expect(market).toBeDefined();
140
+ const mark = Number(market.markPrice);
141
+ if (mark > 0) {
142
+ expect(Math.abs(bestBid - mark) / mark).toBeLessThan(0.001);
143
+ expect(Math.abs(bestAsk - mark) / mark).toBeLessThan(0.001);
144
+ }
145
+ }
146
+ }, 30_000);
147
+ // ────────────────────────────────────────────────────────────
148
+ // 6. Position side mapping — close logic
149
+ // ────────────────────────────────────────────────────────────
150
+ it("maps position close side correctly (long → sell, short → buy)", () => {
151
+ // Pure logic check using real market context types
152
+ const scenarios = [
153
+ { side: "long", expectedClose: "sell" },
154
+ { side: "short", expectedClose: "buy" },
155
+ ];
156
+ for (const { side, expectedClose } of scenarios) {
157
+ // The universal close-position rule: to close, you place
158
+ // the opposite side order.
159
+ const closeSide = side === "long" ? "sell" : "buy";
160
+ expect(closeSide).toBe(expectedClose);
161
+ }
162
+ });
163
+ // ────────────────────────────────────────────────────────────
164
+ // 7. Leverage bounds from real API
165
+ // ────────────────────────────────────────────────────────────
166
+ it("reports maxLeverage >= 20 for BTC and ETH, lower for small-caps", () => {
167
+ const btc = findMarket(markets, "BTC");
168
+ const eth = findMarket(markets, "ETH");
169
+ expect(btc).toBeDefined();
170
+ expect(eth).toBeDefined();
171
+ // HL allows 40-50x on BTC/ETH
172
+ expect(btc.maxLeverage).toBeGreaterThanOrEqual(20);
173
+ expect(eth.maxLeverage).toBeGreaterThanOrEqual(20);
174
+ // Find a lower-liquidity asset (take the last market, or one with low volume)
175
+ const lowCap = markets
176
+ .filter((m) => m.symbol !== "BTC" &&
177
+ m.symbol !== "ETH" &&
178
+ m.symbol !== "SOL" &&
179
+ Number(m.volume24h) > 0)
180
+ .sort((a, b) => Number(a.volume24h) - Number(b.volume24h))[0];
181
+ if (lowCap) {
182
+ // Low-cap coins typically have lower max leverage than BTC
183
+ // Allow equality since some may still be 50x; the key check is that the
184
+ // field is populated and numeric
185
+ expect(lowCap.maxLeverage).toBeGreaterThan(0);
186
+ expect(lowCap.maxLeverage).toBeLessThanOrEqual(btc.maxLeverage);
187
+ }
188
+ });
189
+ // ────────────────────────────────────────────────────────────
190
+ // 8. Funding rate sanity
191
+ // ────────────────────────────────────────────────────────────
192
+ it("returns funding rates in -1% to 1% range for BTC and ETH", () => {
193
+ const btc = findMarket(markets, "BTC");
194
+ const eth = findMarket(markets, "ETH");
195
+ expect(btc).toBeDefined();
196
+ expect(eth).toBeDefined();
197
+ const btcFunding = Number(btc.fundingRate);
198
+ const ethFunding = Number(eth.fundingRate);
199
+ // Hourly funding rates should be tiny decimals (e.g., 0.0001 = 0.01%)
200
+ // Anything outside +-1% would indicate a parsing error
201
+ expect(btcFunding).toBeGreaterThan(-0.01);
202
+ expect(btcFunding).toBeLessThan(0.01);
203
+ expect(ethFunding).toBeGreaterThan(-0.01);
204
+ expect(ethFunding).toBeLessThan(0.01);
205
+ });
206
+ // ────────────────────────────────────────────────────────────
207
+ // 9. Invalid symbol rejection
208
+ // ────────────────────────────────────────────────────────────
209
+ it("rejects an invalid symbol (XYZNOTREAL999)", async () => {
210
+ const result = await validateTrade(adapter, {
211
+ symbol: "XYZNOTREAL999",
212
+ side: "buy",
213
+ size: 1,
214
+ type: "market",
215
+ });
216
+ expect(result.valid).toBe(false);
217
+ const symCheck = result.checks.find((c) => c.check === "symbol_valid");
218
+ expect(symCheck).toBeDefined();
219
+ expect(symCheck.passed).toBe(false);
220
+ expect(symCheck.message).toContain("not found");
221
+ }, 30_000);
222
+ it("handles an empty symbol gracefully", async () => {
223
+ const result = await validateTrade(adapter, {
224
+ symbol: "",
225
+ side: "buy",
226
+ size: 1,
227
+ type: "market",
228
+ });
229
+ expect(result.valid).toBe(false);
230
+ const symCheck = result.checks.find((c) => c.check === "symbol_valid");
231
+ expect(symCheck).toBeDefined();
232
+ expect(symCheck.passed).toBe(false);
233
+ }, 30_000);
234
+ // ────────────────────────────────────────────────────────────
235
+ // 10. Reduce-only without position
236
+ // ────────────────────────────────────────────────────────────
237
+ it("rejects reduce-only when no position exists (dummy account)", async () => {
238
+ const result = await validateTrade(adapter, {
239
+ symbol: "BTC",
240
+ side: "sell",
241
+ size: 0.01,
242
+ type: "market",
243
+ reduceOnly: true,
244
+ });
245
+ // The dummy key has no positions, so the position_exists check should fail
246
+ const posCheck = result.checks.find((c) => c.check === "position_exists");
247
+ expect(posCheck).toBeDefined();
248
+ expect(posCheck.passed).toBe(false);
249
+ expect(posCheck.message).toContain("No position found");
250
+ expect(posCheck.message).toContain("reduce-only");
251
+ }, 30_000);
252
+ // ────────────────────────────────────────────────────────────
253
+ // 11. Price freshness with real data
254
+ // ────────────────────────────────────────────────────────────
255
+ it("passes price freshness at mark +-1% and fails at +-15%", async () => {
256
+ // Fetch the current BTC mark price
257
+ const btcMarket = findMarket(markets, "BTC");
258
+ expect(btcMarket).toBeDefined();
259
+ const mark = Number(btcMarket.markPrice);
260
+ expect(mark).toBeGreaterThan(10_000);
261
+ // Limit order at mark + 1% → should pass freshness
262
+ const closeResult = await validateTrade(adapter, {
263
+ symbol: "BTC",
264
+ side: "buy",
265
+ size: 0.001,
266
+ type: "limit",
267
+ price: mark * 1.01,
268
+ });
269
+ const closeFresh = closeResult.checks.find((c) => c.check === "price_fresh");
270
+ expect(closeFresh).toBeDefined();
271
+ expect(closeFresh.passed).toBe(true);
272
+ // Limit order at mark + 15% → should fail freshness (>10% deviation)
273
+ const farResult = await validateTrade(adapter, {
274
+ symbol: "BTC",
275
+ side: "buy",
276
+ size: 0.001,
277
+ type: "limit",
278
+ price: mark * 1.15,
279
+ });
280
+ const farFresh = farResult.checks.find((c) => c.check === "price_fresh");
281
+ expect(farFresh).toBeDefined();
282
+ expect(farFresh.passed).toBe(false);
283
+ expect(farFresh.message).toContain("deviates");
284
+ }, 30_000);
285
+ // ────────────────────────────────────────────────────────────
286
+ // 12. Cross-exchange spread: native HL vs HIP-3 dex
287
+ // ────────────────────────────────────────────────────────────
288
+ it("compares BTC price on native HL against a HIP-3 dex (hyna)", async () => {
289
+ // Native HL BTC price
290
+ const btcNative = findMarket(markets, "BTC");
291
+ expect(btcNative).toBeDefined();
292
+ const nativePrice = Number(btcNative.markPrice);
293
+ expect(nativePrice).toBeGreaterThan(10_000);
294
+ // Try to get hyna dex markets — if dex doesn't exist or BTC isn't listed,
295
+ // skip gracefully (the dex landscape changes over time).
296
+ let dexPrice = null;
297
+ try {
298
+ const dexAdapter = new HyperliquidAdapter(DUMMY_KEY, false);
299
+ await dexAdapter.init();
300
+ dexAdapter.setDex("hyna");
301
+ // Re-init asset map for the dex context
302
+ const dexMarkets = await dexAdapter.getMarkets();
303
+ const btcDex = findMarket(dexMarkets, "BTC");
304
+ if (btcDex) {
305
+ dexPrice = Number(btcDex.markPrice);
306
+ }
307
+ }
308
+ catch {
309
+ // hyna dex may not exist — skip comparison
310
+ }
311
+ if (dexPrice !== null && dexPrice > 0) {
312
+ // Prices should be within 0.5% of each other (same underlying)
313
+ const divergence = Math.abs(nativePrice - dexPrice) / nativePrice;
314
+ expect(divergence).toBeLessThan(0.005);
315
+ }
316
+ else {
317
+ // No comparable dex market — test passes vacuously with a note
318
+ console.log("Skipped cross-dex comparison: hyna BTC market not available");
319
+ }
320
+ }, 30_000);
321
+ });
@@ -0,0 +1,75 @@
1
+ import { describe, it, expect, beforeAll } from "vitest";
2
+ import { PacificaAdapter } from "../../exchanges/pacifica.js";
3
+ import { parseSolanaKeypair } from "../../config.js";
4
+ /**
5
+ * Integration tests for Pacifica exchange adapter (Solana Devnet).
6
+ *
7
+ * Prerequisites:
8
+ * 1. Set PACIFICA_PRIVATE_KEY env var (base58 or JSON array)
9
+ * 2. Have devnet SOL: solana airdrop 2 --url devnet
10
+ * 3. Have devnet USDC deposited into Pacifica testnet
11
+ *
12
+ * Run: PACIFICA_PRIVATE_KEY=<key> pnpm --filter perp-cli test -- --testPathPattern integration/pacifica
13
+ */
14
+ const SKIP = !process.env.PACIFICA_PRIVATE_KEY;
15
+ describe.skipIf(SKIP)("Pacifica Integration (Devnet)", () => {
16
+ let adapter;
17
+ beforeAll(() => {
18
+ const keypair = parseSolanaKeypair(process.env.PACIFICA_PRIVATE_KEY);
19
+ adapter = new PacificaAdapter(keypair, "testnet");
20
+ });
21
+ describe("Read-only operations", () => {
22
+ it("fetches markets", async () => {
23
+ const markets = await adapter.getMarkets();
24
+ expect(markets.length).toBeGreaterThan(0);
25
+ expect(markets[0].symbol).toBeTruthy();
26
+ expect(Number(markets[0].maxLeverage)).toBeGreaterThan(0);
27
+ });
28
+ it("fetches orderbook for BTC", async () => {
29
+ const book = await adapter.getOrderbook("BTC");
30
+ expect(book).toHaveProperty("bids");
31
+ expect(book).toHaveProperty("asks");
32
+ });
33
+ it("fetches balance", async () => {
34
+ const balance = await adapter.getBalance();
35
+ expect(balance).toHaveProperty("equity");
36
+ expect(balance).toHaveProperty("available");
37
+ expect(Number(balance.equity)).toBeGreaterThanOrEqual(0);
38
+ });
39
+ it("fetches positions", async () => {
40
+ const positions = await adapter.getPositions();
41
+ expect(Array.isArray(positions)).toBe(true);
42
+ });
43
+ it("fetches open orders", async () => {
44
+ const orders = await adapter.getOpenOrders();
45
+ expect(Array.isArray(orders)).toBe(true);
46
+ });
47
+ });
48
+ describe("Trading operations", () => {
49
+ // WARNING: These tests place REAL orders on testnet
50
+ const TEST_SYMBOL = "BTC";
51
+ let limitOrderId;
52
+ it("places a limit buy order (far from market)", async () => {
53
+ // Place far below market so it won't fill
54
+ const result = await adapter.limitOrder(TEST_SYMBOL, "buy", "10000", "0.001");
55
+ expect(result).toBeTruthy();
56
+ // Check it appears in open orders
57
+ const orders = await adapter.getOpenOrders();
58
+ const myOrder = orders.find((o) => o.symbol === TEST_SYMBOL && o.side === "buy" && o.price === "10000");
59
+ if (myOrder) {
60
+ limitOrderId = myOrder.orderId;
61
+ expect(myOrder.status).toBe("open");
62
+ }
63
+ });
64
+ it("cancels the limit order", async () => {
65
+ if (!limitOrderId)
66
+ return;
67
+ const result = await adapter.cancelOrder(TEST_SYMBOL, limitOrderId);
68
+ expect(result).toBeTruthy();
69
+ });
70
+ it("cancel all orders succeeds", async () => {
71
+ const result = await adapter.cancelAllOrders();
72
+ expect(result).toBeTruthy();
73
+ });
74
+ });
75
+ });