perp-cli 0.6.3 → 0.7.1

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 (177) 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 +5 -1
  23. package/dist/bot/config.js +1 -1
  24. package/dist/bot/engine.d.ts +23 -2
  25. package/dist/bot/engine.js +296 -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 +49 -0
  77. package/dist/bot/tui/BotDashboard.js +93 -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 +106 -75
  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 +1 -1
  91. package/dist/commands/arb.d.ts +1 -1
  92. package/dist/commands/bot.d.ts +1 -1
  93. package/dist/commands/bot.js +198 -14
  94. package/dist/commands/dashboard.d.ts +1 -1
  95. package/dist/commands/dashboard.js +1 -1
  96. package/dist/commands/funds.d.ts +1 -1
  97. package/dist/commands/funds.js +33 -20
  98. package/dist/commands/history.d.ts +1 -1
  99. package/dist/commands/history.js +5 -4
  100. package/dist/commands/manage.d.ts +2 -3
  101. package/dist/commands/manage.js +11 -18
  102. package/dist/commands/market.d.ts +1 -1
  103. package/dist/commands/market.js +7 -7
  104. package/dist/commands/plan.d.ts +1 -1
  105. package/dist/commands/rebalance.d.ts +1 -1
  106. package/dist/commands/rebalance.js +8 -9
  107. package/dist/commands/risk.d.ts +1 -1
  108. package/dist/commands/settings.d.ts +1 -1
  109. package/dist/commands/settings.js +3 -1
  110. package/dist/commands/trade.d.ts +1 -1
  111. package/dist/commands/trade.js +39 -52
  112. package/dist/commands/wallet.d.ts +0 -2
  113. package/dist/commands/wallet.js +0 -5
  114. package/dist/constants.d.ts +0 -3
  115. package/dist/constants.js +0 -4
  116. package/dist/cross-chain-margin.d.ts +1 -1
  117. package/dist/dashboard/index.d.ts +4 -0
  118. package/dist/dashboard/index.js +3 -0
  119. package/dist/dashboard/server.d.ts +1 -1
  120. package/dist/dashboard/server.js +1 -1
  121. package/dist/dashboard/ws-feeds.d.ts +1 -1
  122. package/dist/event-stream.d.ts +1 -1
  123. package/dist/exchanges/aster.d.ts +58 -0
  124. package/dist/exchanges/aster.js +409 -0
  125. package/dist/exchanges/capabilities.d.ts +74 -0
  126. package/dist/exchanges/capabilities.js +44 -0
  127. package/dist/exchanges/hyperliquid-spot.d.ts +1 -1
  128. package/dist/exchanges/hyperliquid-spot.js +20 -12
  129. package/dist/exchanges/hyperliquid.d.ts +8 -2
  130. package/dist/exchanges/hyperliquid.js +21 -6
  131. package/dist/exchanges/index.d.ts +11 -0
  132. package/dist/exchanges/index.js +10 -0
  133. package/dist/exchanges/interface.d.ts +3 -0
  134. package/dist/exchanges/lighter-spot.d.ts +13 -0
  135. package/dist/exchanges/lighter-spot.js +98 -26
  136. package/dist/exchanges/lighter.d.ts +9 -3
  137. package/dist/exchanges/lighter.js +18 -8
  138. package/dist/exchanges/nado.d.ts +52 -0
  139. package/dist/exchanges/nado.js +223 -0
  140. package/dist/exchanges/pacifica.d.ts +7 -1
  141. package/dist/exchanges/pacifica.js +17 -2
  142. package/dist/exchanges/registry.d.ts +13 -0
  143. package/dist/exchanges/registry.js +37 -0
  144. package/dist/exchanges/spot-interface.d.ts +9 -0
  145. package/dist/funding/rates.js +1 -1
  146. package/dist/index.js +2 -6
  147. package/dist/jobs.js +1 -1
  148. package/dist/liquidity.d.ts +1 -2
  149. package/dist/liquidity.js +12 -5
  150. package/dist/mcp-server.js +4 -2
  151. package/dist/pacifica/constants.d.ts +0 -3
  152. package/dist/pacifica/constants.js +0 -4
  153. package/dist/pacifica/types/account.d.ts +0 -8
  154. package/dist/pacifica/types/lake.d.ts +0 -6
  155. package/dist/pacifica/types/position.d.ts +0 -12
  156. package/dist/pacifica/types/ws.d.ts +0 -24
  157. package/dist/pacifica/types/ws.js +1 -41
  158. package/dist/plan-executor.d.ts +1 -1
  159. package/dist/rebalance.d.ts +1 -7
  160. package/dist/rebalance.js +0 -6
  161. package/dist/risk.d.ts +1 -1
  162. package/dist/risk.js +3 -1
  163. package/dist/smart-order.d.ts +1 -1
  164. package/dist/strategies/dca.d.ts +1 -1
  165. package/dist/strategies/funding-arb.d.ts +1 -1
  166. package/dist/strategies/funding-arb.js +193 -181
  167. package/dist/strategies/grid.d.ts +1 -1
  168. package/dist/strategies/grid.js +1 -1
  169. package/dist/strategies/index.d.ts +12 -0
  170. package/dist/strategies/index.js +7 -0
  171. package/dist/strategies/split-order.d.ts +1 -1
  172. package/dist/strategies/trailing-stop.d.ts +1 -1
  173. package/dist/strategies/twap.d.ts +1 -1
  174. package/dist/trade-validator.d.ts +1 -1
  175. package/package.json +7 -1
  176. package/skills/perp-cli/SKILL.md +20 -0
  177. package/skills/perp-cli/references/commands.md +22 -8
