perp-cli 0.6.4 → 0.7.2

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 (181) hide show
  1. package/README.md +7 -2
  2. package/dist/api/public/hyperliquid.d.ts +2 -2
  3. package/dist/api/public/hyperliquid.js +0 -10
  4. package/dist/api/public/lighter.d.ts +2 -1
  5. package/dist/api/public/pacifica.d.ts +2 -1
  6. package/dist/arb/index.d.ts +7 -4
  7. package/dist/arb/index.js +5 -4
  8. package/dist/arb/sizing.d.ts +1 -1
  9. package/dist/arb/state.d.ts +1 -1
  10. package/dist/arb/state.js +4 -3
  11. package/dist/arb/utils.d.ts +0 -10
  12. package/dist/arb/utils.js +0 -42
  13. package/dist/bot/apex/guard.d.ts +26 -0
  14. package/dist/bot/apex/guard.js +86 -0
  15. package/dist/bot/apex/index.d.ts +6 -0
  16. package/dist/bot/apex/index.js +4 -0
  17. package/dist/bot/apex/pulse.d.ts +22 -0
  18. package/dist/bot/apex/pulse.js +107 -0
  19. package/dist/bot/apex/radar.d.ts +17 -0
  20. package/dist/bot/apex/radar.js +150 -0
  21. package/dist/bot/conditions.d.ts +1 -1
  22. package/dist/bot/config.d.ts +6 -1
  23. package/dist/bot/config.js +2 -2
  24. package/dist/bot/engine.d.ts +23 -2
  25. package/dist/bot/engine.js +331 -351
  26. package/dist/bot/index.d.ts +16 -0
  27. package/dist/bot/index.js +10 -0
  28. package/dist/bot/indicators.d.ts +29 -0
  29. package/dist/bot/indicators.js +144 -0
  30. package/dist/bot/reflect.d.ts +34 -0
  31. package/dist/bot/reflect.js +155 -0
  32. package/dist/bot/strategies/aggressive-taker.d.ts +36 -0
  33. package/dist/bot/strategies/aggressive-taker.js +125 -0
  34. package/dist/bot/strategies/apex-strategy.d.ts +36 -0
  35. package/dist/bot/strategies/apex-strategy.js +185 -0
  36. package/dist/bot/strategies/avellaneda-mm.d.ts +27 -0
  37. package/dist/bot/strategies/avellaneda-mm.js +111 -0
  38. package/dist/bot/strategies/basis-arb.d.ts +27 -0
  39. package/dist/bot/strategies/basis-arb.js +97 -0
  40. package/dist/bot/strategies/claude-agent.d.ts +31 -0
  41. package/dist/bot/strategies/claude-agent.js +155 -0
  42. package/dist/bot/strategies/dca-strategy.d.ts +25 -0
  43. package/dist/bot/strategies/dca-strategy.js +59 -0
  44. package/dist/bot/strategies/engine-mm.d.ts +29 -0
  45. package/dist/bot/strategies/engine-mm.js +141 -0
  46. package/dist/bot/strategies/funding-arb-strategy.d.ts +32 -0
  47. package/dist/bot/strategies/funding-arb-strategy.js +168 -0
  48. package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
  49. package/dist/bot/strategies/funding-arb-v2.js +222 -0
  50. package/dist/bot/strategies/funding-auto.d.ts +1 -0
  51. package/dist/bot/strategies/funding-auto.js +572 -0
  52. package/dist/bot/strategies/grid-mm.d.ts +25 -0
  53. package/dist/bot/strategies/grid-mm.js +99 -0
  54. package/dist/bot/strategies/grid-strategy.d.ts +39 -0
  55. package/dist/bot/strategies/grid-strategy.js +176 -0
  56. package/dist/bot/strategies/hedge-agent.d.ts +25 -0
  57. package/dist/bot/strategies/hedge-agent.js +79 -0
  58. package/dist/bot/strategies/liquidation-mm.d.ts +25 -0
  59. package/dist/bot/strategies/liquidation-mm.js +108 -0
  60. package/dist/bot/strategies/mean-reversion.d.ts +35 -0
  61. package/dist/bot/strategies/mean-reversion.js +118 -0
  62. package/dist/bot/strategies/momentum-breakout.d.ts +36 -0
  63. package/dist/bot/strategies/momentum-breakout.js +159 -0
  64. package/dist/bot/strategies/regime-mm.d.ts +25 -0
  65. package/dist/bot/strategies/regime-mm.js +111 -0
  66. package/dist/bot/strategies/rfq-agent.d.ts +25 -0
  67. package/dist/bot/strategies/rfq-agent.js +71 -0
  68. package/dist/bot/strategies/simple-mm.d.ts +25 -0
  69. package/dist/bot/strategies/simple-mm.js +76 -0
  70. package/dist/bot/strategy-registry.d.ts +9 -0
  71. package/dist/bot/strategy-registry.js +14 -0
  72. package/dist/bot/strategy-types.d.ts +64 -0
  73. package/dist/bot/strategy-types.js +5 -0
  74. package/dist/bot/trade-journal.d.ts +27 -0
  75. package/dist/bot/trade-journal.js +53 -0
  76. package/dist/bot/tui/BotDashboard.d.ts +56 -0
  77. package/dist/bot/tui/BotDashboard.js +94 -0
  78. package/dist/bot/tui/index.d.ts +6 -0
  79. package/dist/bot/tui/index.js +8 -0
  80. package/dist/bridge-engine.d.ts +0 -1
  81. package/dist/bridge-engine.js +0 -1
  82. package/dist/cache.d.ts +0 -1
  83. package/dist/cache.js +0 -1
  84. package/dist/commands/account.d.ts +1 -1
  85. package/dist/commands/account.js +94 -91
  86. package/dist/commands/agent.d.ts +1 -1
  87. package/dist/commands/arb/index.d.ts +1 -1
  88. package/dist/commands/arb/index.js +1 -1
  89. package/dist/commands/arb-auto.d.ts +1 -1
  90. package/dist/commands/arb-auto.js +5 -3
  91. package/dist/commands/arb.d.ts +2 -1
  92. package/dist/commands/arb.js +2 -0
  93. package/dist/commands/bot.d.ts +1 -1
  94. package/dist/commands/bot.js +236 -15
  95. package/dist/commands/bridge.js +1 -1
  96. package/dist/commands/dashboard.d.ts +1 -1
  97. package/dist/commands/dashboard.js +1 -1
  98. package/dist/commands/funds.d.ts +1 -1
  99. package/dist/commands/funds.js +34 -21
  100. package/dist/commands/history.d.ts +1 -1
  101. package/dist/commands/history.js +10 -5
  102. package/dist/commands/manage.d.ts +2 -3
  103. package/dist/commands/manage.js +11 -18
  104. package/dist/commands/market.d.ts +1 -1
  105. package/dist/commands/market.js +26 -17
  106. package/dist/commands/plan.d.ts +1 -1
  107. package/dist/commands/rebalance.d.ts +1 -1
  108. package/dist/commands/rebalance.js +8 -9
  109. package/dist/commands/risk.d.ts +1 -1
  110. package/dist/commands/settings.d.ts +1 -1
  111. package/dist/commands/settings.js +3 -1
  112. package/dist/commands/trade.d.ts +1 -1
  113. package/dist/commands/trade.js +42 -55
  114. package/dist/commands/wallet.d.ts +0 -2
  115. package/dist/commands/wallet.js +0 -5
  116. package/dist/constants.d.ts +0 -3
  117. package/dist/constants.js +0 -4
  118. package/dist/cross-chain-margin.d.ts +1 -1
  119. package/dist/dashboard/index.d.ts +4 -0
  120. package/dist/dashboard/index.js +3 -0
  121. package/dist/dashboard/server.d.ts +1 -1
  122. package/dist/dashboard/server.js +1 -1
  123. package/dist/dashboard/ws-feeds.d.ts +1 -1
  124. package/dist/event-stream.d.ts +1 -1
  125. package/dist/exchanges/aster.d.ts +62 -0
  126. package/dist/exchanges/aster.js +420 -0
  127. package/dist/exchanges/capabilities.d.ts +74 -0
  128. package/dist/exchanges/capabilities.js +44 -0
  129. package/dist/exchanges/edgex.d.ts +62 -0
  130. package/dist/exchanges/edgex.js +552 -0
  131. package/dist/exchanges/hyperliquid-spot.d.ts +1 -1
  132. package/dist/exchanges/hyperliquid-spot.js +20 -12
  133. package/dist/exchanges/hyperliquid.d.ts +8 -2
  134. package/dist/exchanges/hyperliquid.js +21 -6
  135. package/dist/exchanges/index.d.ts +11 -0
  136. package/dist/exchanges/index.js +10 -0
  137. package/dist/exchanges/interface.d.ts +3 -0
  138. package/dist/exchanges/lighter-spot.d.ts +13 -0
  139. package/dist/exchanges/lighter-spot.js +98 -26
  140. package/dist/exchanges/lighter.d.ts +9 -3
  141. package/dist/exchanges/lighter.js +18 -8
  142. package/dist/exchanges/nado.d.ts +52 -0
  143. package/dist/exchanges/nado.js +223 -0
  144. package/dist/exchanges/pacifica.d.ts +7 -1
  145. package/dist/exchanges/pacifica.js +17 -2
  146. package/dist/exchanges/registry.d.ts +13 -0
  147. package/dist/exchanges/registry.js +37 -0
  148. package/dist/exchanges/spot-interface.d.ts +9 -0
  149. package/dist/funding/rates.js +1 -1
  150. package/dist/index.js +2 -6
  151. package/dist/jobs.js +1 -1
  152. package/dist/liquidity.d.ts +1 -2
  153. package/dist/liquidity.js +12 -5
  154. package/dist/mcp-server.js +4 -2
  155. package/dist/pacifica/constants.d.ts +0 -3
  156. package/dist/pacifica/constants.js +0 -4
  157. package/dist/pacifica/types/account.d.ts +0 -8
  158. package/dist/pacifica/types/lake.d.ts +0 -6
  159. package/dist/pacifica/types/position.d.ts +0 -12
  160. package/dist/pacifica/types/ws.d.ts +0 -24
  161. package/dist/pacifica/types/ws.js +1 -41
  162. package/dist/plan-executor.d.ts +1 -1
  163. package/dist/rebalance.d.ts +1 -7
  164. package/dist/rebalance.js +0 -6
  165. package/dist/risk.d.ts +1 -1
  166. package/dist/risk.js +3 -1
  167. package/dist/smart-order.d.ts +1 -1
  168. package/dist/strategies/dca.d.ts +1 -1
  169. package/dist/strategies/funding-arb.d.ts +1 -1
  170. package/dist/strategies/funding-arb.js +193 -181
  171. package/dist/strategies/grid.d.ts +1 -1
  172. package/dist/strategies/grid.js +1 -1
  173. package/dist/strategies/index.d.ts +12 -0
  174. package/dist/strategies/index.js +7 -0
  175. package/dist/strategies/split-order.d.ts +1 -1
  176. package/dist/strategies/trailing-stop.d.ts +1 -1
  177. package/dist/strategies/twap.d.ts +1 -1
  178. package/dist/trade-validator.d.ts +1 -1
  179. package/package.json +7 -1
  180. package/skills/perp-cli/SKILL.md +20 -0
  181. package/skills/perp-cli/references/commands.md +22 -8
