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,931 @@
1
+ import { Hyperliquid } from "hyperliquid";
2
+ export class HyperliquidAdapter {
3
+ name = "hyperliquid";
4
+ sdk;
5
+ _address;
6
+ _privateKey;
7
+ _testnet;
8
+ _assetMap = new Map();
9
+ _assetMapReverse = new Map();
10
+ /** HIP-3 deployed perp dex name. Empty string = native (validator) perps. */
11
+ _dex = "";
12
+ // In-memory cache removed — using file-based cache (src/cache.ts) for cross-process dedup
13
+ constructor(privateKey, testnet = false) {
14
+ this.sdk = new Hyperliquid({
15
+ privateKey,
16
+ testnet,
17
+ walletAddress: undefined,
18
+ });
19
+ this._address = "";
20
+ this._privateKey = privateKey;
21
+ this._testnet = testnet;
22
+ }
23
+ /** Set the HIP-3 deployed perp dex to query/trade on. */
24
+ setDex(dex) {
25
+ this._dex = dex;
26
+ // Rebuild asset map for the new dex
27
+ this._assetMap.clear();
28
+ this._assetMapReverse.clear();
29
+ }
30
+ get dex() {
31
+ return this._dex;
32
+ }
33
+ get client() {
34
+ return this.sdk;
35
+ }
36
+ get address() {
37
+ return this._address;
38
+ }
39
+ get isTestnet() {
40
+ return this._testnet;
41
+ }
42
+ async init() {
43
+ // Suppress "WebSocket connected" noise from hyperliquid SDK
44
+ const origLog = console.log;
45
+ console.log = () => { };
46
+ try {
47
+ await this.sdk.connect();
48
+ }
49
+ finally {
50
+ console.log = origLog;
51
+ }
52
+ // Derive address from SDK or from private key directly
53
+ const wallet = this.sdk.wallet;
54
+ if (wallet && typeof wallet === "object" && "address" in wallet) {
55
+ this._address = String(wallet.address);
56
+ }
57
+ if (!this._address) {
58
+ const { ethers } = await import("ethers");
59
+ this._address = new ethers.Wallet(this._privateKey).address;
60
+ }
61
+ // Build asset index map
62
+ await this._loadAssetMap();
63
+ }
64
+ /** Load asset index map — supports native and HIP-3 dex. */
65
+ async _loadAssetMap() {
66
+ try {
67
+ if (this._dex) {
68
+ // HIP-3 deployed dex: use raw info POST with dex param
69
+ const meta = await this._infoPost({ type: "meta", dex: this._dex });
70
+ if (meta?.universe) {
71
+ meta.universe.forEach((asset, idx) => {
72
+ // Store both with and without dex prefix for flexible lookup
73
+ // API returns "km:GOOGL" but callers may use "GOOGL" or "KM:GOOGL"
74
+ const fullName = asset.name; // e.g., "km:GOOGL"
75
+ const baseName = fullName.includes(":") ? fullName.split(":").pop() : fullName;
76
+ this._assetMap.set(fullName, idx);
77
+ this._assetMap.set(baseName, idx);
78
+ this._assetMapReverse.set(idx, fullName);
79
+ });
80
+ }
81
+ }
82
+ else {
83
+ const meta = await this.sdk.info.perpetuals.getMeta();
84
+ if (meta && meta.universe) {
85
+ meta.universe.forEach((asset, idx) => {
86
+ this._assetMap.set(asset.name, idx);
87
+ this._assetMapReverse.set(idx, asset.name);
88
+ });
89
+ }
90
+ }
91
+ }
92
+ catch {
93
+ // non-critical
94
+ }
95
+ }
96
+ getAssetIndex(symbol) {
97
+ const sym = symbol.toUpperCase();
98
+ // Try exact match first (handles both "BTC" for native and "km:GOOGL" for dex)
99
+ let idx = this._assetMap.get(sym);
100
+ if (idx !== undefined)
101
+ return idx;
102
+ // For dex: try lowercase prefix (API returns "km:GOOGL" but input may be "KM:GOOGL")
103
+ if (sym.includes(":")) {
104
+ const [prefix, base] = sym.split(":");
105
+ idx = this._assetMap.get(`${prefix.toLowerCase()}:${base}`);
106
+ if (idx !== undefined)
107
+ return idx;
108
+ // Try base name only (e.g., "GOOGL")
109
+ idx = this._assetMap.get(base);
110
+ if (idx !== undefined)
111
+ return idx;
112
+ }
113
+ throw new Error(`Unknown symbol: ${symbol}`);
114
+ }
115
+ async getMarkets() {
116
+ let universe;
117
+ let ctxs;
118
+ let mids = {};
119
+ if (this._dex) {
120
+ // HIP-3: use raw info POST with dex param
121
+ const meta = await this._infoPost({ type: "metaAndAssetCtxs", dex: this._dex });
122
+ universe = meta[0]?.universe ?? [];
123
+ ctxs = meta[1] ?? [];
124
+ }
125
+ else {
126
+ const [meta, allMids] = await Promise.all([
127
+ this.sdk.info.perpetuals.getMetaAndAssetCtxs(),
128
+ this.sdk.info.getAllMids(),
129
+ ]);
130
+ universe = meta[0]?.universe ?? [];
131
+ ctxs = meta[1] ?? [];
132
+ mids = allMids;
133
+ }
134
+ // Rebuild asset map if empty
135
+ if (this._assetMap.size === 0) {
136
+ universe.forEach((asset, i) => {
137
+ const sym = String(asset.name);
138
+ this._assetMap.set(sym, i);
139
+ this._assetMapReverse.set(i, sym);
140
+ });
141
+ }
142
+ return universe.map((asset, i) => {
143
+ const ctx = (ctxs[i] ?? {});
144
+ const sym = String(asset.name);
145
+ return {
146
+ symbol: sym,
147
+ markPrice: String(ctx.markPx ?? mids[sym] ?? "0"),
148
+ indexPrice: String(ctx.oraclePx ?? "0"),
149
+ fundingRate: String(ctx.funding ?? "0"),
150
+ volume24h: String(ctx.dayNtlVlm ?? "0"),
151
+ openInterest: String(ctx.openInterest ?? "0"),
152
+ maxLeverage: Number(asset.maxLeverage ?? 50),
153
+ };
154
+ });
155
+ }
156
+ async getOrderbook(symbol) {
157
+ const book = await this.sdk.info.getL2Book(symbol.toUpperCase());
158
+ const levels = book?.levels ?? [[], []];
159
+ return {
160
+ bids: (levels[0] ?? []).map((l) => [
161
+ String(l.px ?? "0"),
162
+ String(l.sz ?? "0"),
163
+ ]),
164
+ asks: (levels[1] ?? []).map((l) => [
165
+ String(l.px ?? "0"),
166
+ String(l.sz ?? "0"),
167
+ ]),
168
+ };
169
+ }
170
+ /** Always fetches live clearinghouseState, writes result to shared cache for dashboard */
171
+ async _getClearinghouseState() {
172
+ const { fetchAndCache, TTL_ACCOUNT } = await import("../cache.js");
173
+ const key = this._dex ? `acct:hl:chs:${this._address}:${this._dex}` : `acct:hl:chs:${this._address}`;
174
+ return fetchAndCache(key, TTL_ACCOUNT, async () => {
175
+ const state = this._dex
176
+ ? await this._infoPost({ type: "clearinghouseState", user: this._address, dex: this._dex })
177
+ : await this.sdk.info.perpetuals.getClearinghouseState(this._address);
178
+ return state;
179
+ });
180
+ }
181
+ async getBalance() {
182
+ const state = await this._getClearinghouseState();
183
+ const s = state;
184
+ const margin = (s?.marginSummary ?? {});
185
+ const cross = (s?.crossMarginSummary ?? {});
186
+ const marginUsed = Number(margin.totalMarginUsed ?? cross.totalMarginUsed ?? 0);
187
+ // Sum unrealized PnL directly from positions (reliable for both main + dex accounts)
188
+ const positions = (s?.assetPositions ?? []);
189
+ let unrealizedPnl = 0;
190
+ for (const entry of positions) {
191
+ const pos = (entry.position ?? entry);
192
+ unrealizedPnl += Number(pos.unrealizedPnl ?? 0);
193
+ }
194
+ let equity;
195
+ let available;
196
+ if (!this._dex) {
197
+ // Unified account: spot USDC total IS the true equity (includes perp margin as "hold").
198
+ // perp accountValue is a subset — adding both double-counts.
199
+ try {
200
+ const spotState = await this.sdk.info.spot.getSpotClearinghouseState(this._address);
201
+ const balances = spotState?.balances ?? [];
202
+ const usdc = balances.find((b) => String(b.coin).startsWith("USDC"));
203
+ const spotTotal = Number(usdc?.total ?? 0);
204
+ const spotHold = Number(usdc?.hold ?? 0);
205
+ equity = spotTotal > 0 ? spotTotal : Number(margin.accountValue ?? cross.accountValue ?? 0);
206
+ available = spotTotal > 0 ? spotTotal - spotHold : Number(s?.withdrawable ?? 0);
207
+ }
208
+ catch {
209
+ // Spot API failed — fall back to perp-only values
210
+ equity = Number(margin.accountValue ?? cross.accountValue ?? 0);
211
+ available = Number(s?.withdrawable ?? 0);
212
+ }
213
+ }
214
+ else {
215
+ // Dex account: perp clearinghouse is the only source
216
+ equity = Number(margin.accountValue ?? cross.accountValue ?? 0);
217
+ available = Number(s?.withdrawable ?? 0);
218
+ }
219
+ return {
220
+ equity: String(equity),
221
+ available: String(available),
222
+ marginUsed: String(marginUsed),
223
+ unrealizedPnl: String(unrealizedPnl),
224
+ };
225
+ }
226
+ async getPositions() {
227
+ const state = await this._getClearinghouseState();
228
+ const positions = (state?.assetPositions ?? []);
229
+ return positions
230
+ .filter((p) => {
231
+ const pos = (p.position ?? p);
232
+ return Number(pos.szi ?? 0) !== 0;
233
+ })
234
+ .map((p) => {
235
+ const pos = (p.position ?? p);
236
+ const szi = Number(pos.szi ?? 0);
237
+ return {
238
+ symbol: String(pos.coin ?? ""),
239
+ side: szi > 0 ? "long" : "short",
240
+ size: String(Math.abs(szi)),
241
+ entryPrice: String(pos.entryPx ?? "0"),
242
+ markPrice: String(pos.positionValue ? (Number(pos.positionValue) / Math.abs(szi)).toFixed(2) : "0"),
243
+ liquidationPrice: String(pos.liquidationPx ?? "N/A"),
244
+ unrealizedPnl: String(pos.unrealizedPnl ?? "0"),
245
+ leverage: Number(pos.leverage?.value ?? 1),
246
+ };
247
+ });
248
+ }
249
+ async getOpenOrders() {
250
+ const orders = await this.sdk.info.getUserOpenOrders(this._address);
251
+ return (orders ?? []).map((o) => ({
252
+ orderId: String(o.oid ?? ""),
253
+ symbol: String(o.coin ?? ""),
254
+ side: o.side === "B" ? "buy" : "sell",
255
+ price: String(o.limitPx ?? "0"),
256
+ size: String(o.sz ?? "0"),
257
+ filled: "0",
258
+ status: "open",
259
+ type: "limit",
260
+ }));
261
+ }
262
+ async _invalidateAccountCache() {
263
+ try {
264
+ const { invalidateCache } = await import("../cache.js");
265
+ invalidateCache("acct");
266
+ }
267
+ catch { /* ignore */ }
268
+ }
269
+ async marketOrder(symbol, side, size) {
270
+ if (this._dex) {
271
+ const r = await this._dexMarketOrder(symbol, side, size);
272
+ await this._invalidateAccountCache();
273
+ return r;
274
+ }
275
+ // Suppress SDK console.log noise (slippage price, decimals, order details)
276
+ const origLog = console.log;
277
+ console.log = () => { };
278
+ try {
279
+ const result = await this.sdk.custom.marketOpen(symbol.toUpperCase(), side === "buy", parseFloat(size));
280
+ await this._invalidateAccountCache();
281
+ return result;
282
+ }
283
+ finally {
284
+ console.log = origLog;
285
+ }
286
+ }
287
+ /**
288
+ * Place a market order on a HIP-3 deployed dex.
289
+ * Bypasses SDK's symbolConversion (which only knows native perps)
290
+ * and constructs + signs the order action directly.
291
+ */
292
+ async _dexMarketOrder(symbol, side, size) {
293
+ const assetIndex = this.getAssetIndex(symbol.toUpperCase());
294
+ // Get current mark price from dex meta
295
+ const meta = await this._infoPost({
296
+ type: "metaAndAssetCtxs",
297
+ dex: this._dex,
298
+ });
299
+ const ctx = (meta[1] ?? [])[assetIndex];
300
+ const midPrice = Number(ctx?.markPx ?? 0);
301
+ if (midPrice <= 0)
302
+ throw new Error(`Cannot get price for ${symbol} on dex ${this._dex}`);
303
+ // Apply 5% slippage (same as SDK default)
304
+ const slippage = 0.05;
305
+ const isBuy = side === "buy";
306
+ const slippagePrice = isBuy ? midPrice * (1 + slippage) : midPrice * (1 - slippage);
307
+ const decimals = midPrice.toString().split(".")[1]?.length || 0;
308
+ const limitPrice = slippagePrice.toFixed(Math.max(0, decimals - 1));
309
+ return this._rawPlaceOrder({
310
+ assetIndex,
311
+ isBuy,
312
+ price: limitPrice,
313
+ size,
314
+ orderType: { limit: { tif: "Ioc" } },
315
+ reduceOnly: false,
316
+ });
317
+ }
318
+ /**
319
+ * Place an order via raw exchange API with EIP-712 signing.
320
+ * This bypasses the SDK's symbolConversion and supports dex-specific orders.
321
+ */
322
+ async _rawPlaceOrder(opts) {
323
+ const { encode } = await import("@msgpack/msgpack");
324
+ const { ethers, keccak256 } = await import("ethers");
325
+ const wallet = new ethers.Wallet(this._privateKey);
326
+ const isMainnet = !this._testnet;
327
+ const baseUrl = isMainnet
328
+ ? "https://api.hyperliquid.xyz"
329
+ : "https://api.hyperliquid-testnet.xyz";
330
+ // Remove trailing zeros (HL API requirement)
331
+ const trimZeros = (s) => {
332
+ if (!s.includes("."))
333
+ return s;
334
+ const n = s.replace(/\.?0+$/, "");
335
+ return n === "-0" ? "0" : n;
336
+ };
337
+ const orderWire = {
338
+ a: opts.assetIndex,
339
+ b: opts.isBuy,
340
+ p: trimZeros(opts.price),
341
+ s: trimZeros(opts.size),
342
+ r: opts.reduceOnly,
343
+ t: opts.orderType,
344
+ };
345
+ const action = {
346
+ type: "order",
347
+ orders: [orderWire],
348
+ grouping: "na",
349
+ };
350
+ // Add dex field for HIP-3 deployed perps
351
+ if (this._dex) {
352
+ action.dex = this._dex;
353
+ }
354
+ // Sign L1 action (replicates SDK's signL1Action)
355
+ const nonce = Date.now();
356
+ const msgPackBytes = encode(action);
357
+ const data = new Uint8Array(msgPackBytes.length + 9); // +8 nonce +1 vault flag
358
+ data.set(msgPackBytes);
359
+ const view = new DataView(data.buffer);
360
+ view.setBigUint64(msgPackBytes.length, BigInt(nonce), false);
361
+ view.setUint8(msgPackBytes.length + 8, 0); // no vault
362
+ const hash = keccak256(data);
363
+ const phantomDomain = {
364
+ name: "Exchange",
365
+ version: "1",
366
+ chainId: 1337,
367
+ verifyingContract: "0x0000000000000000000000000000000000000000",
368
+ };
369
+ const agentTypes = {
370
+ Agent: [
371
+ { name: "source", type: "string" },
372
+ { name: "connectionId", type: "bytes32" },
373
+ ],
374
+ };
375
+ const phantomAgent = {
376
+ source: isMainnet ? "a" : "b",
377
+ connectionId: hash,
378
+ };
379
+ const sig = await wallet.signTypedData(phantomDomain, agentTypes, phantomAgent);
380
+ const parsed = ethers.Signature.from(sig);
381
+ const payload = {
382
+ action,
383
+ nonce,
384
+ signature: { r: parsed.r, s: parsed.s, v: parsed.v },
385
+ vaultAddress: null,
386
+ };
387
+ const res = await fetch(`${baseUrl}/exchange`, {
388
+ method: "POST",
389
+ headers: { "Content-Type": "application/json" },
390
+ body: JSON.stringify(payload),
391
+ });
392
+ const result = await res.json();
393
+ if (result?.status === "err") {
394
+ throw new Error(result.response ?? JSON.stringify(result));
395
+ }
396
+ return result;
397
+ }
398
+ async limitOrder(symbol, side, price, size, opts) {
399
+ const tif = (opts?.tif ?? "Gtc");
400
+ const reduceOnly = opts?.reduceOnly ?? false;
401
+ let result;
402
+ if (this._dex) {
403
+ result = await this._rawPlaceOrder({
404
+ assetIndex: this.getAssetIndex(symbol.toUpperCase()),
405
+ isBuy: side === "buy",
406
+ price,
407
+ size,
408
+ orderType: { limit: { tif } },
409
+ reduceOnly,
410
+ });
411
+ }
412
+ else {
413
+ result = await this.sdk.exchange.placeOrder({
414
+ coin: symbol.toUpperCase(),
415
+ is_buy: side === "buy",
416
+ sz: parseFloat(size),
417
+ limit_px: parseFloat(price),
418
+ order_type: { limit: { tif } },
419
+ reduce_only: reduceOnly,
420
+ });
421
+ }
422
+ await this._invalidateAccountCache();
423
+ return result;
424
+ }
425
+ async cancelOrder(symbol, orderId) {
426
+ const result = await this.sdk.exchange.cancelOrder({
427
+ coin: symbol.toUpperCase(),
428
+ o: parseInt(orderId),
429
+ });
430
+ await this._invalidateAccountCache();
431
+ return result;
432
+ }
433
+ async cancelAllOrders(symbol) {
434
+ const orders = await this.getOpenOrders();
435
+ const toCancel = symbol
436
+ ? orders.filter((o) => o.symbol.toUpperCase() === symbol.toUpperCase())
437
+ : orders;
438
+ const results = [];
439
+ for (const o of toCancel) {
440
+ results.push(await this.sdk.exchange.cancelOrder({
441
+ coin: o.symbol,
442
+ o: parseInt(o.orderId),
443
+ }));
444
+ }
445
+ return results;
446
+ }
447
+ // ── Interface methods ──
448
+ async editOrder(symbol, orderId, price, size) {
449
+ return this.modifyOrder(symbol, parseInt(orderId), "buy", price, size);
450
+ }
451
+ async setLeverage(symbol, leverage, marginMode = "cross") {
452
+ return this.updateLeverage(symbol, leverage, marginMode === "cross");
453
+ }
454
+ async stopOrder(symbol, side, size, triggerPrice, opts) {
455
+ return this.triggerOrder(symbol, side, size, triggerPrice, "sl", {
456
+ isMarket: !opts?.limitPrice,
457
+ reduceOnly: opts?.reduceOnly ?? true,
458
+ });
459
+ }
460
+ async getRecentTrades(symbol, limit = 20) {
461
+ const trades = await this._infoPost({ type: "recentTrades", coin: symbol.toUpperCase() });
462
+ return (trades ?? [])
463
+ .slice(0, limit)
464
+ .map((t) => ({
465
+ time: Number(t.time ?? 0),
466
+ symbol: String(t.coin ?? symbol.toUpperCase()),
467
+ side: String(t.side) === "B" ? "buy" : "sell",
468
+ price: String(t.px ?? "0"),
469
+ size: String(t.sz ?? ""),
470
+ fee: "0",
471
+ }));
472
+ }
473
+ async getKlines(symbol, interval, startTime, endTime) {
474
+ const candles = await this.client.info.getCandleSnapshot(symbol.toUpperCase(), interval, startTime, endTime);
475
+ return (candles ?? []).map((c) => ({
476
+ time: Number(c.t ?? 0),
477
+ open: String(c.o ?? "0"),
478
+ high: String(c.h ?? "0"),
479
+ low: String(c.l ?? "0"),
480
+ close: String(c.c ?? "0"),
481
+ volume: String(c.v ?? ""),
482
+ trades: Number(c.n ?? 0),
483
+ }));
484
+ }
485
+ async getOrderHistory(limit = 30) {
486
+ const fills = await this.client.info.getUserFills(this._address);
487
+ return (fills ?? []).slice(0, limit).map((f) => ({
488
+ orderId: String(f.oid ?? ""),
489
+ symbol: String(f.coin ?? ""),
490
+ side: String(f.side) === "B" ? "buy" : "sell",
491
+ price: String(f.px ?? "0"),
492
+ size: String(f.sz ?? ""),
493
+ filled: String(f.sz ?? ""),
494
+ status: "filled",
495
+ type: String(f.dir ?? ""),
496
+ }));
497
+ }
498
+ async getTradeHistory(limit = 30) {
499
+ const fills = await this.client.info.getUserFills(this._address);
500
+ return (fills ?? []).slice(0, limit).map((f) => ({
501
+ time: Number(f.time ?? 0),
502
+ symbol: String(f.coin ?? ""),
503
+ side: String(f.side) === "B" ? "buy" : "sell",
504
+ price: String(f.px ?? "0"),
505
+ size: String(f.sz ?? ""),
506
+ fee: String(f.fee ?? "0"),
507
+ }));
508
+ }
509
+ async getFundingPayments(limit = 30) {
510
+ const now = Date.now();
511
+ const history = await this.client.info.perpetuals.getUserFunding(this._address, now - 7 * 24 * 60 * 60 * 1000);
512
+ return (history ?? []).slice(0, limit).map((h) => {
513
+ const delta = (h.delta ?? {});
514
+ return {
515
+ time: Number(h.time ?? 0),
516
+ symbol: String(delta.coin ?? ""),
517
+ payment: String(delta.usdc ?? "0"),
518
+ };
519
+ });
520
+ }
521
+ async getFundingHistory(symbol, limit = 10) {
522
+ const now = Date.now();
523
+ const history = await this.client.info.perpetuals.getFundingHistory(symbol.toUpperCase(), now - 24 * 60 * 60 * 1000);
524
+ return (history ?? []).slice(-limit).map((h) => ({
525
+ time: Number(h.time ?? 0),
526
+ rate: String(h.fundingRate ?? "0"),
527
+ price: "-",
528
+ }));
529
+ }
530
+ // ────────────────────────────────────────────────────────────
531
+ // Extended methods (from Python SDK / nktkas TS SDK analysis)
532
+ // ────────────────────────────────────────────────────────────
533
+ /**
534
+ * Place a trigger order (stop loss / take profit).
535
+ * Python SDK: order() with trigger type {"trigger": {triggerPx, isMarket, tpsl: "tp"|"sl"}}
536
+ */
537
+ async triggerOrder(symbol, side, size, triggerPrice, tpsl, opts) {
538
+ const orderParams = {
539
+ coin: symbol.toUpperCase(),
540
+ is_buy: side === "buy",
541
+ sz: parseFloat(size),
542
+ limit_px: parseFloat(triggerPrice),
543
+ order_type: {
544
+ trigger: {
545
+ triggerPx: triggerPrice,
546
+ isMarket: opts?.isMarket ?? true,
547
+ tpsl,
548
+ },
549
+ },
550
+ reduce_only: opts?.reduceOnly ?? true,
551
+ grouping: opts?.grouping ?? "positionTpsl",
552
+ };
553
+ return this.sdk.exchange.placeOrder(orderParams);
554
+ }
555
+ /**
556
+ * Place a TWAP order.
557
+ * Python SDK: not available in old SDK, available via raw exchange action.
558
+ * nktkas TS SDK: twapOrder({twap: {a, b, s, r, m, t}})
559
+ */
560
+ async twapOrder(symbol, side, size, durationMinutes, opts) {
561
+ const assetIndex = this.getAssetIndex(symbol);
562
+ const action = {
563
+ type: "twapOrder",
564
+ twap: {
565
+ a: assetIndex,
566
+ b: side === "buy",
567
+ s: size,
568
+ r: opts?.reduceOnly ?? false,
569
+ m: durationMinutes,
570
+ t: opts?.randomize ?? true,
571
+ },
572
+ };
573
+ return this._sendExchangeAction(action);
574
+ }
575
+ /**
576
+ * Cancel a TWAP order.
577
+ */
578
+ async twapCancel(symbol, twapId) {
579
+ const assetIndex = this.getAssetIndex(symbol);
580
+ const action = {
581
+ type: "twapCancel",
582
+ a: assetIndex,
583
+ t: twapId,
584
+ };
585
+ return this._sendExchangeAction(action);
586
+ }
587
+ /**
588
+ * Update leverage for a symbol.
589
+ * Python SDK: update_leverage(leverage, name, is_cross)
590
+ */
591
+ async updateLeverage(symbol, leverage, isCross = true) {
592
+ const assetIndex = this.getAssetIndex(symbol);
593
+ const action = {
594
+ type: "updateLeverage",
595
+ asset: assetIndex,
596
+ isCross,
597
+ leverage,
598
+ };
599
+ return this._sendExchangeAction(action);
600
+ }
601
+ /**
602
+ * Update isolated margin for a position.
603
+ * amount > 0 to add margin, amount < 0 to remove
604
+ */
605
+ async updateIsolatedMargin(symbol, amount) {
606
+ const assetIndex = this.getAssetIndex(symbol);
607
+ const action = {
608
+ type: "updateIsolatedMargin",
609
+ asset: assetIndex,
610
+ isBuy: true,
611
+ ntli: Math.round(amount * 1e6), // USD to 6 decimals
612
+ };
613
+ return this._sendExchangeAction(action);
614
+ }
615
+ /**
616
+ * Withdraw from Hyperliquid L1 bridge.
617
+ * Python SDK: withdraw_from_bridge(amount, destination) → action type "withdraw3"
618
+ */
619
+ async withdraw(amount, destination) {
620
+ try {
621
+ return await this.sdk.exchange.initiateWithdrawal(destination, parseFloat(amount));
622
+ }
623
+ catch {
624
+ // Fallback: try raw action if SDK method signature changed
625
+ const action = {
626
+ type: "withdraw3",
627
+ hyperliquidChain: this._testnet ? "Testnet" : "Mainnet",
628
+ signatureChainId: this._testnet ? "0x66eee" : "0xa4b1",
629
+ destination,
630
+ amount,
631
+ time: Date.now(),
632
+ };
633
+ return this._sendExchangeAction(action);
634
+ }
635
+ }
636
+ /**
637
+ * Transfer USD between accounts on Hyperliquid L1.
638
+ * Python SDK: usd_transfer(amount, destination)
639
+ */
640
+ async usdTransfer(amount, destination) {
641
+ return this.sdk.exchange.usdTransfer(destination, amount);
642
+ }
643
+ /**
644
+ * Create a sub-account.
645
+ */
646
+ async createSubAccount(name) {
647
+ const action = {
648
+ type: "createSubAccount",
649
+ name,
650
+ };
651
+ return this._sendExchangeAction(action);
652
+ }
653
+ /**
654
+ * Transfer USD between main and sub-account.
655
+ */
656
+ async subAccountTransfer(subAccountUser, isDeposit, amount) {
657
+ const action = {
658
+ type: "subAccountTransfer",
659
+ subAccountUser,
660
+ isDeposit,
661
+ usd: Math.round(amount * 1e6), // 6 decimals
662
+ };
663
+ return this._sendExchangeAction(action);
664
+ }
665
+ /**
666
+ * Modify an existing order.
667
+ */
668
+ async modifyOrder(symbol, orderId, newSide, newPrice, newSize, opts) {
669
+ const assetIndex = this.getAssetIndex(symbol);
670
+ const action = {
671
+ type: "batchModify",
672
+ modifies: [{
673
+ oid: orderId,
674
+ order: {
675
+ a: assetIndex,
676
+ b: newSide === "buy",
677
+ p: newPrice,
678
+ s: newSize,
679
+ r: opts?.reduceOnly ?? false,
680
+ t: { limit: { tif: "Gtc" } },
681
+ },
682
+ }],
683
+ };
684
+ return this._sendExchangeAction(action);
685
+ }
686
+ /**
687
+ * Schedule cancel: cancel all orders at a future time.
688
+ * Max 10 triggers per day.
689
+ */
690
+ async scheduleCancel(timeMs) {
691
+ const action = {
692
+ type: "scheduleCancel",
693
+ time: timeMs,
694
+ };
695
+ return this._sendExchangeAction(action);
696
+ }
697
+ /**
698
+ * Set referral code. Silent — does not throw.
699
+ */
700
+ async autoSetReferrer(code) {
701
+ const referralCode = code || process.env.HL_REFERRAL_CODE || "HYPERCASH";
702
+ try {
703
+ const exchange = this.sdk.exchange;
704
+ if (typeof exchange.setReferrer === "function") {
705
+ await exchange.setReferrer(referralCode);
706
+ return;
707
+ }
708
+ }
709
+ catch {
710
+ // Already referred or method not available — both OK
711
+ }
712
+ }
713
+ /**
714
+ * Get funding history for a symbol.
715
+ */
716
+ async getFundingHistoryRaw(symbol, startTime, endTime) {
717
+ const baseUrl = this._testnet
718
+ ? "https://api.hyperliquid-testnet.xyz"
719
+ : "https://api.hyperliquid.xyz";
720
+ const body = {
721
+ type: "fundingHistory",
722
+ coin: symbol.toUpperCase(),
723
+ startTime,
724
+ };
725
+ if (endTime)
726
+ body.endTime = endTime;
727
+ const res = await fetch(`${baseUrl}/info`, {
728
+ method: "POST",
729
+ headers: { "Content-Type": "application/json" },
730
+ body: JSON.stringify(body),
731
+ });
732
+ return res.json();
733
+ }
734
+ /**
735
+ * Get user trade fills.
736
+ */
737
+ async getUserFills(startTime, endTime) {
738
+ const baseUrl = this._testnet
739
+ ? "https://api.hyperliquid-testnet.xyz"
740
+ : "https://api.hyperliquid.xyz";
741
+ const body = {
742
+ type: "userFillsByTime",
743
+ user: this._address,
744
+ };
745
+ if (startTime)
746
+ body.startTime = startTime;
747
+ if (endTime)
748
+ body.endTime = endTime;
749
+ const res = await fetch(`${baseUrl}/info`, {
750
+ method: "POST",
751
+ headers: { "Content-Type": "application/json" },
752
+ body: JSON.stringify(body),
753
+ });
754
+ return res.json();
755
+ }
756
+ /**
757
+ * Get user portfolio analytics.
758
+ */
759
+ async getPortfolio() {
760
+ const baseUrl = this._testnet
761
+ ? "https://api.hyperliquid-testnet.xyz"
762
+ : "https://api.hyperliquid.xyz";
763
+ const res = await fetch(`${baseUrl}/info`, {
764
+ method: "POST",
765
+ headers: { "Content-Type": "application/json" },
766
+ body: JSON.stringify({ type: "portfolio", user: this._address }),
767
+ });
768
+ return res.json();
769
+ }
770
+ /**
771
+ * Query a specific order by OID.
772
+ */
773
+ async queryOrder(orderId) {
774
+ const baseUrl = this._testnet
775
+ ? "https://api.hyperliquid-testnet.xyz"
776
+ : "https://api.hyperliquid.xyz";
777
+ const res = await fetch(`${baseUrl}/info`, {
778
+ method: "POST",
779
+ headers: { "Content-Type": "application/json" },
780
+ body: JSON.stringify({ type: "orderStatus", user: this._address, oid: orderId }),
781
+ });
782
+ return res.json();
783
+ }
784
+ /**
785
+ * Approve a builder fee.
786
+ * Action type: approveBuilderFee
787
+ */
788
+ async approveBuilderFee(builder, maxFeeRate) {
789
+ const action = {
790
+ type: "approveBuilderFee",
791
+ hyperliquidChain: this._testnet ? "Testnet" : "Mainnet",
792
+ signatureChainId: this._testnet ? "0x66eee" : "0xa4b1",
793
+ maxFeeRate,
794
+ builder,
795
+ };
796
+ return this._sendExchangeAction(action);
797
+ }
798
+ /**
799
+ * Vault deposit/withdraw.
800
+ */
801
+ async vaultTransfer(vaultAddress, isDeposit, usd) {
802
+ const action = {
803
+ type: "vaultTransfer",
804
+ vaultAddress,
805
+ isDeposit,
806
+ usd: Math.round(usd * 1e6),
807
+ };
808
+ return this._sendExchangeAction(action);
809
+ }
810
+ /**
811
+ * Delegate/undelegate tokens for staking.
812
+ */
813
+ async tokenDelegate(validator, wei, isUndelegate = false) {
814
+ const action = {
815
+ type: "tokenDelegate",
816
+ hyperliquidChain: this._testnet ? "Testnet" : "Mainnet",
817
+ signatureChainId: this._testnet ? "0x66eee" : "0xa4b1",
818
+ validator,
819
+ isUndelegate,
820
+ wei,
821
+ };
822
+ return this._sendExchangeAction(action);
823
+ }
824
+ /**
825
+ * Claim staking rewards.
826
+ */
827
+ async claimRewards() {
828
+ return this._sendExchangeAction({ type: "claimRewards" });
829
+ }
830
+ /**
831
+ * List all available HIP-3 deployed perp dexes.
832
+ */
833
+ async listDeployedDexes() {
834
+ const allMetas = await this._infoPost({ type: "allPerpMetas" });
835
+ if (!Array.isArray(allMetas))
836
+ return [];
837
+ const dexes = [];
838
+ // allPerpMetas returns an array: [nativePerps, dex1, dex2, ...]
839
+ // Each entry has { universe, marginTables, collateralToken }
840
+ // Dex name is derived from the asset prefix (e.g., "xyz:TSLA" → "xyz")
841
+ // Skip index 0 (native/validator perps — no prefix)
842
+ for (let i = 1; i < allMetas.length; i++) {
843
+ const meta = allMetas[i];
844
+ const universe = (meta.universe ?? []);
845
+ if (universe.length === 0)
846
+ continue;
847
+ // Extract dex name from the first asset's prefix
848
+ const firstAsset = universe[0].name;
849
+ const colonIdx = firstAsset.indexOf(":");
850
+ const dexName = colonIdx > 0 ? firstAsset.slice(0, colonIdx) : `dex-${i}`;
851
+ dexes.push({
852
+ name: dexName,
853
+ deployer: "", // not exposed by API
854
+ assets: universe.map(a => a.name),
855
+ });
856
+ }
857
+ return dexes;
858
+ }
859
+ /**
860
+ * Get referral info.
861
+ */
862
+ async getReferralInfo() {
863
+ return this._infoPost({ type: "referral", user: this._address });
864
+ }
865
+ /**
866
+ * Get fee info.
867
+ */
868
+ async getUserFees() {
869
+ return this._infoPost({ type: "userFees", user: this._address });
870
+ }
871
+ /**
872
+ * Get sub-accounts.
873
+ */
874
+ async getSubAccounts() {
875
+ return this._infoPost({ type: "subAccounts", user: this._address });
876
+ }
877
+ /**
878
+ * Get historical orders (up to 2000).
879
+ */
880
+ async getHistoricalOrders() {
881
+ return this._infoPost({ type: "historicalOrders", user: this._address });
882
+ }
883
+ /**
884
+ * Get approved builders.
885
+ */
886
+ async getApprovedBuilders() {
887
+ return this._infoPost({ type: "approvedBuilders", user: this._address });
888
+ }
889
+ /**
890
+ * Get vault details.
891
+ */
892
+ async getVaultDetails(vaultAddress) {
893
+ return this._infoPost({ type: "vaultDetails", vaultAddress, user: this._address });
894
+ }
895
+ /**
896
+ * Get delegations (staking).
897
+ */
898
+ async getDelegations() {
899
+ return this._infoPost({ type: "delegations", user: this._address });
900
+ }
901
+ /**
902
+ * POST to /info endpoint.
903
+ */
904
+ async _infoPost(body) {
905
+ const baseUrl = this._testnet
906
+ ? "https://api.hyperliquid-testnet.xyz"
907
+ : "https://api.hyperliquid.xyz";
908
+ const res = await fetch(`${baseUrl}/info`, {
909
+ method: "POST",
910
+ headers: { "Content-Type": "application/json" },
911
+ body: JSON.stringify(body),
912
+ });
913
+ return res.json();
914
+ }
915
+ /**
916
+ * Send a raw exchange action through the SDK.
917
+ * Used for methods not directly exposed by the SDK.
918
+ */
919
+ async _sendExchangeAction(action) {
920
+ const exchange = this.sdk.exchange;
921
+ // Try using the SDK's internal _postAction if available
922
+ if (typeof exchange.postAction === "function") {
923
+ return exchange.postAction(action);
924
+ }
925
+ // Fallback: direct POST to /exchange
926
+ // This requires signing which the SDK normally handles internally
927
+ // If we get here, the SDK doesn't expose the method we need
928
+ throw new Error(`Hyperliquid SDK does not expose postAction(). ` +
929
+ `Action "${action.type}" may need a newer SDK version.`);
930
+ }
931
+ }