@@ -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) => [
@@ -351,7 +351,7 @@ export function registerMarketCommands(program, getAdapter, isJson, getAdapterFo
351
351
  .action(async () => {
352
352
  await withJsonErrors(isJson(), async () => {
353
353
  const adapter = await getAdapter();
354
- if (!(adapter instanceof HyperliquidAdapter)) {
354
+ if (!(isDexCapable(adapter))) {
355
355
  if (isJson())
356
356
  return printJson(jsonError("INVALID_EXCHANGE", "HIP-3 dexes are only available on Hyperliquid. Use -e hyperliquid."));
357
357
  console.error(chalk.red("\n HIP-3 dexes are only available on Hyperliquid. Use -e hyperliquid.\n"));
@@ -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,
@@ -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,7 +490,6 @@ 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
@@ -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,10 +551,7 @@ 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 (분할익절) ===
@@ -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;
@@ -318,7 +318,7 @@ export async function startDashboard(exchanges, opts = {}) {
318
318
  }, { once: true });
319
319
  }
320
320
  return new Promise((resolve) => {
321
- server.listen(port, async () => {
321
+ server.listen(port, "127.0.0.1", async () => {
322
322
  // Start WS feeds (connects to exchange WS APIs)
323
323
  await feedMgr.start();
324
324
  // Start arb REST polling (30s cycle)
@@ -5,7 +5,7 @@
5
5
  * Falls back to REST polling if WS connection fails.
6
6
  * Arb/market data stays on REST (cross-exchange aggregation, no single WS covers it).
7
7
  */
8
- import type { ExchangeBalance, ExchangePosition, ExchangeOrder } from "../exchanges/interface.js";
8
+ import type { ExchangeBalance, ExchangePosition, ExchangeOrder } from "../exchanges/index.js";
9
9
  import type { DashboardExchange } from "./server.js";
10
10
  export interface WsFeedState {
11
11
  balance: ExchangeBalance;
@@ -4,7 +4,7 @@
4
4
  * Streams normalized account events as NDJSON (one JSON per line).
5
5
  * Uses poll-based diffing for cross-exchange compatibility.
6
6
  */
7
- import type { ExchangeAdapter } from "./exchanges/interface.js";
7
+ import type { ExchangeAdapter } from "./exchanges/index.js";
8
8
  export type EventType = "position_opened" | "position_closed" | "position_updated" | "order_placed" | "order_filled" | "order_cancelled" | "balance_update" | "liquidation_warning" | "margin_call" | "heartbeat" | "error";
9
9
  export interface StreamEvent {
10
10
  type: EventType;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Aster DEX adapter — Binance Futures API compatible REST wrapper.
3
+ * Docs: https://docs.asterdex.com/product/aster-perpetuals/api/api-documentation
4
+ * Base: https://fapi.asterdex.com
5
+ */
6
+ import type { ExchangeAdapter, ExchangeMarketInfo, ExchangePosition, ExchangeOrder, ExchangeBalance, ExchangeTrade, ExchangeFundingPayment, ExchangeKline } from "./interface.js";
7
+ export declare class AsterAdapter implements ExchangeAdapter {
8
+ readonly name = "aster";
9
+ readonly chain = "bnb";
10
+ readonly aliases: readonly ["ast"];
11
+ private _apiKey;
12
+ private _apiSecret;
13
+ private _baseUrl;
14
+ private _testnet;
15
+ constructor(apiKey?: string, apiSecret?: string, testnet?: boolean);
16
+ get isReadOnly(): boolean;
17
+ init(): Promise<void>;
18
+ getMarkets(): Promise<ExchangeMarketInfo[]>;
19
+ getOrderbook(symbol: string): Promise<{
20
+ bids: [string, string][];
21
+ asks: [string, string][];
22
+ }>;
23
+ getRecentTrades(symbol: string, limit?: number): Promise<ExchangeTrade[]>;
24
+ getFundingHistory(symbol: string, limit?: number): Promise<{
25
+ time: number;
26
+ rate: string;
27
+ price: string | null;
28
+ }[]>;
29
+ getKlines(symbol: string, interval: string, startTime: number, endTime: number): Promise<ExchangeKline[]>;
30
+ getBalance(): Promise<ExchangeBalance>;
31
+ getPositions(): Promise<ExchangePosition[]>;
32
+ getOpenOrders(): Promise<ExchangeOrder[]>;
33
+ getOrderHistory(limit?: number): Promise<ExchangeOrder[]>;
34
+ getTradeHistory(limit?: number): Promise<ExchangeTrade[]>;
35
+ getFundingPayments(limit?: number): Promise<ExchangeFundingPayment[]>;
36
+ marketOrder(symbol: string, side: "buy" | "sell", size: string): Promise<unknown>;
37
+ limitOrder(symbol: string, side: "buy" | "sell", price: string, size: string, opts?: {
38
+ reduceOnly?: boolean;
39
+ tif?: string;
40
+ }): Promise<unknown>;
41
+ editOrder(symbol: string, orderId: string, price: string, size: string): Promise<unknown>;
42
+ cancelOrder(symbol: string, orderId: string): Promise<unknown>;
43
+ cancelAllOrders(symbol?: string): Promise<unknown>;
44
+ setLeverage(symbol: string, leverage: number, marginMode?: "cross" | "isolated"): Promise<unknown>;
45
+ stopOrder(symbol: string, side: "buy" | "sell", size: string, triggerPrice: string, opts?: {
46
+ limitPrice?: string;
47
+ reduceOnly?: boolean;
48
+ }): Promise<unknown>;
49
+ withdraw(amount: string, _destination: string, _opts?: {
50
+ assetId?: number;
51
+ routeType?: number;
52
+ }): Promise<unknown>;
53
+ private _sign;
54
+ private _publicGet;
55
+ private _signedGet;
56
+ private _signedPost;
57
+ private _signedDelete;
58
+ }