perp-cli 0.7.6 → 0.7.8

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.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![npm downloads](https://img.shields.io/npm/dw/perp-cli.svg)](https://www.npmjs.com/package/perp-cli)
5
5
  [![license](https://img.shields.io/npm/l/perp-cli.svg)](https://github.com/hypurrquant/perp-cli/blob/main/LICENSE)
6
6
 
7
- Multi-DEX perpetual futures CLI — **Pacifica** (Solana), **Hyperliquid** (HyperEVM), **Lighter** (Ethereum).
7
+ Multi-DEX perpetual futures CLI — **Pacifica** (Solana), **Hyperliquid** (HyperEVM), **Lighter** (Ethereum), **Aster** (BNB Chain).
8
8
 
9
9
  ```bash
10
10
  npm install -g perp-cli # global install
@@ -16,7 +16,7 @@ npx -y perp-cli --json portfolio
16
16
 
17
17
  ## Features
18
18
 
19
- - **3 Exchanges** — trade, bridge, arbitrage across Pacifica, Hyperliquid, Lighter
19
+ - **4 Exchanges** — trade, bridge, arbitrage across Pacifica, Hyperliquid, Lighter, Aster
20
20
  - **Funding Rate Arb** — perp-perp + spot-perp scan & one-command dual-leg execution
21
21
  - **Portfolio** — single call returns balances, positions, risk level across all exchanges
22
22
  - **Funds** — deposit, withdraw, CCTP bridge, internal transfer in one group
@@ -28,9 +28,13 @@ npx -y perp-cli --json portfolio
28
28
 
29
29
  ```bash
30
30
  # Set exchange keys
31
- perp wallet set hl <EVM_KEY> # Hyperliquid
32
31
  perp wallet set pac <SOLANA_KEY> # Pacifica
32
+ perp wallet set hl <EVM_KEY> # Hyperliquid
33
33
  perp wallet set lt <EVM_KEY> # Lighter (API key auto-generated)
34
+ perp wallet set aster <API_KEY> # Aster (BNB Chain)
35
+
36
+ # Or interactive setup wizard
37
+ perp setup
34
38
 
35
39
  # Verify
36
40
  perp wallet show
@@ -64,6 +68,8 @@ Same EVM key works for both Hyperliquid and Lighter.
64
68
  | `jobs` | Background job management (tmux) |
65
69
  | `alerts` | Telegram funding rate alerts with background daemon |
66
70
  | `agent` | Schema introspection, capabilities, health check |
71
+ | `setup` | Interactive setup wizard (alias: `init`) |
72
+ | `status` | Unified dashboard: balances, positions, arb opps |
67
73
 
68
74
  ## Core Commands
69
75
 
@@ -159,7 +165,7 @@ perp alerts stop # stop background daemon
159
165
 
160
166
  Setup flow: BotFather token → bot validation → send `/start` to bot → auto-detect chat ID → test message.
161
167
 
162
- Exchange flag: `-e hyperliquid` / `-e pacifica` / `-e lighter` (aliases: `hl`, `pac`, `lt`).
168
+ Exchange flag: `-e hyperliquid` / `-e pacifica` / `-e lighter` / `-e aster` (aliases: `hl`, `pac`, `lt`).
163
169
 
164
170
  Global flags: `--json`, `--fields <f>`, `--ndjson`, `--dry-run`, `--dex <name>` (HIP-3), `-w, --wallet <name>`.
165
171
 
@@ -186,8 +192,10 @@ Optional: add keys for trading and account data:
186
192
  ```json
187
193
  {
188
194
  "env": {
195
+ "PACIFICA_PRIVATE_KEY": "your-solana-key",
189
196
  "HYPERLIQUID_PRIVATE_KEY": "your-evm-key",
190
- "PACIFICA_PRIVATE_KEY": "your-solana-key"
197
+ "LIGHTER_PRIVATE_KEY": "your-evm-key",
198
+ "ASTER_API_KEY": "your-api-key"
191
199
  }
192
200
  }
193
201
  ```
@@ -532,6 +532,17 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
532
532
  .command("example")
533
533
  .description("Print example YAML bot configs")
534
534
  .action(() => {
535
+ if (isJson()) {
536
+ printJson(jsonOk({
537
+ examples: [
538
+ { name: "eth-grid-bot", type: "grid", file: "~/.perp/bots/eth-grid.yaml", config: { exchange: "hyperliquid", symbol: "ETH", strategy: { type: "grid", grids: 15, size: 0.1, side: "neutral", range_mode: "auto", range_pct: 3, rebalance: true, leverage: 5 }, entry_conditions: [{ type: "volatility_below", value: 5 }], exit_conditions: [{ type: "volatility_above", value: 10 }, { type: "time_after", value: 86400 }], risk: { max_drawdown: 50, max_daily_loss: 30, pause_after_loss_sec: 300 } } },
539
+ { name: "eth-dca-dip", type: "dca", file: "~/.perp/bots/eth-dca.yaml", config: { exchange: "hyperliquid", symbol: "ETH", strategy: { type: "dca", amount: 0.01, interval_sec: 3600, total_orders: 24 }, entry_conditions: [{ type: "price_below", value: 2500 }], exit_conditions: [{ type: "price_above", value: 2800 }], risk: { max_drawdown: 100, max_daily_loss: 50 } } },
540
+ { name: "funding-arb", type: "funding-arb", file: "~/.perp/bots/funding-arb.yaml", config: { exchange: "hyperliquid", symbol: "ETH", strategy: { type: "funding-arb", min_spread: 20, close_spread: 5, size_usd: 100, max_positions: 3, exchanges: ["pacifica", "hyperliquid"] }, entry_conditions: [{ type: "always", value: 0 }], risk: { max_drawdown: 200, max_daily_loss: 50 } } },
541
+ ],
542
+ usage: ["perp bot start ~/.perp/bots/eth-grid.yaml", "perp bot start ~/.perp/bots/eth-grid.yaml --background"],
543
+ }));
544
+ return;
545
+ }
535
546
  console.log(chalk.cyan.bold("\n Example Bot Configs\n"));
536
547
  console.log(chalk.white.bold(" 1. Grid Bot (auto range)"));
537
548
  console.log(chalk.gray(` Save to ~/.perp/bots/eth-grid.yaml:\n`));
@@ -181,7 +181,7 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork) {
181
181
  pk = await loadPrivateKey("hyperliquid");
182
182
  }
183
183
  catch (err) {
184
- throw new Error(`Private key not configured for Hyperliquid. Run: perp init`);
184
+ throw new Error(`Private key not configured for Hyperliquid. Run: perp setup`);
185
185
  }
186
186
  const wallet = new ethers.Wallet(pk);
187
187
  try {
@@ -31,7 +31,7 @@ function writeEnvFile(env) {
31
31
  ensurePerpDir();
32
32
  const lines = [
33
33
  "# perp-cli configuration",
34
- "# Generated by 'perp init' — edit freely",
34
+ "# Generated by 'perp setup' — edit freely",
35
35
  "",
36
36
  ];
37
37
  for (const [key, value] of Object.entries(env)) {
@@ -109,9 +109,10 @@ export async function validateKey(chain, key) {
109
109
  }
110
110
  // ── Commands ──────────────────────────────────────────────────
111
111
  export function registerInitCommand(program) {
112
- // ── perp init (interactive) ──
112
+ // ── perp setup (interactive) ──
113
113
  program
114
- .command("init")
114
+ .command("setup")
115
+ .alias("init")
115
116
  .description("Interactive setup wizard — configure keys & default exchange")
116
117
  .action(async () => {
117
118
  const rl = createInterface({ input: process.stdin, output: process.stdout });
@@ -4,10 +4,21 @@ import { loadRiskLimits, saveRiskLimits, assessRisk, getLiquidationDistances } f
4
4
  import { pingPacifica, pingHyperliquid, pingLighter } from "../shared-api.js";
5
5
  /** Run health check and return results + render. Exported for use by status --health. */
6
6
  export async function runHealthCheck(isJson) {
7
- const [pacPing, hlPing, ltPing] = await Promise.all([
7
+ const pingAster = async () => {
8
+ const start = Date.now();
9
+ try {
10
+ const res = await fetch("https://fapi.asterdex.com/fapi/v1/time");
11
+ return { ok: res.ok, latencyMs: Date.now() - start, status: res.status };
12
+ }
13
+ catch {
14
+ return { ok: false, latencyMs: Date.now() - start, status: 0 };
15
+ }
16
+ };
17
+ const [pacPing, hlPing, ltPing, astPing] = await Promise.all([
8
18
  pingPacifica(),
9
19
  pingHyperliquid(),
10
20
  pingLighter(),
21
+ pingAster(),
11
22
  ]);
12
23
  const toPingResult = (name, p) => ({
13
24
  exchange: name,
@@ -19,6 +30,7 @@ export async function runHealthCheck(isJson) {
19
30
  toPingResult("pacifica", pacPing),
20
31
  toPingResult("hyperliquid", hlPing),
21
32
  toPingResult("lighter", ltPing),
33
+ toPingResult("aster", astPing),
22
34
  ];
23
35
  const allOk = checks.every(c => c.status === "ok");
24
36
  if (isJson()) {
@@ -115,7 +115,7 @@ export function registerSettingsCommands(program, isJson, getAdapterForExchange)
115
115
  .action(async (opts) => {
116
116
  await withJsonErrors(isJson(), async () => {
117
117
  if (!getAdapterForExchange) {
118
- console.error(chalk.red(" Fee sync requires exchange adapters. Set up keys first with `perp init`."));
118
+ console.error(chalk.red(" Fee sync requires exchange adapters. Set up keys first with `perp setup`."));
119
119
  return;
120
120
  }
121
121
  const s = loadSettings();
@@ -284,7 +284,7 @@ export function registerSettingsCommands(program, isJson, getAdapterForExchange)
284
284
  console.log(chalk.cyan.bold("\n perp-cli Configuration\n"));
285
285
  console.log(` File: ${chalk.gray(ENV_FILE)}\n`);
286
286
  if (results.length === 0) {
287
- console.log(chalk.gray(" No keys configured. Run 'perp init' or 'perp settings env set <exchange> <key>'\n"));
287
+ console.log(chalk.gray(" No keys configured. Run 'perp setup' or 'perp settings env set <exchange> <key>'\n"));
288
288
  return;
289
289
  }
290
290
  for (const { name, address, source } of results) {
@@ -347,7 +347,7 @@ export function registerSettingsCommands(program, isJson, getAdapterForExchange)
347
347
  delete env[envKey];
348
348
  // Rewrite file
349
349
  const { writeFileSync } = await import("fs");
350
- const lines = ["# perp-cli configuration", "# Generated by 'perp init' — edit freely", ""];
350
+ const lines = ["# perp-cli configuration", "# Generated by 'perp setup' — edit freely", ""];
351
351
  for (const [k, v] of Object.entries(env))
352
352
  lines.push(`${k}=${v}`);
353
353
  lines.push("");
@@ -464,7 +464,7 @@ export function registerWalletCommands(program, isJson) {
464
464
  console.log(chalk.cyan.bold("\n Configured Wallets\n"));
465
465
  if (results.length === 0) {
466
466
  console.log(chalk.gray(" No keys configured."));
467
- console.log(chalk.gray(` Run ${chalk.cyan("perp init")} or ${chalk.cyan("perp wallet set <exchange> <key>")}\n`));
467
+ console.log(chalk.gray(` Run ${chalk.cyan("perp setup")} or ${chalk.cyan("perp wallet set <exchange> <key>")}\n`));
468
468
  return;
469
469
  }
470
470
  for (const { name, address, source } of results) {
@@ -579,9 +579,46 @@ export function registerWalletCommands(program, isJson) {
579
579
  const activeEntries = Object.entries(store.active)
580
580
  .map(([ex, wn]) => ({ exchange: ex, ...store.wallets[wn] }))
581
581
  .filter((e) => e.address);
582
+ // Fallback to .env-based wallets when no named wallets are active
582
583
  if (activeEntries.length === 0) {
583
- console.error(chalk.gray("\n No active wallets. Use 'perp wallet use <name>' to set one.\n"));
584
- process.exit(1);
584
+ const stored = loadEnvFile();
585
+ const envEntries = [];
586
+ for (const [exchange, info] of Object.entries(EXCHANGE_ENV_MAP)) {
587
+ if (info.chain === "apikey")
588
+ continue; // API key exchanges don't have on-chain addresses
589
+ const key = stored[info.envKey] || process.env[info.envKey];
590
+ if (key) {
591
+ const { valid, address } = await validateKey(info.chain, key);
592
+ if (valid)
593
+ envEntries.push({ exchange, chain: info.chain, address });
594
+ }
595
+ }
596
+ if (envEntries.length === 0) {
597
+ if (isJson()) {
598
+ printJson({ ok: false, error: { code: "INVALID_PARAMS", message: "No wallets configured. Run 'perp setup' or 'perp wallet set <exchange> <key>'." }, meta: { timestamp: new Date().toISOString() } });
599
+ }
600
+ else {
601
+ console.error(chalk.gray("\n No wallets configured. Run 'perp setup' or 'perp wallet set <exchange> <key>'.\n"));
602
+ }
603
+ process.exit(1);
604
+ }
605
+ for (const we of envEntries) {
606
+ if (!isJson())
607
+ console.log(chalk.cyan.bold(`\n ${we.exchange} (${we.address.slice(0, 8)}...)`));
608
+ const balances = we.chain === "solana"
609
+ ? await getSolanaBalances(we.address, opts.testnet)
610
+ : await getEvmBalances(we.address, opts.testnet);
611
+ if (isJson()) {
612
+ printJson(jsonOk({ exchange: we.exchange, address: we.address, balances }));
613
+ continue;
614
+ }
615
+ const rows = balances.map((b) => [
616
+ chalk.white.bold(b.token), b.balance, b.usdValue || chalk.gray("-"),
617
+ ]);
618
+ console.log(makeTable(["Token", "Balance", "USD Value"], rows));
619
+ }
620
+ console.log();
621
+ return;
585
622
  }
586
623
  for (const ae of activeEntries) {
587
624
  if (!isJson())
package/dist/config.js CHANGED
@@ -57,7 +57,7 @@ export async function loadPrivateKey(exchange, pkOverride, walletName) {
57
57
  return readFileSync(keyFile, "utf-8").trim();
58
58
  }
59
59
  throw new Error(`No private key configured for ${exchange}.\n\n` +
60
- ` Quick start: ${chalk.cyan("perp init")}\n\n` +
60
+ ` Quick start: ${chalk.cyan("perp setup")}\n\n` +
61
61
  ` Or manually:\n` +
62
62
  ` perp wallet set ${exchange} <key>\n` +
63
63
  ` ${envMap[exchange][0]}=<key> (env var)\n` +
@@ -253,7 +253,7 @@ export class HyperliquidAdapter {
253
253
  }
254
254
  ensureAddress() {
255
255
  if (!this._address) {
256
- throw new Error("No private key configured — account data unavailable. Run: perp init");
256
+ throw new Error("No private key configured — account data unavailable. Run: perp setup");
257
257
  }
258
258
  }
259
259
  /** Always fetches live clearinghouseState, writes result to shared cache for dashboard */
@@ -447,7 +447,7 @@ export class HyperliquidAdapter {
447
447
  */
448
448
  ensureSigner() {
449
449
  if (!this._evmSigner) {
450
- throw new Error("No private key configured. Run: perp init");
450
+ throw new Error("No private key configured. Run: perp setup");
451
451
  }
452
452
  }
453
453
  /**
@@ -317,7 +317,7 @@ export class LighterAdapter {
317
317
  }
318
318
  async getBalance() {
319
319
  if (!this._address)
320
- throw new Error("No private key configured — account data unavailable. Run: perp init");
320
+ throw new Error("No private key configured — account data unavailable. Run: perp setup");
321
321
  const acct = await this.fetchAccount();
322
322
  if (!acct)
323
323
  return { equity: "0", available: "0", marginUsed: "0", unrealizedPnl: "0" };
@@ -334,7 +334,7 @@ export class LighterAdapter {
334
334
  }
335
335
  async getPositions() {
336
336
  if (!this._address)
337
- throw new Error("No private key configured — account data unavailable. Run: perp init");
337
+ throw new Error("No private key configured — account data unavailable. Run: perp setup");
338
338
  const acct = await this.fetchAccount();
339
339
  if (!acct)
340
340
  return [];
@@ -380,7 +380,7 @@ export class LighterAdapter {
380
380
  }
381
381
  async getOpenOrders() {
382
382
  if (!this._address)
383
- throw new Error("No private key configured — account data unavailable. Run: perp init");
383
+ throw new Error("No private key configured — account data unavailable. Run: perp setup");
384
384
  if (this._accountIndex < 0 || this._readOnly)
385
385
  return [];
386
386
  try {
@@ -648,7 +648,7 @@ export class LighterAdapter {
648
648
  }
649
649
  async getOrderHistory(limit = 30) {
650
650
  if (!this._address)
651
- throw new Error("No private key configured — account data unavailable. Run: perp init");
651
+ throw new Error("No private key configured — account data unavailable. Run: perp setup");
652
652
  const raw = await this._getOrderHistoryRaw();
653
653
  const orders = (raw.orders ?? []);
654
654
  return orders.slice(0, limit).map((o) => ({
@@ -664,7 +664,7 @@ export class LighterAdapter {
664
664
  }
665
665
  async getTradeHistory(limit = 30) {
666
666
  if (!this._address)
667
- throw new Error("No private key configured — account data unavailable. Run: perp init");
667
+ throw new Error("No private key configured — account data unavailable. Run: perp setup");
668
668
  const raw = await this._getTradeHistoryRaw(limit);
669
669
  const trades = (raw.trades ?? []);
670
670
  return trades.slice(0, limit).map((t) => ({
@@ -678,7 +678,7 @@ export class LighterAdapter {
678
678
  }
679
679
  async getFundingPayments(limit = 200) {
680
680
  if (!this._address)
681
- throw new Error("No private key configured — account data unavailable. Run: perp init");
681
+ throw new Error("No private key configured — account data unavailable. Run: perp setup");
682
682
  const raw = await this._getPositionFundingRaw();
683
683
  const items = (raw.funding ?? []);
684
684
  return items.slice(0, limit).map((f) => ({
@@ -1027,7 +1027,7 @@ export class LighterAdapter {
1027
1027
  */
1028
1028
  async setupApiKey(apiKeyIndex = 4) {
1029
1029
  if (!this._evmSigner) {
1030
- throw new Error("No EVM private key configured. Run: perp init");
1030
+ throw new Error("No EVM private key configured. Run: perp setup");
1031
1031
  }
1032
1032
  if (this._accountIndex < 0) {
1033
1033
  throw new Error("Account index not available. Call init() first or set LIGHTER_ACCOUNT_INDEX.");
@@ -19,7 +19,7 @@ export class PacificaAdapter {
19
19
  }
20
20
  ensureSigner() {
21
21
  if (!this._hasRealKey) {
22
- throw new Error("No private key configured. Run: perp init");
22
+ throw new Error("No private key configured. Run: perp setup");
23
23
  }
24
24
  }
25
25
  /** Inject an external Solana signer. */
@@ -80,7 +80,7 @@ export class PacificaAdapter {
80
80
  }
81
81
  async getBalance() {
82
82
  if (!this._hasRealKey)
83
- throw new Error("No private key configured — account data unavailable. Run: perp init");
83
+ throw new Error("No private key configured — account data unavailable. Run: perp setup");
84
84
  const [info, positions, prices] = await Promise.all([
85
85
  this.client.getAccount(this.account),
86
86
  this._getPositions(),
@@ -110,7 +110,7 @@ export class PacificaAdapter {
110
110
  }
111
111
  async getPositions() {
112
112
  if (!this._hasRealKey)
113
- throw new Error("No private key configured — account data unavailable. Run: perp init");
113
+ throw new Error("No private key configured — account data unavailable. Run: perp setup");
114
114
  const [positions, prices] = await Promise.all([
115
115
  this._getPositions(),
116
116
  this._getPrices(),
@@ -152,7 +152,7 @@ export class PacificaAdapter {
152
152
  }
153
153
  async getOpenOrders() {
154
154
  if (!this._hasRealKey)
155
- throw new Error("No private key configured — account data unavailable. Run: perp init");
155
+ throw new Error("No private key configured — account data unavailable. Run: perp setup");
156
156
  const orders = await this.client.getOrders(this.account);
157
157
  return orders.map((o) => {
158
158
  const raw = o;
@@ -264,7 +264,7 @@ export class PacificaAdapter {
264
264
  }
265
265
  async getOrderHistory(limit = 30) {
266
266
  if (!this._hasRealKey)
267
- throw new Error("No private key configured — account data unavailable. Run: perp init");
267
+ throw new Error("No private key configured — account data unavailable. Run: perp setup");
268
268
  const result = await this.client.getOrderHistory(this.account);
269
269
  const data = (result.data ?? result.orders ?? []);
270
270
  if (!Array.isArray(data))
@@ -282,7 +282,7 @@ export class PacificaAdapter {
282
282
  }
283
283
  async getTradeHistory(limit = 30) {
284
284
  if (!this._hasRealKey)
285
- throw new Error("No private key configured — account data unavailable. Run: perp init");
285
+ throw new Error("No private key configured — account data unavailable. Run: perp setup");
286
286
  const raw = await this.client.getTradeHistory(this.account);
287
287
  const trades = (raw.data ?? raw);
288
288
  if (!Array.isArray(trades))
@@ -298,7 +298,7 @@ export class PacificaAdapter {
298
298
  }
299
299
  async getFundingPayments(limit = 200) {
300
300
  if (!this._hasRealKey)
301
- throw new Error("No private key configured — account data unavailable. Run: perp init");
301
+ throw new Error("No private key configured — account data unavailable. Run: perp setup");
302
302
  const raw = await this.client.getFundingAccountHistory(this.account);
303
303
  const history = (raw.data ?? raw);
304
304
  if (!Array.isArray(history))
package/dist/index.js CHANGED
@@ -646,12 +646,12 @@ if (rawArgs.length === 0 || (!hasSubcommand && !rawArgs.includes("-h") && !rawAr
646
646
  if (!status.hasWallets && !hasEnvKey && !settings.defaultExchange) {
647
647
  // Fresh install — onboarding
648
648
  console.log(chalk.yellow.bold("\n ⚡ Get started:\n"));
649
- console.log(` ${chalk.cyan("perp wallet set <exchange> <key>")}`);
650
- console.log(chalk.gray(`\n Or explore without a wallet:`));
651
- console.log(` ${chalk.green("perp market list")} available markets`);
652
- console.log(` ${chalk.green("perp -e hl market list")} Hyperliquid markets`);
653
- console.log(` ${chalk.green("perp arb scan")} funding rate arbitrage`);
654
- console.log(` ${chalk.green("perp --help")} all commands\n`);
649
+ console.log(` ${chalk.cyan("perp setup")} setup wizard`);
650
+ console.log(chalk.gray(`\n Explore without a wallet:`));
651
+ console.log(` ${chalk.green("perp market mid BTC")} quick price check`);
652
+ console.log(` ${chalk.green("perp arb scan")} funding rate arbitrage`);
653
+ console.log(` ${chalk.green("perp market list")} available markets`);
654
+ console.log(` ${chalk.green("perp --help")} all commands\n`);
655
655
  }
656
656
  else {
657
657
  // Configured — show exchange status + balance
@@ -721,12 +721,13 @@ if (rawArgs.length === 0 || (!hasSubcommand && !rawArgs.includes("-h") && !rawAr
721
721
  console.log(chalk.gray(" ─".repeat(20)));
722
722
  console.log(` ${chalk.white.bold("Total".padEnd(16))} ${chalk.white.bold(`$${totalEquity.toLocaleString("en", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`)}${totalPositions > 0 ? chalk.yellow(` ${totalPositions} pos`) : ""}`);
723
723
  }
724
- console.log(chalk.white.bold("\n Commands:"));
725
- console.log(` ${chalk.green("perp portfolio")} balances + positions + risk`);
726
- console.log(` ${chalk.green("perp status")} full dashboard`);
727
- console.log(` ${chalk.green("perp arb scan")} funding rate arbitrage`);
728
- console.log(` ${chalk.green("perp dashboard")} live web monitoring`);
729
- console.log(` ${chalk.green("perp --help")} all commands\n`);
724
+ console.log(chalk.white.bold("\n Quick:"));
725
+ console.log(` ${chalk.green("perp portfolio")} balances + positions + risk`);
726
+ console.log(` ${chalk.green("perp arb scan")} funding arbitrage opportunities`);
727
+ console.log(` ${chalk.green("perp arb status")} open arb positions + PnL`);
728
+ console.log(` ${chalk.green("perp status")} full dashboard`);
729
+ console.log(` ${chalk.green("perp dashboard")} live web monitoring`);
730
+ console.log(` ${chalk.green("perp --help")} all commands\n`);
730
731
  }
731
732
  }
732
733
  catch {
@@ -27,7 +27,7 @@ async function getOrCreateAdapter(exchange) {
27
27
  return adapters.get(key);
28
28
  const pk = await tryLoadPrivateKey(key);
29
29
  if (!pk)
30
- throw new Error(`No private key configured for ${exchange}. Run: perp init`);
30
+ throw new Error(`No private key configured for ${exchange}. Run: perp setup`);
31
31
  let adapter;
32
32
  switch (key) {
33
33
  case "pacifica": {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "perp-cli",
3
- "version": "0.7.6",
4
- "description": "Multi-DEX Perpetual Futures CLI - Pacifica, Hyperliquid, Lighter",
3
+ "version": "0.7.8",
4
+ "description": "Multi-DEX Perpetual Futures CLI - Pacifica, Hyperliquid, Lighter, Aster",
5
5
  "bin": {
6
6
  "perp": "dist/index.js",
7
7
  "perp-mcp": "dist/mcp-server.js"
@@ -1,16 +1,16 @@
1
1
  ---
2
2
  name: perp-cli
3
- description: "Multi-DEX perpetual futures trading CLI for Pacifica (Solana), Hyperliquid (EVM), and Lighter (Ethereum). Use when user asks to: trade perps, check funding rates, scan arbitrage (perp-perp or spot-perp), delta-neutral strategies, bridge USDC, manage positions/orders, deposit/withdraw, spot+perp hedge, HIP-3 DEX trading, or mentions perp-cli, hypurrquant, Pacifica, Hyperliquid, Lighter, HyperEVM, funding arb, U-token (UBTC/UETH/USOL)."
3
+ description: "Multi-DEX perpetual futures trading CLI for Pacifica (Solana), Hyperliquid (EVM), Lighter (Ethereum), and Aster (BNB Chain). Use when user asks to: trade perps, check funding rates, scan arbitrage (perp-perp or spot-perp), delta-neutral strategies, bridge USDC, manage positions/orders, deposit/withdraw, spot+perp hedge, HIP-3 DEX trading, or mentions perp-cli, hypurrquant, Pacifica, Hyperliquid, Lighter, Aster, HyperEVM, funding arb, U-token (UBTC/UETH/USOL)."
4
4
  allowed-tools: "Bash(perp:*), Bash(npx perp-cli:*), Bash(npx -y perp-cli:*)"
5
5
  license: MIT
6
6
  metadata:
7
7
  author: hypurrquant
8
- version: "0.6.1"
8
+ version: "0.7.7"
9
9
  ---
10
10
 
11
11
  # perp-cli Agent Guide
12
12
 
13
- Multi-DEX perpetual futures CLI + MCP server — Pacifica (Solana), Hyperliquid (HyperEVM), Lighter (Ethereum).
13
+ Multi-DEX perpetual futures CLI + MCP server — Pacifica (Solana), Hyperliquid (HyperEVM), Lighter (Ethereum), Aster (BNB Chain).
14
14
 
15
15
  [![Glama MCP server](https://glama.ai/mcp/servers/hypurrquant/perp-cli/badges/score.svg)](https://glama.ai/mcp/servers/hypurrquant/perp-cli)
16
16
 
@@ -34,14 +34,14 @@ Multi-DEX perpetual futures CLI + MCP server — Pacifica (Solana), Hyperliquid
34
34
  1. **Always use `--json`** on every command.
35
35
  2. **Always use `--dry-run`** before any mutating trade (then execute without it after user confirms).
36
36
  3. **Always use `--fields`** to reduce output when you only need specific data (saves tokens).
37
- 4. **NEVER use `perp init`** — interactive, will hang.
37
+ 4. **NEVER use `perp setup`** (alias: `perp init`) — interactive, will hang.
38
38
  5. **NEVER trade without user confirmation.**
39
39
  6. **NEVER read ~/.perp/.env or key files.**
40
40
 
41
41
  ## Install
42
42
 
43
43
  ```bash
44
- perp --version 2>/dev/null # check if exists (must be >= 0.6.1)
44
+ perp --version 2>/dev/null # check if exists (must be >= 0.7.7)
45
45
  npm install -g perp-cli@latest 2>/dev/null || npx -y perp-cli@latest --json --version
46
46
  ```
47
47
 
@@ -49,14 +49,15 @@ Use `perp` if global install works, otherwise `npx -y perp-cli@latest` as prefix
49
49
 
50
50
  ## Global Flags
51
51
 
52
- `--json` (required) | `-e hl/pac/lt` (exchange) | `--dex <name>` (HIP-3) | `-w <wallet>` | `--dry-run` | `--fields <f1,f2>` | `--ndjson`
52
+ `--json` (required) | `-e hl/pac/lt/aster` (exchange) | `--dex <name>` (HIP-3) | `-w <wallet>` | `--dry-run` | `--fields <f1,f2>` | `--ndjson`
53
53
 
54
54
  ## Wallet Setup
55
55
 
56
56
  ```bash
57
- perp --json wallet set hl <EVM_KEY> # Hyperliquid
58
57
  perp --json wallet set pac <SOLANA_KEY> # Pacifica
58
+ perp --json wallet set hl <EVM_KEY> # Hyperliquid
59
59
  perp --json wallet set lt <EVM_KEY> # Lighter (API key auto-generated)
60
+ perp --json wallet set aster <API_KEY> # Aster (BNB Chain)
60
61
  perp --json wallet show # verify
61
62
  ```
62
63
 
@@ -96,7 +97,7 @@ perp --json arb exec BTC spot:lt pac 50 # spot buy(LT) + perp short(PAC)
96
97
  perp --json arb close ETH # sells spot + buys back perp
97
98
  ```
98
99
 
99
- Spot exchanges: HL, LT. Pacifica is perp-only.
100
+ Spot exchanges: HL, LT. Pacifica and Aster are perp-only.
100
101
 
101
102
  ## Bot Engine (19 Strategies)
102
103
 
@@ -114,7 +115,7 @@ perp bot quick-dca <symbol> <side> <amount> <interval>
114
115
  perp bot quick-arb # quick funding arb
115
116
  ```
116
117
 
117
- **Strategies:** simple-mm, engine-mm, avellaneda-mm, regime-mm, grid-mm, liquidation-mm, momentum-breakout, mean-reversion, aggressive-taker, funding-arb, funding-auto, basis-arb, hedge-agent, rfq-agent, claude-agent, grid, dca, twap, apex
118
+ **Strategies:** grid, dca, funding-arb, funding-arb-v2, funding-auto, basis-arb, simple-mm, engine-mm, avellaneda-mm, regime-mm, grid-mm, liquidation-mm, momentum-breakout, mean-reversion, aggressive-taker, hedge-agent, rfq-agent, claude-agent, apex
118
119
 
119
120
  **Flags:** `--dry-run` (simulate without executing), `--headless` (no TUI, log only)
120
121
 
@@ -6,8 +6,7 @@ Complete reference for non-interactive CLI operations. Every command here is saf
6
6
 
7
7
  ### NEVER use these (interactive, will hang):
8
8
  ```
9
- perp init # interactive wizard — asks questions via stdin
10
- perp wallet setup # removed, but may appear in old docs
9
+ perp setup # interactive wizard — asks questions via stdin (alias: perp init)
11
10
  ```
12
11
 
13
12
  ### ALWAYS use these (non-interactive, agent-safe):
@@ -101,14 +100,16 @@ perp --json wallet set lt 0xKEY # same key, different exchange binding
101
100
 
102
101
  | Exchange | Chain | Key Format | Example |
103
102
  |----------|-------|-----------|---------|
103
+ | Pacifica | Solana | Base58 string | `5KQwrPbwdL6PhXu...` |
104
104
  | Hyperliquid | EVM | Hex with 0x prefix, 66 chars | `0x4c0883a69102937d...` |
105
105
  | Lighter | EVM | Hex with 0x prefix, 66 chars | `0x4c0883a69102937d...` |
106
- | Pacifica | Solana | Base58 string | `5KQwrPbwdL6PhXu...` |
106
+ | Aster | API Key | Hex string | `3971f62b...` |
107
107
 
108
108
  Aliases for exchange names:
109
- - `hl` or `hyperliquid`
110
109
  - `pac` or `pacifica`
110
+ - `hl` or `hyperliquid`
111
111
  - `lt` or `lighter`
112
+ - `aster`
112
113
 
113
114
  ## Deposit & Withdraw Flows
114
115
 
@@ -256,11 +257,11 @@ Error case:
256
257
 
257
258
  Symbols are auto-resolved by the CLI. **Always use bare symbols** (e.g., `BTC`, `SOL`, `ICP`) — the CLI handles exchange-specific naming automatically:
258
259
 
259
- | Input | Hyperliquid | Pacifica | Lighter |
260
- |-------|-------------|----------|---------|
261
- | `ICP` | → `ICP-PERP` | → `ICP` | → `ICP` |
262
- | `BTC` | → `BTC` | → `BTC` | → `BTC` |
263
- | `SOL` | → `SOL` | → `SOL` | → `SOL` |
260
+ | Input | Pacifica | Hyperliquid | Lighter | Aster |
261
+ |-------|----------|-------------|---------|-------|
262
+ | `ICP` | → `ICP` | → `ICP-PERP` | → `ICP` | → `ICP` |
263
+ | `BTC` | → `BTC` | → `BTC` | → `BTC` | → `BTC` |
264
+ | `SOL` | → `SOL` | → `SOL` | → `SOL` | → `SOL` |
264
265
 
265
266
  - `arb scan` returns bare symbols — pass them directly to trade/leverage commands on any exchange.
266
267
  - Do NOT manually add `-PERP` suffix — the CLI resolves this automatically.
@@ -269,15 +270,16 @@ Symbols are auto-resolved by the CLI. **Always use bare symbols** (e.g., `BTC`,
269
270
 
270
271
  | Exchange | Min Order (notional) | Notes |
271
272
  |----------|---------------------|-------|
272
- | Hyperliquid | **$10** | Rejects orders below $10 notional |
273
273
  | Pacifica | ~$1 (varies by symbol) | Lower minimums |
274
+ | Hyperliquid | **$10** | Rejects orders below $10 notional |
274
275
  | Lighter | Varies by symbol | Check market info |
276
+ | Aster | Varies by symbol | BNB Chain, API key based |
275
277
 
276
278
  **`trade check` is advisory only** — it returns `valid: true/false` but does NOT block execution. The exchange itself enforces minimums and will reject with an error if the order is too small.
277
279
 
278
280
  ## Common Agent Mistakes
279
281
 
280
- 1. **Using `perp init`** — interactive, will hang forever. Use `wallet set` instead.
282
+ 1. **Using `perp setup`** (alias: `perp init`) — interactive, will hang forever. Use `wallet set` instead.
281
283
  2. **Forgetting `--json`** — output becomes unparseable human text.
282
284
  3. **Trading with zero balance** — check `account balance` first, tell user to deposit.
283
285
  4. **Retrying a trade without checking** — leads to double positions. Always check `account positions` after a trade, even if it seemed to fail.