perp-cli 0.3.4 → 0.3.5
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/dist/commands/manage.js +7 -5
- package/dist/commands/wallet.js +29 -1
- package/dist/index.js +3 -3
- package/dist/mcp-server.js +1 -1
- package/package.json +1 -1
- package/skills/perp-cli/SKILL.md +2 -1
- package/skills/perp-cli/references/agent-operations.md +17 -0
- package/skills/perp-cli/references/strategies.md +34 -0
package/dist/commands/manage.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PacificaAdapter } from "../exchanges/pacifica.js";
|
|
2
2
|
import { printJson, jsonOk } from "../utils.js";
|
|
3
|
+
import { setEnvVar } from "./init.js";
|
|
3
4
|
import chalk from "chalk";
|
|
4
5
|
export function registerManageCommands(program, getAdapter, isJson, getPacificaAdapter) {
|
|
5
6
|
const manage = program.command("manage").description("Account management");
|
|
@@ -288,6 +289,9 @@ export function registerManageCommands(program, getAdapter, isJson, getPacificaA
|
|
|
288
289
|
console.log(chalk.gray(" Generating key pair + registering on-chain...\n"));
|
|
289
290
|
}
|
|
290
291
|
const { privateKey, publicKey } = await adapter.setupApiKey(keyIndex);
|
|
292
|
+
// Auto-save to .env
|
|
293
|
+
setEnvVar("LIGHTER_API_KEY", privateKey);
|
|
294
|
+
setEnvVar("LIGHTER_ACCOUNT_INDEX", String(adapter.accountIndex));
|
|
291
295
|
if (isJson()) {
|
|
292
296
|
return printJson(jsonOk({
|
|
293
297
|
privateKey,
|
|
@@ -295,15 +299,13 @@ export function registerManageCommands(program, getAdapter, isJson, getPacificaA
|
|
|
295
299
|
address: adapter.address,
|
|
296
300
|
accountIndex: adapter.accountIndex,
|
|
297
301
|
apiKeyIndex: keyIndex,
|
|
302
|
+
savedToEnv: true,
|
|
298
303
|
}));
|
|
299
304
|
}
|
|
300
|
-
console.log(chalk.green(" API Key Registered
|
|
305
|
+
console.log(chalk.green(" API Key Registered & saved to ~/.perp/.env\n"));
|
|
301
306
|
console.log(` ${chalk.bold("Private Key:")} ${privateKey}`);
|
|
302
307
|
console.log(` ${chalk.bold("Public Key:")} ${publicKey}`);
|
|
303
|
-
console.log();
|
|
304
|
-
console.log(chalk.yellow(" Add to your .env file:"));
|
|
305
|
-
console.log(chalk.white(` LIGHTER_API_KEY=${privateKey}`));
|
|
306
|
-
console.log(chalk.white(` LIGHTER_ACCOUNT_INDEX=${adapter.accountIndex}`));
|
|
308
|
+
console.log(` ${chalk.bold("Account:")} ${adapter.accountIndex}`);
|
|
307
309
|
console.log();
|
|
308
310
|
});
|
|
309
311
|
}
|
package/dist/commands/wallet.js
CHANGED
|
@@ -382,13 +382,41 @@ export function registerWalletCommands(program, isJson) {
|
|
|
382
382
|
settings.defaultExchange = resolved;
|
|
383
383
|
saveSettings(settings);
|
|
384
384
|
}
|
|
385
|
+
// Auto-setup Lighter API key if setting lighter PK
|
|
386
|
+
let lighterApiSetup = {};
|
|
387
|
+
if (resolved === "lighter") {
|
|
388
|
+
try {
|
|
389
|
+
const { LighterAdapter } = await import("../exchanges/lighter.js");
|
|
390
|
+
const adapter = new LighterAdapter(normalized);
|
|
391
|
+
await adapter.init();
|
|
392
|
+
const { privateKey: apiKey } = await adapter.setupApiKey();
|
|
393
|
+
setEnvVar("LIGHTER_API_KEY", apiKey);
|
|
394
|
+
setEnvVar("LIGHTER_ACCOUNT_INDEX", String(adapter.accountIndex));
|
|
395
|
+
lighterApiSetup = { apiKey, accountIndex: adapter.accountIndex };
|
|
396
|
+
}
|
|
397
|
+
catch (e) {
|
|
398
|
+
lighterApiSetup = { error: e instanceof Error ? e.message : String(e) };
|
|
399
|
+
}
|
|
400
|
+
}
|
|
385
401
|
if (isJson())
|
|
386
|
-
return printJson(jsonOk({
|
|
402
|
+
return printJson(jsonOk({
|
|
403
|
+
exchange: resolved, address, envFile: ENV_FILE, default: !!opts.default,
|
|
404
|
+
...(resolved === "lighter" && { lighterApiKey: lighterApiSetup }),
|
|
405
|
+
}));
|
|
387
406
|
console.log(chalk.green(`\n ${resolved} configured.`));
|
|
388
407
|
console.log(` Address: ${chalk.green(address)}`);
|
|
389
408
|
console.log(` Saved to: ${chalk.gray("~/.perp/.env")}`);
|
|
390
409
|
if (opts.default)
|
|
391
410
|
console.log(` Default: ${chalk.cyan(resolved)}`);
|
|
411
|
+
if (resolved === "lighter") {
|
|
412
|
+
if (lighterApiSetup.apiKey) {
|
|
413
|
+
console.log(chalk.green(` API Key: auto-registered (index: ${lighterApiSetup.accountIndex})`));
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
console.log(chalk.yellow(` API Key: setup failed — ${lighterApiSetup.error}`));
|
|
417
|
+
console.log(chalk.gray(` You can retry: perp -e lighter manage setup-api-key`));
|
|
418
|
+
}
|
|
419
|
+
}
|
|
392
420
|
console.log();
|
|
393
421
|
});
|
|
394
422
|
// ── show (show configured exchanges with public addresses) ──
|
package/dist/index.js
CHANGED
|
@@ -48,7 +48,7 @@ const _defaultExchange = _settings.defaultExchange || "pacifica";
|
|
|
48
48
|
program
|
|
49
49
|
.name("perp")
|
|
50
50
|
.description("Multi-DEX Perpetual Futures CLI (Pacifica, Hyperliquid, Lighter)")
|
|
51
|
-
.version("0.3.
|
|
51
|
+
.version("0.3.5")
|
|
52
52
|
.option("-e, --exchange <exchange>", `Exchange: pacifica, hyperliquid, lighter (default: ${_defaultExchange})`, _defaultExchange)
|
|
53
53
|
.option("-n, --network <network>", "Network: mainnet or testnet", "mainnet")
|
|
54
54
|
.option("-k, --private-key <key>", "Private key")
|
|
@@ -383,7 +383,7 @@ if (rawArgs.length === 0 || (!hasSubcommand && !rawArgs.includes("-h") && !rawAr
|
|
|
383
383
|
process.env.LIGHTER_PRIVATE_KEY);
|
|
384
384
|
if (!status.hasWallets && !hasEnvKey && !settings.defaultExchange) {
|
|
385
385
|
// Fresh install — onboarding
|
|
386
|
-
console.log(chalk.cyan.bold("\n Welcome to perp-cli!") + chalk.gray(" v0.3.
|
|
386
|
+
console.log(chalk.cyan.bold("\n Welcome to perp-cli!") + chalk.gray(" v0.3.5\n"));
|
|
387
387
|
console.log(" Multi-DEX perpetual futures CLI for Pacifica, Hyperliquid, and Lighter.\n");
|
|
388
388
|
console.log(` Get started: ${chalk.cyan("perp init")}`);
|
|
389
389
|
console.log(chalk.gray(`\n Or explore without a wallet:`));
|
|
@@ -396,7 +396,7 @@ if (rawArgs.length === 0 || (!hasSubcommand && !rawArgs.includes("-h") && !rawAr
|
|
|
396
396
|
// Configured — show status overview
|
|
397
397
|
const defaultEx = settings.defaultExchange || "pacifica";
|
|
398
398
|
const activeEntries = Object.entries(status.active);
|
|
399
|
-
console.log(chalk.cyan.bold("\n perp-cli") + chalk.gray(" v0.3.
|
|
399
|
+
console.log(chalk.cyan.bold("\n perp-cli") + chalk.gray(" v0.3.5\n"));
|
|
400
400
|
console.log(` Default exchange: ${chalk.cyan(defaultEx)}`);
|
|
401
401
|
if (activeEntries.length > 0) {
|
|
402
402
|
console.log(chalk.white.bold("\n Wallets:"));
|
package/dist/mcp-server.js
CHANGED
|
@@ -56,7 +56,7 @@ function err(error, meta) {
|
|
|
56
56
|
return JSON.stringify({ ok: false, error, meta }, null, 2);
|
|
57
57
|
}
|
|
58
58
|
// ── MCP Server ──
|
|
59
|
-
const server = new McpServer({ name: "perp-cli", version: "0.3.
|
|
59
|
+
const server = new McpServer({ name: "perp-cli", version: "0.3.5" }, { capabilities: { tools: {}, resources: {} } });
|
|
60
60
|
// ============================================================
|
|
61
61
|
// Market Data tools (read-only, no private key needed)
|
|
62
62
|
// ============================================================
|
package/package.json
CHANGED
package/skills/perp-cli/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ 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.3.
|
|
8
|
+
version: "0.3.5"
|
|
9
9
|
mcp-server: perp-cli
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -22,6 +22,7 @@ Multi-DEX perpetual futures CLI — Pacifica (Solana), Hyperliquid (HyperEVM), L
|
|
|
22
22
|
5. **Verify wallet before any operation.** Run `perp --json wallet show` first.
|
|
23
23
|
6. **Use ISOLATED margin for arb.** Set `perp --json manage margin <SYM> isolated` before opening positions. Cross margin can cascade liquidations.
|
|
24
24
|
7. **Monitor positions continuously.** Run `perp --json risk status` and `perp --json -e <EX> account positions` every 15 minutes while positions are open.
|
|
25
|
+
8. **NEVER read ~/.perp/.env or any key files directly.** Private keys are managed by the CLI internally. Use `perp --json wallet show` to check wallet status. Never attempt to read, cat, or access key files — this is a security violation.
|
|
25
26
|
|
|
26
27
|
## Step 1: Install
|
|
27
28
|
|
|
@@ -81,6 +81,23 @@ perp --json bridge status <orderId> # wait for completion
|
|
|
81
81
|
perp --json arb rates
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
+
### Lighter API Key Setup
|
|
85
|
+
Lighter requires a separate API key in addition to the EVM private key. **`wallet set` handles this automatically:**
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# wallet set automatically generates & registers API key + saves to .env
|
|
89
|
+
perp --json wallet set lt 0xEVM_KEY
|
|
90
|
+
# → saves LIGHTER_PRIVATE_KEY, LIGHTER_API_KEY, LIGHTER_ACCOUNT_INDEX to ~/.perp/.env
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
If auto-setup failed (e.g. no ETH for gas), retry manually:
|
|
94
|
+
```bash
|
|
95
|
+
perp --json -e lighter manage setup-api-key
|
|
96
|
+
# → generates API key, registers on-chain, auto-saves to ~/.perp/.env
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Note:** On-chain registration requires a small amount of ETH on the Lighter chain for gas.
|
|
100
|
+
|
|
84
101
|
### Using the Same EVM Key for Multiple Exchanges
|
|
85
102
|
One EVM private key works for both Hyperliquid and Lighter:
|
|
86
103
|
```bash
|
|
@@ -79,6 +79,40 @@ When evaluating an arb opportunity:
|
|
|
79
79
|
- Both positions should open near-simultaneously to minimize directional exposure
|
|
80
80
|
- If capital needs to bridge first, you're exposed during transit
|
|
81
81
|
|
|
82
|
+
### Hold Duration Target
|
|
83
|
+
|
|
84
|
+
**Before entering any arb position, set a target hold duration.** This is critical for calculating whether the position is worth entering.
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
Expected Profit = hourly_spread × target_hours
|
|
88
|
+
Entry/Exit Cost = open_fees + close_fees + slippage (both legs)
|
|
89
|
+
Net Profit = Expected Profit - Entry/Exit Cost
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Only enter if Net Profit > 0 with a comfortable margin.**
|
|
93
|
+
|
|
94
|
+
Guidelines for hold duration:
|
|
95
|
+
- **Stable spreads (20-50 bps):** target 8-24 hours. These are reliable but low-yield — you need time for the funding to accumulate past your entry/exit costs.
|
|
96
|
+
- **Elevated spreads (50-100 bps):** target 4-8 hours. Higher yield, but likely to compress. Take profit earlier.
|
|
97
|
+
- **Spike spreads (>100 bps):** target 1-4 hours. These revert quickly. Must cover entry/exit costs within a few funding cycles.
|
|
98
|
+
|
|
99
|
+
**Re-evaluate at each funding settlement (every hour):**
|
|
100
|
+
1. Is the spread still above breakeven for the remaining target hours?
|
|
101
|
+
2. If spread compressed, should I exit early or extend the hold?
|
|
102
|
+
3. Has a better opportunity appeared? (Remember: switching has its own cost)
|
|
103
|
+
|
|
104
|
+
**Track your actual hold durations vs targets over time.** This builds intuition for how long spreads persist on each exchange pair.
|
|
105
|
+
|
|
106
|
+
Example entry decision log:
|
|
107
|
+
```
|
|
108
|
+
Entry: BTC HL↔PAC | Spread: 35 bps | Target hold: 12h
|
|
109
|
+
Expected: 35 bps × 12h = 420 bps gross
|
|
110
|
+
Entry/exit cost: ~80 bps (fees + slippage both legs)
|
|
111
|
+
Net expected: ~340 bps → ENTER
|
|
112
|
+
Hour 6 check: spread compressed to 15 bps → below breakeven for remaining 6h → EXIT
|
|
113
|
+
Actual hold: 6h | Actual net: ~130 bps
|
|
114
|
+
```
|
|
115
|
+
|
|
82
116
|
### Monitoring Active Positions
|
|
83
117
|
```bash
|
|
84
118
|
perp --json portfolio # unified multi-exchange view
|