@@ -1,15 +1,16 @@
1
- import { PacificaAdapter } from "../exchanges/pacifica.js";
2
1
  import { printJson, jsonOk } from "../utils.js";
3
2
  import { setEnvVar } from "./init.js";
4
3
  import chalk from "chalk";
4
+ import { hasPacificaSdk, hasLighterAccount } from "../exchanges/capabilities.js";
5
5
  export function registerManageCommands(program, getAdapter, isJson, getPacificaAdapter) {
6
6
  const manage = program.command("manage").description("Exchange account settings (margin, subaccount, API keys, builder)");
7
7
  // Ensure adapter is initialized before accessing PacificaAdapter
8
8
  async function pac() {
9
9
  const adapter = await getAdapter();
10
- if (!(adapter instanceof PacificaAdapter)) {
10
+ if (!hasPacificaSdk(adapter)) {
11
11
  throw new Error("This command requires --exchange pacifica");
12
12
  }
13
+ // Cast sdk to any to allow calling Pacifica SDK methods without importing the concrete type
13
14
  return adapter;
14
15
  }
15
16
  manage
@@ -30,13 +31,8 @@ export function registerManageCommands(program, getAdapter, isJson, getPacificaA
30
31
  const withdrawCmd = manage
31
32
  .command("withdraw <amount> <address>")
32
33
  .description("Use 'perp withdraw pacifica <amount>'")
33
- .action(async (amount, address) => {
34
+ .action(async (_amount, _address) => {
34
35
  console.log(chalk.yellow("\n Use 'perp withdraw pacifica <amount>' instead.\n"));
35
- const a = await pac();
36
- const result = await a.sdk.withdraw({ amount, dest_address: address }, a.publicKey, a.signer);
37
- if (isJson())
38
- return printJson(jsonOk(result));
39
- console.log(chalk.green(`\n Withdrawal of $${amount} to ${address} submitted.\n`));
40
36
  });
41
37
  withdrawCmd._hidden = true;
42
38
  // Subaccounts
@@ -59,7 +55,7 @@ export function registerManageCommands(program, getAdapter, isJson, getPacificaA
59
55
  const result = await a.sdk.listSubaccounts(a.publicKey, a.signer);
60
56
  if (isJson())
61
57
  return printJson(jsonOk(result));
62
- printJson(jsonOk(result));
58
+ console.log(JSON.stringify(result, null, 2));
63
59
  });
64
60
  sub
65
61
  .command("transfer <from> <to> <amount>")
@@ -91,7 +87,7 @@ export function registerManageCommands(program, getAdapter, isJson, getPacificaA
91
87
  const result = await a.sdk.listAgentWallets(a.publicKey, a.signer);
92
88
  if (isJson())
93
89
  return printJson(jsonOk(result));
94
- printJson(jsonOk(result));
90
+ console.log(JSON.stringify(result, null, 2));
95
91
  });
96
92
  agent
97
93
  .command("revoke <wallet>")
@@ -124,7 +120,6 @@ export function registerManageCommands(program, getAdapter, isJson, getPacificaA
124
120
  if (isJson())
125
121
  return printJson(jsonOk(result));
126
122
  console.log(chalk.green(`\n Lake created for ${symbol.toUpperCase()} with $${amount}.\n`));
127
- printJson(jsonOk(result));
128
123
  });
129
124
  lake
130
125
  .command("deposit <lakeId> <amount>")
@@ -195,7 +190,7 @@ export function registerManageCommands(program, getAdapter, isJson, getPacificaA
195
190
  const result = await a.sdk.getBuilderOverview(a.publicKey);
196
191
  if (isJson())
197
192
  return printJson(jsonOk(result));
198
- printJson(jsonOk(result));
193
+ console.log(JSON.stringify(result, null, 2));
199
194
  });
200
195
  builder
201
196
  .command("trades <code>")
@@ -205,7 +200,7 @@ export function registerManageCommands(program, getAdapter, isJson, getPacificaA
205
200
  const result = await a.sdk.getBuilderTrades(code);
206
201
  if (isJson())
207
202
  return printJson(jsonOk(result));
208
- printJson(jsonOk(result));
203
+ console.log(JSON.stringify(result, null, 2));
209
204
  });
