perp-cli 0.7.7 → 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 +13 -5
- package/dist/commands/bot.js +11 -0
- package/dist/commands/funds.js +1 -1
- package/dist/commands/init.js +4 -3
- package/dist/commands/settings.js +3 -3
- package/dist/commands/wallet.js +40 -3
- package/dist/config.js +1 -1
- package/dist/exchanges/hyperliquid.js +2 -2
- package/dist/exchanges/lighter.js +7 -7
- package/dist/exchanges/pacifica.js +7 -7
- package/dist/index.js +13 -12
- package/dist/mcp-server.js +1 -1
- package/package.json +2 -2
- package/skills/perp-cli/SKILL.md +10 -9
- package/skills/perp-cli/references/agent-operations.md +13 -11
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/perp-cli)
|
|
5
5
|
[](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
|
-
- **
|
|
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
|
-
"
|
|
197
|
+
"LIGHTER_PRIVATE_KEY": "your-evm-key",
|
|
198
|
+
"ASTER_API_KEY": "your-api-key"
|
|
191
199
|
}
|
|
192
200
|
}
|
|
193
201
|
```
|
package/dist/commands/bot.js
CHANGED
|
@@ -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`));
|
package/dist/commands/funds.js
CHANGED
|
@@ -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
|
|
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 {
|
package/dist/commands/init.js
CHANGED
|
@@ -31,7 +31,7 @@ function writeEnvFile(env) {
|
|
|
31
31
|
ensurePerpDir();
|
|
32
32
|
const lines = [
|
|
33
33
|
"# perp-cli configuration",
|
|
34
|
-
"# Generated by 'perp
|
|
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
|
|
112
|
+
// ── perp setup (interactive) ──
|
|
113
113
|
program
|
|
114
|
-
.command("
|
|
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 });
|
|
@@ -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
|
|
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
|
|
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
|
|
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("");
|
package/dist/commands/wallet.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
584
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
650
|
-
console.log(chalk.gray(`\n
|
|
651
|
-
console.log(` ${chalk.green("perp market
|
|
652
|
-
console.log(` ${chalk.green("perp
|
|
653
|
-
console.log(` ${chalk.green("perp
|
|
654
|
-
console.log(` ${chalk.green("perp --help")}
|
|
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
|
|
725
|
-
console.log(` ${chalk.green("perp portfolio")}
|
|
726
|
-
console.log(` ${chalk.green("perp
|
|
727
|
-
console.log(` ${chalk.green("perp arb
|
|
728
|
-
console.log(` ${chalk.green("perp
|
|
729
|
-
console.log(` ${chalk.green("perp
|
|
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 {
|
package/dist/mcp-server.js
CHANGED
|
@@ -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
|
|
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.
|
|
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"
|
package/skills/perp-cli/SKILL.md
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: perp-cli
|
|
3
|
-
description: "Multi-DEX perpetual futures trading CLI for Pacifica (Solana), Hyperliquid (EVM),
|
|
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.
|
|
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
|
[](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
|
|
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.
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
|
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 |
|
|
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
|
|
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.
|