perp-cli 0.3.3
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/LICENSE +21 -0
- package/README.md +293 -0
- package/dist/__tests__/alert-logic.test.d.ts +1 -0
- package/dist/__tests__/alert-logic.test.js +107 -0
- package/dist/__tests__/arb-auto-3dex.test.d.ts +1 -0
- package/dist/__tests__/arb-auto-3dex.test.js +397 -0
- package/dist/__tests__/arb-history-stats.test.d.ts +1 -0
- package/dist/__tests__/arb-history-stats.test.js +176 -0
- package/dist/__tests__/arb-logic.test.d.ts +1 -0
- package/dist/__tests__/arb-logic.test.js +84 -0
- package/dist/__tests__/arb-manage.test.d.ts +1 -0
- package/dist/__tests__/arb-manage.test.js +253 -0
- package/dist/__tests__/arb-new-features.test.d.ts +1 -0
- package/dist/__tests__/arb-new-features.test.js +457 -0
- package/dist/__tests__/arb-sizing.test.d.ts +1 -0
- package/dist/__tests__/arb-sizing.test.js +48 -0
- package/dist/__tests__/arb-state.test.d.ts +1 -0
- package/dist/__tests__/arb-state.test.js +284 -0
- package/dist/__tests__/arb-userflow.test.d.ts +1 -0
- package/dist/__tests__/arb-userflow.test.js +945 -0
- package/dist/__tests__/arb-utils.test.d.ts +1 -0
- package/dist/__tests__/arb-utils.test.js +264 -0
- package/dist/__tests__/bot-conditions.test.d.ts +1 -0
- package/dist/__tests__/bot-conditions.test.js +341 -0
- package/dist/__tests__/client-id-tracker.test.d.ts +1 -0
- package/dist/__tests__/client-id-tracker.test.js +137 -0
- package/dist/__tests__/commands/new-atomic-commands.test.d.ts +1 -0
- package/dist/__tests__/commands/new-atomic-commands.test.js +502 -0
- package/dist/__tests__/commands/order-intent.test.d.ts +1 -0
- package/dist/__tests__/commands/order-intent.test.js +600 -0
- package/dist/__tests__/commands/trade-commands.test.d.ts +1 -0
- package/dist/__tests__/commands/trade-commands.test.js +821 -0
- package/dist/__tests__/config.test.d.ts +1 -0
- package/dist/__tests__/config.test.js +86 -0
- package/dist/__tests__/cross-chain-margin.test.d.ts +1 -0
- package/dist/__tests__/cross-chain-margin.test.js +287 -0
- package/dist/__tests__/dex-asset-map.test.d.ts +1 -0
- package/dist/__tests__/dex-asset-map.test.js +191 -0
- package/dist/__tests__/errors.test.d.ts +1 -0
- package/dist/__tests__/errors.test.js +110 -0
- package/dist/__tests__/event-stream.test.d.ts +1 -0
- package/dist/__tests__/event-stream.test.js +276 -0
- package/dist/__tests__/exchanges/interface.test.d.ts +1 -0
- package/dist/__tests__/exchanges/interface.test.js +132 -0
- package/dist/__tests__/exchanges/mock-adapter.d.ts +69 -0
- package/dist/__tests__/exchanges/mock-adapter.js +137 -0
- package/dist/__tests__/execution-log.test.d.ts +1 -0
- package/dist/__tests__/execution-log.test.js +106 -0
- package/dist/__tests__/funding-calc.test.d.ts +1 -0
- package/dist/__tests__/funding-calc.test.js +71 -0
- package/dist/__tests__/funding-history.test.d.ts +1 -0
- package/dist/__tests__/funding-history.test.js +343 -0
- package/dist/__tests__/funding-rates.test.d.ts +1 -0
- package/dist/__tests__/funding-rates.test.js +342 -0
- package/dist/__tests__/funding.test.d.ts +1 -0
- package/dist/__tests__/funding.test.js +173 -0
- package/dist/__tests__/gap-logic.test.d.ts +1 -0
- package/dist/__tests__/gap-logic.test.js +43 -0
- package/dist/__tests__/hip3-dex.test.d.ts +1 -0
- package/dist/__tests__/hip3-dex.test.js +234 -0
- package/dist/__tests__/integration/agent-features.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/agent-features.integration.test.js +553 -0
- package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +13 -0
- package/dist/__tests__/integration/atomic-commands.integration.test.js +246 -0
- package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/bridge-simulation.integration.test.js +453 -0
- package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/bridge-strict.integration.test.js +812 -0
- package/dist/__tests__/integration/bridge.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/bridge.integration.test.js +309 -0
- package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/cli-e2e.integration.test.js +202 -0
- package/dist/__tests__/integration/dex-arb.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/dex-arb.integration.test.js +116 -0
- package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +13 -0
- package/dist/__tests__/integration/envelope-consistency.integration.test.js +205 -0
- package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/hip3-dex.integration.test.js +147 -0
- package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/hyperliquid.integration.test.js +79 -0
- package/dist/__tests__/integration/lighter.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/lighter.integration.test.js +53 -0
- package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +9 -0
- package/dist/__tests__/integration/new-commands-e2e.integration.test.js +236 -0
- package/dist/__tests__/integration/order-verification.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/order-verification.integration.test.js +321 -0
- package/dist/__tests__/integration/pacifica.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/pacifica.integration.test.js +75 -0
- package/dist/__tests__/integration/response-shapes.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/response-shapes.integration.test.js +278 -0
- package/dist/__tests__/liquidity.test.d.ts +1 -0
- package/dist/__tests__/liquidity.test.js +225 -0
- package/dist/__tests__/plan-executor.test.d.ts +1 -0
- package/dist/__tests__/plan-executor.test.js +314 -0
- package/dist/__tests__/position-history.test.d.ts +1 -0
- package/dist/__tests__/position-history.test.js +367 -0
- package/dist/__tests__/retry.test.d.ts +1 -0
- package/dist/__tests__/retry.test.js +310 -0
- package/dist/__tests__/risk-assessment.test.d.ts +1 -0
- package/dist/__tests__/risk-assessment.test.js +145 -0
- package/dist/__tests__/security-adversarial.test.d.ts +1 -0
- package/dist/__tests__/security-adversarial.test.js +574 -0
- package/dist/__tests__/strategies.test.d.ts +1 -0
- package/dist/__tests__/strategies.test.js +539 -0
- package/dist/__tests__/trade-execution.test.d.ts +1 -0
- package/dist/__tests__/trade-execution.test.js +129 -0
- package/dist/__tests__/trade-validator.test.d.ts +1 -0
- package/dist/__tests__/trade-validator.test.js +655 -0
- package/dist/__tests__/utils.test.d.ts +1 -0
- package/dist/__tests__/utils.test.js +76 -0
- package/dist/api/public/hyperliquid.d.ts +18 -0
- package/dist/api/public/hyperliquid.js +82 -0
- package/dist/api/public/index.d.ts +8 -0
- package/dist/api/public/index.js +8 -0
- package/dist/api/public/lighter.d.ts +24 -0
- package/dist/api/public/lighter.js +100 -0
- package/dist/api/public/pacifica.d.ts +17 -0
- package/dist/api/public/pacifica.js +54 -0
- package/dist/api/public/urls.d.ts +12 -0
- package/dist/api/public/urls.js +33 -0
- package/dist/arb/history-stats.d.ts +44 -0
- package/dist/arb/history-stats.js +135 -0
- package/dist/arb/index.d.ts +4 -0
- package/dist/arb/index.js +4 -0
- package/dist/arb/sizing.d.ts +23 -0
- package/dist/arb/sizing.js +96 -0
- package/dist/arb/state.d.ts +51 -0
- package/dist/arb/state.js +112 -0
- package/dist/arb/utils.d.ts +81 -0
- package/dist/arb/utils.js +267 -0
- package/dist/arb-history-stats.d.ts +5 -0
- package/dist/arb-history-stats.js +5 -0
- package/dist/arb-sizing.d.ts +5 -0
- package/dist/arb-sizing.js +5 -0
- package/dist/arb-state.d.ts +5 -0
- package/dist/arb-state.js +5 -0
- package/dist/arb-utils.d.ts +5 -0
- package/dist/arb-utils.js +5 -0
- package/dist/bot/conditions.d.ts +32 -0
- package/dist/bot/conditions.js +141 -0
- package/dist/bot/config.d.ts +76 -0
- package/dist/bot/config.js +160 -0
- package/dist/bot/engine.d.ts +8 -0
- package/dist/bot/engine.js +519 -0
- package/dist/bot/presets.d.ts +11 -0
- package/dist/bot/presets.js +296 -0
- package/dist/bridge-engine.d.ts +133 -0
- package/dist/bridge-engine.js +1487 -0
- package/dist/cache.d.ts +25 -0
- package/dist/cache.js +99 -0
- package/dist/cli-spec.d.ts +50 -0
- package/dist/cli-spec.js +75 -0
- package/dist/client-id-tracker.d.ts +25 -0
- package/dist/client-id-tracker.js +76 -0
- package/dist/commands/account.d.ts +3 -0
- package/dist/commands/account.js +425 -0
- package/dist/commands/agent.d.ts +3 -0
- package/dist/commands/agent.js +386 -0
- package/dist/commands/alert.d.ts +2 -0
- package/dist/commands/alert.js +421 -0
- package/dist/commands/analytics.d.ts +3 -0
- package/dist/commands/analytics.js +311 -0
- package/dist/commands/arb/index.d.ts +3 -0
- package/dist/commands/arb/index.js +921 -0
- package/dist/commands/arb-auto.d.ts +54 -0
- package/dist/commands/arb-auto.js +1328 -0
- package/dist/commands/arb-manage.d.ts +5 -0
- package/dist/commands/arb-manage.js +5 -0
- package/dist/commands/arb.d.ts +2 -0
- package/dist/commands/arb.js +347 -0
- package/dist/commands/backtest.d.ts +2 -0
- package/dist/commands/backtest.js +327 -0
- package/dist/commands/bot.d.ts +3 -0
- package/dist/commands/bot.js +412 -0
- package/dist/commands/bridge.d.ts +2 -0
- package/dist/commands/bridge.js +396 -0
- package/dist/commands/dashboard.d.ts +3 -0
- package/dist/commands/dashboard.js +176 -0
- package/dist/commands/deposit.d.ts +4 -0
- package/dist/commands/deposit.js +573 -0
- package/dist/commands/dex.d.ts +3 -0
- package/dist/commands/dex.js +114 -0
- package/dist/commands/env.d.ts +2 -0
- package/dist/commands/env.js +136 -0
- package/dist/commands/funding.d.ts +2 -0
- package/dist/commands/funding.js +347 -0
- package/dist/commands/gap.d.ts +2 -0
- package/dist/commands/gap.js +305 -0
- package/dist/commands/health.d.ts +2 -0
- package/dist/commands/health.js +67 -0
- package/dist/commands/history.d.ts +2 -0
- package/dist/commands/history.js +235 -0
- package/dist/commands/init.d.ts +15 -0
- package/dist/commands/init.js +266 -0
- package/dist/commands/jobs.d.ts +2 -0
- package/dist/commands/jobs.js +133 -0
- package/dist/commands/manage.d.ts +4 -0
- package/dist/commands/manage.js +309 -0
- package/dist/commands/market.d.ts +3 -0
- package/dist/commands/market.js +225 -0
- package/dist/commands/plan.d.ts +3 -0
- package/dist/commands/plan.js +95 -0
- package/dist/commands/portfolio.d.ts +3 -0
- package/dist/commands/portfolio.js +169 -0
- package/dist/commands/rebalance.d.ts +3 -0
- package/dist/commands/rebalance.js +293 -0
- package/dist/commands/risk.d.ts +3 -0
- package/dist/commands/risk.js +169 -0
- package/dist/commands/run.d.ts +3 -0
- package/dist/commands/run.js +202 -0
- package/dist/commands/settings.d.ts +2 -0
- package/dist/commands/settings.js +102 -0
- package/dist/commands/stream.d.ts +5 -0
- package/dist/commands/stream.js +123 -0
- package/dist/commands/trade.d.ts +3 -0
- package/dist/commands/trade.js +1273 -0
- package/dist/commands/wallet.d.ts +14 -0
- package/dist/commands/wallet.js +602 -0
- package/dist/commands/withdraw.d.ts +3 -0
- package/dist/commands/withdraw.js +187 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +68 -0
- package/dist/cross-chain-margin.d.ts +46 -0
- package/dist/cross-chain-margin.js +107 -0
- package/dist/dashboard/server.d.ts +80 -0
- package/dist/dashboard/server.js +340 -0
- package/dist/dashboard/ui.d.ts +4 -0
- package/dist/dashboard/ui.js +538 -0
- package/dist/dashboard/ws-feeds.d.ts +29 -0
- package/dist/dashboard/ws-feeds.js +660 -0
- package/dist/dex-asset-map.d.ts +80 -0
- package/dist/dex-asset-map.js +201 -0
- package/dist/errors.d.ts +109 -0
- package/dist/errors.js +84 -0
- package/dist/event-stream.d.ts +25 -0
- package/dist/event-stream.js +168 -0
- package/dist/exchanges/hyperliquid.d.ts +212 -0
- package/dist/exchanges/hyperliquid.js +931 -0
- package/dist/exchanges/interface.d.ts +95 -0
- package/dist/exchanges/interface.js +5 -0
- package/dist/exchanges/lighter.d.ts +159 -0
- package/dist/exchanges/lighter.js +793 -0
- package/dist/exchanges/pacifica.d.ts +51 -0
- package/dist/exchanges/pacifica.js +248 -0
- package/dist/execution-log.d.ts +36 -0
- package/dist/execution-log.js +102 -0
- package/dist/funding/history.d.ts +63 -0
- package/dist/funding/history.js +266 -0
- package/dist/funding/index.d.ts +3 -0
- package/dist/funding/index.js +3 -0
- package/dist/funding/normalize.d.ts +39 -0
- package/dist/funding/normalize.js +66 -0
- package/dist/funding/rates.d.ts +45 -0
- package/dist/funding/rates.js +172 -0
- package/dist/funding-history.d.ts +5 -0
- package/dist/funding-history.js +5 -0
- package/dist/funding-rates.d.ts +5 -0
- package/dist/funding-rates.js +5 -0
- package/dist/funding.d.ts +5 -0
- package/dist/funding.js +5 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +458 -0
- package/dist/jobs.d.ts +37 -0
- package/dist/jobs.js +152 -0
- package/dist/liquidity.d.ts +34 -0
- package/dist/liquidity.js +100 -0
- package/dist/mcp-server.d.ts +9 -0
- package/dist/mcp-server.js +1206 -0
- package/dist/pacifica/client.d.ts +111 -0
- package/dist/pacifica/client.js +310 -0
- package/dist/pacifica/constants.d.ts +27 -0
- package/dist/pacifica/constants.js +47 -0
- package/dist/pacifica/deposit.d.ts +14 -0
- package/dist/pacifica/deposit.js +78 -0
- package/dist/pacifica/index.d.ts +6 -0
- package/dist/pacifica/index.js +11 -0
- package/dist/pacifica/signing.d.ts +49 -0
- package/dist/pacifica/signing.js +97 -0
- package/dist/pacifica/types/account.d.ts +42 -0
- package/dist/pacifica/types/account.js +1 -0
- package/dist/pacifica/types/index.d.ts +6 -0
- package/dist/pacifica/types/index.js +6 -0
- package/dist/pacifica/types/lake.d.ts +18 -0
- package/dist/pacifica/types/lake.js +1 -0
- package/dist/pacifica/types/market.d.ts +64 -0
- package/dist/pacifica/types/market.js +1 -0
- package/dist/pacifica/types/order.d.ts +92 -0
- package/dist/pacifica/types/order.js +1 -0
- package/dist/pacifica/types/position.d.ts +25 -0
- package/dist/pacifica/types/position.js +1 -0
- package/dist/pacifica/types/ws.d.ts +34 -0
- package/dist/pacifica/types/ws.js +41 -0
- package/dist/pacifica/ws-client.d.ts +42 -0
- package/dist/pacifica/ws-client.js +180 -0
- package/dist/plan-executor.d.ts +48 -0
- package/dist/plan-executor.js +280 -0
- package/dist/position-history.d.ts +68 -0
- package/dist/position-history.js +222 -0
- package/dist/rebalance.d.ts +64 -0
- package/dist/rebalance.js +142 -0
- package/dist/retry.d.ts +74 -0
- package/dist/retry.js +129 -0
- package/dist/risk.d.ts +48 -0
- package/dist/risk.js +156 -0
- package/dist/settings.d.ts +19 -0
- package/dist/settings.js +45 -0
- package/dist/shared-api.d.ts +5 -0
- package/dist/shared-api.js +5 -0
- package/dist/strategies/dca.d.ts +25 -0
- package/dist/strategies/dca.js +114 -0
- package/dist/strategies/funding-arb.d.ts +15 -0
- package/dist/strategies/funding-arb.js +281 -0
- package/dist/strategies/grid.d.ts +34 -0
- package/dist/strategies/grid.js +185 -0
- package/dist/strategies/trailing-stop.d.ts +17 -0
- package/dist/strategies/trailing-stop.js +121 -0
- package/dist/strategies/twap.d.ts +20 -0
- package/dist/strategies/twap.js +78 -0
- package/dist/trade-validator.d.ts +39 -0
- package/dist/trade-validator.js +154 -0
- package/dist/utils.d.ts +38 -0
- package/dist/utils.js +110 -0
- package/package.json +63 -0
- package/skills/perp-cli/SKILL.md +149 -0
- package/skills/perp-cli/references/commands.md +143 -0
|
@@ -0,0 +1,1206 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MCP (Model Context Protocol) server for perp-cli.
|
|
4
|
+
*
|
|
5
|
+
* Read-only advisor mode: provides market data, account info, and CLI command suggestions.
|
|
6
|
+
* Does NOT execute trades directly — instead suggests CLI commands for the user to run.
|
|
7
|
+
* Adapters are created lazily from environment variables.
|
|
8
|
+
*/
|
|
9
|
+
import "dotenv/config";
|
|
10
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
11
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import { loadPrivateKey, parseSolanaKeypair } from "./config.js";
|
|
14
|
+
import { PacificaAdapter } from "./exchanges/pacifica.js";
|
|
15
|
+
import { HyperliquidAdapter } from "./exchanges/hyperliquid.js";
|
|
16
|
+
import { LighterAdapter } from "./exchanges/lighter.js";
|
|
17
|
+
import { fetchPacificaPrices, fetchHyperliquidMeta, fetchLighterOrderBookDetails, pingPacifica, pingHyperliquid, pingLighter, } from "./shared-api.js";
|
|
18
|
+
import { fetchAllFundingRates } from "./funding-rates.js";
|
|
19
|
+
// ── Adapter cache & factory ──
|
|
20
|
+
const adapters = new Map();
|
|
21
|
+
async function getOrCreateAdapter(exchange) {
|
|
22
|
+
const key = exchange.toLowerCase();
|
|
23
|
+
if (adapters.has(key))
|
|
24
|
+
return adapters.get(key);
|
|
25
|
+
const pk = await loadPrivateKey(key);
|
|
26
|
+
let adapter;
|
|
27
|
+
switch (key) {
|
|
28
|
+
case "pacifica": {
|
|
29
|
+
const keypair = parseSolanaKeypair(pk);
|
|
30
|
+
adapter = new PacificaAdapter(keypair);
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
case "hyperliquid": {
|
|
34
|
+
const hl = new HyperliquidAdapter(pk);
|
|
35
|
+
await hl.init();
|
|
36
|
+
adapter = hl;
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
case "lighter": {
|
|
40
|
+
const lt = new LighterAdapter(pk);
|
|
41
|
+
await lt.init();
|
|
42
|
+
adapter = lt;
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
default:
|
|
46
|
+
throw new Error(`Unknown exchange: ${exchange}. Supported: pacifica, hyperliquid, lighter`);
|
|
47
|
+
}
|
|
48
|
+
adapters.set(key, adapter);
|
|
49
|
+
return adapter;
|
|
50
|
+
}
|
|
51
|
+
// ── JSON envelope helpers ──
|
|
52
|
+
function ok(data, meta) {
|
|
53
|
+
return JSON.stringify({ ok: true, data, meta }, null, 2);
|
|
54
|
+
}
|
|
55
|
+
function err(error, meta) {
|
|
56
|
+
return JSON.stringify({ ok: false, error, meta }, null, 2);
|
|
57
|
+
}
|
|
58
|
+
// ── MCP Server ──
|
|
59
|
+
const server = new McpServer({ name: "perp-cli", version: "0.3.3" }, { capabilities: { tools: {}, resources: {} } });
|
|
60
|
+
// ============================================================
|
|
61
|
+
// Market Data tools (read-only, no private key needed)
|
|
62
|
+
// ============================================================
|
|
63
|
+
server.tool("get_markets", "Get all available perpetual futures markets on an exchange, including price, funding rate, volume, and max leverage", { exchange: z.string().describe("Exchange name: pacifica, hyperliquid, or lighter") }, async ({ exchange }) => {
|
|
64
|
+
try {
|
|
65
|
+
const adapter = await getOrCreateAdapter(exchange);
|
|
66
|
+
const markets = await adapter.getMarkets();
|
|
67
|
+
return { content: [{ type: "text", text: ok(markets, { exchange, count: markets.length }) }] };
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e), { exchange }) }], isError: true };
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
server.tool("get_orderbook", "Get the order book (bids and asks) for a symbol on an exchange", {
|
|
74
|
+
exchange: z.string().describe("Exchange name: pacifica, hyperliquid, or lighter"),
|
|
75
|
+
symbol: z.string().describe("Trading pair symbol, e.g. BTC, ETH, SOL"),
|
|
76
|
+
}, async ({ exchange, symbol }) => {
|
|
77
|
+
try {
|
|
78
|
+
const adapter = await getOrCreateAdapter(exchange);
|
|
79
|
+
const book = await adapter.getOrderbook(symbol);
|
|
80
|
+
return { content: [{ type: "text", text: ok(book, { exchange, symbol }) }] };
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e), { exchange, symbol }) }], isError: true };
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
server.tool("get_funding_rates", "Compare funding rates across all 3 exchanges (Pacifica, Hyperliquid, Lighter). Returns rates per symbol with spread analysis", {
|
|
87
|
+
symbols: z
|
|
88
|
+
.array(z.string())
|
|
89
|
+
.optional()
|
|
90
|
+
.describe("Filter to specific symbols (e.g. ['BTC','ETH']). Omit for all available"),
|
|
91
|
+
minSpread: z.number().optional().describe("Minimum annualized spread % to include (default: 0)"),
|
|
92
|
+
}, async ({ symbols, minSpread }) => {
|
|
93
|
+
try {
|
|
94
|
+
const snapshot = await fetchAllFundingRates({ symbols, minSpread });
|
|
95
|
+
return {
|
|
96
|
+
content: [{
|
|
97
|
+
type: "text",
|
|
98
|
+
text: ok(snapshot, { symbolCount: snapshot.symbols.length, exchangeStatus: snapshot.exchangeStatus }),
|
|
99
|
+
}],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e)) }], isError: true };
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
server.tool("get_prices", "Get cross-exchange prices for symbols. Fetches mark prices from all 3 exchanges for comparison", {
|
|
107
|
+
symbols: z
|
|
108
|
+
.array(z.string())
|
|
109
|
+
.optional()
|
|
110
|
+
.describe("Symbols to fetch prices for (e.g. ['BTC','ETH']). Omit for top assets"),
|
|
111
|
+
}, async ({ symbols }) => {
|
|
112
|
+
try {
|
|
113
|
+
const [pacifica, hl, lighter] = await Promise.all([
|
|
114
|
+
fetchPacificaPrices(),
|
|
115
|
+
fetchHyperliquidMeta(),
|
|
116
|
+
fetchLighterOrderBookDetails(),
|
|
117
|
+
]);
|
|
118
|
+
const filter = symbols ? new Set(symbols.map(s => s.toUpperCase())) : null;
|
|
119
|
+
// Build symbol → exchange prices map
|
|
120
|
+
const priceMap = new Map();
|
|
121
|
+
for (const p of pacifica) {
|
|
122
|
+
const sym = p.symbol.toUpperCase();
|
|
123
|
+
if (filter && !filter.has(sym))
|
|
124
|
+
continue;
|
|
125
|
+
if (!priceMap.has(sym))
|
|
126
|
+
priceMap.set(sym, {});
|
|
127
|
+
priceMap.get(sym).pacifica = p.mark;
|
|
128
|
+
}
|
|
129
|
+
for (const a of hl) {
|
|
130
|
+
const sym = a.symbol.toUpperCase();
|
|
131
|
+
if (filter && !filter.has(sym))
|
|
132
|
+
continue;
|
|
133
|
+
if (!priceMap.has(sym))
|
|
134
|
+
priceMap.set(sym, {});
|
|
135
|
+
priceMap.get(sym).hyperliquid = a.markPx;
|
|
136
|
+
}
|
|
137
|
+
for (const m of lighter) {
|
|
138
|
+
const sym = m.symbol.toUpperCase();
|
|
139
|
+
if (filter && !filter.has(sym))
|
|
140
|
+
continue;
|
|
141
|
+
if (!priceMap.has(sym))
|
|
142
|
+
priceMap.set(sym, {});
|
|
143
|
+
priceMap.get(sym).lighter = m.lastTradePrice;
|
|
144
|
+
}
|
|
145
|
+
const data = Array.from(priceMap.entries()).map(([symbol, prices]) => ({ symbol, prices }));
|
|
146
|
+
return { content: [{ type: "text", text: ok(data, { count: data.length }) }] };
|
|
147
|
+
}
|
|
148
|
+
catch (e) {
|
|
149
|
+
return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e)) }], isError: true };
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
// ============================================================
|
|
153
|
+
// Account tools (need private key)
|
|
154
|
+
// ============================================================
|
|
155
|
+
server.tool("get_balance", "Get account balance (equity, available margin, margin used, unrealized PnL) on an exchange", { exchange: z.string().describe("Exchange name: pacifica, hyperliquid, or lighter") }, async ({ exchange }) => {
|
|
156
|
+
try {
|
|
157
|
+
const adapter = await getOrCreateAdapter(exchange);
|
|
158
|
+
const balance = await adapter.getBalance();
|
|
159
|
+
return { content: [{ type: "text", text: ok(balance, { exchange }) }] };
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e), { exchange }) }], isError: true };
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
server.tool("get_positions", "Get all open positions on an exchange, including size, entry price, PnL, leverage", { exchange: z.string().describe("Exchange name: pacifica, hyperliquid, or lighter") }, async ({ exchange }) => {
|
|
166
|
+
try {
|
|
167
|
+
const adapter = await getOrCreateAdapter(exchange);
|
|
168
|
+
const positions = await adapter.getPositions();
|
|
169
|
+
return { content: [{ type: "text", text: ok(positions, { exchange, count: positions.length }) }] };
|
|
170
|
+
}
|
|
171
|
+
catch (e) {
|
|
172
|
+
return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e), { exchange }) }], isError: true };
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
server.tool("get_open_orders", "Get all open/pending orders on an exchange", { exchange: z.string().describe("Exchange name: pacifica, hyperliquid, or lighter") }, async ({ exchange }) => {
|
|
176
|
+
try {
|
|
177
|
+
const adapter = await getOrCreateAdapter(exchange);
|
|
178
|
+
const orders = await adapter.getOpenOrders();
|
|
179
|
+
return { content: [{ type: "text", text: ok(orders, { exchange, count: orders.length }) }] };
|
|
180
|
+
}
|
|
181
|
+
catch (e) {
|
|
182
|
+
return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e), { exchange }) }], isError: true };
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
server.tool("portfolio", "Cross-exchange portfolio summary: balances, positions, and risk metrics across all exchanges", {}, async () => {
|
|
186
|
+
const EXCHANGES = ["pacifica", "hyperliquid", "lighter"];
|
|
187
|
+
const snapshots = await Promise.all(EXCHANGES.map(async (name) => {
|
|
188
|
+
try {
|
|
189
|
+
const adapter = await getOrCreateAdapter(name);
|
|
190
|
+
const [balance, positions, orders] = await Promise.all([
|
|
191
|
+
adapter.getBalance(),
|
|
192
|
+
adapter.getPositions(),
|
|
193
|
+
adapter.getOpenOrders(),
|
|
194
|
+
]);
|
|
195
|
+
return { exchange: name, connected: true, balance, positions, openOrders: orders.length };
|
|
196
|
+
}
|
|
197
|
+
catch (e) {
|
|
198
|
+
return {
|
|
199
|
+
exchange: name,
|
|
200
|
+
connected: false,
|
|
201
|
+
balance: null,
|
|
202
|
+
positions: [],
|
|
203
|
+
openOrders: 0,
|
|
204
|
+
error: e instanceof Error ? e.message : String(e),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
}));
|
|
208
|
+
let totalEquity = 0;
|
|
209
|
+
let totalAvailable = 0;
|
|
210
|
+
let totalMarginUsed = 0;
|
|
211
|
+
let totalUnrealizedPnl = 0;
|
|
212
|
+
let totalPositions = 0;
|
|
213
|
+
let totalOpenOrders = 0;
|
|
214
|
+
const allPositions = [];
|
|
215
|
+
for (const snap of snapshots) {
|
|
216
|
+
if (snap.balance) {
|
|
217
|
+
totalEquity += Number(snap.balance.equity);
|
|
218
|
+
totalAvailable += Number(snap.balance.available);
|
|
219
|
+
totalMarginUsed += Number(snap.balance.marginUsed);
|
|
220
|
+
totalUnrealizedPnl += Number(snap.balance.unrealizedPnl);
|
|
221
|
+
}
|
|
222
|
+
totalPositions += snap.positions.length;
|
|
223
|
+
totalOpenOrders += snap.openOrders;
|
|
224
|
+
for (const pos of snap.positions) {
|
|
225
|
+
allPositions.push({ ...pos, exchange: snap.exchange });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const marginUtilization = totalEquity > 0 ? (totalMarginUsed / totalEquity) * 100 : 0;
|
|
229
|
+
let largestPosition = null;
|
|
230
|
+
for (const pos of allPositions) {
|
|
231
|
+
const notional = Math.abs(Number(pos.size) * Number(pos.markPrice));
|
|
232
|
+
if (!largestPosition || notional > largestPosition.notional) {
|
|
233
|
+
largestPosition = { symbol: pos.symbol, exchange: pos.exchange, notional };
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const exchangeConcentration = snapshots
|
|
237
|
+
.filter(s => s.balance && Number(s.balance.equity) > 0)
|
|
238
|
+
.map(s => ({
|
|
239
|
+
exchange: s.exchange,
|
|
240
|
+
pct: totalEquity > 0 ? (Number(s.balance.equity) / totalEquity) * 100 : 0,
|
|
241
|
+
}))
|
|
242
|
+
.sort((a, b) => b.pct - a.pct);
|
|
243
|
+
const summary = {
|
|
244
|
+
totalEquity,
|
|
245
|
+
totalAvailable,
|
|
246
|
+
totalMarginUsed,
|
|
247
|
+
totalUnrealizedPnl,
|
|
248
|
+
totalPositions,
|
|
249
|
+
totalOpenOrders,
|
|
250
|
+
exchanges: snapshots,
|
|
251
|
+
positions: allPositions,
|
|
252
|
+
riskMetrics: { marginUtilization, largestPosition, exchangeConcentration },
|
|
253
|
+
};
|
|
254
|
+
return { content: [{ type: "text", text: ok(summary) }] };
|
|
255
|
+
});
|
|
256
|
+
// ============================================================
|
|
257
|
+
// Advisory tools (suggest CLI commands, do NOT execute trades)
|
|
258
|
+
// ============================================================
|
|
259
|
+
server.tool("suggest_command", "Given a natural language trading goal, suggest the exact perp CLI commands to run. Does NOT execute anything — only returns commands for the user to review and run manually", {
|
|
260
|
+
goal: z.string().describe("Natural language goal, e.g. 'buy 0.1 BTC on pacifica', 'close all positions', 'check funding arb opportunities'"),
|
|
261
|
+
exchange: z.string().optional().describe("Preferred exchange (default: pacifica). Options: pacifica, hyperliquid, lighter"),
|
|
262
|
+
}, async ({ goal, exchange }) => {
|
|
263
|
+
try {
|
|
264
|
+
const ex = exchange ?? "pacifica";
|
|
265
|
+
const g = goal.toLowerCase();
|
|
266
|
+
const steps = [];
|
|
267
|
+
if (g.includes("buy") || g.includes("long")) {
|
|
268
|
+
const symbol = extractSymbol(g) || "BTC";
|
|
269
|
+
const size = extractNumber(g) || "<size>";
|
|
270
|
+
steps.push({ step: 1, command: `perp -e ${ex} --json market book ${symbol}`, description: `Check ${symbol} orderbook and liquidity` }, { step: 2, command: `perp -e ${ex} --json account info`, description: "Check available balance and margin" }, { step: 3, command: `perp -e ${ex} --json trade check ${symbol} buy ${size}`, description: "Pre-flight validation (dry run)" }, { step: 4, command: `perp -e ${ex} --json trade market ${symbol} buy ${size}`, description: `Buy ${size} ${symbol} at market`, dangerous: true }, { step: 5, command: `perp -e ${ex} --json account positions`, description: "Verify position opened" });
|
|
271
|
+
}
|
|
272
|
+
else if (g.includes("sell") || g.includes("short")) {
|
|
273
|
+
const symbol = extractSymbol(g) || "BTC";
|
|
274
|
+
const size = extractNumber(g) || "<size>";
|
|
275
|
+
steps.push({ step: 1, command: `perp -e ${ex} --json market book ${symbol}`, description: `Check ${symbol} orderbook and liquidity` }, { step: 2, command: `perp -e ${ex} --json account info`, description: "Check available balance and margin" }, { step: 3, command: `perp -e ${ex} --json trade check ${symbol} sell ${size}`, description: "Pre-flight validation (dry run)" }, { step: 4, command: `perp -e ${ex} --json trade market ${symbol} sell ${size}`, description: `Sell ${size} ${symbol} at market`, dangerous: true }, { step: 5, command: `perp -e ${ex} --json account positions`, description: "Verify position opened" });
|
|
276
|
+
}
|
|
277
|
+
else if (g.includes("limit")) {
|
|
278
|
+
const symbol = extractSymbol(g) || "BTC";
|
|
279
|
+
const size = extractNumber(g) || "<size>";
|
|
280
|
+
const side = g.includes("sell") || g.includes("short") ? "sell" : "buy";
|
|
281
|
+
steps.push({ step: 1, command: `perp -e ${ex} --json market book ${symbol}`, description: `Check ${symbol} orderbook for price levels` }, { step: 2, command: `perp -e ${ex} --json account info`, description: "Check available balance" }, { step: 3, command: `perp -e ${ex} --json trade limit ${symbol} ${side} <price> ${size}`, description: `Place limit ${side} order`, dangerous: true }, { step: 4, command: `perp -e ${ex} --json account orders`, description: "Verify order placed" });
|
|
282
|
+
}
|
|
283
|
+
else if (g.includes("close") || g.includes("exit") || g.includes("flatten")) {
|
|
284
|
+
const symbol = extractSymbol(g);
|
|
285
|
+
steps.push({ step: 1, command: `perp -e ${ex} --json account positions`, description: "Get current positions" });
|
|
286
|
+
if (g.includes("flatten")) {
|
|
287
|
+
steps.push({ step: 2, command: `perp -e ${ex} --json trade flatten`, description: "Cancel all orders + close all positions", dangerous: true });
|
|
288
|
+
}
|
|
289
|
+
else if (symbol) {
|
|
290
|
+
steps.push({ step: 2, command: `perp -e ${ex} --json trade close ${symbol}`, description: `Close ${symbol} position at market`, dangerous: true });
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
steps.push({ step: 2, command: `perp -e ${ex} --json trade cancel-all`, description: "Cancel any open orders first", dangerous: true }, { step: 3, command: `perp -e ${ex} --json trade close-all`, description: "Close all positions at market", dangerous: true });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
else if (g.includes("tp") || g.includes("take profit") || g.includes("scale-tp")) {
|
|
297
|
+
const symbol = extractSymbol(g) || "<symbol>";
|
|
298
|
+
steps.push({ step: 1, command: `perp -e ${ex} --json account positions`, description: "Check current position size and entry" }, { step: 2, command: `perp -e ${ex} --json market book ${symbol}`, description: "Check current prices" }, { step: 3, command: `perp -e ${ex} --json trade scale-tp ${symbol} --levels '<price1>:25%,<price2>:50%,<price3>:25%'`, description: "Place scaled take-profit orders", dangerous: true });
|
|
299
|
+
}
|
|
300
|
+
else if (g.includes("scale-in") || g.includes("scale in") || g.includes("dca in")) {
|
|
301
|
+
const symbol = extractSymbol(g) || "<symbol>";
|
|
302
|
+
const side = g.includes("sell") || g.includes("short") ? "sell" : "buy";
|
|
303
|
+
steps.push({ step: 1, command: `perp -e ${ex} --json market book ${symbol}`, description: "Check current prices" }, { step: 2, command: `perp -e ${ex} --json account info`, description: "Check available balance" }, { step: 3, command: `perp -e ${ex} --json trade scale-in ${symbol} ${side} --levels '<price1>:<size>,<price2>:<size>'`, description: "Place scaled entry orders at multiple levels", dangerous: true });
|
|
304
|
+
}
|
|
305
|
+
else if (g.includes("tpsl") || g.includes("tp/sl") || g.includes("tp sl") || (g.includes("take profit") && g.includes("stop loss"))) {
|
|
306
|
+
const symbol = extractSymbol(g) || "<symbol>";
|
|
307
|
+
const side = g.includes("sell") || g.includes("short") ? "sell" : "buy";
|
|
308
|
+
steps.push({ step: 1, command: `perp -e ${ex} --json account positions`, description: "Check current position" }, { step: 2, command: `perp -e ${ex} --json trade tpsl ${symbol} ${side} --tp <price> --sl <price>`, description: "Set TP/SL bracket orders", dangerous: true }, { step: 3, command: `perp -e ${ex} --json account orders`, description: "Verify TP/SL orders placed" });
|
|
309
|
+
}
|
|
310
|
+
else if (g.includes("trailing") || g.includes("trail")) {
|
|
311
|
+
const symbol = extractSymbol(g) || "<symbol>";
|
|
312
|
+
steps.push({ step: 1, command: `perp -e ${ex} --json account positions`, description: "Check current position" }, { step: 2, command: `perp -e ${ex} --json trade trailing-stop ${symbol}`, description: "Set trailing stop order", dangerous: true });
|
|
313
|
+
}
|
|
314
|
+
else if (g.includes("stop") || g.includes("sl") || g.includes("stop loss")) {
|
|
315
|
+
const symbol = extractSymbol(g) || "<symbol>";
|
|
316
|
+
steps.push({ step: 1, command: `perp -e ${ex} --json account positions`, description: "Check current position" }, { step: 2, command: `perp -e ${ex} --json trade stop ${symbol} <side> <stopPrice> <size>`, description: "Place stop order", dangerous: true });
|
|
317
|
+
}
|
|
318
|
+
else if (g.includes("twap")) {
|
|
319
|
+
const symbol = extractSymbol(g) || "<symbol>";
|
|
320
|
+
const size = extractNumber(g) || "<size>";
|
|
321
|
+
const side = g.includes("sell") || g.includes("short") ? "sell" : "buy";
|
|
322
|
+
steps.push({ step: 1, command: `perp -e ${ex} --json account info`, description: "Check available balance" }, { step: 2, command: `perp -e ${ex} --json trade twap ${symbol} ${side} ${size} <duration>`, description: `TWAP ${side} ${size} ${symbol} over duration`, dangerous: true });
|
|
323
|
+
}
|
|
324
|
+
else if (g.includes("reduce")) {
|
|
325
|
+
const symbol = extractSymbol(g) || "<symbol>";
|
|
326
|
+
const pct = extractNumber(g) || "<percent>";
|
|
327
|
+
steps.push({ step: 1, command: `perp -e ${ex} --json account positions`, description: "Check current position size" }, { step: 2, command: `perp -e ${ex} --json trade reduce ${symbol} ${pct}`, description: `Reduce ${symbol} position by ${pct}%`, dangerous: true });
|
|
328
|
+
}
|
|
329
|
+
else if (g.includes("edit") || g.includes("modify order")) {
|
|
330
|
+
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 });
|
|
331
|
+
}
|
|
332
|
+
else if (g.includes("arb") || g.includes("arbitrage")) {
|
|
333
|
+
steps.push({ step: 1, command: "perp --json arb rates", description: "Compare funding rates across exchanges" }, { step: 2, command: "perp --json arb scan", description: "Find high-spread opportunities" }, { step: 3, command: "perp --json gap show", description: "Check cross-exchange price gaps" }, { step: 4, command: "perp --json arb dex", description: "HIP-3 cross-dex arb opportunities (Hyperliquid)" });
|
|
334
|
+
}
|
|
335
|
+
else if (g.includes("funding")) {
|
|
336
|
+
const symbol = extractSymbol(g);
|
|
337
|
+
if (symbol) {
|
|
338
|
+
steps.push({ step: 1, command: `perp --json funding compare ${symbol}`, description: `Compare ${symbol} funding rates` }, { step: 2, command: `perp -e ${ex} --json market funding ${symbol}`, description: `${symbol} funding history` });
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
steps.push({ step: 1, command: "perp --json funding rates", description: "Funding rates across all exchanges" }, { step: 2, command: "perp --json funding spread", description: "Funding rate spreads" });
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
else if (g.includes("bridge") || g.includes("transfer") || g.includes("cross-chain")) {
|
|
345
|
+
const amount = extractNumber(g) || "<amount>";
|
|
346
|
+
steps.push({ step: 1, command: "perp --json bridge chains", description: "List supported chains" }, { step: 2, command: `perp --json bridge quote --from <chain> --to <chain> --amount ${amount}`, description: "Get bridge quote with fees" }, { step: 3, command: `perp --json bridge send --from <chain> --to <chain> --amount ${amount}`, description: "Execute bridge transfer", dangerous: true }, { step: 4, command: "perp --json bridge status <orderId>", description: "Track bridge completion" });
|
|
347
|
+
}
|
|
348
|
+
else if (g.includes("alert") || g.includes("notify") || g.includes("alarm")) {
|
|
349
|
+
steps.push({ step: 1, command: "perp --json alert list", description: "List existing alerts" }, { step: 2, command: "perp --json alert add", description: "Add a new price/funding/pnl/liquidation alert" }, { step: 3, command: "perp --json alert daemon", description: "Start alert monitoring daemon" });
|
|
350
|
+
}
|
|
351
|
+
else if (g.includes("grid") || g.includes("dca") || g.includes("bot")) {
|
|
352
|
+
const symbol = extractSymbol(g) || "<symbol>";
|
|
353
|
+
if (g.includes("grid")) {
|
|
354
|
+
steps.push({ step: 1, command: `perp -e ${ex} --json market book ${symbol}`, description: "Check current price" }, { step: 2, command: `perp -e ${ex} --json bot quick-grid ${symbol}`, description: "Start grid bot", dangerous: true }, { step: 3, command: "perp --json jobs list", description: "Verify bot is running" });
|
|
355
|
+
}
|
|
356
|
+
else if (g.includes("dca")) {
|
|
357
|
+
const amount = extractNumber(g) || "<amount>";
|
|
358
|
+
const side = g.includes("sell") || g.includes("short") ? "sell" : "buy";
|
|
359
|
+
steps.push({ step: 1, command: `perp -e ${ex} --json account info`, description: "Check available balance" }, { step: 2, command: `perp -e ${ex} --json bot quick-dca ${symbol} ${side} ${amount} <interval>`, description: "Start DCA bot", dangerous: true }, { step: 3, command: "perp --json jobs list", description: "Verify bot is running" });
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
steps.push({ step: 1, command: "perp --json bot preset-list", description: "List available bot presets" }, { step: 2, command: `perp -e ${ex} --json bot quick-arb`, description: "Start arb bot", dangerous: true }, { step: 3, command: "perp --json jobs list", description: "Verify bot is running" });
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
else if (g.includes("backtest")) {
|
|
366
|
+
if (g.includes("grid")) {
|
|
367
|
+
steps.push({ step: 1, command: "perp --json backtest grid", description: "Backtest grid strategy on historical data" });
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
steps.push({ step: 1, command: "perp --json backtest funding-arb", description: "Backtest funding arb strategy" });
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
else if (g.includes("setup") || g.includes("init") || g.includes("configure") || g.includes("connect") || g.includes("set key") || g.includes("set wallet") || g.includes("private key")) {
|
|
374
|
+
steps.push({ step: 1, command: "perp --json wallet show", description: "Check if any wallets are already configured" }, { step: 2, command: "perp --json wallet set <exchange> <privateKey>", description: "Set private key for exchange (exchange: pacifica, hyperliquid, lighter, or aliases: hl, pac, lt)" }, { step: 3, command: "perp --json wallet set <exchange> <privateKey> --default", description: "Set key + make it the default exchange" }, { step: 4, command: "perp --json wallet show", description: "Verify wallet is configured (shows public address)" });
|
|
375
|
+
}
|
|
376
|
+
else if (g.includes("wallet") || g.includes("balance") || g.includes("on-chain")) {
|
|
377
|
+
steps.push({ step: 1, command: "perp --json wallet show", description: "Show configured wallets with public addresses" }, { step: 2, command: "perp --json wallet balance", description: "Check on-chain balances" });
|
|
378
|
+
}
|
|
379
|
+
else if (g.includes("risk")) {
|
|
380
|
+
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" });
|
|
381
|
+
}
|
|
382
|
+
else if (g.includes("analytic") || g.includes("performance") || g.includes("pnl") || g.includes("p&l")) {
|
|
383
|
+
steps.push({ step: 1, command: `perp -e ${ex} --json analytics summary`, description: "Trading performance summary" }, { step: 2, command: `perp -e ${ex} --json analytics pnl`, description: "P&L breakdown" }, { step: 3, command: `perp -e ${ex} --json analytics funding`, description: "Funding payment history" });
|
|
384
|
+
}
|
|
385
|
+
else if (g.includes("history") || g.includes("log") || g.includes("audit")) {
|
|
386
|
+
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" });
|
|
387
|
+
}
|
|
388
|
+
else if (g.includes("stream") || g.includes("live") || g.includes("watch") || g.includes("realtime")) {
|
|
389
|
+
const symbol = extractSymbol(g);
|
|
390
|
+
if (symbol) {
|
|
391
|
+
steps.push({ step: 1, command: `perp -e ${ex} stream book ${symbol}`, description: `Live ${symbol} orderbook` }, { step: 2, command: `perp -e ${ex} stream trades ${symbol}`, description: `Live ${symbol} trades` });
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
steps.push({ step: 1, command: `perp -e ${ex} stream prices`, description: "Live price stream" }, { step: 2, command: `perp -e ${ex} stream events`, description: "Live account events (fills, liquidations)" });
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
else if (g.includes("gap")) {
|
|
398
|
+
steps.push({ step: 1, command: "perp --json gap show", description: "Cross-exchange price gaps" }, { step: 2, command: "perp --json gap watch", description: "Live gap monitor" });
|
|
399
|
+
}
|
|
400
|
+
else if (g.includes("rebalance")) {
|
|
401
|
+
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 });
|
|
402
|
+
}
|
|
403
|
+
else if (g.includes("job") || g.includes("running") || g.includes("background")) {
|
|
404
|
+
steps.push({ step: 1, command: "perp --json jobs list", description: "List running background jobs" });
|
|
405
|
+
}
|
|
406
|
+
else if (g.includes("dex") || g.includes("hip-3") || g.includes("hip3")) {
|
|
407
|
+
steps.push({ step: 1, command: "perp --json dex list", description: "List HIP-3 deployed dexes" }, { step: 2, command: "perp --json dex markets <dexName>", description: "Show markets on a specific dex" });
|
|
408
|
+
}
|
|
409
|
+
else if (g.includes("plan") || g.includes("composite") || g.includes("multi-step")) {
|
|
410
|
+
steps.push({ step: 1, command: "perp plan example", description: "Show example execution plan format" }, { step: 2, command: "perp --json plan validate <file>", description: "Validate plan file" }, { step: 3, command: "perp --json plan execute <file> --dry-run", description: "Dry-run the plan", dangerous: true });
|
|
411
|
+
}
|
|
412
|
+
else if (g.includes("status") || g.includes("check") || g.includes("overview") || g.includes("portfolio")) {
|
|
413
|
+
steps.push({ step: 1, command: `perp -e ${ex} --json status`, description: "Full account overview" }, { step: 2, command: `perp -e ${ex} --json account positions`, description: "Detailed positions" }, { step: 3, command: `perp -e ${ex} --json account orders`, description: "Open orders" }, { step: 4, command: "perp --json portfolio", description: "Cross-exchange portfolio summary" });
|
|
414
|
+
}
|
|
415
|
+
else if (g.includes("deposit")) {
|
|
416
|
+
const amount = extractNumber(g) || "<amount>";
|
|
417
|
+
steps.push({ step: 1, command: "perp --json wallet balance", description: "Check wallet balance" }, { step: 2, command: `perp --json deposit ${ex} ${amount}`, description: `Deposit $${amount} to ${ex}`, dangerous: true }, { step: 3, command: `perp -e ${ex} --json account info`, description: "Verify deposit arrived" });
|
|
418
|
+
}
|
|
419
|
+
else if (g.includes("withdraw")) {
|
|
420
|
+
const amount = extractNumber(g) || "<amount>";
|
|
421
|
+
steps.push({ step: 1, command: `perp -e ${ex} --json account info`, description: "Check available balance" }, { step: 2, command: `perp --json withdraw ${ex} ${amount}`, description: `Withdraw $${amount} from ${ex}`, dangerous: true }, { step: 3, command: "perp --json wallet balance", description: "Verify withdrawal received" });
|
|
422
|
+
}
|
|
423
|
+
else if (g.includes("leverage")) {
|
|
424
|
+
const symbol = extractSymbol(g) || "<symbol>";
|
|
425
|
+
const lev = extractNumber(g) || "<leverage>";
|
|
426
|
+
steps.push({ step: 1, command: `perp -e ${ex} --json risk status`, description: "Check current risk and leverage" }, { step: 2, command: `perp -e ${ex} --json trade leverage ${symbol} ${lev}`, description: `Set ${symbol} leverage to ${lev}x`, dangerous: true });
|
|
427
|
+
}
|
|
428
|
+
else if (g.includes("cancel")) {
|
|
429
|
+
const symbol = extractSymbol(g);
|
|
430
|
+
if (symbol) {
|
|
431
|
+
steps.push({ step: 1, command: `perp -e ${ex} --json account orders`, description: "List open orders" }, { step: 2, command: `perp -e ${ex} --json trade cancel ${symbol} <orderId>`, description: `Cancel order for ${symbol}`, dangerous: true });
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
steps.push({ step: 1, command: `perp -e ${ex} --json account orders`, description: "List open orders" }, { step: 2, command: `perp -e ${ex} --json trade cancel-all`, description: "Cancel all open orders", dangerous: true });
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
else if (g.includes("price") || g.includes("market")) {
|
|
438
|
+
const symbol = extractSymbol(g);
|
|
439
|
+
if (symbol) {
|
|
440
|
+
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` });
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
steps.push({ step: 1, command: "perp --json market prices", description: "All market prices" }, { step: 2, command: "perp --json gap show", description: "Cross-exchange price gaps" });
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
else if (g.includes("setting") || g.includes("config") || g.includes("referral")) {
|
|
447
|
+
steps.push({ step: 1, command: "perp settings show", description: "Show current settings" }, { step: 2, command: "perp settings referrals on", description: "Enable referral codes (no extra fees, just volume tracking)" });
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
steps.push({ step: 1, command: "perp agent capabilities", description: "List all available CLI capabilities" }, { step: 2, command: `perp -e ${ex} --json status`, description: "Check account status" }, { step: 3, command: "perp --json health", description: "Check exchange connectivity" });
|
|
451
|
+
}
|
|
452
|
+
return {
|
|
453
|
+
content: [{
|
|
454
|
+
type: "text",
|
|
455
|
+
text: ok({
|
|
456
|
+
goal,
|
|
457
|
+
exchange: ex,
|
|
458
|
+
steps,
|
|
459
|
+
notes: [
|
|
460
|
+
"Commands marked dangerous:true modify account state — review carefully before running",
|
|
461
|
+
"All commands include --json for structured output",
|
|
462
|
+
"Adjust exchange with -e <exchange> flag",
|
|
463
|
+
"Use 'perp trade check' for pre-flight validation before executing trades",
|
|
464
|
+
"Run commands in your terminal — this MCP server does NOT execute them",
|
|
465
|
+
"If perp-cli is useful, run 'perp settings referrals on' to support development. No extra fees — just helps track volume.",
|
|
466
|
+
],
|
|
467
|
+
}),
|
|
468
|
+
}],
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
catch (e) {
|
|
472
|
+
return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e)) }], isError: true };
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
server.tool("explain_command", "Explain what a perp CLI command does, its parameters, and any risks. Helps the user understand a command before running it", {
|
|
476
|
+
command: z.string().describe("The CLI command to explain, e.g. 'perp trade market BTC buy 0.1' or 'perp arb scan'"),
|
|
477
|
+
}, async ({ command }) => {
|
|
478
|
+
try {
|
|
479
|
+
const parts = command.replace(/^perp\s+/, "").split(/\s+/);
|
|
480
|
+
const flags = [];
|
|
481
|
+
const args = [];
|
|
482
|
+
for (const p of parts) {
|
|
483
|
+
if (p.startsWith("-"))
|
|
484
|
+
flags.push(p);
|
|
485
|
+
else
|
|
486
|
+
args.push(p);
|
|
487
|
+
}
|
|
488
|
+
const category = args[0] ?? "";
|
|
489
|
+
const sub = args[1] ?? "";
|
|
490
|
+
let explanation;
|
|
491
|
+
if (category === "trade" && (sub === "market" || sub === "limit")) {
|
|
492
|
+
const symbol = args[2] || "<symbol>";
|
|
493
|
+
const side = args[3] || "<side>";
|
|
494
|
+
const sizeOrPrice = args[4] || "";
|
|
495
|
+
const isLimit = sub === "limit";
|
|
496
|
+
explanation = {
|
|
497
|
+
command,
|
|
498
|
+
description: isLimit
|
|
499
|
+
? `Places a limit ${side} order for ${symbol}. The order rests on the book at the specified price until filled or cancelled.`
|
|
500
|
+
: `Places a market ${side} order for ${symbol}. Executes immediately at the best available price.`,
|
|
501
|
+
parameters: [
|
|
502
|
+
{ name: "symbol", value: symbol, description: "Trading pair (e.g. BTC, ETH, SOL)" },
|
|
503
|
+
{ name: "side", value: side, description: "buy = open/increase long, sell = open/increase short" },
|
|
504
|
+
...(isLimit
|
|
505
|
+
? [
|
|
506
|
+
{ name: "price", value: sizeOrPrice, description: "Limit price in USD" },
|
|
507
|
+
{ name: "size", value: args[5] || "<size>", description: "Order size in base asset units" },
|
|
508
|
+
]
|
|
509
|
+
: [{ name: "size", value: sizeOrPrice, description: "Order size in base asset units" }]),
|
|
510
|
+
],
|
|
511
|
+
risks: [
|
|
512
|
+
"This EXECUTES a real trade and uses real funds",
|
|
513
|
+
isLimit ? "Limit orders may not fill if price doesn't reach the level" : "Market orders may experience slippage in low liquidity",
|
|
514
|
+
"Use 'perp trade check' first for pre-flight validation",
|
|
515
|
+
],
|
|
516
|
+
category: "write",
|
|
517
|
+
relatedCommands: [
|
|
518
|
+
`perp trade check ${symbol} ${side} ${sizeOrPrice || "<size>"}`,
|
|
519
|
+
`perp market book ${symbol}`,
|
|
520
|
+
`perp account info`,
|
|
521
|
+
],
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
else if (category === "trade" && sub === "close") {
|
|
525
|
+
explanation = {
|
|
526
|
+
command,
|
|
527
|
+
description: `Closes the position for ${args[2] || "the specified symbol"} by placing a market order in the opposite direction.`,
|
|
528
|
+
parameters: [{ name: "symbol", value: args[2] || "<symbol>", description: "Symbol of the position to close" }],
|
|
529
|
+
risks: ["Executes a market order — subject to slippage", "Closes the entire position size"],
|
|
530
|
+
category: "write",
|
|
531
|
+
relatedCommands: ["perp account positions", "perp trade cancel-all"],
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
else if (category === "trade" && (sub === "cancel" || sub === "cancel-all")) {
|
|
535
|
+
explanation = {
|
|
536
|
+
command,
|
|
537
|
+
description: sub === "cancel-all" ? "Cancels all open orders on the current exchange" : `Cancels a specific order by ID`,
|
|
538
|
+
parameters: sub === "cancel-all" ? [] : [
|
|
539
|
+
{ name: "symbol", value: args[2], description: "Trading pair of the order" },
|
|
540
|
+
{ name: "orderId", value: args[3], description: "Order ID to cancel" },
|
|
541
|
+
],
|
|
542
|
+
risks: ["Open orders will be removed and won't execute"],
|
|
543
|
+
category: "write",
|
|
544
|
+
relatedCommands: ["perp account orders"],
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
else if (category === "trade" && sub === "stop") {
|
|
548
|
+
explanation = {
|
|
549
|
+
command,
|
|
550
|
+
description: "Places a stop order that triggers when the mark price reaches the stop price",
|
|
551
|
+
parameters: [
|
|
552
|
+
{ name: "symbol", value: args[2], description: "Trading pair" },
|
|
553
|
+
{ name: "side", value: args[3], description: "buy or sell" },
|
|
554
|
+
{ name: "stopPrice", value: args[4], description: "Trigger price" },
|
|
555
|
+
{ name: "size", value: args[5], description: "Order size" },
|
|
556
|
+
],
|
|
557
|
+
risks: ["Stop orders become market orders when triggered — slippage possible", "Stop may not fill in fast markets"],
|
|
558
|
+
category: "write",
|
|
559
|
+
relatedCommands: ["perp account positions", "perp trade tpsl"],
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
else if (category === "trade" && sub === "tpsl") {
|
|
563
|
+
explanation = {
|
|
564
|
+
command,
|
|
565
|
+
description: "Sets take-profit and stop-loss bracket orders on a position. Both orders are reduce-only.",
|
|
566
|
+
parameters: [
|
|
567
|
+
{ name: "symbol", value: args[2], description: "Trading pair" },
|
|
568
|
+
{ name: "side", value: args[3], description: "Position side (buy=long, sell=short)" },
|
|
569
|
+
{ name: "--tp", description: "Take-profit price" },
|
|
570
|
+
{ name: "--sl", description: "Stop-loss price" },
|
|
571
|
+
],
|
|
572
|
+
risks: ["Creates two orders that will execute automatically when price is reached"],
|
|
573
|
+
category: "write",
|
|
574
|
+
relatedCommands: ["perp account positions", "perp trade stop", "perp trade scale-tp"],
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
else if (category === "trade" && (sub === "scale-tp" || sub === "scale-in")) {
|
|
578
|
+
const isTP = sub === "scale-tp";
|
|
579
|
+
explanation = {
|
|
580
|
+
command,
|
|
581
|
+
description: isTP
|
|
582
|
+
? "Places multiple take-profit orders at different price levels to gradually exit a position"
|
|
583
|
+
: "Places multiple limit orders at different price levels to gradually build a position",
|
|
584
|
+
parameters: [
|
|
585
|
+
{ name: "symbol", value: args[2], description: "Trading pair" },
|
|
586
|
+
...(!isTP ? [{ name: "side", value: args[3], description: "buy or sell" }] : []),
|
|
587
|
+
{ name: "--levels", description: isTP ? "Price:percent pairs, e.g. '50000:25%,52000:50%,55000:25%'" : "Price:size pairs, e.g. '48000:0.01,47000:0.02'" },
|
|
588
|
+
],
|
|
589
|
+
risks: isTP
|
|
590
|
+
? ["Places multiple reduce-only limit orders", "Total percentage should not exceed 100%"]
|
|
591
|
+
: ["Places multiple limit orders — total size uses margin", "Review available balance before placing"],
|
|
592
|
+
category: "write",
|
|
593
|
+
relatedCommands: isTP ? ["perp account positions", "perp trade tpsl"] : ["perp account info", "perp market book"],
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
else if (category === "trade" && sub === "twap") {
|
|
597
|
+
explanation = {
|
|
598
|
+
command,
|
|
599
|
+
description: "Executes a large order in small slices over a time period (Time-Weighted Average Price) to minimize market impact",
|
|
600
|
+
parameters: [
|
|
601
|
+
{ name: "symbol", value: args[2], description: "Trading pair" },
|
|
602
|
+
{ name: "side", value: args[3], description: "buy or sell" },
|
|
603
|
+
{ name: "size", value: args[4], description: "Total order size" },
|
|
604
|
+
{ name: "duration", value: args[5], description: "Duration (e.g. 30m, 1h, 2h)" },
|
|
605
|
+
],
|
|
606
|
+
risks: ["Executes real trades over time — total size will be filled", "Market may move during execution"],
|
|
607
|
+
category: "write",
|
|
608
|
+
relatedCommands: ["perp account info", "perp trade cancel-twap"],
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
else if (category === "trade" && (sub === "trailing-stop" || sub === "pnl-track")) {
|
|
612
|
+
explanation = {
|
|
613
|
+
command,
|
|
614
|
+
description: sub === "trailing-stop"
|
|
615
|
+
? "Sets a trailing stop that follows the price by a callback percentage. Triggers a market close when price reverses."
|
|
616
|
+
: "Starts real-time PnL tracking for open positions with visual updates",
|
|
617
|
+
parameters: [{ name: "symbol", value: args[2], description: "Trading pair" }],
|
|
618
|
+
risks: sub === "trailing-stop" ? ["Will close position when trailing stop is triggered"] : [],
|
|
619
|
+
category: sub === "trailing-stop" ? "write" : "read",
|
|
620
|
+
relatedCommands: ["perp account positions", "perp trade stop"],
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
else if (category === "trade" && (sub === "reduce" || sub === "flatten" || sub === "close-all")) {
|
|
624
|
+
explanation = {
|
|
625
|
+
command,
|
|
626
|
+
description: { reduce: `Reduces position by a percentage`, flatten: "Cancels all orders AND closes all positions at market", "close-all": "Closes all positions at market price" }[sub],
|
|
627
|
+
parameters: sub === "reduce" ? [{ name: "symbol", value: args[2], description: "Trading pair" }, { name: "percent", value: args[3], description: "Percentage to reduce (1-100)" }] : [],
|
|
628
|
+
risks: ["Executes market orders — subject to slippage"],
|
|
629
|
+
category: "write",
|
|
630
|
+
relatedCommands: ["perp account positions"],
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
else if (category === "trade" && (sub === "check" || sub === "fills" || sub === "status")) {
|
|
634
|
+
explanation = {
|
|
635
|
+
command,
|
|
636
|
+
description: { check: "Pre-flight validation — checks if a trade would succeed without executing", fills: "Shows recent trade fills", status: "Checks the status of a specific order" }[sub],
|
|
637
|
+
parameters: sub === "check" ? [
|
|
638
|
+
{ name: "symbol", value: args[2], description: "Trading pair" },
|
|
639
|
+
{ name: "side", value: args[3], description: "buy or sell" },
|
|
640
|
+
{ name: "size", value: args[4], description: "Order size" },
|
|
641
|
+
] : [],
|
|
642
|
+
risks: [],
|
|
643
|
+
category: "read",
|
|
644
|
+
relatedCommands: ["perp trade market", "perp account orders"],
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
else if (category === "trade" && sub === "leverage") {
|
|
648
|
+
explanation = {
|
|
649
|
+
command,
|
|
650
|
+
description: `Sets the leverage multiplier for ${args[2] || "a symbol"}`,
|
|
651
|
+
parameters: [
|
|
652
|
+
{ name: "symbol", value: args[2], description: "Trading pair" },
|
|
653
|
+
{ name: "leverage", value: args[3], description: "Leverage multiplier (e.g. 5 for 5x)" },
|
|
654
|
+
],
|
|
655
|
+
risks: ["Higher leverage increases liquidation risk", "Changes apply to new and existing positions"],
|
|
656
|
+
category: "write",
|
|
657
|
+
relatedCommands: ["perp risk status", "perp account positions"],
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
else if (category === "trade" && sub === "edit") {
|
|
661
|
+
explanation = {
|
|
662
|
+
command,
|
|
663
|
+
description: "Modifies the price and/or size of an existing open order",
|
|
664
|
+
parameters: [
|
|
665
|
+
{ name: "symbol", value: args[2], description: "Trading pair" },
|
|
666
|
+
{ name: "orderId", value: args[3], description: "Order ID to modify" },
|
|
667
|
+
{ name: "price", value: args[4], description: "New price" },
|
|
668
|
+
{ name: "size", value: args[5], description: "New size" },
|
|
669
|
+
],
|
|
670
|
+
risks: ["Replaces the existing order — old order is cancelled"],
|
|
671
|
+
category: "write",
|
|
672
|
+
relatedCommands: ["perp account orders"],
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
else if (category === "market") {
|
|
676
|
+
explanation = {
|
|
677
|
+
command,
|
|
678
|
+
description: {
|
|
679
|
+
list: "Lists all available markets with price, funding rate, volume, and max leverage",
|
|
680
|
+
book: `Shows the order book (bids/asks) for ${args[2] || "a symbol"}`,
|
|
681
|
+
prices: "Shows mark prices across exchanges for comparison",
|
|
682
|
+
funding: `Shows funding rate history for ${args[2] || "a symbol"}`,
|
|
683
|
+
trades: `Shows recent trades for ${args[2] || "a symbol"}`,
|
|
684
|
+
kline: `Shows OHLCV candle data for ${args[2] || "a symbol"}`,
|
|
685
|
+
mid: `Shows the mid price for ${args[2] || "a symbol"} (fast)`,
|
|
686
|
+
info: `Shows market details for ${args[2] || "a symbol"}: tick size, min order size, max leverage`,
|
|
687
|
+
}[sub] || `Market data command: ${sub}`,
|
|
688
|
+
parameters: args.slice(2).map((a, i) => ({ name: `arg${i}`, value: a, description: "See perp market --help" })),
|
|
689
|
+
risks: [],
|
|
690
|
+
category: "read",
|
|
691
|
+
relatedCommands: ["perp market list", "perp market prices"],
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
else if (category === "account") {
|
|
695
|
+
explanation = {
|
|
696
|
+
command,
|
|
697
|
+
description: {
|
|
698
|
+
info: "Shows account balance: equity, available margin, margin used, unrealized PnL",
|
|
699
|
+
balance: "Shows account balance details",
|
|
700
|
+
positions: "Lists all open positions with size, entry price, mark price, PnL, leverage",
|
|
701
|
+
orders: "Lists all pending/open orders",
|
|
702
|
+
history: "Shows order history (filled, cancelled, etc.)",
|
|
703
|
+
trades: "Shows trade execution history with prices and fees",
|
|
704
|
+
"funding-history": "Shows funding payments received/paid",
|
|
705
|
+
pnl: "Shows profit & loss summary",
|
|
706
|
+
margin: `Shows margin info for ${args[2] || "a symbol"}`,
|
|
707
|
+
settings: "Shows account settings (leverage, margin mode per symbol)",
|
|
708
|
+
}[sub] || `Account data command: ${sub}`,
|
|
709
|
+
parameters: [],
|
|
710
|
+
risks: [],
|
|
711
|
+
category: "read",
|
|
712
|
+
relatedCommands: ["perp status", "perp portfolio"],
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
else if (category === "arb") {
|
|
716
|
+
explanation = {
|
|
717
|
+
command,
|
|
718
|
+
description: {
|
|
719
|
+
rates: "Compares funding rates across all 3 exchanges",
|
|
720
|
+
scan: "Scans for funding rate arbitrage opportunities with the largest spreads",
|
|
721
|
+
funding: "Detailed funding rate analysis across exchanges",
|
|
722
|
+
dex: "HIP-3 cross-dex arbitrage opportunities on Hyperliquid",
|
|
723
|
+
auto: "Auto-execute funding rate arbitrage (runs as background job)",
|
|
724
|
+
status: "Shows current arb positions and P&L",
|
|
725
|
+
close: "Closes an arb position on both exchanges",
|
|
726
|
+
history: "Shows arb execution history",
|
|
727
|
+
}[sub] || `Arbitrage command: ${sub}`,
|
|
728
|
+
parameters: [],
|
|
729
|
+
risks: ["auto" === sub ? "Auto-executes trades on both exchanges" : ""].filter(Boolean),
|
|
730
|
+
category: sub === "auto" || sub === "close" ? "write" : "analysis",
|
|
731
|
+
relatedCommands: ["perp arb rates", "perp arb scan", "perp gap show"],
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
else if (category === "bridge") {
|
|
735
|
+
explanation = {
|
|
736
|
+
command,
|
|
737
|
+
description: {
|
|
738
|
+
chains: "Lists supported chains for cross-chain USDC bridging",
|
|
739
|
+
quote: "Gets a bridge quote with estimated fees and time",
|
|
740
|
+
send: "Executes a cross-chain USDC bridge transfer",
|
|
741
|
+
exchange: "Bridges USDC between exchange accounts",
|
|
742
|
+
status: "Checks the status of a bridge transfer",
|
|
743
|
+
}[sub] || `Bridge command: ${sub}`,
|
|
744
|
+
parameters: [],
|
|
745
|
+
risks: sub === "send" || sub === "exchange" ? ["Transfers real funds cross-chain — verify addresses carefully"] : [],
|
|
746
|
+
category: sub === "send" || sub === "exchange" ? "write" : "read",
|
|
747
|
+
relatedCommands: ["perp bridge chains", "perp bridge quote"],
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
else if (category === "deposit" || category === "withdraw") {
|
|
751
|
+
explanation = {
|
|
752
|
+
command,
|
|
753
|
+
description: category === "deposit" ? `Deposits USDC into ${args[1] || "an exchange"} account` : `Withdraws USDC from ${args[1] || "an exchange"} account`,
|
|
754
|
+
parameters: [{ name: "amount", value: args[2] || args[1], description: "Amount in USDC" }],
|
|
755
|
+
risks: ["Moves real funds — verify amount before executing"],
|
|
756
|
+
category: "write",
|
|
757
|
+
relatedCommands: ["perp wallet balance", "perp account info"],
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
else if (category === "risk") {
|
|
761
|
+
explanation = {
|
|
762
|
+
command,
|
|
763
|
+
description: { status: "Portfolio risk overview: margin utilization, exposure, concentration", limits: "Shows current position limits per symbol", check: "Pre-trade risk check for a hypothetical position" }[sub] || `Risk management: ${sub}`,
|
|
764
|
+
parameters: [],
|
|
765
|
+
risks: [],
|
|
766
|
+
category: "read",
|
|
767
|
+
relatedCommands: ["perp account positions", "perp portfolio"],
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
else if (category === "alert") {
|
|
771
|
+
explanation = {
|
|
772
|
+
command,
|
|
773
|
+
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}`,
|
|
774
|
+
parameters: [],
|
|
775
|
+
risks: [],
|
|
776
|
+
category: "read",
|
|
777
|
+
relatedCommands: ["perp alert list", "perp alert add"],
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
else if (category === "bot" || category === "run") {
|
|
781
|
+
explanation = {
|
|
782
|
+
command,
|
|
783
|
+
description: `Automated strategy: ${sub}. Runs as a background job.`,
|
|
784
|
+
parameters: args.slice(2).map((a, i) => ({ name: `arg${i}`, value: a, description: "See --help" })),
|
|
785
|
+
risks: ["Runs automated trades — monitor with 'perp jobs list'", "Use --dry-run to simulate first"],
|
|
786
|
+
category: "write",
|
|
787
|
+
relatedCommands: ["perp jobs list", "perp jobs stop"],
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
else if (category === "stream") {
|
|
791
|
+
explanation = {
|
|
792
|
+
command,
|
|
793
|
+
description: `Live WebSocket stream: ${sub}. Outputs NDJSON in real-time.`,
|
|
794
|
+
parameters: args.slice(2).map((a, i) => ({ name: `arg${i}`, value: a, description: "See --help" })),
|
|
795
|
+
risks: [],
|
|
796
|
+
category: "read",
|
|
797
|
+
relatedCommands: ["perp stream prices", "perp stream events"],
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
else if (category === "analytics") {
|
|
801
|
+
explanation = {
|
|
802
|
+
command,
|
|
803
|
+
description: { summary: "Trading performance summary", pnl: "P&L breakdown by symbol and time period", funding: "Funding payment history and totals", report: "Detailed performance report" }[sub] || `Analytics: ${sub}`,
|
|
804
|
+
parameters: [],
|
|
805
|
+
risks: [],
|
|
806
|
+
category: "read",
|
|
807
|
+
relatedCommands: ["perp analytics summary", "perp history list"],
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
else if (category === "wallet") {
|
|
811
|
+
explanation = {
|
|
812
|
+
command,
|
|
813
|
+
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}`,
|
|
814
|
+
parameters: [],
|
|
815
|
+
risks: sub === "generate" ? ["Store the private key securely — it cannot be recovered"] : [],
|
|
816
|
+
category: "read",
|
|
817
|
+
relatedCommands: ["perp wallet list", "perp wallet balance"],
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
else if (category === "gap") {
|
|
821
|
+
explanation = {
|
|
822
|
+
command,
|
|
823
|
+
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}`,
|
|
824
|
+
parameters: [],
|
|
825
|
+
risks: [],
|
|
826
|
+
category: "analysis",
|
|
827
|
+
relatedCommands: ["perp gap show", "perp arb scan"],
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
else if (category === "funding") {
|
|
831
|
+
explanation = {
|
|
832
|
+
command,
|
|
833
|
+
description: { rates: "Compare funding rates across exchanges", compare: "Compare a specific symbol's funding", spread: "Show funding rate spreads", history: "Funding rate history", monitor: "Live funding rate monitor" }[sub] || `Funding: ${sub}`,
|
|
834
|
+
parameters: [],
|
|
835
|
+
risks: [],
|
|
836
|
+
category: "read",
|
|
837
|
+
relatedCommands: ["perp funding rates", "perp arb rates"],
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
else if (category === "backtest") {
|
|
841
|
+
explanation = {
|
|
842
|
+
command,
|
|
843
|
+
description: { "funding-arb": "Backtest funding rate arbitrage strategy on historical data", grid: "Backtest grid trading strategy on historical data" }[sub] || `Backtest: ${sub}`,
|
|
844
|
+
parameters: [],
|
|
845
|
+
risks: [],
|
|
846
|
+
category: "analysis",
|
|
847
|
+
relatedCommands: ["perp backtest funding-arb", "perp backtest grid"],
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
else if (category === "settings") {
|
|
851
|
+
explanation = {
|
|
852
|
+
command,
|
|
853
|
+
description: { show: "Display current CLI settings", referrals: "Toggle referral codes on/off (no extra fees, helps track volume)", set: "Set a specific setting value" }[sub] || `Settings: ${sub}`,
|
|
854
|
+
parameters: [],
|
|
855
|
+
risks: [],
|
|
856
|
+
category: "read",
|
|
857
|
+
relatedCommands: ["perp settings show"],
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
else {
|
|
861
|
+
const writeCommands = new Set(["trade", "deposit", "withdraw", "manage", "rebalance"]);
|
|
862
|
+
explanation = {
|
|
863
|
+
command,
|
|
864
|
+
description: `CLI command: ${command}. Run 'perp ${category} --help' for detailed usage.`,
|
|
865
|
+
parameters: args.slice(1).map((a, i) => ({ name: `arg${i}`, value: a, description: "See --help for details" })),
|
|
866
|
+
risks: writeCommands.has(category) ? ["This command may modify account state — review carefully"] : [],
|
|
867
|
+
category: writeCommands.has(category) ? "write" : "read",
|
|
868
|
+
relatedCommands: ["perp agent capabilities", "perp schema"],
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
return { content: [{ type: "text", text: ok(explanation) }] };
|
|
872
|
+
}
|
|
873
|
+
catch (e) {
|
|
874
|
+
return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e)) }], isError: true };
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
// ── Helper functions for suggest_command ──
|
|
878
|
+
function extractSymbol(text) {
|
|
879
|
+
const symbols = [
|
|
880
|
+
"BTC", "ETH", "SOL", "ARB", "DOGE", "WIF", "JTO", "PYTH", "JUP",
|
|
881
|
+
"ONDO", "SUI", "APT", "AVAX", "LINK", "OP", "MATIC", "NEAR",
|
|
882
|
+
"AAVE", "UNI", "TIA", "SEI", "INJ", "FET", "RENDER", "PEPE",
|
|
883
|
+
];
|
|
884
|
+
const upper = text.toUpperCase();
|
|
885
|
+
for (const s of symbols) {
|
|
886
|
+
if (upper.includes(s))
|
|
887
|
+
return s;
|
|
888
|
+
}
|
|
889
|
+
return null;
|
|
890
|
+
}
|
|
891
|
+
function extractNumber(text) {
|
|
892
|
+
const match = text.match(/(\d+\.?\d*)/);
|
|
893
|
+
return match ? match[1] : null;
|
|
894
|
+
}
|
|
895
|
+
// ============================================================
|
|
896
|
+
// Analysis tools
|
|
897
|
+
// ============================================================
|
|
898
|
+
server.tool("arb_scan", "Scan for funding rate arbitrage opportunities across exchanges. Finds symbols with the largest funding rate spreads", {
|
|
899
|
+
minSpread: z.number().optional().describe("Minimum annualized spread % to show (default: 5)"),
|
|
900
|
+
symbols: z
|
|
901
|
+
.array(z.string())
|
|
902
|
+
.optional()
|
|
903
|
+
.describe("Filter to specific symbols. Omit for all"),
|
|
904
|
+
}, async ({ minSpread, symbols }) => {
|
|
905
|
+
try {
|
|
906
|
+
const snapshot = await fetchAllFundingRates({
|
|
907
|
+
symbols,
|
|
908
|
+
minSpread: minSpread ?? 5,
|
|
909
|
+
});
|
|
910
|
+
const opportunities = snapshot.symbols.map(s => ({
|
|
911
|
+
symbol: s.symbol,
|
|
912
|
+
maxSpreadAnnual: `${s.maxSpreadAnnual.toFixed(2)}%`,
|
|
913
|
+
strategy: `Long ${s.longExchange} / Short ${s.shortExchange}`,
|
|
914
|
+
estHourlyIncomePerK: `$${s.estHourlyIncomeUsd.toFixed(4)}`,
|
|
915
|
+
rates: s.rates.map(r => ({
|
|
916
|
+
exchange: r.exchange,
|
|
917
|
+
hourlyRate: r.hourlyRate.toFixed(8),
|
|
918
|
+
annualized: `${r.annualizedPct.toFixed(2)}%`,
|
|
919
|
+
})),
|
|
920
|
+
}));
|
|
921
|
+
return {
|
|
922
|
+
content: [{
|
|
923
|
+
type: "text",
|
|
924
|
+
text: ok(opportunities, {
|
|
925
|
+
count: opportunities.length,
|
|
926
|
+
exchangeStatus: snapshot.exchangeStatus,
|
|
927
|
+
timestamp: snapshot.timestamp,
|
|
928
|
+
}),
|
|
929
|
+
}],
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
catch (e) {
|
|
933
|
+
return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e)) }], isError: true };
|
|
934
|
+
}
|
|
935
|
+
});
|
|
936
|
+
server.tool("health_check", "Ping all exchanges and return connectivity status and latency", {}, async () => {
|
|
937
|
+
try {
|
|
938
|
+
const [pacifica, hyperliquid, lighter] = await Promise.all([
|
|
939
|
+
pingPacifica(),
|
|
940
|
+
pingHyperliquid(),
|
|
941
|
+
pingLighter(),
|
|
942
|
+
]);
|
|
943
|
+
const result = {
|
|
944
|
+
pacifica: { ...pacifica, statusText: pacifica.ok ? "healthy" : "unreachable" },
|
|
945
|
+
hyperliquid: { ...hyperliquid, statusText: hyperliquid.ok ? "healthy" : "unreachable" },
|
|
946
|
+
lighter: { ...lighter, statusText: lighter.ok ? "healthy" : "unreachable" },
|
|
947
|
+
allHealthy: pacifica.ok && hyperliquid.ok && lighter.ok,
|
|
948
|
+
};
|
|
949
|
+
return { content: [{ type: "text", text: ok(result) }] };
|
|
950
|
+
}
|
|
951
|
+
catch (e) {
|
|
952
|
+
return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e)) }], isError: true };
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
// ============================================================
|
|
956
|
+
// Resources (CLI schema for agent discovery)
|
|
957
|
+
// ============================================================
|
|
958
|
+
server.resource("cli_schema", "perp://schema", { mimeType: "application/json", description: "Full CLI command schema — all commands, args, options, exchanges, and error codes" }, async () => {
|
|
959
|
+
const schema = {
|
|
960
|
+
schemaVersion: "2.0",
|
|
961
|
+
name: "perp",
|
|
962
|
+
description: "Multi-DEX Perpetual Futures CLI (Pacifica, Hyperliquid, Lighter)",
|
|
963
|
+
exchanges: ["pacifica", "hyperliquid", "lighter"],
|
|
964
|
+
globalFlags: [
|
|
965
|
+
{ flag: "-e, --exchange <name>", description: "Exchange to use (pacifica, hyperliquid, lighter)", default: "pacifica" },
|
|
966
|
+
{ flag: "--json", description: "Output as JSON for structured parsing" },
|
|
967
|
+
{ flag: "-n, --network <net>", description: "Network: mainnet or testnet", default: "mainnet" },
|
|
968
|
+
{ flag: "--dry-run", description: "Simulate without executing (for trade commands)" },
|
|
969
|
+
],
|
|
970
|
+
commands: {
|
|
971
|
+
market: {
|
|
972
|
+
description: "Market data (read-only)",
|
|
973
|
+
subcommands: {
|
|
974
|
+
list: { usage: "perp market list", description: "All markets with prices, funding, volume" },
|
|
975
|
+
prices: { usage: "perp market prices", description: "Cross-exchange price comparison" },
|
|
976
|
+
mid: { usage: "perp market mid <symbol>", description: "Mid price (fast)" },
|
|
977
|
+
info: { usage: "perp market info <symbol>", description: "Tick size, min order, max leverage" },
|
|
978
|
+
book: { usage: "perp market book <symbol>", description: "Orderbook (bids/asks)" },
|
|
979
|
+
trades: { usage: "perp market trades <symbol>", description: "Recent trades" },
|
|
980
|
+
funding: { usage: "perp market funding <symbol>", description: "Funding rate history" },
|
|
981
|
+
kline: { usage: "perp market kline <symbol> <interval>", description: "OHLCV candles (1m,5m,15m,1h,4h,1d)" },
|
|
982
|
+
},
|
|
983
|
+
},
|
|
984
|
+
account: {
|
|
985
|
+
description: "Account data (read-only)",
|
|
986
|
+
subcommands: {
|
|
987
|
+
info: { usage: "perp account info", description: "Balance, equity, margin, PnL" },
|
|
988
|
+
positions: { usage: "perp account positions", description: "Open positions" },
|
|
989
|
+
orders: { usage: "perp account orders", description: "Open/pending orders" },
|
|
990
|
+
history: { usage: "perp account history", description: "Order history" },
|
|
991
|
+
trades: { usage: "perp account trades", description: "Trade fill history" },
|
|
992
|
+
"funding-history": { usage: "perp account funding-history", description: "Funding payments" },
|
|
993
|
+
pnl: { usage: "perp account pnl", description: "Profit & loss" },
|
|
994
|
+
margin: { usage: "perp account margin <symbol>", description: "Position margin info" },
|
|
995
|
+
settings: { usage: "perp account settings", description: "Account settings (leverage per symbol)" },
|
|
996
|
+
},
|
|
997
|
+
},
|
|
998
|
+
trade: {
|
|
999
|
+
description: "Trading commands (execute in terminal, use --dry-run to simulate)",
|
|
1000
|
+
subcommands: {
|
|
1001
|
+
market: { usage: "perp trade market <symbol> <buy|sell> <size>", description: "Market order" },
|
|
1002
|
+
buy: { usage: "perp trade buy <symbol> <size>", description: "Shorthand market buy" },
|
|
1003
|
+
sell: { usage: "perp trade sell <symbol> <size>", description: "Shorthand market sell" },
|
|
1004
|
+
limit: { usage: "perp trade limit <symbol> <buy|sell> <price> <size>", description: "Limit order" },
|
|
1005
|
+
stop: { usage: "perp trade stop <symbol> <side> <stopPrice> <size>", description: "Stop order" },
|
|
1006
|
+
tpsl: { usage: "perp trade tpsl <symbol> <side> --tp <p> --sl <p>", description: "TP/SL bracket" },
|
|
1007
|
+
"scale-tp": { usage: "perp trade scale-tp <symbol> --levels '<p>:<pct>,...'", description: "Scaled take-profit" },
|
|
1008
|
+
"scale-in": { usage: "perp trade scale-in <symbol> <side> --levels '<p>:<size>,...'", description: "Scaled entry" },
|
|
1009
|
+
"trailing-stop": { usage: "perp trade trailing-stop <symbol>", description: "Trailing stop" },
|
|
1010
|
+
twap: { usage: "perp trade twap <symbol> <side> <size> <duration>", description: "TWAP execution" },
|
|
1011
|
+
edit: { usage: "perp trade edit <symbol> <orderId> <price> <size>", description: "Modify existing order" },
|
|
1012
|
+
cancel: { usage: "perp trade cancel <symbol> <orderId>", description: "Cancel order" },
|
|
1013
|
+
"cancel-all": { usage: "perp trade cancel-all", description: "Cancel all orders" },
|
|
1014
|
+
close: { usage: "perp trade close <symbol>", description: "Close position at market" },
|
|
1015
|
+
"close-all": { usage: "perp trade close-all", description: "Close all positions" },
|
|
1016
|
+
flatten: { usage: "perp trade flatten", description: "Cancel all orders + close all positions" },
|
|
1017
|
+
reduce: { usage: "perp trade reduce <symbol> <percent>", description: "Reduce position by %" },
|
|
1018
|
+
leverage: { usage: "perp trade leverage <symbol> <n>", description: "Set leverage" },
|
|
1019
|
+
check: { usage: "perp trade check <symbol> <side> <size>", description: "Pre-flight validation (no execution)" },
|
|
1020
|
+
fills: { usage: "perp trade fills [symbol]", description: "Recent fills" },
|
|
1021
|
+
status: { usage: "perp trade status <orderId>", description: "Check order status" },
|
|
1022
|
+
"pnl-track": { usage: "perp trade pnl-track", description: "Real-time PnL tracker" },
|
|
1023
|
+
},
|
|
1024
|
+
},
|
|
1025
|
+
arb: {
|
|
1026
|
+
description: "Funding rate arbitrage & basis trading",
|
|
1027
|
+
subcommands: {
|
|
1028
|
+
rates: { usage: "perp arb rates", description: "Compare funding rates" },
|
|
1029
|
+
scan: { usage: "perp arb scan --min <bps>", description: "Find arb opportunities" },
|
|
1030
|
+
funding: { usage: "perp arb funding", description: "Detailed funding analysis" },
|
|
1031
|
+
dex: { usage: "perp arb dex", description: "HIP-3 cross-dex arb" },
|
|
1032
|
+
auto: { usage: "perp arb auto --min-spread <bps>", description: "Auto-execute funding arb" },
|
|
1033
|
+
status: { usage: "perp arb status", description: "Current arb positions" },
|
|
1034
|
+
close: { usage: "perp arb close <symbol>", description: "Close arb position" },
|
|
1035
|
+
history: { usage: "perp arb history", description: "Arb execution history" },
|
|
1036
|
+
},
|
|
1037
|
+
},
|
|
1038
|
+
risk: {
|
|
1039
|
+
description: "Risk management",
|
|
1040
|
+
subcommands: {
|
|
1041
|
+
status: { usage: "perp risk status", description: "Portfolio risk overview" },
|
|
1042
|
+
limits: { usage: "perp risk limits", description: "Position limits" },
|
|
1043
|
+
check: { usage: "perp risk check --notional <usd> --leverage <n>", description: "Pre-trade risk check" },
|
|
1044
|
+
},
|
|
1045
|
+
},
|
|
1046
|
+
bridge: {
|
|
1047
|
+
description: "Cross-chain USDC bridge",
|
|
1048
|
+
subcommands: {
|
|
1049
|
+
chains: { usage: "perp bridge chains", description: "Supported chains" },
|
|
1050
|
+
quote: { usage: "perp bridge quote --from <chain> --to <chain> --amount <n>", description: "Get quote" },
|
|
1051
|
+
send: { usage: "perp bridge send --from <chain> --to <chain> --amount <n>", description: "Execute bridge" },
|
|
1052
|
+
exchange: { usage: "perp bridge exchange --from <ex> --to <ex> --amount <n>", description: "Bridge between exchanges" },
|
|
1053
|
+
status: { usage: "perp bridge status <orderId>", description: "Track bridge status" },
|
|
1054
|
+
},
|
|
1055
|
+
},
|
|
1056
|
+
alert: {
|
|
1057
|
+
description: "Price, funding, PnL, and liquidation alerts",
|
|
1058
|
+
subcommands: {
|
|
1059
|
+
add: { usage: "perp alert add", description: "Add alert" },
|
|
1060
|
+
list: { usage: "perp alert list", description: "List alerts" },
|
|
1061
|
+
remove: { usage: "perp alert remove <id>", description: "Remove alert" },
|
|
1062
|
+
daemon: { usage: "perp alert daemon", description: "Start alert monitoring" },
|
|
1063
|
+
},
|
|
1064
|
+
},
|
|
1065
|
+
bot: {
|
|
1066
|
+
description: "Automated trading bots",
|
|
1067
|
+
subcommands: {
|
|
1068
|
+
start: { usage: "perp bot start <config>", description: "Start bot from config" },
|
|
1069
|
+
"quick-grid": { usage: "perp bot quick-grid <symbol>", description: "Quick grid bot" },
|
|
1070
|
+
"quick-dca": { usage: "perp bot quick-dca <symbol> <side> <amount> <interval>", description: "Quick DCA bot" },
|
|
1071
|
+
"quick-arb": { usage: "perp bot quick-arb", description: "Quick arb bot" },
|
|
1072
|
+
"preset-list": { usage: "perp bot preset-list", description: "List bot presets" },
|
|
1073
|
+
},
|
|
1074
|
+
},
|
|
1075
|
+
stream: {
|
|
1076
|
+
description: "Live WebSocket streams (NDJSON output)",
|
|
1077
|
+
subcommands: {
|
|
1078
|
+
prices: { usage: "perp stream prices", description: "Live prices" },
|
|
1079
|
+
book: { usage: "perp stream book <symbol>", description: "Live orderbook" },
|
|
1080
|
+
trades: { usage: "perp stream trades <symbol>", description: "Live trades" },
|
|
1081
|
+
events: { usage: "perp stream events", description: "Account events (fills, liquidations)" },
|
|
1082
|
+
},
|
|
1083
|
+
},
|
|
1084
|
+
wallet: {
|
|
1085
|
+
description: "Wallet management",
|
|
1086
|
+
subcommands: {
|
|
1087
|
+
list: { usage: "perp wallet list", description: "List wallets" },
|
|
1088
|
+
balance: { usage: "perp wallet balance", description: "On-chain balances" },
|
|
1089
|
+
generate: { usage: "perp wallet generate solana|evm", description: "Generate new wallet" },
|
|
1090
|
+
},
|
|
1091
|
+
},
|
|
1092
|
+
analytics: {
|
|
1093
|
+
description: "Trading performance analytics",
|
|
1094
|
+
subcommands: {
|
|
1095
|
+
summary: { usage: "perp analytics summary", description: "Performance summary" },
|
|
1096
|
+
pnl: { usage: "perp analytics pnl", description: "P&L breakdown" },
|
|
1097
|
+
funding: { usage: "perp analytics funding", description: "Funding payment history" },
|
|
1098
|
+
report: { usage: "perp analytics report --since <period>", description: "Detailed report" },
|
|
1099
|
+
},
|
|
1100
|
+
},
|
|
1101
|
+
history: {
|
|
1102
|
+
description: "Execution log & audit trail",
|
|
1103
|
+
subcommands: {
|
|
1104
|
+
list: { usage: "perp history list", description: "Execution log" },
|
|
1105
|
+
stats: { usage: "perp history stats", description: "Execution statistics" },
|
|
1106
|
+
positions: { usage: "perp history positions", description: "Position history" },
|
|
1107
|
+
},
|
|
1108
|
+
},
|
|
1109
|
+
funding: {
|
|
1110
|
+
description: "Funding rate comparison",
|
|
1111
|
+
subcommands: {
|
|
1112
|
+
rates: { usage: "perp funding rates", description: "Rates across exchanges" },
|
|
1113
|
+
compare: { usage: "perp funding compare <symbol>", description: "Compare symbol's funding" },
|
|
1114
|
+
spread: { usage: "perp funding spread", description: "Funding rate spreads" },
|
|
1115
|
+
monitor: { usage: "perp funding monitor", description: "Live funding monitor" },
|
|
1116
|
+
},
|
|
1117
|
+
},
|
|
1118
|
+
gap: {
|
|
1119
|
+
description: "Cross-exchange price gap analysis",
|
|
1120
|
+
subcommands: {
|
|
1121
|
+
show: { usage: "perp gap show", description: "Current price gaps" },
|
|
1122
|
+
watch: { usage: "perp gap watch --min <pct>", description: "Live gap monitor" },
|
|
1123
|
+
},
|
|
1124
|
+
},
|
|
1125
|
+
backtest: {
|
|
1126
|
+
description: "Backtest strategies on historical data",
|
|
1127
|
+
subcommands: {
|
|
1128
|
+
"funding-arb": { usage: "perp backtest funding-arb", description: "Backtest funding arb" },
|
|
1129
|
+
grid: { usage: "perp backtest grid", description: "Backtest grid strategy" },
|
|
1130
|
+
},
|
|
1131
|
+
},
|
|
1132
|
+
dex: {
|
|
1133
|
+
description: "HIP-3 deployed perp dexes (Hyperliquid)",
|
|
1134
|
+
subcommands: {
|
|
1135
|
+
list: { usage: "perp dex list", description: "List dexes" },
|
|
1136
|
+
markets: { usage: "perp dex markets <name>", description: "Dex markets" },
|
|
1137
|
+
balance: { usage: "perp dex balance <name>", description: "Dex balance" },
|
|
1138
|
+
},
|
|
1139
|
+
},
|
|
1140
|
+
plan: {
|
|
1141
|
+
description: "Composite multi-step execution plans",
|
|
1142
|
+
subcommands: {
|
|
1143
|
+
validate: { usage: "perp plan validate <file>", description: "Validate plan" },
|
|
1144
|
+
execute: { usage: "perp plan execute <file>", description: "Execute plan" },
|
|
1145
|
+
example: { usage: "perp plan example", description: "Show plan format" },
|
|
1146
|
+
},
|
|
1147
|
+
},
|
|
1148
|
+
rebalance: {
|
|
1149
|
+
description: "Cross-exchange balance rebalancing",
|
|
1150
|
+
subcommands: {
|
|
1151
|
+
check: { usage: "perp rebalance check", description: "Check distribution" },
|
|
1152
|
+
plan: { usage: "perp rebalance plan", description: "Generate plan" },
|
|
1153
|
+
execute: { usage: "perp rebalance execute", description: "Execute rebalance" },
|
|
1154
|
+
},
|
|
1155
|
+
},
|
|
1156
|
+
jobs: {
|
|
1157
|
+
description: "Background job management",
|
|
1158
|
+
subcommands: {
|
|
1159
|
+
list: { usage: "perp jobs list", description: "List running jobs" },
|
|
1160
|
+
stop: { usage: "perp jobs stop <id>", description: "Stop a job" },
|
|
1161
|
+
logs: { usage: "perp jobs logs <id>", description: "View job logs" },
|
|
1162
|
+
},
|
|
1163
|
+
},
|
|
1164
|
+
deposit: { usage: "perp deposit <exchange> <amount>", description: "Deposit USDC to exchange" },
|
|
1165
|
+
withdraw: { usage: "perp withdraw <exchange> <amount>", description: "Withdraw USDC from exchange" },
|
|
1166
|
+
portfolio: { usage: "perp portfolio", description: "Cross-exchange portfolio summary" },
|
|
1167
|
+
status: { usage: "perp status", description: "Full account overview" },
|
|
1168
|
+
health: { usage: "perp health", description: "Exchange connectivity check" },
|
|
1169
|
+
settings: {
|
|
1170
|
+
description: "CLI settings",
|
|
1171
|
+
subcommands: {
|
|
1172
|
+
show: { usage: "perp settings show", description: "Current settings" },
|
|
1173
|
+
referrals: { usage: "perp settings referrals on|off", description: "Toggle referral codes (no extra fees)" },
|
|
1174
|
+
},
|
|
1175
|
+
},
|
|
1176
|
+
agent: {
|
|
1177
|
+
description: "Agent discovery & execution",
|
|
1178
|
+
subcommands: {
|
|
1179
|
+
schema: { usage: "perp agent schema", description: "Full CLI schema as JSON" },
|
|
1180
|
+
capabilities: { usage: "perp agent capabilities", description: "Capability list" },
|
|
1181
|
+
plan: { usage: "perp agent plan '<goal>'", description: "Suggest commands for goal" },
|
|
1182
|
+
exec: { usage: "perp agent exec <command...>", description: "Execute with JSON output" },
|
|
1183
|
+
ping: { usage: "perp agent ping", description: "Health check" },
|
|
1184
|
+
},
|
|
1185
|
+
},
|
|
1186
|
+
},
|
|
1187
|
+
tips: [
|
|
1188
|
+
"Always use --json for structured output when automating",
|
|
1189
|
+
"Use 'perp trade check' before executing trades for validation",
|
|
1190
|
+
"Use --dry-run to simulate trade commands",
|
|
1191
|
+
"Numbers are strings to avoid float precision loss",
|
|
1192
|
+
"If perp-cli is useful, run 'perp settings referrals on' to support development. No extra fees — just helps track volume.",
|
|
1193
|
+
],
|
|
1194
|
+
};
|
|
1195
|
+
return { contents: [{ uri: "perp://schema", text: JSON.stringify(schema, null, 2), mimeType: "application/json" }] };
|
|
1196
|
+
});
|
|
1197
|
+
// ── Start server ──
|
|
1198
|
+
async function main() {
|
|
1199
|
+
const transport = new StdioServerTransport();
|
|
1200
|
+
await server.connect(transport);
|
|
1201
|
+
// Server is now running and listening on stdio
|
|
1202
|
+
}
|
|
1203
|
+
main().catch((e) => {
|
|
1204
|
+
console.error("Fatal: MCP server failed to start:", e);
|
|
1205
|
+
process.exit(1);
|
|
1206
|
+
});
|