210
205
  builder
211
206
  .command("leaderboard <code>")
@@ -215,7 +210,7 @@ export function registerManageCommands(program, getAdapter, isJson, getPacificaA
215
210
  const result = await a.sdk.getBuilderLeaderboard(code);
216
211
  if (isJson())
217
212
  return printJson(jsonOk(result));
218
- printJson(jsonOk(result));
213
+ console.log(JSON.stringify(result, null, 2));
219
214
  });
220
215
  builder
221
216
  .command("update-fee <code> <feeRate>")
@@ -250,7 +245,6 @@ export function registerManageCommands(program, getAdapter, isJson, getPacificaA
250
245
  if (isJson())
251
246
  return printJson(jsonOk(result));
252
247
  console.log(chalk.green(`\n API key "${name}" created.\n`));
253
- printJson(jsonOk(result));
254
248
  });
255
249
  apikey
256
250
  .command("list")
@@ -260,7 +254,7 @@ export function registerManageCommands(program, getAdapter, isJson, getPacificaA
260
254
  const result = await a.sdk.listApiKeys(a.publicKey, a.signer);
261
255
  if (isJson())
262
256
  return printJson(jsonOk(result));
263
- printJson(jsonOk(result));
257
+ console.log(JSON.stringify(result, null, 2));
264
258
  });
265
259
  apikey
266
260
  .command("revoke <key>")
