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,793 @@
1
+ import { createRequire } from "node:module";
2
+ // Use createRequire to load the CJS build of lighter-sdk
3
+ // (the ESM build has a broken require polyfill that fails on Node built-ins)
4
+ const require = createRequire(import.meta.url);
5
+ export class LighterAdapter {
6
+ name = "lighter";
7
+ _signer;
8
+ _accountIndex = -1;
9
+ _address;
10
+ _marketMap = new Map(); // symbol → marketIndex
11
+ _marketDecimals = new Map(); // symbol → decimals
12
+ _evmKey;
13
+ _apiKey;
14
+ _accountIndexInit;
15
+ _baseUrl;
16
+ _chainId;
17
+ _testnet;
18
+ _readOnly;
19
+ // In-memory cache removed — using file-based cache (src/cache.ts) for cross-process dedup
20
+ /**
21
+ * @param evmKey EVM private key (0x-prefixed, 32 bytes) — for deposits & key registration
22
+ * @param testnet Use testnet (chain ID 300) instead of mainnet (chain ID 304)
23
+ * @param opts Optional: apiKey (40-byte Lighter signing key), accountIndex
24
+ */
25
+ constructor(evmKey, testnet = false, opts) {
26
+ this._evmKey = evmKey;
27
+ this._apiKey = opts?.apiKey || process.env.LIGHTER_API_KEY || "";
28
+ this._accountIndexInit = opts?.accountIndex ?? parseInt(process.env.LIGHTER_ACCOUNT_INDEX || "-1");
29
+ this._address = "";
30
+ this._testnet = testnet;
31
+ this._readOnly = !this._apiKey;
32
+ this._baseUrl = testnet
33
+ ? (process.env.LIGHTER_TESTNET_URL || "https://testnet.zklighter.elliot.ai")
34
+ : "https://mainnet.zklighter.elliot.ai";
35
+ this._chainId = testnet ? 300 : 304;
36
+ }
37
+ get signer() {
38
+ return this._signer;
39
+ }
40
+ get accountIndex() {
41
+ return this._accountIndex;
42
+ }
43
+ get address() {
44
+ return this._address;
45
+ }
46
+ get evmKey() {
47
+ return this._evmKey;
48
+ }
49
+ get isReadOnly() {
50
+ return this._readOnly;
51
+ }
52
+ async init() {
53
+ // Resolve address and account index from EVM key via REST
54
+ const { ethers } = await import("ethers");
55
+ const wallet = new ethers.Wallet(this._evmKey);
56
+ this._address = wallet.address;
57
+ // Fetch account index from REST API
58
+ const res = await fetch(`${this._baseUrl}/api/v1/account?by=l1_address&value=${this._address}`);
59
+ const json = await res.json();
60
+ if (json.accounts && json.accounts.length > 0) {
61
+ this._accountIndex = this._accountIndexInit >= 0
62
+ ? this._accountIndexInit
63
+ : json.accounts[0].account_index;
64
+ }
65
+ // Initialize signer for trading if we have an API key
66
+ if (this._apiKey) {
67
+ const { LighterSignerClient } = require("lighter-sdk");
68
+ this._signer = new LighterSignerClient({
69
+ url: this._baseUrl,
70
+ privateKey: this._apiKey,
71
+ chainId: this._chainId,
72
+ accountIndex: this._accountIndex,
73
+ apiKeyIndex: parseInt(process.env.LIGHTER_API_KEY_INDEX || "3"),
74
+ loaderType: "wasm",
75
+ });
76
+ // Only initialize (load WASM + create client), skip checkClient which does HTTP from WASM
77
+ await this._signer.initialize();
78
+ this._readOnly = false;
79
+ }
80
+ // Build symbol → marketIndex map + decimals from orderBookDetails
81
+ try {
82
+ const res = await this.restGet("/orderBookDetails", {});
83
+ for (const d of res.order_book_details ?? []) {
84
+ this._marketMap.set(d.symbol.toUpperCase(), d.market_id);
85
+ this._marketDecimals.set(d.symbol.toUpperCase(), {
86
+ size: d.size_decimals ?? 0,
87
+ price: d.price_decimals ?? 0,
88
+ });
89
+ }
90
+ }
91
+ catch {
92
+ // Market map will be empty, use fallback
93
+ }
94
+ }
95
+ getMarketIndex(symbol) {
96
+ const idx = this._marketMap.get(symbol.toUpperCase());
97
+ if (idx === undefined)
98
+ throw new Error(`Unknown Lighter market: ${symbol}`);
99
+ return idx;
100
+ }
101
+ async getMarkPrice(symbol) {
102
+ try {
103
+ const res = await this.restGet("/orderBookDetails", {});
104
+ const m = res.order_book_details?.find(d => d.symbol.toUpperCase() === symbol.toUpperCase());
105
+ return m?.last_trade_price ?? 0;
106
+ }
107
+ catch {
108
+ return 0;
109
+ }
110
+ }
111
+ toTicks(symbol, size, price) {
112
+ const dec = this._marketDecimals.get(symbol.toUpperCase()) ?? { size: 0, price: 0 };
113
+ return {
114
+ baseAmount: Math.round(size * Math.pow(10, dec.size)),
115
+ priceTicks: Math.round(price * Math.pow(10, dec.price)),
116
+ };
117
+ }
118
+ async getMarkets() {
119
+ const markets = [];
120
+ try {
121
+ const res = await this.restGet("/orderBookDetails", {});
122
+ for (const d of res.order_book_details ?? []) {
123
+ if (d.market_type !== "perp")
124
+ continue;
125
+ // max_leverage from min_initial_margin_fraction (e.g. 400 = 4% margin = 25x)
126
+ const imf = d.min_initial_margin_fraction ?? d.default_initial_margin_fraction ?? 500;
127
+ const maxLev = imf > 0 ? Math.floor(10000 / imf) : 50;
128
+ markets.push({
129
+ symbol: d.symbol,
130
+ markPrice: String(d.last_trade_price ?? 0),
131
+ indexPrice: String(d.last_trade_price ?? 0),
132
+ fundingRate: "0", // funding rates require auth
133
+ volume24h: String(d.daily_quote_token_volume ?? 0),
134
+ openInterest: String(d.open_interest ?? 0),
135
+ maxLeverage: maxLev,
136
+ });
137
+ }
138
+ }
139
+ catch { /* non-critical */ }
140
+ return markets;
141
+ }
142
+ async fetchAccount() {
143
+ if (!this._address)
144
+ return null;
145
+ const { fetchAndCache, TTL_ACCOUNT } = await import("../cache.js");
146
+ return fetchAndCache(`acct:lt:account:${this._address}`, TTL_ACCOUNT, async () => {
147
+ const res = await this.restGet("/account", {
148
+ by: "l1_address",
149
+ value: this._address,
150
+ });
151
+ const acct = this._accountIndex >= 0
152
+ ? res.accounts?.find(a => a.account_index === this._accountIndex || a.index === this._accountIndex)
153
+ : res.accounts?.[0];
154
+ return acct ?? res.accounts?.[0] ?? null;
155
+ });
156
+ }
157
+ async getOrderbook(symbol) {
158
+ try {
159
+ const marketId = this.getMarketIndex(symbol);
160
+ const res = await this.restGet("/orderBookOrders", {
161
+ market_id: String(marketId),
162
+ limit: "50",
163
+ });
164
+ return {
165
+ bids: (res.bids ?? []).map((l) => [l.price, l.remaining_base_amount ?? l.size ?? "0"]),
166
+ asks: (res.asks ?? []).map((l) => [l.price, l.remaining_base_amount ?? l.size ?? "0"]),
167
+ };
168
+ }
169
+ catch {
170
+ return { bids: [], asks: [] };
171
+ }
172
+ }
173
+ async getBalance() {
174
+ const acct = await this.fetchAccount();
175
+ if (!acct)
176
+ return { equity: "0", available: "0", marginUsed: "0", unrealizedPnl: "0" };
177
+ const totalAsset = Number(acct.total_asset_value || 0);
178
+ const available = Number(acct.available_balance || 0);
179
+ const collateral = Number(acct.collateral || 0);
180
+ const unrealizedPnl = acct.positions?.reduce((sum, p) => sum + Number(p.unrealized_pnl || 0), 0) ?? 0;
181
+ return {
182
+ equity: String(totalAsset),
183
+ available: String(available),
184
+ marginUsed: String(Math.max(0, collateral - available)),
185
+ unrealizedPnl: String(unrealizedPnl),
186
+ };
187
+ }
188
+ async getPositions() {
189
+ const acct = await this.fetchAccount();
190
+ if (!acct)
191
+ return [];
192
+ return (acct.positions ?? [])
193
+ .filter((p) => Number(p.position || 0) !== 0)
194
+ .map((p) => {
195
+ const posSize = Number(p.position || 0);
196
+ return {
197
+ symbol: String(p.symbol || `Market-${p.market_id}`),
198
+ side: (Number(p.sign) > 0 ? "long" : "short"),
199
+ size: String(Math.abs(posSize)),
200
+ entryPrice: String(p.avg_entry_price || "0"),
201
+ markPrice: String(posSize !== 0
202
+ ? (Number(p.position_value || 0) / Math.abs(posSize)).toFixed(4)
203
+ : "0"),
204
+ liquidationPrice: String(p.liquidation_price || "N/A"),
205
+ unrealizedPnl: String(p.unrealized_pnl || "0"),
206
+ leverage: Number(p.initial_margin_fraction || 0) > 0
207
+ ? Math.round(100 / Number(p.initial_margin_fraction))
208
+ : 1,
209
+ };
210
+ });
211
+ }
212
+ async getAuthToken() {
213
+ if (this._readOnly)
214
+ throw new Error("Auth requires API key");
215
+ const deadline = Math.floor(Date.now() / 1000) + 3600;
216
+ const auth = await this._signer.createAuthToken(deadline);
217
+ return auth.authToken;
218
+ }
219
+ async restGetAuth(path, params) {
220
+ const auth = await this.getAuthToken();
221
+ params.auth = auth;
222
+ return this.restGet(path, params);
223
+ }
224
+ async getOpenOrders() {
225
+ if (this._accountIndex < 0 || this._readOnly)
226
+ return [];
227
+ try {
228
+ // Check if account has any orders first
229
+ const acct = await this.fetchAccount();
230
+ const totalOrders = Number(acct?.total_order_count ?? 0);
231
+ if (totalOrders === 0)
232
+ return [];
233
+ // Query each market in parallel (accountActiveOrders requires auth + market_id)
234
+ const allOrders = [];
235
+ const entries = Array.from(this._marketMap.entries());
236
+ const results = await Promise.allSettled(entries.map(([sym, marketId]) => this.restGetAuth("/accountActiveOrders", {
237
+ account_index: String(this._accountIndex),
238
+ market_id: String(marketId),
239
+ }).then(res => ({ sym, orders: (res.orders ?? []) }))));
240
+ for (const r of results) {
241
+ if (r.status !== "fulfilled" || !r.value.orders.length)
242
+ continue;
243
+ for (const o of r.value.orders) {
244
+ allOrders.push({
245
+ orderId: String(o.order_id ?? o.order_index ?? ""),
246
+ symbol: String(o.symbol ?? r.value.sym),
247
+ side: o.is_ask ? "sell" : "buy",
248
+ price: String(o.price ?? "0"),
249
+ size: String(o.initial_base_amount ?? o.remaining_base_amount ?? "0"),
250
+ filled: String(o.filled_base_amount ?? "0"),
251
+ status: String(o.status ?? "open"),
252
+ type: String(o.type ?? "limit"),
253
+ });
254
+ }
255
+ }
256
+ return allOrders;
257
+ }
258
+ catch {
259
+ return [];
260
+ }
261
+ }
262
+ async marketOrder(symbol, side, size) {
263
+ this.ensureSigner();
264
+ const nonce = await this.getNextNonce();
265
+ const marketIndex = this.getMarketIndex(symbol);
266
+ const { baseAmount } = this.toTicks(symbol, parseFloat(size), 0);
267
+ // Market orders need a max slippage price (buy=high, sell=low)
268
+ const markPrice = await this.getMarkPrice(symbol);
269
+ const slippagePrice = side === "buy" ? markPrice * 2 : markPrice * 0.5;
270
+ const { priceTicks: slippageTicks } = this.toTicks(symbol, 0, slippagePrice);
271
+ const signed = await this.signOrder({
272
+ marketIndex,
273
+ clientOrderIndex: 0,
274
+ baseAmount,
275
+ price: Math.max(slippageTicks, 1),
276
+ isAsk: side === "sell" ? 1 : 0,
277
+ type: 1, // ORDER_TYPE_MARKET
278
+ timeInForce: 0, // IOC (Immediate or Cancel)
279
+ reduceOnly: 0,
280
+ triggerPrice: 0,
281
+ orderExpiry: 0, // DEFAULT_IOC_EXPIRY
282
+ nonce,
283
+ });
284
+ return this.sendTx(signed);
285
+ }
286
+ async limitOrder(symbol, side, price, size, opts) {
287
+ this.ensureSigner();
288
+ const nonce = await this.getNextNonce();
289
+ const marketIndex = this.getMarketIndex(symbol);
290
+ const { baseAmount, priceTicks } = this.toTicks(symbol, parseFloat(size), parseFloat(price));
291
+ const signed = await this.signOrder({
292
+ marketIndex,
293
+ clientOrderIndex: 0,
294
+ baseAmount,
295
+ price: priceTicks,
296
+ isAsk: side === "sell" ? 1 : 0,
297
+ type: 0, // ORDER_TYPE_LIMIT
298
+ timeInForce: 1, // GTT (Good Till Time) — rests on orderbook
299
+ reduceOnly: opts?.reduceOnly ? 1 : 0,
300
+ triggerPrice: 0,
301
+ orderExpiry: -1, // DEFAULT_28_DAY_ORDER_EXPIRY (WASM auto-computes 28 days)
302
+ nonce,
303
+ });
304
+ return this.sendTx(signed);
305
+ }
306
+ async cancelOrder(symbol, orderId) {
307
+ this.ensureSigner();
308
+ const nonce = await this.getNextNonce();
309
+ const marketIndex = this.getMarketIndex(symbol);
310
+ const signed = await this._signer.signCancelOrder(marketIndex, parseInt(orderId), nonce);
311
+ return this.sendTx(signed);
312
+ }
313
+ async cancelAllOrders(_symbol) {
314
+ this.ensureSigner();
315
+ const nonce = await this.getNextNonce();
316
+ const signed = await this._signer.signCancelAllOrders(0, Math.floor(Date.now() / 1000) + 86400, nonce);
317
+ return this.sendTx(signed);
318
+ }
319
+ async modifyOrder(symbol, orderId, price, size) {
320
+ this.ensureSigner();
321
+ const nonce = await this.getNextNonce();
322
+ const marketIndex = this.getMarketIndex(symbol);
323
+ const { baseAmount, priceTicks } = this.toTicks(symbol, parseFloat(size), parseFloat(price));
324
+ const signed = await this._signer.signModifyOrder({
325
+ marketIndex,
326
+ index: parseInt(orderId),
327
+ baseAmount,
328
+ price: priceTicks,
329
+ triggerPrice: 0,
330
+ nonce,
331
+ });
332
+ if (signed.error) {
333
+ throw new Error(`Signer: ${signed.error}`);
334
+ }
335
+ return this.sendTx(signed);
336
+ }
337
+ async stopOrder(symbol, side, size, triggerPrice, opts) {
338
+ this.ensureSigner();
339
+ const nonce = await this.getNextNonce();
340
+ const marketIndex = this.getMarketIndex(symbol);
341
+ const { baseAmount } = this.toTicks(symbol, parseFloat(size), 0);
342
+ const { priceTicks: triggerTicks } = this.toTicks(symbol, 0, parseFloat(triggerPrice));
343
+ // If limitPrice given → stop-limit (type 0, GTT), else stop-market (type 1, IOC)
344
+ const isMarket = !opts?.limitPrice;
345
+ let priceTicks;
346
+ if (isMarket) {
347
+ const markPrice = await this.getMarkPrice(symbol);
348
+ const slippagePrice = side === "buy" ? markPrice * 2 : markPrice * 0.5;
349
+ priceTicks = this.toTicks(symbol, 0, slippagePrice).priceTicks;
350
+ }
351
+ else {
352
+ priceTicks = this.toTicks(symbol, 0, parseFloat(opts.limitPrice)).priceTicks;
353
+ }
354
+ const signed = await this.signOrder({
355
+ marketIndex,
356
+ clientOrderIndex: 0,
357
+ baseAmount,
358
+ price: Math.max(priceTicks, 1),
359
+ isAsk: side === "sell" ? 1 : 0,
360
+ type: isMarket ? 1 : 0,
361
+ timeInForce: isMarket ? 0 : 1, // IOC for market, GTT for limit
362
+ reduceOnly: opts?.reduceOnly ? 1 : 0,
363
+ triggerPrice: triggerTicks,
364
+ orderExpiry: isMarket ? 0 : -1,
365
+ nonce,
366
+ });
367
+ return this.sendTx(signed);
368
+ }
369
+ async updateLeverage(symbol, leverage, marginMode = "cross") {
370
+ this.ensureSigner();
371
+ const nonce = await this.getNextNonce();
372
+ const marketIndex = this.getMarketIndex(symbol);
373
+ // fraction = initial margin fraction in basis points: 10000/leverage
374
+ const fraction = Math.round(10000 / leverage);
375
+ const mode = marginMode === "isolated" ? 1 : 0;
376
+ const signed = await this._signer.signUpdateLeverage(marketIndex, fraction, mode, nonce);
377
+ if (signed.error) {
378
+ throw new Error(`Signer: ${signed.error}`);
379
+ }
380
+ return this.sendTx(signed);
381
+ }
382
+ async withdraw(amount, assetId = 2, routeType = 0) {
383
+ this.ensureSigner();
384
+ const nonce = await this.getNextNonce();
385
+ const signed = await this._signer.signWithdraw(amount, assetId, routeType, nonce);
386
+ return this.sendTx(signed);
387
+ }
388
+ // ── Interface aliases ──
389
+ async editOrder(symbol, orderId, price, size) {
390
+ return this.modifyOrder(symbol, orderId, price, size);
391
+ }
392
+ async setLeverage(symbol, leverage, marginMode = "cross") {
393
+ return this.updateLeverage(symbol, leverage, marginMode);
394
+ }
395
+ async getRecentTrades(symbol, limit = 20) {
396
+ const marketId = this.getMarketIndex(symbol);
397
+ const res = await this.restGet("/recentTrades", { market_id: String(marketId), limit: String(limit) });
398
+ const trades = (res.trades ?? []);
399
+ return trades.map((t) => ({
400
+ time: Number(t.timestamp ?? 0) * 1000,
401
+ symbol,
402
+ side: t.is_ask ? "sell" : "buy",
403
+ price: String(t.price ?? "0"),
404
+ size: String(t.base_amount ?? t.amount ?? ""),
405
+ fee: String(t.fee ?? "0"),
406
+ }));
407
+ }
408
+ async getFundingHistory(symbol, limit = 10) {
409
+ const rates = await this.getFundingRates();
410
+ const r = rates.get(symbol.toUpperCase());
411
+ if (!r)
412
+ return [];
413
+ // Lighter funding-rates is current only, not historical
414
+ return [{ time: Date.now(), rate: r.rate, price: r.markPrice }];
415
+ }
416
+ async getKlines(symbol, interval, startTime, endTime) {
417
+ const res = await this.getCandles(symbol, interval, startTime, endTime);
418
+ const candles = (res.candles ?? []);
419
+ return candles.map((c) => ({
420
+ time: Number(c.start_timestamp ?? c.t ?? 0),
421
+ open: String(c.open ?? c.o ?? "0"),
422
+ high: String(c.high ?? c.h ?? "0"),
423
+ low: String(c.low ?? c.l ?? "0"),
424
+ close: String(c.close ?? c.c ?? "0"),
425
+ volume: String(c.base_token_volume ?? c.v ?? ""),
426
+ trades: Number(c.trades_count ?? c.n ?? 0),
427
+ }));
428
+ }
429
+ async getOrderHistory(limit = 30) {
430
+ const raw = await this._getOrderHistoryRaw();
431
+ const orders = (raw.orders ?? []);
432
+ return orders.slice(0, limit).map((o) => ({
433
+ orderId: String(o.order_index ?? o.order_id ?? ""),
434
+ symbol: String(o.symbol ?? ""),
435
+ side: o.is_ask ? "sell" : "buy",
436
+ price: String(o.price ?? "0"),
437
+ size: String(o.initial_base_amount ?? o.base_amount ?? ""),
438
+ filled: String(o.filled_base_amount ?? "0"),
439
+ status: String(o.status ?? "done"),
440
+ type: String(o.type ?? "limit"),
441
+ }));
442
+ }
443
+ async getTradeHistory(limit = 30) {
444
+ const raw = await this._getTradeHistoryRaw(limit);
445
+ const trades = (raw.trades ?? []);
446
+ return trades.slice(0, limit).map((t) => ({
447
+ time: Number(t.timestamp ?? 0) * 1000,
448
+ symbol: String(t.symbol ?? ""),
449
+ side: t.is_ask ? "sell" : "buy",
450
+ price: String(t.price ?? "0"),
451
+ size: String(t.base_amount ?? t.amount ?? ""),
452
+ fee: String(t.fee ?? "0"),
453
+ }));
454
+ }
455
+ async getFundingPayments(limit = 30) {
456
+ const raw = await this._getPositionFundingRaw();
457
+ const items = (raw.funding ?? []);
458
+ return items.slice(0, limit).map((f) => ({
459
+ time: Number(f.timestamp ?? 0) * 1000,
460
+ symbol: String(f.symbol ?? ""),
461
+ payment: String(f.amount ?? f.payment ?? "0"),
462
+ }));
463
+ }
464
+ /**
465
+ * Get funding rates for all markets.
466
+ * API: GET /api/v1/funding-rates
467
+ */
468
+ async getFundingRates() {
469
+ const map = new Map();
470
+ try {
471
+ const res = await this.restGet("/funding-rates", {});
472
+ const reverseMap = new Map();
473
+ for (const [sym, idx] of this._marketMap) {
474
+ reverseMap.set(idx, sym);
475
+ }
476
+ for (const fr of res.funding_rates ?? []) {
477
+ const symbol = fr.symbol || reverseMap.get(fr.market_id);
478
+ if (symbol) {
479
+ map.set(symbol, {
480
+ rate: String(fr.rate ?? fr.funding_rate ?? "0"),
481
+ markPrice: fr.mark_price ?? "0",
482
+ });
483
+ }
484
+ }
485
+ }
486
+ catch { /* non-critical */ }
487
+ return map;
488
+ }
489
+ /**
490
+ * Get candle data for a market.
491
+ */
492
+ async getCandles(symbol, resolution, startTime, endTime, countBack = 100) {
493
+ const marketId = this.getMarketIndex(symbol);
494
+ return this.restGet("/candles", {
495
+ market_id: String(marketId),
496
+ resolution,
497
+ start_timestamp: String(startTime),
498
+ end_timestamp: String(endTime),
499
+ count_back: String(countBack),
500
+ });
501
+ }
502
+ /**
503
+ * Get order history (inactive orders) — raw response.
504
+ */
505
+ async _getOrderHistoryRaw() {
506
+ if (this._accountIndex < 0 || this._readOnly)
507
+ return { orders: [] };
508
+ const allOrders = [];
509
+ const entries = Array.from(this._marketMap.entries());
510
+ const results = await Promise.allSettled(entries.map(([sym, marketId]) => this.restGetAuth("/accountInactiveOrders", {
511
+ account_index: String(this._accountIndex),
512
+ market_id: String(marketId),
513
+ }).then((res) => {
514
+ const orders = (res.orders ?? []);
515
+ return orders.map((o) => ({ ...o, symbol: o.symbol ?? sym }));
516
+ })));
517
+ for (const r of results) {
518
+ if (r.status === "fulfilled")
519
+ allOrders.push(...r.value);
520
+ }
521
+ return { orders: allOrders };
522
+ }
523
+ /**
524
+ * Get trade history — raw response.
525
+ */
526
+ async _getTradeHistoryRaw(limit = 100) {
527
+ if (this._accountIndex < 0)
528
+ return { trades: [] };
529
+ const allTrades = [];
530
+ const entries = Array.from(this._marketMap.entries());
531
+ const results = await Promise.allSettled(entries.map(([sym, marketId]) => this.restGetAuth("/trades", {
532
+ account_index: String(this._accountIndex),
533
+ market_id: String(marketId),
534
+ limit: String(Math.min(limit, 20)),
535
+ }).then((res) => {
536
+ const trades = (res.trades ?? []);
537
+ return trades.map((t) => ({ ...t, symbol: t.symbol ?? sym }));
538
+ })));
539
+ for (const r of results) {
540
+ if (r.status === "fulfilled")
541
+ allTrades.push(...r.value);
542
+ }
543
+ // Sort by timestamp descending
544
+ allTrades.sort((a, b) => Number(b.timestamp ?? 0) - Number(a.timestamp ?? 0));
545
+ return { trades: allTrades.slice(0, limit) };
546
+ }
547
+ /**
548
+ * Get position funding history — raw response.
549
+ */
550
+ async _getPositionFundingRaw() {
551
+ if (this._accountIndex < 0)
552
+ return { funding: [] };
553
+ const allFunding = [];
554
+ const entries = Array.from(this._marketMap.entries());
555
+ const results = await Promise.allSettled(entries.map(([sym, marketId]) => this.restGetAuth("/positionFunding", {
556
+ account_index: String(this._accountIndex),
557
+ market_id: String(marketId),
558
+ }).then((res) => {
559
+ const items = (res.funding ?? res.data ?? []);
560
+ return items.map((f) => ({ ...f, symbol: f.symbol ?? sym }));
561
+ })));
562
+ for (const r of results) {
563
+ if (r.status === "fulfilled")
564
+ allFunding.push(...r.value);
565
+ }
566
+ allFunding.sort((a, b) => Number(b.timestamp ?? 0) - Number(a.timestamp ?? 0));
567
+ return { funding: allFunding };
568
+ }
569
+ /**
570
+ * Get PnL chart data.
571
+ */
572
+ async getPnl(period = "1d") {
573
+ return this.restGet("/pnl", {
574
+ account_index: String(this._accountIndex),
575
+ period,
576
+ });
577
+ }
578
+ /**
579
+ * Get deposit history.
580
+ */
581
+ async getDepositHistory() {
582
+ return this.restGet("/deposit_history", {
583
+ l1_address: this._address,
584
+ });
585
+ }
586
+ /**
587
+ * Get withdrawal history.
588
+ */
589
+ async getWithdrawHistory() {
590
+ return this.restGet("/withdraw_history", {
591
+ account_index: String(this._accountIndex),
592
+ });
593
+ }
594
+ /**
595
+ * Get transfer history.
596
+ */
597
+ async getTransferHistory() {
598
+ return this.restGet("/transfer_history", {
599
+ account_index: String(this._accountIndex),
600
+ });
601
+ }
602
+ /**
603
+ * Get asset details.
604
+ */
605
+ async getAssetDetails(assetId) {
606
+ const params = {};
607
+ if (assetId !== undefined)
608
+ params.asset_id = String(assetId);
609
+ return this.restGet("/assetDetails", params);
610
+ }
611
+ /**
612
+ * Get exchange metrics.
613
+ */
614
+ async getExchangeMetrics(symbol) {
615
+ const params = {};
616
+ if (symbol)
617
+ params.symbol = symbol;
618
+ return this.restGet("/exchangeMetrics", params);
619
+ }
620
+ /**
621
+ * Get account limits.
622
+ */
623
+ async getAccountLimits() {
624
+ return this.restGet("/accountLimits", {
625
+ account_index: String(this._accountIndex),
626
+ });
627
+ }
628
+ /**
629
+ * Create CCTP intent address for cross-chain deposit.
630
+ */
631
+ async createIntentAddress(chainId) {
632
+ const res = await fetch(`${this._baseUrl}/api/v1/createIntentAddress`, {
633
+ method: "POST",
634
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
635
+ body: new URLSearchParams({
636
+ chain_id: String(chainId),
637
+ from_addr: this._address,
638
+ amount: "0",
639
+ is_external_deposit: "true",
640
+ }),
641
+ });
642
+ if (!res.ok)
643
+ throw new Error(`createIntentAddress failed: ${await res.text()}`);
644
+ return res.json();
645
+ }
646
+ /**
647
+ * Use a referral code.
648
+ */
649
+ async useReferralCode(code) {
650
+ // Referral endpoint requires POST with l1_address, referral_code, and auth (form-urlencoded)
651
+ const auth = await this.getAuthToken();
652
+ const res = await fetch(`${this._baseUrl}/api/v1/referral/use`, {
653
+ method: "POST",
654
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
655
+ body: new URLSearchParams({ l1_address: this._address, referral_code: code, auth }),
656
+ });
657
+ const json = await res.json();
658
+ if (json.code && json.code !== 200)
659
+ throw new Error(`Referral failed: ${json.message ?? JSON.stringify(json)}`);
660
+ return json;
661
+ }
662
+ /**
663
+ * Direct REST GET helper.
664
+ */
665
+ async getNextNonce() {
666
+ const apiKeyIndex = parseInt(process.env.LIGHTER_API_KEY_INDEX || "3");
667
+ const res = await this.restGet("/nextNonce", {
668
+ account_index: String(this._accountIndex),
669
+ api_key_index: String(apiKeyIndex),
670
+ });
671
+ return res.nonce ?? res.next_nonce ?? 0;
672
+ }
673
+ async signOrder(params) {
674
+ try {
675
+ const result = await this._signer.signCreateOrder(params);
676
+ if (result.error) {
677
+ throw new Error(`Signer: ${result.error}`);
678
+ }
679
+ return result;
680
+ }
681
+ catch (e) {
682
+ if (e instanceof Error)
683
+ throw e;
684
+ if (typeof e === "object" && e !== null && "error" in e) {
685
+ throw new Error(`Signer: ${e.error}`);
686
+ }
687
+ throw new Error(`Signer: ${JSON.stringify(e)}`);
688
+ }
689
+ }
690
+ async sendTx(signed) {
691
+ if (signed.error)
692
+ throw new Error(`Signer error: ${signed.error}`);
693
+ if (!signed.txInfo)
694
+ throw new Error("Signer returned empty txInfo");
695
+ const res = await fetch(`${this._baseUrl}/api/v1/sendTx`, {
696
+ method: "POST",
697
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
698
+ body: new URLSearchParams({
699
+ tx_type: String(signed.txType ?? 0),
700
+ tx_info: signed.txInfo,
701
+ }),
702
+ });
703
+ const json = await res.json();
704
+ if (json.code !== 200)
705
+ throw new Error(`sendTx failed: ${json.message ?? JSON.stringify(json)}`);
706
+ return json;
707
+ }
708
+ async restGet(path, params) {
709
+ const url = new URL(`${this._baseUrl}/api/v1${path}`);
710
+ for (const [k, v] of Object.entries(params))
711
+ url.searchParams.set(k, v);
712
+ const res = await fetch(url.toString());
713
+ if (!res.ok)
714
+ throw new Error(`GET ${path} failed (${res.status}): ${await res.text()}`);
715
+ return res.json();
716
+ }
717
+ ensureSigner() {
718
+ if (this._readOnly) {
719
+ throw new Error("This command requires a Lighter API key. Run `perp -e lighter manage setup-api-key` first, " +
720
+ "then set LIGHTER_API_KEY in your .env");
721
+ }
722
+ }
723
+ static async getWasmOps() {
724
+ const { WasmLoader } = require("lighter-sdk");
725
+ const loader = WasmLoader.getInstance();
726
+ await loader.load({});
727
+ return loader.getOperations();
728
+ }
729
+ /**
730
+ * Generate a new Lighter API key pair using the WASM signer.
731
+ * Returns { privateKey, publicKey } (both 0x-prefixed, 40 bytes).
732
+ */
733
+ static async generateApiKey(seed) {
734
+ const ops = await LighterAdapter.getWasmOps();
735
+ const result = ops
736
+ .GenerateAPIKey(seed ?? `pacifica-cli-${Date.now()}-${Math.random()}`);
737
+ if (result.err)
738
+ throw new Error(`GenerateAPIKey failed: ${result.err}`);
739
+ return { privateKey: result.privateKey, publicKey: result.publicKey };
740
+ }
741
+ /**
742
+ * Generate an API key and register it on-chain via ChangePubKey.
743
+ * Uses WASM signer to sign + ETH key for L1 signature.
744
+ * Returns the generated key pair.
745
+ */
746
+ async setupApiKey(apiKeyIndex = 2) {
747
+ const { ethers } = await import("ethers");
748
+ // 1. Generate key pair
749
+ const { privateKey, publicKey } = await LighterAdapter.generateApiKey();
750
+ // 2. Get nonce from API
751
+ const nonceRes = await this.restGet("/nextNonce", {
752
+ account_index: String(this._accountIndex),
753
+ api_key_index: String(apiKeyIndex),
754
+ });
755
+ const nonce = nonceRes.next_nonce ?? 0;
756
+ // 3. Create signer client with new key and sign ChangePubKey
757
+ const ops = await LighterAdapter.getWasmOps();
758
+ const opsAny = ops;
759
+ // CreateClient with the new API key
760
+ opsAny.CreateClient(this._baseUrl, privateKey, this._chainId, apiKeyIndex, this._accountIndex);
761
+ // Sign ChangePubKey
762
+ const signed = await opsAny.SignChangePubKey(publicKey, nonce, apiKeyIndex, this._accountIndex);
763
+ if (signed.error)
764
+ throw new Error(`SignChangePubKey failed: ${signed.error}`);
765
+ if (!signed.txInfo || !signed.messageToSign) {
766
+ throw new Error("SignChangePubKey returned incomplete response");
767
+ }
768
+ // 4. Sign messageToSign with ETH key (EIP-191 personal_sign)
769
+ const wallet = new ethers.Wallet(this._evmKey);
770
+ const l1Sig = await wallet.signMessage(signed.messageToSign);
771
+ // 5. Add L1Sig to txInfo
772
+ const txInfo = JSON.parse(signed.txInfo);
773
+ txInfo.L1Sig = l1Sig;
774
+ // 6. Submit to Lighter API
775
+ const sendRes = await fetch(`${this._baseUrl}/api/v1/sendTx`, {
776
+ method: "POST",
777
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
778
+ body: new URLSearchParams({
779
+ tx_type: String(signed.txType ?? 0),
780
+ tx_info: JSON.stringify(txInfo),
781
+ }),
782
+ });
783
+ if (!sendRes.ok) {
784
+ const errText = await sendRes.text();
785
+ throw new Error(`ChangePubKey sendTx failed (${sendRes.status}): ${errText}`);
786
+ }
787
+ const result = await sendRes.json();
788
+ if (result.code !== 200) {
789
+ throw new Error(`ChangePubKey failed: ${result.message ?? JSON.stringify(result)}`);
790
+ }
791
+ return { privateKey, publicKey };
792
+ }
793
+ }