perp-cli 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -10
- package/dist/cli-spec.js +2 -2
- package/dist/commands/account.js +33 -8
- package/dist/commands/arb/index.js +26 -0
- package/dist/commands/arb.js +3 -0
- package/dist/commands/trade.js +60 -20
- package/dist/exchanges/hyperliquid.d.ts +9 -0
- package/dist/exchanges/hyperliquid.js +40 -2
- package/dist/exchanges/lighter.js +1 -1
- package/dist/mcp-server.js +53 -50
- package/package.json +1 -1
- package/skills/perp-cli/SKILL.md +90 -189
package/README.md
CHANGED
|
@@ -69,26 +69,48 @@ perp --json portfolio
|
|
|
69
69
|
# Market data
|
|
70
70
|
perp --json -e <EX> market list
|
|
71
71
|
perp --json -e <EX> market book <SYM>
|
|
72
|
+
perp --json -e <EX> market mid <SYM> # fast mid-price lookup
|
|
72
73
|
perp --json -e <EX> market funding <SYM>
|
|
74
|
+
perp --json -e <EX> market kline <SYM> 1h # candlestick data
|
|
75
|
+
perp --json -e hl market hip3 # list HIP-3 deployed dexes
|
|
73
76
|
|
|
74
77
|
# Trading
|
|
75
|
-
perp --json -e <EX> trade
|
|
76
|
-
perp --json -e <EX> trade
|
|
77
|
-
perp --json -e <EX> trade
|
|
78
|
+
perp --json -e <EX> trade buy <SYM> <SIZE> # shortcut for market buy
|
|
79
|
+
perp --json -e <EX> trade sell <SYM> <SIZE> # shortcut for market sell
|
|
80
|
+
perp --json -e <EX> trade market <SYM> buy <SIZE> --smart # IOC limit (less slippage)
|
|
81
|
+
perp --json -e <EX> trade split <SYM> buy 5000 # orderbook-aware split (large orders)
|
|
78
82
|
perp --json -e <EX> trade close <SYM>
|
|
83
|
+
perp --json -e <EX> trade flatten # close ALL positions on exchange
|
|
84
|
+
perp --json -e <EX> trade reduce <SYM> 50 # reduce position by 50%
|
|
85
|
+
perp --json -e <EX> trade cancel <SYM> # cancel by symbol (or orderId)
|
|
86
|
+
perp --json -e <EX> trade tpsl <SYM> long # set take-profit / stop-loss
|
|
79
87
|
perp --json -e <EX> trade leverage <SYM> <N>
|
|
80
88
|
|
|
89
|
+
# Account
|
|
90
|
+
perp --json -e <EX> account balance
|
|
91
|
+
perp --json -e <EX> account positions
|
|
92
|
+
perp --json -e <EX> account pnl # realized + unrealized + funding
|
|
93
|
+
perp --json -e <EX> account funding # personal funding payment history
|
|
94
|
+
perp --json -e <EX> account settings # per-market leverage & margin mode
|
|
95
|
+
|
|
81
96
|
# Funding rate arbitrage
|
|
82
|
-
perp --json arb scan --min 5
|
|
83
|
-
perp --json arb scan --mode spot-perp
|
|
84
|
-
perp --json arb
|
|
85
|
-
perp --json arb
|
|
97
|
+
perp --json arb scan --min 5 # perp-perp opportunities
|
|
98
|
+
perp --json arb scan --mode spot-perp # spot+perp opportunities
|
|
99
|
+
perp --json arb scan --rates # funding rates across all exchanges
|
|
100
|
+
perp --json arb scan --basis # cross-exchange basis opportunities
|
|
101
|
+
perp --json arb scan --gaps # cross-exchange price gaps
|
|
102
|
+
perp --json arb scan --hip3 # HIP-3 cross-dex funding spreads
|
|
103
|
+
perp --json arb scan --live # continuous live monitoring
|
|
104
|
+
perp --json arb exec <SYM> <longEx> <shortEx> <$> # perp-perp dual-leg entry
|
|
105
|
+
perp --json arb exec <SYM> spot:<exch> <perpEx> <$> # spot+perp entry
|
|
106
|
+
perp --json arb config # show arb defaults
|
|
107
|
+
perp --json arb history # past arb trade performance (alias: log)
|
|
86
108
|
|
|
87
109
|
# Funds (deposit, withdraw, transfer)
|
|
88
110
|
perp --json funds deposit hyperliquid 100
|
|
89
111
|
perp --json funds withdraw pacifica 50
|
|
90
|
-
perp --json funds transfer 100 <ADDRESS>
|
|
91
|
-
perp --json funds info
|
|
112
|
+
perp --json funds transfer 100 <ADDRESS> # HL internal transfer
|
|
113
|
+
perp --json funds info # all routes & limits
|
|
92
114
|
|
|
93
115
|
# Risk
|
|
94
116
|
perp --json risk limits --max-leverage 5
|
|
@@ -103,7 +125,9 @@ perp --json bridge quote --from solana --to arbitrum --amount 100
|
|
|
103
125
|
perp --json bridge send --from solana --to arbitrum --amount 100
|
|
104
126
|
```
|
|
105
127
|
|
|
106
|
-
Exchange
|
|
128
|
+
Exchange flag: `-e hyperliquid` / `-e pacifica` / `-e lighter` (aliases: `hl`, `pac`, `lt`).
|
|
129
|
+
|
|
130
|
+
Global flags: `--json`, `--fields <f>`, `--ndjson`, `--dry-run`, `--dex <name>` (HIP-3), `-w, --wallet <name>`.
|
|
107
131
|
|
|
108
132
|
## AI Agent Skill
|
|
109
133
|
|
package/dist/cli-spec.js
CHANGED
|
@@ -75,8 +75,8 @@ export function getCliSpec(program) {
|
|
|
75
75
|
"Check error.retryable before retrying failed commands",
|
|
76
76
|
"Use 'perp --json trade check <symbol> <side> <size>' to validate before trading",
|
|
77
77
|
"Use 'perp --json market mid <symbol>' for fast price lookups",
|
|
78
|
-
"Use 'perp
|
|
79
|
-
"If this CLI is useful, run 'perp settings
|
|
78
|
+
"Use 'perp agent ping' to check exchange connectivity before operations",
|
|
79
|
+
"If this CLI is useful, run 'perp settings referrals on' to support development",
|
|
80
80
|
],
|
|
81
81
|
};
|
|
82
82
|
}
|
package/dist/commands/account.js
CHANGED
|
@@ -97,7 +97,7 @@ function buildSummary(snapshots) {
|
|
|
97
97
|
}
|
|
98
98
|
function pac(adapter) {
|
|
99
99
|
if (!(adapter instanceof PacificaAdapter))
|
|
100
|
-
throw new Error("
|
|
100
|
+
throw new Error("Market settings are only available on Pacifica.");
|
|
101
101
|
return adapter;
|
|
102
102
|
}
|
|
103
103
|
export function registerAccountCommands(program, getAdapter, isJson, getAdapterForExchange) {
|
|
@@ -622,14 +622,38 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
622
622
|
.description("Show per-market account settings (leverage, margin mode)")
|
|
623
623
|
.action(async () => {
|
|
624
624
|
const adapter = await getAdapter();
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
625
|
+
// Pacifica: use dedicated SDK endpoint
|
|
626
|
+
if (adapter instanceof PacificaAdapter) {
|
|
627
|
+
const settings = await adapter.sdk.getAccountSettings(adapter.publicKey);
|
|
628
|
+
if (isJson())
|
|
629
|
+
return printJson(jsonOk(settings));
|
|
630
|
+
if (!Array.isArray(settings) || settings.length === 0) {
|
|
631
|
+
console.log(chalk.gray("\n No market settings configured.\n"));
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
const rows = settings.map((s) => [
|
|
635
|
+
chalk.white.bold(s.symbol),
|
|
636
|
+
s.margin_mode,
|
|
637
|
+
`${s.leverage}x`,
|
|
638
|
+
]);
|
|
639
|
+
console.log(makeTable(["Symbol", "Margin Mode", "Leverage"], rows));
|
|
631
640
|
return;
|
|
632
641
|
}
|
|
642
|
+
// HL / Lighter: derive settings from open positions
|
|
643
|
+
const positions = await adapter.getPositions();
|
|
644
|
+
if (positions.length === 0) {
|
|
645
|
+
if (isJson())
|
|
646
|
+
return printJson(jsonOk([]));
|
|
647
|
+
console.log(chalk.gray("\n No open positions — settings shown per active position.\n"));
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
const settings = positions.map(p => ({
|
|
651
|
+
symbol: p.symbol,
|
|
652
|
+
leverage: p.leverage,
|
|
653
|
+
margin_mode: "cross", // HL/Lighter default to cross
|
|
654
|
+
}));
|
|
655
|
+
if (isJson())
|
|
656
|
+
return printJson(jsonOk(settings));
|
|
633
657
|
const rows = settings.map((s) => [
|
|
634
658
|
chalk.white.bold(s.symbol),
|
|
635
659
|
s.margin_mode,
|
|
@@ -661,6 +685,7 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
661
685
|
});
|
|
662
686
|
account
|
|
663
687
|
.command("funding-history")
|
|
688
|
+
.alias("funding")
|
|
664
689
|
.description("Personal funding payment history")
|
|
665
690
|
.action(async () => {
|
|
666
691
|
const adapter = await getAdapter();
|
|
@@ -899,7 +924,7 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
899
924
|
if (getAdapterForExchange) {
|
|
900
925
|
program
|
|
901
926
|
.command("portfolio")
|
|
902
|
-
.description("Cross-exchange portfolio overview
|
|
927
|
+
.description("Cross-exchange portfolio overview with risk metrics")
|
|
903
928
|
.option("--exchange <exchanges>", "Comma-separated exchanges to include (default: all)")
|
|
904
929
|
.action(async (opts) => {
|
|
905
930
|
await withJsonErrors(isJson(), async () => {
|
|
@@ -914,6 +914,7 @@ export function registerArbManageCommands(program, getAdapterForExchange, isJson
|
|
|
914
914
|
// ── arb history ──
|
|
915
915
|
arb
|
|
916
916
|
.command("history")
|
|
917
|
+
.alias("log")
|
|
917
918
|
.description("Past arb trade performance and statistics")
|
|
918
919
|
.option("--period <days>", "Number of days to look back", "30")
|
|
919
920
|
.action(async (opts) => {
|
|
@@ -1177,6 +1178,31 @@ export function registerArbManageCommands(program, getAdapterForExchange, isJson
|
|
|
1177
1178
|
console.log();
|
|
1178
1179
|
});
|
|
1179
1180
|
// ── arb rebalance ──
|
|
1181
|
+
arb
|
|
1182
|
+
.command("config")
|
|
1183
|
+
.description("Show arb configuration defaults (use 'arb auto --help' for all options)")
|
|
1184
|
+
.action(async () => {
|
|
1185
|
+
if (isJson()) {
|
|
1186
|
+
return printJson(jsonOk({
|
|
1187
|
+
minSpread: 30, closeSpread: 5, size: 100, holdDays: 7,
|
|
1188
|
+
bridgeCost: 0, interval: 60, cooldown: 30, minMargin: 30,
|
|
1189
|
+
settleStrategy: "block", maxLegs: 5,
|
|
1190
|
+
}));
|
|
1191
|
+
}
|
|
1192
|
+
console.log(chalk.cyan.bold("\n Arb Auto Defaults\n"));
|
|
1193
|
+
console.log(` Min Spread: 30% annual`);
|
|
1194
|
+
console.log(` Close Spread: 5% annual`);
|
|
1195
|
+
console.log(` Size: $100 per leg`);
|
|
1196
|
+
console.log(` Hold Days: 7d (for cost amortization)`);
|
|
1197
|
+
console.log(` Bridge Cost: $0`);
|
|
1198
|
+
console.log(` Check Interval: 60s`);
|
|
1199
|
+
console.log(` Cooldown: 30min (after closing a symbol)`);
|
|
1200
|
+
console.log(` Min Margin: 30%`);
|
|
1201
|
+
console.log(` Settle Strategy: block`);
|
|
1202
|
+
console.log(` Max Legs: 5`);
|
|
1203
|
+
console.log(chalk.gray(`\n Override with 'perp arb auto --min-spread 20 --size 50 ...'\n`));
|
|
1204
|
+
console.log(chalk.gray(` Run 'perp arb auto --help' for all options.\n`));
|
|
1205
|
+
});
|
|
1180
1206
|
arb
|
|
1181
1207
|
.command("rebalance")
|
|
1182
1208
|
.description("Cross-exchange balance rebalancing for arb")
|
package/dist/commands/arb.js
CHANGED
|
@@ -262,9 +262,12 @@ export async function handleBasisScan(isJson, opts) {
|
|
|
262
262
|
console.log(chalk.cyan(" Fetching prices for basis calculation...\n"));
|
|
263
263
|
const [pacRates, hlRates, ltRates] = await Promise.all([fetchPacificaRates(), fetchHyperliquidRates(), fetchLighterRates()]);
|
|
264
264
|
const exchangePrices = new Map();
|
|
265
|
+
const filterSymbol = opts.symbol?.toUpperCase();
|
|
265
266
|
for (const r of [...pacRates, ...hlRates, ...ltRates]) {
|
|
266
267
|
if (r.markPrice <= 0)
|
|
267
268
|
continue;
|
|
269
|
+
if (filterSymbol && r.symbol.toUpperCase() !== filterSymbol)
|
|
270
|
+
continue;
|
|
268
271
|
if (!exchangePrices.has(r.symbol))
|
|
269
272
|
exchangePrices.set(r.symbol, new Map());
|
|
270
273
|
exchangePrices.get(r.symbol).set(r.exchange, r.markPrice);
|
package/dist/commands/trade.js
CHANGED
|
@@ -312,19 +312,33 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
|
|
|
312
312
|
});
|
|
313
313
|
trade
|
|
314
314
|
.command("cancel <symbolOrOrderId> [orderId]")
|
|
315
|
-
.description("Cancel
|
|
315
|
+
.description("Cancel order(s) — by orderId, by symbol + orderId, or by symbol name (cancels all orders for that symbol)")
|
|
316
316
|
.action(async (symbolOrOrderId, orderId) => {
|
|
317
317
|
const adapter = await getAdapter();
|
|
318
|
-
let symbol;
|
|
319
|
-
let oid;
|
|
320
318
|
if (orderId) {
|
|
321
319
|
// cancel <symbol> <orderId>
|
|
322
|
-
symbol = symbolOrOrderId.toUpperCase();
|
|
323
|
-
oid = orderId;
|
|
320
|
+
const symbol = symbolOrOrderId.toUpperCase();
|
|
321
|
+
const oid = orderId;
|
|
322
|
+
if (dryRunGuard("cancel", { exchange: adapter.name, symbol, orderId: oid }))
|
|
323
|
+
return;
|
|
324
|
+
try {
|
|
325
|
+
const result = await adapter.cancelOrder(symbol, oid);
|
|
326
|
+
logExecution({ type: "cancel", exchange: adapter.name, symbol, side: "cancel", size: "0", status: "success", dryRun: false, meta: { orderId: oid } });
|
|
327
|
+
if (isJson())
|
|
328
|
+
return printJson(jsonOk(result));
|
|
329
|
+
console.log(chalk.green(`\n Order ${oid} cancelled on ${adapter.name}.\n`));
|
|
330
|
+
}
|
|
331
|
+
catch (err) {
|
|
332
|
+
logExecution({ type: "cancel", exchange: adapter.name, symbol, side: "cancel", size: "0", status: "failed", dryRun: false, error: err instanceof Error ? err.message : String(err), meta: { orderId: oid } });
|
|
333
|
+
throw err;
|
|
334
|
+
}
|
|
335
|
+
return;
|
|
324
336
|
}
|
|
325
|
-
|
|
337
|
+
// Single argument — is it an orderId (numeric) or a symbol name?
|
|
338
|
+
const isNumeric = /^\d+$/.test(symbolOrOrderId);
|
|
339
|
+
if (isNumeric) {
|
|
326
340
|
// cancel <orderId> — look up symbol from open orders
|
|
327
|
-
oid = symbolOrOrderId;
|
|
341
|
+
const oid = symbolOrOrderId;
|
|
328
342
|
const orders = await adapter.getOpenOrders();
|
|
329
343
|
const match = orders.find((o) => String(o.orderId) === oid);
|
|
330
344
|
if (!match) {
|
|
@@ -334,20 +348,46 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
|
|
|
334
348
|
console.log(chalk.yellow(`\n ${err}\n`));
|
|
335
349
|
return;
|
|
336
350
|
}
|
|
337
|
-
symbol = match.symbol;
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
351
|
+
const symbol = match.symbol;
|
|
352
|
+
if (dryRunGuard("cancel", { exchange: adapter.name, symbol, orderId: oid }))
|
|
353
|
+
return;
|
|
354
|
+
try {
|
|
355
|
+
const result = await adapter.cancelOrder(symbol, oid);
|
|
356
|
+
logExecution({ type: "cancel", exchange: adapter.name, symbol, side: "cancel", size: "0", status: "success", dryRun: false, meta: { orderId: oid } });
|
|
357
|
+
if (isJson())
|
|
358
|
+
return printJson(jsonOk(result));
|
|
359
|
+
console.log(chalk.green(`\n Order ${oid} cancelled on ${adapter.name}.\n`));
|
|
360
|
+
}
|
|
361
|
+
catch (err) {
|
|
362
|
+
logExecution({ type: "cancel", exchange: adapter.name, symbol, side: "cancel", size: "0", status: "failed", dryRun: false, error: err instanceof Error ? err.message : String(err), meta: { orderId: oid } });
|
|
363
|
+
throw err;
|
|
364
|
+
}
|
|
347
365
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
366
|
+
else {
|
|
367
|
+
// cancel <symbol> — cancel all open orders for this symbol
|
|
368
|
+
const symbol = symbolOrOrderId.toUpperCase();
|
|
369
|
+
const orders = await adapter.getOpenOrders();
|
|
370
|
+
const matching = orders.filter((o) => symbolMatch(o.symbol, symbol));
|
|
371
|
+
if (matching.length === 0) {
|
|
372
|
+
const err = `No open orders found for ${symbol}`;
|
|
373
|
+
if (isJson())
|
|
374
|
+
return printJson(jsonOk({ cancelled: false, symbol, reason: err }));
|
|
375
|
+
console.log(chalk.yellow(`\n ${err}\n`));
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (dryRunGuard("cancel_by_symbol", { exchange: adapter.name, symbol, count: matching.length }))
|
|
379
|
+
return;
|
|
380
|
+
try {
|
|
381
|
+
const result = await adapter.cancelAllOrders(symbol);
|
|
382
|
+
logExecution({ type: "cancel", exchange: adapter.name, symbol, side: "cancel", size: "0", status: "success", dryRun: false, meta: { bySymbol: true, count: matching.length } });
|
|
383
|
+
if (isJson())
|
|
384
|
+
return printJson(jsonOk({ cancelled: true, symbol, count: matching.length, result }));
|
|
385
|
+
console.log(chalk.green(`\n ${matching.length} order(s) for ${symbol} cancelled on ${adapter.name}.\n`));
|
|
386
|
+
}
|
|
387
|
+
catch (err) {
|
|
388
|
+
logExecution({ type: "cancel", exchange: adapter.name, symbol, side: "cancel", size: "0", status: "failed", dryRun: false, error: err instanceof Error ? err.message : String(err), meta: { bySymbol: true } });
|
|
389
|
+
throw err;
|
|
390
|
+
}
|
|
351
391
|
}
|
|
352
392
|
});
|
|
353
393
|
trade
|
|
@@ -59,6 +59,15 @@ export declare class HyperliquidAdapter implements ExchangeAdapter {
|
|
|
59
59
|
* Bypasses the SDK entirely — works for order, batchModify, twapOrder, etc.
|
|
60
60
|
*/
|
|
61
61
|
private ensureSigner;
|
|
62
|
+
/**
|
|
63
|
+
* Normalize trailing zeros from price ('p') and size ('s') string fields
|
|
64
|
+
* in an action object, recursively. Matches the SDK's normalizeTrailingZeros.
|
|
65
|
+
*
|
|
66
|
+
* The HL validator normalizes these fields before hashing; if we hash without
|
|
67
|
+
* normalizing, the recovered signer address won't match → "User or API Wallet
|
|
68
|
+
* does not exist" error.
|
|
69
|
+
*/
|
|
70
|
+
private static _normalizeAction;
|
|
62
71
|
private _signAndSendAction;
|
|
63
72
|
/**
|
|
64
73
|
* Place an order via raw exchange API with EIP-712 signing.
|
|
@@ -430,6 +430,40 @@ export class HyperliquidAdapter {
|
|
|
430
430
|
throw new Error("No private key configured. Run: perp init");
|
|
431
431
|
}
|
|
432
432
|
}
|
|
433
|
+
/**
|
|
434
|
+
* Normalize trailing zeros from price ('p') and size ('s') string fields
|
|
435
|
+
* in an action object, recursively. Matches the SDK's normalizeTrailingZeros.
|
|
436
|
+
*
|
|
437
|
+
* The HL validator normalizes these fields before hashing; if we hash without
|
|
438
|
+
* normalizing, the recovered signer address won't match → "User or API Wallet
|
|
439
|
+
* does not exist" error.
|
|
440
|
+
*/
|
|
441
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
442
|
+
static _normalizeAction(obj) {
|
|
443
|
+
if (!obj || typeof obj !== "object")
|
|
444
|
+
return obj;
|
|
445
|
+
if (Array.isArray(obj)) {
|
|
446
|
+
return obj.map(item => HyperliquidAdapter._normalizeAction(item));
|
|
447
|
+
}
|
|
448
|
+
const result = { ...obj };
|
|
449
|
+
for (const key in result) {
|
|
450
|
+
if (!Object.prototype.hasOwnProperty.call(result, key))
|
|
451
|
+
continue;
|
|
452
|
+
const value = result[key];
|
|
453
|
+
if (value && typeof value === "object") {
|
|
454
|
+
result[key] = HyperliquidAdapter._normalizeAction(value);
|
|
455
|
+
}
|
|
456
|
+
else if ((key === "p" || key === "s") && typeof value === "string") {
|
|
457
|
+
let v = value;
|
|
458
|
+
if (v.includes("."))
|
|
459
|
+
v = v.replace(/\.?0+$/, "");
|
|
460
|
+
if (v === "-0")
|
|
461
|
+
v = "0";
|
|
462
|
+
result[key] = v;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return result;
|
|
466
|
+
}
|
|
433
467
|
async _signAndSendAction(action) {
|
|
434
468
|
this.ensureSigner();
|
|
435
469
|
const { encode } = await import("@msgpack/msgpack");
|
|
@@ -438,9 +472,13 @@ export class HyperliquidAdapter {
|
|
|
438
472
|
const baseUrl = isMainnet
|
|
439
473
|
? "https://api.hyperliquid.xyz"
|
|
440
474
|
: "https://api.hyperliquid-testnet.xyz";
|
|
475
|
+
// Normalize trailing zeros from 'p' and 's' fields before hashing.
|
|
476
|
+
// The HL validator does the same; without this, the hash differs and the
|
|
477
|
+
// recovered signer address is wrong → "User or API Wallet does not exist".
|
|
478
|
+
const normalizedAction = HyperliquidAdapter._normalizeAction(action);
|
|
441
479
|
// Sign L1 action (replicates SDK's signL1Action)
|
|
442
480
|
const nonce = Date.now();
|
|
443
|
-
const msgPackBytes = encode(
|
|
481
|
+
const msgPackBytes = encode(normalizedAction);
|
|
444
482
|
const data = new Uint8Array(msgPackBytes.length + 9); // +8 nonce +1 vault flag
|
|
445
483
|
data.set(msgPackBytes);
|
|
446
484
|
const view = new DataView(data.buffer);
|
|
@@ -707,8 +745,8 @@ export class HyperliquidAdapter {
|
|
|
707
745
|
r: opts?.reduceOnly ?? true,
|
|
708
746
|
t: {
|
|
709
747
|
trigger: {
|
|
710
|
-
triggerPx: trimZeros(roundedTrigger),
|
|
711
748
|
isMarket: opts?.isMarket ?? true,
|
|
749
|
+
triggerPx: trimZeros(roundedTrigger),
|
|
712
750
|
tpsl,
|
|
713
751
|
},
|
|
714
752
|
},
|
|
@@ -598,7 +598,7 @@ export class LighterAdapter {
|
|
|
598
598
|
// /recentTrades returns is_maker_ask: maker was asking → taker bought
|
|
599
599
|
side: t.is_maker_ask ? "buy" : "sell",
|
|
600
600
|
price: String(t.price ?? "0"),
|
|
601
|
-
size: String(t.base_amount ?? t.amount ?? ""),
|
|
601
|
+
size: String(t.size ?? t.base_amount ?? t.amount ?? ""),
|
|
602
602
|
fee: String(t.fee ?? "0"),
|
|
603
603
|
}));
|
|
604
604
|
}
|
package/dist/mcp-server.js
CHANGED
|
@@ -339,15 +339,15 @@ server.tool("suggest_command", "Given a natural language trading goal, suggest t
|
|
|
339
339
|
steps.push({ step: 1, command: `perp -e ${ex} --json account orders`, description: "List open orders to find order ID" }, { step: 2, command: `perp -e ${ex} --json trade edit <symbol> <orderId> <newPrice> <newSize>`, description: "Modify the order", dangerous: true });
|
|
340
340
|
}
|
|
341
341
|
else if (g.includes("arb") || g.includes("arbitrage")) {
|
|
342
|
-
steps.push({ step: 1, command: "perp --json arb scan --min 5", description: "Scan funding rate arbitrage opportunities" }, { step: 2, command: "perp --json arb scan
|
|
342
|
+
steps.push({ step: 1, command: "perp --json arb scan --min 5", description: "Scan funding rate arbitrage opportunities" }, { step: 2, command: "perp --json arb scan --gaps", description: "Check cross-exchange price gaps" }, { step: 3, command: "perp --json arb scan --hip3", description: "HIP-3 cross-dex arb opportunities (Hyperliquid)" });
|
|
343
343
|
}
|
|
344
344
|
else if (g.includes("funding")) {
|
|
345
345
|
const symbol = extractSymbol(g);
|
|
346
346
|
if (symbol) {
|
|
347
|
-
steps.push({ step: 1, command: `perp --json arb rates`, description: `Funding rates across all exchanges` }, { step: 2, command: `perp -e ${ex} --json market funding ${symbol}`, description: `${symbol} funding history` });
|
|
347
|
+
steps.push({ step: 1, command: `perp --json arb scan --rates`, description: `Funding rates across all exchanges` }, { step: 2, command: `perp -e ${ex} --json market funding ${symbol}`, description: `${symbol} funding history` });
|
|
348
348
|
}
|
|
349
349
|
else {
|
|
350
|
-
steps.push({ step: 1, command: "perp --json arb rates", description: "Funding rates across all exchanges" }, { step: 2, command: "perp --json arb scan --min 5", description: "Funding rate arb opportunities" });
|
|
350
|
+
steps.push({ step: 1, command: "perp --json arb scan --rates", description: "Funding rates across all exchanges" }, { step: 2, command: "perp --json arb scan --min 5", description: "Funding rate arb opportunities" });
|
|
351
351
|
}
|
|
352
352
|
}
|
|
353
353
|
else if (g.includes("bridge") || g.includes("transfer") || g.includes("cross-chain")) {
|
|
@@ -386,22 +386,22 @@ server.tool("suggest_command", "Given a natural language trading goal, suggest t
|
|
|
386
386
|
steps.push({ step: 1, command: `perp -e ${ex} --json risk status`, description: "Portfolio risk overview" }, { step: 2, command: `perp -e ${ex} --json risk limits`, description: "Current position limits" });
|
|
387
387
|
}
|
|
388
388
|
else if (g.includes("analytic") || g.includes("performance") || g.includes("pnl") || g.includes("p&l")) {
|
|
389
|
-
steps.push({ step: 1, command: `perp -e ${ex} --json
|
|
389
|
+
steps.push({ step: 1, command: `perp -e ${ex} --json history summary`, description: "Trading performance summary" }, { step: 2, command: `perp -e ${ex} --json history pnl`, description: "P&L breakdown" }, { step: 3, command: `perp -e ${ex} --json history funding`, description: "Funding payment history" });
|
|
390
390
|
}
|
|
391
391
|
else if (g.includes("history") || g.includes("log") || g.includes("audit")) {
|
|
392
392
|
steps.push({ step: 1, command: `perp -e ${ex} --json history list`, description: "Execution audit trail" }, { step: 2, command: `perp -e ${ex} --json history stats`, description: "Execution statistics" });
|
|
393
393
|
}
|
|
394
|
-
else if (g.includes("
|
|
394
|
+
else if (g.includes("live") || g.includes("watch") || g.includes("realtime")) {
|
|
395
395
|
const symbol = extractSymbol(g);
|
|
396
396
|
if (symbol) {
|
|
397
|
-
steps.push({ step: 1, command: `perp -e ${ex}
|
|
397
|
+
steps.push({ step: 1, command: `perp -e ${ex} --json market book ${symbol}`, description: `${symbol} orderbook` }, { step: 2, command: `perp -e ${ex} --json market trades ${symbol}`, description: `${symbol} recent trades` }, { step: 3, command: `perp --json arb scan --gaps --live`, description: "Live cross-exchange price gap monitor" });
|
|
398
398
|
}
|
|
399
399
|
else {
|
|
400
|
-
steps.push({ step: 1, command:
|
|
400
|
+
steps.push({ step: 1, command: "perp --json arb scan --gaps --live", description: "Live cross-exchange price gap monitor" }, { step: 2, command: `perp -e ${ex} --json market prices`, description: "Current prices across exchanges" });
|
|
401
401
|
}
|
|
402
402
|
}
|
|
403
403
|
else if (g.includes("gap")) {
|
|
404
|
-
steps.push({ step: 1, command: "perp --json
|
|
404
|
+
steps.push({ step: 1, command: "perp --json arb scan --gaps", description: "Cross-exchange price gaps" }, { step: 2, command: "perp --json arb scan --gaps --live", description: "Live gap monitor" });
|
|
405
405
|
}
|
|
406
406
|
else if (g.includes("rebalance")) {
|
|
407
407
|
steps.push({ step: 1, command: "perp --json rebalance check", description: "Check balance distribution" }, { step: 2, command: "perp --json rebalance plan", description: "Generate rebalance plan" }, { step: 3, command: "perp --json rebalance execute", description: "Execute rebalance", dangerous: true });
|
|
@@ -446,7 +446,7 @@ server.tool("suggest_command", "Given a natural language trading goal, suggest t
|
|
|
446
446
|
steps.push({ step: 1, command: `perp -e ${ex} --json market book ${symbol}`, description: `${symbol} orderbook` }, { step: 2, command: `perp -e ${ex} --json market funding ${symbol}`, description: `${symbol} funding history` }, { step: 3, command: `perp -e ${ex} --json market kline ${symbol} 1h`, description: `${symbol} hourly candles` });
|
|
447
447
|
}
|
|
448
448
|
else {
|
|
449
|
-
steps.push({ step: 1, command: "perp --json market prices", description: "All market prices" }, { step: 2, command: "perp --json
|
|
449
|
+
steps.push({ step: 1, command: "perp --json market prices", description: "All market prices" }, { step: 2, command: "perp --json arb scan --gaps", description: "Cross-exchange price gaps" });
|
|
450
450
|
}
|
|
451
451
|
}
|
|
452
452
|
else if (g.includes("setting") || g.includes("config") || g.includes("referral")) {
|
|
@@ -708,6 +708,8 @@ server.tool("explain_command", "Explain what a perp CLI command does, its parame
|
|
|
708
708
|
history: "Shows order history (filled, cancelled, etc.)",
|
|
709
709
|
trades: "Shows trade execution history with prices and fees",
|
|
710
710
|
"funding-history": "Shows funding payments received/paid",
|
|
711
|
+
funding: "Shows funding payments received/paid (alias for funding-history)",
|
|
712
|
+
"twap-orders": "Shows active TWAP orders",
|
|
711
713
|
pnl: "Shows profit & loss summary",
|
|
712
714
|
margin: `Shows margin info for ${args[2] || "a symbol"}`,
|
|
713
715
|
settings: "Shows account settings (leverage, margin mode per symbol)",
|
|
@@ -715,26 +717,26 @@ server.tool("explain_command", "Explain what a perp CLI command does, its parame
|
|
|
715
717
|
parameters: [],
|
|
716
718
|
risks: [],
|
|
717
719
|
category: "read",
|
|
718
|
-
relatedCommands: ["perp portfolio", "perp
|
|
720
|
+
relatedCommands: ["perp portfolio", "perp account balance"],
|
|
719
721
|
};
|
|
720
722
|
}
|
|
721
723
|
else if (category === "arb") {
|
|
722
724
|
explanation = {
|
|
723
725
|
command,
|
|
724
726
|
description: {
|
|
725
|
-
|
|
726
|
-
scan: "Scans for funding rate arbitrage opportunities with the largest spreads",
|
|
727
|
-
funding: "Detailed funding rate analysis across exchanges",
|
|
728
|
-
dex: "HIP-3 cross-dex arbitrage opportunities on Hyperliquid",
|
|
727
|
+
scan: "Scans for funding rate arbitrage opportunities. Use --rates for funding rates, --gaps for price gaps, --hip3 for cross-dex arb, --positions for funding impact, --basis for basis trading",
|
|
729
728
|
auto: "Auto-execute funding rate arbitrage (runs as background job)",
|
|
729
|
+
exec: "Execute an arb trade (enter paired positions on two exchanges)",
|
|
730
730
|
status: "Shows current arb positions and P&L",
|
|
731
731
|
close: "Closes an arb position on both exchanges",
|
|
732
732
|
history: "Shows arb execution history",
|
|
733
|
+
config: "View/edit arb configuration",
|
|
734
|
+
rebalance: "Rebalance funds across exchanges for arb positions",
|
|
733
735
|
}[sub] || `Arbitrage command: ${sub}`,
|
|
734
736
|
parameters: [],
|
|
735
|
-
risks: ["auto"
|
|
736
|
-
category: sub === "auto" || sub === "close" ? "write" : "analysis",
|
|
737
|
-
relatedCommands: ["perp arb scan", "perp arb
|
|
737
|
+
risks: [sub === "auto" || sub === "exec" ? "Executes trades on exchanges" : ""].filter(Boolean),
|
|
738
|
+
category: sub === "auto" || sub === "close" || sub === "exec" || sub === "rebalance" ? "write" : "analysis",
|
|
739
|
+
relatedCommands: ["perp arb scan", "perp arb status", "perp arb scan --gaps"],
|
|
738
740
|
};
|
|
739
741
|
}
|
|
740
742
|
else if (category === "bridge") {
|
|
@@ -774,16 +776,6 @@ server.tool("explain_command", "Explain what a perp CLI command does, its parame
|
|
|
774
776
|
relatedCommands: ["perp account positions", "perp portfolio"],
|
|
775
777
|
};
|
|
776
778
|
}
|
|
777
|
-
else if (category === "alert") {
|
|
778
|
-
explanation = {
|
|
779
|
-
command,
|
|
780
|
-
description: { add: "Add a price, funding rate, PnL, or liquidation alert", list: "List all active alerts", remove: "Remove an alert by ID", daemon: "Start the alert monitoring daemon", test: "Send a test alert notification", config: "Configure alert notification channels (Telegram, Discord)" }[sub] || `Alert command: ${sub}`,
|
|
781
|
-
parameters: [],
|
|
782
|
-
risks: [],
|
|
783
|
-
category: "read",
|
|
784
|
-
relatedCommands: ["perp risk status", "perp risk limits"],
|
|
785
|
-
};
|
|
786
|
-
}
|
|
787
779
|
else if (category === "bot" || category === "run") {
|
|
788
780
|
explanation = {
|
|
789
781
|
command,
|
|
@@ -797,9 +789,9 @@ server.tool("explain_command", "Explain what a perp CLI command does, its parame
|
|
|
797
789
|
else if (category === "history") {
|
|
798
790
|
explanation = {
|
|
799
791
|
command,
|
|
800
|
-
description: { summary: "Trading performance summary", pnl: "P&L breakdown by symbol and time period", funding: "Funding payment history and totals", report: "Detailed performance report", perf: "Performance breakdown (daily/weekly/summary)" }[sub] || `History: ${sub}`,
|
|
792
|
+
description: { list: "Execution audit trail", stats: "Execution statistics", positions: "Position history", summary: "Trading performance summary", pnl: "P&L breakdown by symbol and time period", funding: "Funding payment history and totals", report: "Detailed performance report", snapshot: "Portfolio snapshot", track: "Track position over time", perf: "Performance breakdown (daily/weekly/summary)", prune: "Prune old execution records" }[sub] || `History: ${sub}`,
|
|
801
793
|
parameters: [],
|
|
802
|
-
risks: [],
|
|
794
|
+
risks: sub === "prune" ? ["Permanently removes old execution records"] : [],
|
|
803
795
|
category: "read",
|
|
804
796
|
relatedCommands: ["perp history summary", "perp history list"],
|
|
805
797
|
};
|
|
@@ -807,21 +799,11 @@ server.tool("explain_command", "Explain what a perp CLI command does, its parame
|
|
|
807
799
|
else if (category === "wallet") {
|
|
808
800
|
explanation = {
|
|
809
801
|
command,
|
|
810
|
-
description: { list: "List configured wallets", balance: "Check on-chain USDC/SOL/ETH balances", generate: "Generate a new wallet keypair", use: "Set active wallet" }[sub] || `Wallet: ${sub}`,
|
|
802
|
+
description: { show: "Show configured wallets with public addresses", list: "List configured wallets", balance: "Check on-chain USDC/SOL/ETH balances", generate: "Generate a new wallet keypair", import: "Import an existing private key", set: "Set private key for an exchange", use: "Set active wallet", remove: "Remove a wallet", rename: "Rename a wallet" }[sub] || `Wallet: ${sub}`,
|
|
811
803
|
parameters: [],
|
|
812
|
-
risks: sub === "generate" ? ["Store the private key securely — it cannot be recovered"] : [],
|
|
804
|
+
risks: sub === "generate" ? ["Store the private key securely — it cannot be recovered"] : sub === "remove" ? ["Wallet will be removed — ensure you have the private key backed up"] : [],
|
|
813
805
|
category: "read",
|
|
814
|
-
relatedCommands: ["perp wallet
|
|
815
|
-
};
|
|
816
|
-
}
|
|
817
|
-
else if (category === "gap") {
|
|
818
|
-
explanation = {
|
|
819
|
-
command,
|
|
820
|
-
description: { show: "Shows cross-exchange price gaps for all symbols", watch: "Live monitor for price gaps above threshold", track: "Track gap history", alert: "Alert when gaps exceed threshold" }[sub] || `Gap analysis: ${sub}`,
|
|
821
|
-
parameters: [],
|
|
822
|
-
risks: [],
|
|
823
|
-
category: "analysis",
|
|
824
|
-
relatedCommands: ["perp gap show", "perp arb scan"],
|
|
806
|
+
relatedCommands: ["perp wallet show", "perp wallet balance"],
|
|
825
807
|
};
|
|
826
808
|
}
|
|
827
809
|
else if (category === "backtest") {
|
|
@@ -976,7 +958,8 @@ server.resource("cli_schema", "perp://schema", { mimeType: "application/json", d
|
|
|
976
958
|
orders: { usage: "perp account orders", description: "Open/pending orders" },
|
|
977
959
|
history: { usage: "perp account history", description: "Order history" },
|
|
978
960
|
trades: { usage: "perp account trades", description: "Trade fill history" },
|
|
979
|
-
"funding-history": { usage: "perp account funding-history", description: "Funding payments" },
|
|
961
|
+
"funding-history|funding": { usage: "perp account funding-history", description: "Funding payments" },
|
|
962
|
+
"twap-orders": { usage: "perp account twap-orders", description: "Active TWAP orders" },
|
|
980
963
|
pnl: { usage: "perp account pnl", description: "Profit & loss" },
|
|
981
964
|
margin: { usage: "perp account margin <symbol>", description: "Position margin info" },
|
|
982
965
|
settings: { usage: "perp account settings", description: "Account settings (leverage per symbol)" },
|
|
@@ -1007,19 +990,23 @@ server.resource("cli_schema", "perp://schema", { mimeType: "application/json", d
|
|
|
1007
990
|
fills: { usage: "perp trade fills [symbol]", description: "Recent fills" },
|
|
1008
991
|
status: { usage: "perp trade status <orderId>", description: "Check order status" },
|
|
1009
992
|
"pnl-track": { usage: "perp trade pnl-track", description: "Real-time PnL tracker" },
|
|
993
|
+
split: { usage: "perp trade split <symbol> <side> <usd>", description: "Split large order into depth-based slices" },
|
|
994
|
+
multi: { usage: "perp trade multi <legs...>", description: "Execute multi-leg orders (exchange:symbol:side:size)" },
|
|
995
|
+
"cancel-stop": { usage: "perp trade cancel-stop <symbol> <stopOrderId>", description: "Cancel a stop order" },
|
|
996
|
+
"cancel-twap": { usage: "perp trade cancel-twap <symbol> <twapOrderId>", description: "Cancel a TWAP order" },
|
|
1010
997
|
},
|
|
1011
998
|
},
|
|
1012
999
|
arb: {
|
|
1013
1000
|
description: "Funding rate arbitrage & basis trading",
|
|
1014
1001
|
subcommands: {
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
dex: { usage: "perp arb dex", description: "HIP-3 cross-dex arb" },
|
|
1019
|
-
auto: { usage: "perp arb auto --min-spread <bps>", description: "Auto-execute funding arb" },
|
|
1002
|
+
scan: { usage: "perp arb scan --min <pct>", description: "Find arb opportunities (use --rates, --gaps, --hip3, --positions, --basis, --compare, --history, --live)" },
|
|
1003
|
+
auto: { usage: "perp arb auto --min-spread <pct>", description: "Auto-execute funding arb" },
|
|
1004
|
+
exec: { usage: "perp arb exec <symbol> <longEx> <shortEx> <size>", description: "Execute arb trade on two exchanges" },
|
|
1020
1005
|
status: { usage: "perp arb status", description: "Current arb positions" },
|
|
1021
1006
|
close: { usage: "perp arb close <symbol>", description: "Close arb position" },
|
|
1022
|
-
history: { usage: "perp arb history", description: "Arb execution history" },
|
|
1007
|
+
"history|log": { usage: "perp arb history", description: "Arb execution history" },
|
|
1008
|
+
config: { usage: "perp arb config", description: "View/edit arb configuration" },
|
|
1009
|
+
rebalance: { usage: "perp arb rebalance", description: "Rebalance funds across exchanges for arb" },
|
|
1023
1010
|
},
|
|
1024
1011
|
},
|
|
1025
1012
|
risk: {
|
|
@@ -1043,32 +1030,48 @@ server.resource("cli_schema", "perp://schema", { mimeType: "application/json", d
|
|
|
1043
1030
|
bot: {
|
|
1044
1031
|
description: "Automated trading bots",
|
|
1045
1032
|
subcommands: {
|
|
1033
|
+
start: { usage: "perp bot start <config>", description: "Start bot from config file" },
|
|
1046
1034
|
twap: { usage: "perp bot twap <symbol> <side> <size> <duration>", description: "TWAP execution" },
|
|
1047
1035
|
grid: { usage: "perp bot grid <symbol> --range <pct> --grids <n> --size <usd>", description: "Grid trading bot" },
|
|
1048
1036
|
dca: { usage: "perp bot dca <symbol> <side> <amount> <interval>", description: "DCA bot" },
|
|
1037
|
+
"funding-arb": { usage: "perp bot funding-arb", description: "Funding arb bot" },
|
|
1049
1038
|
"trailing-stop": { usage: "perp bot trailing-stop <symbol>", description: "Trailing stop bot" },
|
|
1050
1039
|
"quick-grid": { usage: "perp bot quick-grid <symbol>", description: "Quick grid bot" },
|
|
1051
1040
|
"quick-dca": { usage: "perp bot quick-dca <symbol> <side> <amount> <interval>", description: "Quick DCA bot" },
|
|
1052
1041
|
"quick-arb": { usage: "perp bot quick-arb", description: "Quick arb bot" },
|
|
1042
|
+
"preset-list": { usage: "perp bot preset-list", description: "List available bot presets" },
|
|
1043
|
+
preset: { usage: "perp bot preset <name> [symbol]", description: "Run a bot preset" },
|
|
1044
|
+
example: { usage: "perp bot example", description: "Show example bot config" },
|
|
1053
1045
|
},
|
|
1054
1046
|
},
|
|
1055
1047
|
wallet: {
|
|
1056
1048
|
description: "Wallet management",
|
|
1057
1049
|
subcommands: {
|
|
1050
|
+
show: { usage: "perp wallet show", description: "Show configured wallets with public addresses" },
|
|
1058
1051
|
list: { usage: "perp wallet list", description: "List wallets" },
|
|
1059
|
-
balance: { usage: "perp wallet balance", description: "On-chain balances" },
|
|
1052
|
+
balance: { usage: "perp wallet balance [name]", description: "On-chain balances" },
|
|
1060
1053
|
generate: { usage: "perp wallet generate solana|evm", description: "Generate new wallet" },
|
|
1054
|
+
import: { usage: "perp wallet import solana|evm <privateKey>", description: "Import existing private key" },
|
|
1055
|
+
set: { usage: "perp wallet set <exchange> <key>", description: "Set private key for exchange" },
|
|
1056
|
+
use: { usage: "perp wallet use <name> [exchange]", description: "Set active wallet" },
|
|
1057
|
+
remove: { usage: "perp wallet remove <name>", description: "Remove a wallet" },
|
|
1058
|
+
rename: { usage: "perp wallet rename <oldName> <newName>", description: "Rename a wallet" },
|
|
1061
1059
|
},
|
|
1062
1060
|
},
|
|
1063
1061
|
history: {
|
|
1064
1062
|
description: "Execution log, performance & audit trail",
|
|
1065
1063
|
subcommands: {
|
|
1066
1064
|
list: { usage: "perp history list", description: "Execution log" },
|
|
1065
|
+
stats: { usage: "perp history stats", description: "Execution statistics" },
|
|
1066
|
+
positions: { usage: "perp history positions", description: "Position history" },
|
|
1067
1067
|
summary: { usage: "perp history summary", description: "Performance summary" },
|
|
1068
1068
|
pnl: { usage: "perp history pnl", description: "P&L breakdown" },
|
|
1069
1069
|
funding: { usage: "perp history funding", description: "Funding payment history" },
|
|
1070
1070
|
report: { usage: "perp history report", description: "Full performance report" },
|
|
1071
|
+
snapshot: { usage: "perp history snapshot", description: "Portfolio snapshot" },
|
|
1072
|
+
track: { usage: "perp history track", description: "Track position over time" },
|
|
1071
1073
|
perf: { usage: "perp history perf --period daily|weekly|summary", description: "Performance breakdown" },
|
|
1074
|
+
prune: { usage: "perp history prune", description: "Prune old execution records" },
|
|
1072
1075
|
},
|
|
1073
1076
|
},
|
|
1074
1077
|
backtest: {
|
package/package.json
CHANGED
package/skills/perp-cli/SKILL.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
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, 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), 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)."
|
|
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.5.1"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# perp-cli Agent Guide
|
|
@@ -21,21 +21,18 @@ Multi-DEX perpetual futures CLI — Pacifica (Solana), Hyperliquid (HyperEVM), L
|
|
|
21
21
|
5. **NEVER trade without user confirmation.**
|
|
22
22
|
6. **NEVER read ~/.perp/.env or key files.**
|
|
23
23
|
|
|
24
|
-
## Install
|
|
24
|
+
## Install
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
#
|
|
28
|
-
perp --version 2>/dev/null
|
|
29
|
-
|
|
30
|
-
# 2. If not found or outdated:
|
|
27
|
+
perp --version 2>/dev/null # check if exists (must be >= 0.5.1)
|
|
31
28
|
npm install -g perp-cli@latest 2>/dev/null || npx -y perp-cli@latest --json --version
|
|
32
|
-
|
|
33
|
-
# 3. If version is still old (npx cache), clear cache first:
|
|
34
|
-
npx -y clear-npx-cache 2>/dev/null; npx -y perp-cli@latest --json --version
|
|
35
29
|
```
|
|
36
30
|
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
Use `perp` if global install works, otherwise `npx -y perp-cli@latest` as prefix.
|
|
32
|
+
|
|
33
|
+
## Global Flags
|
|
34
|
+
|
|
35
|
+
`--json` (required) | `-e hl/pac/lt` (exchange) | `--dex <name>` (HIP-3) | `-w <wallet>` | `--dry-run` | `--fields <f1,f2>` | `--ndjson`
|
|
39
36
|
|
|
40
37
|
## Wallet Setup
|
|
41
38
|
|
|
@@ -45,227 +42,131 @@ perp --json wallet set pac <SOLANA_KEY> # Pacifica
|
|
|
45
42
|
perp --json wallet set lt <EVM_KEY> # Lighter (API key auto-generated)
|
|
46
43
|
perp --json wallet show # verify
|
|
47
44
|
```
|
|
48
|
-
Same EVM key works for both HL and Lighter.
|
|
49
45
|
|
|
50
|
-
##
|
|
46
|
+
## Core Workflow: Arb
|
|
51
47
|
|
|
52
|
-
```
|
|
53
|
-
#
|
|
54
|
-
perp --json
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
perp --json arb
|
|
58
|
-
perp --json arb
|
|
59
|
-
|
|
60
|
-
# Single exchange trading (when not doing arb)
|
|
61
|
-
perp --json -e <EX> trade market <SYM> buy <SIZE>
|
|
62
|
-
perp --json -e <EX> trade market <SYM> buy <SIZE> --smart # IOC limit at best ask + 1 tick (less slippage)
|
|
63
|
-
perp --json -e <EX> trade market <SYM> buy <SIZE> --split # orderbook-aware split for large orders
|
|
64
|
-
perp --json -e <EX> trade split <SYM> buy 5000 # dedicated split command (USD notional)
|
|
65
|
-
perp --json -e <EX> trade close <SYM>
|
|
66
|
-
perp --json -e <EX> trade close <SYM> --smart # smart close at best bid/ask
|
|
67
|
-
|
|
68
|
-
# Risk (only if you need to set limits — portfolio already includes risk)
|
|
69
|
-
perp --json risk limits --max-leverage 5
|
|
48
|
+
```
|
|
49
|
+
1. perp --json arb scan # scan opportunities (perp-perp + spot-perp + HIP-3)
|
|
50
|
+
2. perp --json account balance # check balances across all exchanges
|
|
51
|
+
3. perp --json --dry-run arb exec <SYM> <longEx> <shortEx> <$> # dry-run (auto-sizes)
|
|
52
|
+
4. [show result to user, get confirmation]
|
|
53
|
+
5. perp --json arb exec <SYM> <longEx> <shortEx> <$> # execute
|
|
54
|
+
6. perp --json arb status # monitor PnL + funding income
|
|
55
|
+
7. perp --json arb close <SYM> # close both legs
|
|
70
56
|
```
|
|
71
57
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
## Agent Tools
|
|
75
|
-
|
|
76
|
-
```bash
|
|
77
|
-
# Discover all commands and parameters at runtime (don't guess — query this)
|
|
78
|
-
perp --json agent schema
|
|
79
|
-
|
|
80
|
-
# Pre-validate a trade before execution (checks balance, liquidity, risk limits)
|
|
81
|
-
perp --json -e <EX> trade check <SYM> <SIDE> <SIZE>
|
|
82
|
-
|
|
83
|
-
# Dry-run: simulate trade without executing
|
|
84
|
-
perp --json --dry-run -e <EX> trade market <SYM> buy <SIZE>
|
|
85
|
-
|
|
86
|
-
# Filter output to specific fields (saves tokens)
|
|
87
|
-
perp --json --fields totalEquity,positions portfolio
|
|
58
|
+
### Scan Modes
|
|
88
59
|
|
|
89
|
-
|
|
90
|
-
|
|
60
|
+
| Flag | Description |
|
|
61
|
+
|------|-------------|
|
|
62
|
+
| _(default)_ | All modes combined (perp-perp + spot-perp) |
|
|
63
|
+
| `--rates` | Funding rates across exchanges |
|
|
64
|
+
| `--basis [symbol]` | Cross-exchange basis/price diff |
|
|
65
|
+
| `--gaps` | Price gaps |
|
|
66
|
+
| `--hip3` | HIP-3 on-chain dex opportunities |
|
|
67
|
+
| `--history [symbol]` | Historical funding data |
|
|
68
|
+
| `--compare <symbol>` | Compare symbol across exchanges |
|
|
69
|
+
| `--positions` | Existing position exit signals |
|
|
70
|
+
| `--live` | Continuous monitoring |
|
|
91
71
|
|
|
92
|
-
|
|
93
|
-
perp --json -e <EX> trade market <SYM> buy <SIZE> --client-id <UNIQUE_ID>
|
|
72
|
+
### Spot+Perp Arb
|
|
94
73
|
|
|
95
|
-
|
|
96
|
-
# Fills only at top-of-book price — no multi-level sweep slippage
|
|
97
|
-
perp --json -e <EX> trade market <SYM> buy <SIZE> --smart
|
|
98
|
-
perp --json -e <EX> trade close <SYM> --smart
|
|
99
|
-
perp --json arb exec <SYM> <longEx> <shortEx> <$> --smart # smart arb entry
|
|
100
|
-
perp --json arb close <SYM> --smart # smart arb close
|
|
74
|
+
Spot has 0 funding cost. Use `spot:<exch>` prefix for the long leg:
|
|
101
75
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
perp --json
|
|
105
|
-
perp --json
|
|
106
|
-
perp --json -e <EX> trade market <SYM> buy <SIZE> --split # split via market command flag
|
|
76
|
+
```bash
|
|
77
|
+
perp --json arb exec ETH spot:hl hl 100 # spot buy(HL) + perp short(HL)
|
|
78
|
+
perp --json arb exec BTC spot:lt pac 50 # spot buy(LT) + perp short(PAC)
|
|
79
|
+
perp --json arb close ETH # sells spot + buys back perp
|
|
107
80
|
```
|
|
108
81
|
|
|
109
|
-
|
|
82
|
+
Spot exchanges: HL, LT. Pacifica is perp-only.
|
|
110
83
|
|
|
111
|
-
##
|
|
84
|
+
## Core Workflow: Single Trade
|
|
112
85
|
|
|
86
|
+
```bash
|
|
87
|
+
perp --json -e <EX> trade buy <SYM> <SIZE> # market buy (SIZE = token units)
|
|
88
|
+
perp --json -e <EX> trade sell <SYM> <SIZE> # market sell
|
|
89
|
+
perp --json -e <EX> trade buy <SYM> <SIZE> --smart # IOC limit at best ask
|
|
90
|
+
perp --json -e <EX> trade split <SYM> buy <USD> # orderbook-aware split
|
|
91
|
+
perp --json -e <EX> trade limit <SYM> buy <PRICE> <SIZE>
|
|
92
|
+
perp --json -e <EX> trade tpsl <SYM> buy --tp <P> --sl <P>
|
|
93
|
+
perp --json -e <EX> trade close <SYM>
|
|
94
|
+
perp --json -e <EX> trade flatten # close ALL + cancel ALL
|
|
95
|
+
perp --json -e <EX> trade cancel <SYM> # cancel all orders for symbol
|
|
113
96
|
```
|
|
114
|
-
1. perp --json portfolio → check balances across all exchanges
|
|
115
|
-
2. perp --json arb scan --min 5 → find opportunity (longExch, shortExch, netSpread)
|
|
116
|
-
3. [Show opportunity to user, get confirmation]
|
|
117
|
-
4. perp --json arb exec <SYM> <longEx> <shortEx> <$> --leverage 2 --isolated
|
|
118
|
-
→ validates orderbook depth on both sides
|
|
119
|
-
→ rounds size to each exchange's lot size
|
|
120
|
-
→ executes BOTH legs simultaneously
|
|
121
|
-
→ verifies positions exist after execution
|
|
122
|
-
→ auto-rollback if one leg fails
|
|
123
|
-
5. perp --json arb status → monitor: PnL, funding income, daily estimate, breakeven
|
|
124
|
-
6. perp --json arb funding-earned → actual funding payments received/paid + APR
|
|
125
|
-
7. perp --json arb close <SYM> → close both legs simultaneously (retry + verify)
|
|
126
|
-
→ --dry-run: preview without executing
|
|
127
|
-
→ --pair <longEx>:<shortEx>: specify which pair if multiple
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
## Spot+Perp Arb Workflow
|
|
131
|
-
|
|
132
|
-
Spot has 0 funding cost → spread = |perp funding rate|. Only needs 1 exchange with spot + any exchange with perp.
|
|
133
97
|
|
|
134
|
-
|
|
135
|
-
**U-token mapping:** HL spot uses Unit protocol bridged tokens — UBTC=BTC, UETH=ETH, USOL=SOL, UFART=FARTCOIN. Resolved automatically.
|
|
98
|
+
## Agent Tools
|
|
136
99
|
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
Examples:
|
|
143
|
-
perp --json arb spot-exec ETH hl hl 100 # ETH spot(HL/UETH) + perp(HL)
|
|
144
|
-
perp --json arb spot-exec BTC hl lt 50 # BTC spot(HL/UBTC) + perp(LT)
|
|
145
|
-
perp --json arb spot-exec LINK lt hl 100 # LINK spot(LT) + perp(HL)
|
|
146
|
-
→ cross-validates spot vs perp price (>5% deviation = abort, wrong token)
|
|
147
|
-
→ sequential execution if same exchange (nonce collision avoidance)
|
|
148
|
-
→ parallel execution if different exchanges
|
|
149
|
-
→ auto-rollback if one leg fails
|
|
150
|
-
Options:
|
|
151
|
-
--leverage <N> set perp leverage before entry
|
|
152
|
-
--isolated use isolated margin on perp side
|
|
153
|
-
4. perp --json arb status → shows spot-perp positions (spot leg labeled)
|
|
154
|
-
5. perp --json arb spot-close <SYM> --spot-exch <hl|lt>
|
|
155
|
-
→ sells spot + buys back perp simultaneously
|
|
100
|
+
```bash
|
|
101
|
+
perp --json agent schema # discover all commands (don't guess)
|
|
102
|
+
perp --json -e <EX> trade check <SYM> <SIDE> <SIZE> # pre-validate trade
|
|
103
|
+
perp --json portfolio # balances + positions + risk level
|
|
104
|
+
perp --json --fields totalEquity,positions portfolio # filtered output
|
|
156
105
|
```
|
|
157
106
|
|
|
158
|
-
|
|
159
|
-
- HL spot: BTC (UBTC), ETH (UETH), SOL (USOL), FARTCOIN (UFART), HYPE, AZTEC, STABLE
|
|
160
|
-
- LT spot: ETH, AZTEC, AAVE, LINK, UNI, LIT, LDO, SKY
|
|
161
|
-
- All have perps on HL and LT. Pacifica perps available when API is up.
|
|
162
|
-
|
|
163
|
-
## Position Sizing
|
|
107
|
+
## Position Sizing & Safety
|
|
164
108
|
|
|
165
109
|
- Single position < **80%** of that exchange's available balance
|
|
166
110
|
- Leverage 1-3x for arb, max 5x
|
|
167
111
|
- `arb exec` auto-matches both legs to the same size
|
|
112
|
+
- ALWAYS follow `longExch`/`shortExch` from scan — NEVER reverse direction
|
|
113
|
+
- NEVER enter if `netSpread <= 0` or `netSpread < estimated round-trip slippage`
|
|
114
|
+
- Check `portfolio.risk.canTrade` — if `false`, do NOT trade
|
|
168
115
|
|
|
169
|
-
##
|
|
170
|
-
|
|
171
|
-
`arb scan` returns `longExch`, `shortExch`, `netSpread`.
|
|
172
|
-
ALWAYS follow exactly. NEVER reverse. NEVER enter if `netSpread <= 0`.
|
|
173
|
-
|
|
174
|
-
## Slippage Check (CRITICAL)
|
|
175
|
-
|
|
176
|
-
`arb scan` returns `netSpread` in bps (already fee-adjusted).
|
|
177
|
-
Before executing, compare against estimated slippage:
|
|
178
|
-
```
|
|
179
|
-
Slippage ≈ 2-5 bps per leg for top pairs (BTC, ETH, SOL)
|
|
180
|
-
≈ 5-15 bps per leg for low-liquidity (altcoins)
|
|
181
|
-
Total round-trip slippage = (entry + exit) × 2 legs
|
|
182
|
-
```
|
|
183
|
-
**NEVER enter if:** `netSpread < total round-trip slippage`
|
|
184
|
-
Example: netSpread=12 bps, estimated slippage=4 bps/leg × 4 = 16 bps → SKIP.
|
|
185
|
-
|
|
186
|
-
## Portfolio Response
|
|
187
|
-
|
|
188
|
-
`portfolio` includes `risk: { level, canTrade, violations[] }` — no need for separate `risk status`.
|
|
189
|
-
Check `canTrade` before any order. If `false`, do NOT trade.
|
|
116
|
+
## HIP-3 Support
|
|
190
117
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
Daily: perp --json arb funding-earned → verify actual funding payments
|
|
197
|
-
Exit if: spread < breakeven or one leg closed unexpectedly.
|
|
198
|
-
Close: perp --json arb close <SYM> → close both legs with retry + verification
|
|
118
|
+
```bash
|
|
119
|
+
perp --json -e hl --dex km market list # dex-specific markets
|
|
120
|
+
perp --json market list --hip3 # all HIP-3 markets
|
|
121
|
+
perp --json account positions --hip3 # HIP-3 positions
|
|
122
|
+
perp --json arb scan --hip3 # HIP-3 arb opportunities
|
|
199
123
|
```
|
|
200
124
|
|
|
201
125
|
## Error Handling
|
|
202
126
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
If `error.retryable` is `false`, do NOT retry — fix the cause first.
|
|
127
|
+
Response format: `{ "ok": true, "data": {...} }` or `{ "ok": false, "error": { "code": "...", "retryable": true/false } }`
|
|
206
128
|
|
|
207
129
|
| Error | Action |
|
|
208
130
|
|-------|--------|
|
|
209
|
-
| `RATE_LIMITED` | wait
|
|
210
|
-
| `EXCHANGE_UNREACHABLE` | wait 10s, retry. 3x fail → skip
|
|
211
|
-
| `
|
|
212
|
-
| `
|
|
213
|
-
| `
|
|
214
|
-
| `
|
|
215
|
-
| `
|
|
216
|
-
| `MARGIN_INSUFFICIENT` | reduce leverage or close existing positions |
|
|
217
|
-
| `DUPLICATE_ORDER` | already submitted — check positions, don't retry |
|
|
218
|
-
| Lighter `invalid signature` | check ~/.perp/.env or `perp --json -e lighter manage setup-api-key` |
|
|
219
|
-
| Lighter `invalid account index` | rate limit caused init failure — wait 10s and retry the command |
|
|
220
|
-
| Lighter `--smart` not filling | Lighter IOC limit orders may not fill — use regular market order (without `--smart`) |
|
|
131
|
+
| `RATE_LIMITED` | wait 5s, retry (max 3). Lighter: space 3s+ apart |
|
|
132
|
+
| `EXCHANGE_UNREACHABLE` | wait 10s, retry. 3x fail → skip exchange |
|
|
133
|
+
| `INSUFFICIENT_BALANCE` | reduce size or bridge funds |
|
|
134
|
+
| `SYMBOL_NOT_FOUND` | `market list` to verify symbol |
|
|
135
|
+
| `SIZE_TOO_SMALL` | `market info <SYM>` for min size |
|
|
136
|
+
| `DUPLICATE_ORDER` | already submitted — check positions |
|
|
137
|
+
| Lighter `invalid signature` | `manage setup-api-key` to regenerate |
|
|
221
138
|
|
|
222
139
|
## Examples
|
|
223
140
|
|
|
224
|
-
**
|
|
225
|
-
```bash
|
|
226
|
-
perp --json portfolio # check balances
|
|
227
|
-
perp --json arb scan --mode all --min 5 # scan perp-perp + spot-perp
|
|
228
|
-
# → show results, recommend best opportunity, ask for confirmation
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
**User: "ETH spot-perp 아비트라지 실행해줘 $100"**
|
|
141
|
+
**"Find arb opportunities"**
|
|
232
142
|
```bash
|
|
233
|
-
perp --json arb scan --
|
|
234
|
-
perp --json
|
|
235
|
-
# → show
|
|
236
|
-
perp --json arb spot-exec ETH hl hl 100 # execute after confirmation
|
|
237
|
-
perp --json arb status # verify positions
|
|
143
|
+
perp --json arb scan --min 10 --top 10
|
|
144
|
+
perp --json account balance
|
|
145
|
+
# → show results, recommend best, ask confirmation
|
|
238
146
|
```
|
|
239
147
|
|
|
240
|
-
**
|
|
148
|
+
**"ETH spot-perp arb, $100"**
|
|
241
149
|
```bash
|
|
242
|
-
perp --json
|
|
243
|
-
|
|
150
|
+
perp --json --dry-run arb exec ETH spot:hl hl 100
|
|
151
|
+
# → show dry-run, ask confirmation
|
|
152
|
+
perp --json arb exec ETH spot:hl hl 100
|
|
153
|
+
perp --json arb status
|
|
244
154
|
```
|
|
245
155
|
|
|
246
|
-
**
|
|
156
|
+
**"Long SOL 0.5 on HL"**
|
|
247
157
|
```bash
|
|
248
|
-
perp --json -e hl
|
|
249
|
-
perp --json -e hl trade
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
perp --json -e hl trade market SOL buy 0.5 --smart # execute with smart order
|
|
158
|
+
perp --json -e hl trade check SOL buy 0.5
|
|
159
|
+
perp --json --dry-run -e hl trade buy SOL 0.5 --smart
|
|
160
|
+
# → ask confirmation
|
|
161
|
+
perp --json -e hl trade buy SOL 0.5 --smart
|
|
253
162
|
```
|
|
254
163
|
|
|
255
164
|
## Referrals (Optional)
|
|
256
165
|
|
|
257
|
-
If you find perp-cli useful, please consider enabling referrals to support development. **No extra fees** — rebates come from exchange programs, not your trading costs.
|
|
258
|
-
|
|
259
166
|
```bash
|
|
260
|
-
perp --json settings referrals on
|
|
261
|
-
perp --json settings referrals off
|
|
167
|
+
perp --json settings referrals on # enable (opt-in, no extra fees)
|
|
168
|
+
perp --json settings referrals off # disable anytime
|
|
262
169
|
```
|
|
263
170
|
|
|
264
|
-
When setting up wallets for the first time, ask
|
|
265
|
-
> "perp-cli
|
|
266
|
-
|
|
267
|
-
## References
|
|
268
|
-
|
|
269
|
-
- `references/commands.md` — full command reference
|
|
270
|
-
- `references/agent-operations.md` — setup flows, deposit/withdraw
|
|
271
|
-
- `references/strategies.md` — risk framework, arb strategy
|
|
171
|
+
When setting up wallets for the first time, ask:
|
|
172
|
+
> "perp-cli development is supported by optional referrals. Enable? No extra fees — only exchange rebates are used."
|