@@ -279,8 +273,7 @@ export function registerManageCommands(program, getAdapter, isJson, getPacificaA
279
273
  .option("--key-index <n>", "API key index (4-254, default: 4)", "4")
280
274
  .action(async (opts) => {
281
275
  const adapter = await getAdapter();
282
- const { LighterAdapter } = await import("../exchanges/lighter.js");
283
- if (!(adapter instanceof LighterAdapter)) {
276
+ if (!hasLighterAccount(adapter)) {
284
277
  throw new Error("This command requires --exchange lighter");
285
278
  }
286
279
  const keyIndex = parseInt(opts.keyIndex);
@@ -1,3 +1,3 @@
1
1
  import { Command } from "commander";
2
- import type { ExchangeAdapter } from "../exchanges/interface.js";
2
+ import type { ExchangeAdapter } from "../exchanges/index.js";
3
3
  export declare function registerMarketCommands(program: Command, getAdapter: () => Promise<ExchangeAdapter>, isJson: () => boolean, getAdapterForExchange?: (exchange: string) => Promise<ExchangeAdapter>): void;
@@ -1,7 +1,6 @@
1
- import { PacificaAdapter } from "../exchanges/pacifica.js";
2
- import { HyperliquidAdapter } from "../exchanges/hyperliquid.js";
3
1
  import { makeTable, formatUsd, formatPercent, printJson, jsonOk, jsonError, withJsonErrors } from "../utils.js";
4
2
  import chalk from "chalk";
3
+ import { hasPacificaSdk, isDexCapable } from "../exchanges/capabilities.js";
5
4
  const EXCHANGES = ["pacifica", "hyperliquid", "lighter"];
6
5
  export function registerMarketCommands(program, getAdapter, isJson, getAdapterForExchange) {
7
6
  const market = program.command("market").description("Market data commands");
@@ -30,7 +29,7 @@ export function registerMarketCommands(program, getAdapter, isJson, getAdapterFo
30
29
  const adapter = await getAdapterForExchange(ex);
31
30
  grouped[ex] = await adapter.getMarkets();
32
31
  // --hip3: fetch HIP-3 dex markets
33
- if (opts.hip3 && ex === "hyperliquid" && adapter instanceof HyperliquidAdapter) {
32
+ if (opts.hip3 && ex === "hyperliquid" && isDexCapable(adapter)) {
34
33
  const dexes = await adapter.listDeployedDexes();
35
34
  await Promise.allSettled(dexes.map(async (d) => {
36
35
  const dexAdapter = Object.create(adapter);
@@ -67,7 +66,7 @@ export function registerMarketCommands(program, getAdapter, isJson, getAdapterFo
67
66
  const markets = await adapter.getMarkets();
68
67
  // --hip3: fetch HIP-3 dex markets
69
68
  const hip3 = {};
70
- if (opts.hip3 && adapter instanceof HyperliquidAdapter) {
69
+ if (opts.hip3 && isDexCapable(adapter)) {
71
70
  const dexes = await adapter.listDeployedDexes();
72
71
  await Promise.allSettled(dexes.map(async (d) => {
73
72
  const dexAdapter = Object.create(adapter);
@@ -127,8 +126,9 @@ export function registerMarketCommands(program, getAdapter, isJson, getAdapterFo
127
126
  return;
128
127
  }
129
128
  const adapter = await getAdapter();
130
- if (adapter instanceof PacificaAdapter) {
131
- const prices = await adapter.sdk.getPrices();
129
+ if (hasPacificaSdk(adapter)) {
130
+ const sdk = adapter.sdk;
131
+ const prices = await sdk.getPrices();
132
132
  if (isJson())
133
133
  return printJson(jsonOk(prices));
134
134
  const rows = prices.map((p) => [
@@ -163,7 +163,7 @@ export function registerMarketCommands(program, getAdapter, isJson, getAdapterFo
163
163
  if (isJson())
164
164
  return printJson(jsonError("SYMBOL_NOT_FOUND", `Market ${sym} not found`));
165
165
  console.log(chalk.red(`\n Market "${sym}" not found.\n`));
166
- return;
166
+ return process.exit(1);
167
167
  }
168
168
  if (isJson())
169
169
  return printJson(jsonOk(m));
@@ -228,9 +228,10 @@ export function registerMarketCommands(program, getAdapter, isJson, getAdapterFo
228
228
  market
229
229
  .command("trades <symbol>")
230
230
  .description("Recent trades for a symbol")
231
- .action(async (symbol) => {
231
+ .option("--limit <n>", "Number of recent trades to show", "20")
232
+ .action(async (symbol, opts) => {
232
233
  const adapter = await getAdapter();
233
- const trades = await adapter.getRecentTrades(symbol.toUpperCase(), 20);
234
+ const trades = await adapter.getRecentTrades(symbol.toUpperCase(), parseInt(opts.limit));
234
235
  if (isJson())
235
236
  return printJson(jsonOk(trades));
236
237
  if (trades.length === 0) {
@@ -258,12 +259,20 @@ export function registerMarketCommands(program, getAdapter, isJson, getAdapterFo
258
259
  console.log(chalk.gray("\n No funding history.\n"));
259
260
  return;
260
261
  }
261
- const rows = history.map((h) => [
262
- new Date(h.time).toLocaleString(),
263
- formatPercent(h.rate),
264
- h.price != null ? `$${formatUsd(h.price)}` : chalk.gray("n/a"),
265
- ]);
266
- console.log(makeTable(["Time", "Funding Rate", "Oracle"], rows));
262
+ const hasOracle = history.some((h) => h.price != null && h.price !== "0" && h.price !== "");
263
+ const rows = history.map((h) => {
264
+ const base = [
265
+ new Date(h.time).toLocaleString(),
266
+ formatPercent(h.rate),
267
+ ];
268
+ if (hasOracle)
269
+ base.push(h.price != null ? `$${formatUsd(h.price)}` : chalk.gray("n/a"));
270
+ return base;
271
+ });
272
+ const headers = hasOracle ? ["Time", "Funding Rate", "Oracle"] : ["Time", "Funding Rate"];
273
+ console.log(makeTable(headers, rows));
274
+ if (!hasOracle)
275
+ console.log(chalk.gray(" Note: Oracle price not available for this exchange.\n"));
267
276
  });
268
277
  market
269
278
  .command("mid <symbol>")
@@ -351,7 +360,7 @@ export function registerMarketCommands(program, getAdapter, isJson, getAdapterFo
351
360
  .action(async () => {
352
361
  await withJsonErrors(isJson(), async () => {
353
362
  const adapter = await getAdapter();
354
- if (!(adapter instanceof HyperliquidAdapter)) {
363
+ if (!(isDexCapable(adapter))) {
355
364
  if (isJson())
356
365
  return printJson(jsonError("INVALID_EXCHANGE", "HIP-3 dexes are only available on Hyperliquid. Use -e hyperliquid."));
357
366
  console.error(chalk.red("\n HIP-3 dexes are only available on Hyperliquid. Use -e hyperliquid.\n"));
@@ -367,7 +376,7 @@ export function registerMarketCommands(program, getAdapter, isJson, getAdapterFo
367
376
  console.log(chalk.cyan.bold("\n HIP-3 Deployed Perp DEXes\n"));
368
377
  const rows = dexes.map(d => [
369
378
  chalk.white.bold(d.name),
370
- chalk.gray(d.deployer.slice(0, 10) + "..."),
379
+ chalk.gray(d.deployer ? d.deployer.slice(0, 6) + "..." + d.deployer.slice(-4) : "-"),
371
380
  String(d.assets.length),
372
381
  d.assets.slice(0, 5).join(", ") + (d.assets.length > 5 ? ` +${d.assets.length - 5}` : ""),
373
382
  ]);
@@ -1,3 +1,3 @@
1
1
  import { Command } from "commander";
2
- import type { ExchangeAdapter } from "../exchanges/interface.js";
2
+ import type { ExchangeAdapter } from "../exchanges/index.js";
3
3
  export declare function registerPlanCommands(program: Command, getAdapter: () => Promise<ExchangeAdapter>, isJson: () => boolean): void;
@@ -1,3 +1,3 @@
1
1
  import { Command } from "commander";
2
- import type { ExchangeAdapter } from "../exchanges/interface.js";
2
+ import type { ExchangeAdapter } from "../exchanges/index.js";
3
3
  export declare function registerRebalanceCommands(program: Command, getAdapterForExchange: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean): void;
@@ -229,29 +229,28 @@ export function registerRebalanceCommands(program, getAdapterForExchange, isJson
229
229
  * Execute a withdrawal from a source exchange.
230
230
  */
231
231
  async function executeWithdraw(move, adapters) {
232
+ const { hasPacificaSdk, isWithdrawCapable, hasEvmAddress } = await import("../exchanges/capabilities.js");
232
233
  const adapter = adapters.get(move.from);
233
234
  if (!adapter)
234
235
  throw new Error(`No adapter for ${move.from}`);
235
- switch (move.from) {
236
+ switch (adapter.name) {
236
237
  case "pacifica": {
237
- const { PacificaAdapter } = await import("../exchanges/pacifica.js");
238
- if (!(adapter instanceof PacificaAdapter))
238
+ if (!hasPacificaSdk(adapter))
239
239
  throw new Error("Invalid adapter");
240
- await adapter.sdk.withdraw({ amount: String(move.amount), dest_address: adapter.publicKey }, adapter.publicKey, adapter.signer);
240
+ const sdk = adapter.sdk;
241
+ await sdk.withdraw({ amount: String(move.amount), dest_address: adapter.publicKey }, adapter.publicKey, adapter.signer);
241
242
  break;
242
243
  }
243
244
  case "hyperliquid": {
244
- const { HyperliquidAdapter } = await import("../exchanges/hyperliquid.js");
245
- if (!(adapter instanceof HyperliquidAdapter))
245
+ if (!isWithdrawCapable(adapter) || !hasEvmAddress(adapter))
246
246
  throw new Error("Invalid adapter");
247
247
  await adapter.withdraw(String(move.amount), adapter.address);
248
248
  break;
249
249
  }
250
250
  case "lighter": {
251
- const { LighterAdapter } = await import("../exchanges/lighter.js");
252
- if (!(adapter instanceof LighterAdapter))
251
+ if (!isWithdrawCapable(adapter))
253
252
  throw new Error("Invalid adapter");
254
- await adapter.withdraw(move.amount);
253
+ await adapter.withdraw(String(move.amount), "");
255
254
  break;
256
255
  }
257
256
  default:
@@ -1,5 +1,5 @@
1
1
  import { Command } from "commander";
2
- import type { ExchangeAdapter } from "../exchanges/interface.js";
2
+ import type { ExchangeAdapter } from "../exchanges/index.js";
3
3
  /** Run health check and return results + render. Exported for use by status --health. */
4
4
  export declare function runHealthCheck(isJson: () => boolean): Promise<void>;
5
5
  export declare function registerRiskCommands(program: Command, getAdapterForExchange: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean): void;
@@ -1,3 +1,3 @@
1
1
  import { Command } from "commander";
2
- import type { ExchangeAdapter } from "../exchanges/interface.js";
2
+ import type { ExchangeAdapter } from "../exchanges/index.js";
3
3
  export declare function registerSettingsCommands(program: Command, isJson: () => boolean, getAdapterForExchange?: (exchange: string) => Promise<ExchangeAdapter>): void;
@@ -1,5 +1,5 @@
1
1
  import chalk from "chalk";
2
- import { printJson, jsonOk, withJsonErrors } from "../utils.js";
2
+ import { printJson, jsonOk, jsonError, withJsonErrors } from "../utils.js";
3
3
  import { loadSettings, saveSettings } from "../settings.js";
4
4
  import { ENV_FILE, loadEnvFile, setEnvVar, EXCHANGE_ENV_MAP, validateKey } from "./init.js";
5
5
  // Resolve exchange alias → env var key
@@ -80,6 +80,8 @@ export function registerSettingsCommands(program, isJson, getAdapterForExchange)
80
80
  console.log(chalk.yellow("\n Referrals disabled. No codes will be sent.\n"));
81
81
  }
82
82
  else {
83
+ if (isJson())
84
+ return printJson(jsonError("INVALID_ARGS", `Invalid action "${action}". Usage: perp settings referrals <on|off>`));
83
85
  console.error(chalk.red(`\n Usage: perp settings referrals <on|off>\n`));
84
86
  }
85
87
  });
@@ -1,3 +1,3 @@
1
1
  import { Command } from "commander";
2
- import type { ExchangeAdapter } from "../exchanges/interface.js";
2
+ import type { ExchangeAdapter } from "../exchanges/index.js";
3
3
  export declare function registerTradeCommands(program: Command, getAdapter: () => Promise<ExchangeAdapter>, isJson: () => boolean, isDryRun?: () => boolean, getAdapterForExchange?: (exchange: string) => Promise<ExchangeAdapter>): void;
@@ -1,17 +1,10 @@
1
- import { PacificaAdapter } from "../exchanges/pacifica.js";
2
- import { HyperliquidAdapter } from "../exchanges/hyperliquid.js";
3
- import { LighterAdapter } from "../exchanges/lighter.js";
4
1
  import { printJson, errorAndExit, withJsonErrors, jsonOk, jsonError, symbolMatch, formatUsd } from "../utils.js";
5
2
  import { logExecution } from "../execution-log.js";
6
3
  import { validateTrade } from "../trade-validator.js";
7
4
  import { generateClientId, logClientId, isOrderDuplicate } from "../client-id-tracker.js";
8
5
  import { smartOrder } from "../smart-order.js";
9
6
  import chalk from "chalk";
10
- function pac(adapter) {
11
- if (!(adapter instanceof PacificaAdapter))
12
- throw new Error("This command requires --exchange pacifica");
13
- return adapter;
14
- }
7
+ import { hasPacificaSdk, isTwapCapable, isTriggerOrderCapable, isQueryOrderCapable } from "../exchanges/capabilities.js";
15
8
  export function registerTradeCommands(program, getAdapter, isJson, isDryRun = () => false, getAdapterForExchange) {
16
9
  /** Guard: if --dry-run is active, log the intended action and return without executing. */
17
10
  function dryRunGuard(action, details) {
@@ -130,7 +123,6 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
130
123
  if (isJson())
131
124
  return printJson(jsonOk(clientId ? { ...result, clientOrderId: clientId } : result));
132
125
  console.log(chalk.green(`\n Market ${s.toUpperCase()} ${size} ${symbol.toUpperCase()} placed on ${adapter.name}.${opts.smart ? " (smart)" : ""}${clientId ? ` (id: ${clientId})` : ""}\n`));
133
- printJson(jsonOk(result));
134
126
  });
135
127
  // Shortcuts: trade buy / trade sell
136
128
  trade
@@ -170,7 +162,6 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
170
162
  if (isJson())
171
163
  return printJson(jsonOk(clientId ? { ...result, clientOrderId: clientId } : result));
172
164
  console.log(chalk.green(`\n Market BUY ${size} ${symbol.toUpperCase()} placed on ${adapter.name}.${opts.smart ? " (smart)" : ""}\n`));
173
- printJson(jsonOk(result));
174
165
  });
175
166
  trade
176
167
  .command("sell <symbol> <size>")
@@ -209,7 +200,6 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
209
200
  if (isJson())
210
201
  return printJson(jsonOk(clientId ? { ...result, clientOrderId: clientId } : result));
211
202
  console.log(chalk.green(`\n Market SELL ${size} ${symbol.toUpperCase()} placed on ${adapter.name}.${opts.smart ? " (smart)" : ""}\n`));
212
- printJson(jsonOk(result));
213
203
  });
214
204
  // ── Split Order (orderbook-aware) ──
215
205
  trade
@@ -227,10 +217,9 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
227
217
  errorAndExit("Side must be 'buy' or 'sell'");
228
218
  if (isNaN(totalUsd) || totalUsd <= 0)
229
219
  errorAndExit("USD amount must be > 0");
230
- const exchange = program.opts().exchange ?? "unknown";
231
- if (dryRunGuard("split-order", { exchange, symbol: sym, side: orderSide, totalUsd, ...opts }))
232
- return;
233
220
  const adapter = await getAdapter();
221
+ if (dryRunGuard("split-order", { exchange: adapter.name, symbol: sym, side: orderSide, totalUsd, ...opts }))
222
+ return;
234
223
  const { runSplitOrder } = await import("../strategies/split-order.js");
235
224
  const result = await runSplitOrder(adapter, {
236
225
  symbol: sym,
@@ -255,7 +244,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
255
244
  trade
256
245
  .command("limit <symbol> <side> <price> <size>")
257
246
  .description("Place a limit order")
258
- .option("--tif <tif>", "Time in force: GTC, IOC, ALO, TOB", "GTC")
247
+ .option("--tif <tif>", "Time in force: GTC, IOC, ALO (HL only), TOB (HL only)", "GTC")
259
248
  .option("--reduce-only", "Reduce only order")
260
249
  .option("--client-id <id>", "Client order ID for idempotent tracking")
261
250
  .option("--auto-id", "Auto-generate a client order ID")
@@ -308,7 +297,6 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
308
297
  if (isJson())
309
298
  return printJson(jsonOk(clientId ? { ...result, clientOrderId: clientId } : result));
310
299
  console.log(chalk.green(`\n Limit ${s.toUpperCase()} ${size} ${symbol.toUpperCase()} @ $${price} placed on ${adapter.name}.${clientId ? ` (id: ${clientId})` : ""}\n`));
311
- printJson(jsonOk(result));
312
300
  });
313
301
  trade
314
302
  .command("cancel <symbolOrOrderId> [orderId]")
@@ -439,23 +427,12 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
439
427
  return;
440
428
  }
441
429
  const adapter = await getAdapter();
442
- // Lighter or any exchange without native TWAP → client-side TWAP (foreground)
443
- if (adapter instanceof LighterAdapter) {
444
- const { runTWAP } = await import("../strategies/twap.js");
445
- const result = await runTWAP(adapter, {
446
- symbol: symbol.toUpperCase(),
447
- side: s,
448
- totalSize: parseFloat(size),
449
- durationSec: parseInt(duration),
450
- slices: opts.slices ? parseInt(opts.slices) : undefined,
451
- });
452
- if (isJson())
453
- return printJson(jsonOk(result));
454
- return;
455
- }
430
+ // Native TWAP: Pacifica (SDK) or Hyperliquid (twapOrder)
431
+ // Fallback: client-side TWAP for any exchange without native support
456
432
  let result;
457
- if (adapter instanceof PacificaAdapter) {
458
- result = await adapter.sdk.createTWAP({
433
+ if (hasPacificaSdk(adapter)) {
434
+ const sdk = adapter.sdk;
435
+ result = await sdk.createTWAP({
459
436
  symbol: symbol.toUpperCase(),
460
437
  amount: size,
461
438
  side: s === "buy" ? "bid" : "ask",
@@ -464,27 +441,37 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
464
441
  duration_in_seconds: parseInt(duration),
465
442
  }, adapter.publicKey, adapter.signer);
466
443
  }
467
- else if (adapter instanceof HyperliquidAdapter) {
444
+ else if (isTwapCapable(adapter)) {
468
445
  const minutes = parseInt(duration);
469
446
  if (minutes < 5 || minutes > 1440)
470
447
  errorAndExit("HL TWAP duration must be 5-1440 minutes");
471
448
  result = await adapter.twapOrder(symbol.toUpperCase(), s, size, minutes, { reduceOnly: opts.reduceOnly });
472
449
  }
473
450
  else {
474
- errorAndExit("TWAP orders require --exchange pacifica, hyperliquid, or lighter");
451
+ // Client-side TWAP (foreground) for exchanges without native TWAP
452
+ const { runTWAP } = await import("../strategies/twap.js");
453
+ const twapResult = await runTWAP(adapter, {
454
+ symbol: symbol.toUpperCase(),
455
+ side: s,
456
+ totalSize: parseFloat(size),
457
+ durationSec: parseInt(duration),
458
+ slices: opts.slices ? parseInt(opts.slices) : undefined,
459
+ });
460
+ if (isJson())
461
+ return printJson(jsonOk(twapResult));
462
+ return;
475
463
  }
476
464
  if (isJson())
477
465
  return printJson(jsonOk(result));
478
466
  console.log(chalk.green(`\n TWAP ${s.toUpperCase()} ${size} ${symbol.toUpperCase()} over ${duration} placed on ${adapter.name}.\n`));
479
- printJson(jsonOk(result));
480
467
  });
481
468
  // === Stop / Trigger orders — Pacifica + Hyperliquid ===
482
469
  trade
483
- .command("stop <symbol> <side> <stopPrice> <size>")
470
+ .command("stop <symbol> <side> <size> <stopPrice>")
484
471
  .description("Place a stop order")
485
472
  .option("--limit-price <price>", "Limit price (makes it stop-limit)")
486
473
  .option("--reduce-only", "Reduce only order")
487
- .action(async (symbol, side, stopPrice, size, opts) => {
474
+ .action(async (symbol, side, size, stopPrice, opts) => {
488
475
  const s = side.toLowerCase();
489
476
  if (s !== "buy" && s !== "sell")
490
477
  errorAndExit("Side must be buy or sell");
@@ -503,12 +490,11 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
503
490
  if (isJson())
504
491
  return printJson(jsonOk(result));
505
492
  console.log(chalk.green(`\n Stop order placed on ${adapter.name}.\n`));
506
- printJson(jsonOk(result));
507
493
  });
508
494
  // === TP/SL — Pacifica + Hyperliquid ===
509
495
  trade
510
496
  .command("tpsl <symbol> <side>")
511
- .description("Set take-profit / stop-loss on a position")
497
+ .description("Set take-profit / stop-loss on a position (side: the side to CLOSE — buy to close short, sell to close long)")
512
498
  .option("--tp <price>", "Take profit trigger price")
513
499
  .option("--tp-limit <price>", "Take profit limit price")
514
500
  .option("--sl <price>", "Stop loss trigger price")
@@ -522,7 +508,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
522
508
  const adapter = await getAdapter();
523
509
  if (dryRunGuard("tpsl", { exchange: adapter.name, symbol: symbol.toUpperCase(), side: s, tp: opts.tp ?? "none", sl: opts.sl ?? "none" }))
524
510
  return;
525
- if (adapter instanceof PacificaAdapter) {
511
+ if (hasPacificaSdk(adapter)) {
526
512
  // TP/SL side is opposite of position: LONG position → "ask" to close
527
513
  const params = {
528
514
  symbol: symbol.toUpperCase(),
@@ -532,12 +518,13 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
532
518
  params.take_profit = { stop_price: opts.tp, limit_price: opts.tpLimit };
533
519
  if (opts.sl)
534
520
  params.stop_loss = { stop_price: opts.sl };
535
- const result = await adapter.sdk.setTPSL(params, adapter.publicKey, adapter.signer);
521
+ const sdk = adapter.sdk;
522
+ const result = await sdk.setTPSL(params, adapter.publicKey, adapter.signer);
536
523
  if (isJson())
537
524
  return printJson(jsonOk(result));
538
525
  console.log(chalk.green(`\n TP/SL set for ${symbol.toUpperCase()} on Pacifica.\n`));
539
526
  }
540
- else if (adapter instanceof HyperliquidAdapter) {
527
+ else if (isTriggerOrderCapable(adapter)) {
541
528
  // HL uses trigger orders for TP/SL
542
529
  const results = [];
543
530
  const posSize = opts.size || "0"; // 0 = full position via positionTpsl grouping
@@ -552,8 +539,8 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
552
539
  return printJson(jsonOk(results));
553
540
  console.log(chalk.green(`\n TP/SL set for ${symbol.toUpperCase()} on Hyperliquid.\n`));
554
541
  }
555
- else if (adapter instanceof LighterAdapter) {
556
- // Lighter uses triggerPrice in signCreateOrder for TP/SL
542
+ else {
543
+ // Fallback: use stopOrder for TP/SL (Lighter and others)
557
544
  const closeSide = s === "buy" ? "sell" : "buy"; // Close opposite side
558
545
  const results = [];
559
546
  if (opts.tp) {
@@ -564,17 +551,14 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
564
551
  }
565
552
  if (isJson())
566
553
  return printJson(jsonOk(results));
567
- console.log(chalk.green(`\n TP/SL set for ${symbol.toUpperCase()} on Lighter.\n`));
568
- }
569
- else {
570
- errorAndExit("TP/SL requires --exchange pacifica, hyperliquid, or lighter");
554
+ console.log(chalk.green(`\n TP/SL set for ${symbol.toUpperCase()} on ${adapter.name}.\n`));
571
555
  }
572
556
  });
573
557
  // === Scaled Take-Profit (분할익절) ===
574
558
  trade
575
559
  .command("scale-tp <symbol>")
576
560
  .description("Place multiple take-profit limit orders at different price levels (분할익절)")
577
- .requiredOption("--levels <levels>", "Comma-separated price:percent pairs (e.g., 72000:25,75000:25,80000:50)")
561
+ .requiredOption("--levels <levels>", "[required] Comma-separated price:percent pairs (e.g., 72000:25,75000:25,80000:50)")
578
562
  .option("--size <size>", "Override total position size (default: uses current position)")
579
563
  .action(async (symbol, opts) => {
580
564
  const sym = symbol.toUpperCase();
@@ -672,8 +656,10 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
672
656
  .description("Cancel a stop order (Pacifica)")
673
657
  .action(async (symbol, stopOrderId) => {
674
658
  const adapter = await getAdapter();
675
- const p = pac(adapter);
676
- const result = await p.sdk.cancelStopOrder({ symbol: symbol.toUpperCase(), order_id: Number(stopOrderId) }, p.publicKey, p.signer);
659
+ if (!hasPacificaSdk(adapter))
660
+ throw new Error("This command requires --exchange pacifica");
661
+ const sdk = adapter.sdk;
662
+ const result = await sdk.cancelStopOrder({ symbol: symbol.toUpperCase(), order_id: Number(stopOrderId) }, adapter.publicKey, adapter.signer);
677
663
  if (isJson())
678
664
  return printJson(jsonOk(result));
679
665
  console.log(chalk.green(`\n Stop order ${stopOrderId} cancelled.\n`));
@@ -684,10 +670,11 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
684
670
  .action(async (symbol, twapOrderId) => {
685
671
  const adapter = await getAdapter();
686
672
  let result;
687
- if (adapter instanceof PacificaAdapter) {
688
- result = await adapter.sdk.cancelTWAP({ symbol: symbol.toUpperCase(), twap_order_id: Number(twapOrderId) }, adapter.publicKey, adapter.signer);
673
+ if (hasPacificaSdk(adapter)) {
674
+ const sdk = adapter.sdk;
675
+ result = await sdk.cancelTWAP({ symbol: symbol.toUpperCase(), twap_order_id: Number(twapOrderId) }, adapter.publicKey, adapter.signer);
689
676
  }
690
- else if (adapter instanceof HyperliquidAdapter) {
677
+ else if (isTwapCapable(adapter)) {
691
678
  result = await adapter.twapCancel(symbol.toUpperCase(), Number(twapOrderId));
692
679
  }
693
680
  else {
@@ -903,7 +890,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
903
890
  .action(async (orderId) => {
904
891
  await withJsonErrors(isJson(), async () => {
905
892
  const adapter = await getAdapter();
906
- if (adapter instanceof HyperliquidAdapter) {
893
+ if (isQueryOrderCapable(adapter)) {
907
894
  const result = await adapter.queryOrder(Number(orderId));
908
895
  if (isJson())
909
896
  return printJson(jsonOk(result));
@@ -13,6 +13,4 @@ export declare function getWalletSetupStatus(): {
13
13
  export declare function getActiveWalletKey(exchange: string): string | null;
14
14
  /** Get wallet key by name. Returns null if wallet doesn't exist. */
15
15
  export declare function getWalletKeyByName(name: string): string | null;
16
- /** Get all wallet names. */
17
- export declare function getWalletNames(): string[];
18
16
  export declare function registerWalletCommands(program: Command, isJson: () => boolean): void;
@@ -47,11 +47,6 @@ export function getWalletKeyByName(name) {
47
47
  const store = loadStore();
48
48
  return store.wallets[name]?.privateKey ?? null;
49
49
  }
50
- /** Get all wallet names. */
51
- export function getWalletNames() {
52
- const store = loadStore();
53
- return Object.keys(store.wallets);
54
- }
55
50
  async function getSolanaBalances(address, isTestnet) {
56
51
  const { Connection, PublicKey } = await import("@solana/web3.js");
57
52
  const rpcUrl = isTestnet
@@ -1,14 +1,11 @@
1
1
  /**
2
2
  * Shared constants for perp-cli.
3
3
  */
4
- import { type ExchangeFees } from "./settings.js";
5
4
  /** Default taker fee per exchange (as fraction, e.g. 0.00035 = 0.035%) */
6
5
  export declare const TAKER_FEES: Record<string, number>;
7
6
  /** Get taker fee for an exchange. Uses settings if available, falls back to defaults. */
8
7
  export declare function getTakerFee(exchange: string): number;
9
8
  /** Get maker fee for an exchange. Uses settings if available. */
10
9
  export declare function getMakerFee(exchange: string): number;
11
- /** Get full fee info for an exchange. */
12
- export declare function getExchangeFees(exchange: string): ExchangeFees;
13
10
  /** Default taker fee for single-exchange use (when exchange is unknown). */
14
11
  export declare const DEFAULT_TAKER_FEE = 0.00035;
package/dist/constants.js CHANGED
@@ -32,9 +32,5 @@ export function getMakerFee(exchange) {
32
32
  catch { /* fallback */ }
33
33
  return 0;
34
34
  }
35
- /** Get full fee info for an exchange. */
36
- export function getExchangeFees(exchange) {
37
- return { taker: getTakerFee(exchange), maker: getMakerFee(exchange) };
38
- }
39
35
  /** Default taker fee for single-exchange use (when exchange is unknown). */
40
36
  export const DEFAULT_TAKER_FEE = 0.00035;
@@ -5,7 +5,7 @@
5
5
  * - Per-exchange margin status checking
6
6
  * - Auto-size computation based on orderbook depth and margin limits
7
7
  */
8
- import type { ExchangeAdapter } from "./exchanges/interface.js";
8
+ import type { ExchangeAdapter } from "./exchanges/index.js";
9
9
  export interface ChainMarginStatus {
10
10
  exchange: string;
11
11
  chain: string;
@@ -0,0 +1,4 @@
1
+ export { startDashboard } from "./server.js";
2
+ export type { DashboardExchange, DashboardOpts, DashboardSnapshot } from "./server.js";
3
+ export { WsFeedManager } from "./ws-feeds.js";
4
+ export type { WsFeedState, WsFeedManagerOpts } from "./ws-feeds.js";
@@ -0,0 +1,3 @@
1
+ // dashboard/ barrel — public API
2
+ export { startDashboard } from "./server.js";
3
+ export { WsFeedManager } from "./ws-feeds.js";
@@ -4,7 +4,7 @@
4
4
  * Polls all configured exchange adapters and pushes updates to connected clients.
5
5
  * Includes cross-exchange arb data: funding rate comparison + dex arb scan.
6
6
  */
7
- import type { ExchangeAdapter, ExchangeBalance, ExchangePosition, ExchangeOrder, ExchangeMarketInfo } from "../exchanges/interface.js";
7
+ import type { ExchangeAdapter, ExchangeBalance, ExchangePosition, ExchangeOrder, ExchangeMarketInfo } from "../exchanges/index.js";
8
8
  export interface DashboardExchange {
9
9
  name: string;
10
10
  adapter: ExchangeAdapter;