perp-cli 0.11.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -25
- package/dist/_stderr-filter.d.ts +0 -0
- package/dist/_stderr-filter.js +30 -0
- package/dist/agent-wallet/expiry.d.ts +15 -0
- package/dist/agent-wallet/expiry.js +26 -0
- package/dist/agent-wallet/lighter-keystore.d.ts +34 -0
- package/dist/agent-wallet/lighter-keystore.js +232 -0
- package/dist/agent-wallet/passphrase.d.ts +13 -0
- package/dist/agent-wallet/passphrase.js +54 -0
- package/dist/agent-wallet/signing-strategy.d.ts +67 -0
- package/dist/agent-wallet/signing-strategy.js +51 -0
- package/dist/agent-wallet/store.d.ts +37 -0
- package/dist/agent-wallet/store.js +172 -0
- package/dist/api/public/_http.d.ts +15 -0
- package/dist/api/public/_http.js +24 -0
- package/dist/api/public/hyperliquid.js +36 -24
- package/dist/api/public/lighter.d.ts +7 -1
- package/dist/api/public/lighter.js +85 -37
- package/dist/api/public/pacifica.js +47 -18
- package/dist/bridge-engine.js +102 -49
- package/dist/cli-spec.js +1 -2
- package/dist/commands/account.js +2 -500
- package/dist/commands/agent.d.ts +8 -2
- package/dist/commands/agent.js +1903 -366
- package/dist/commands/alerts.js +3 -1
- package/dist/commands/arb/index.js +37 -7
- package/dist/commands/arb-auto.js +44 -11
- package/dist/commands/arb.js +83 -34
- package/dist/commands/bot.d.ts +1 -1
- package/dist/commands/bot.js +18 -14
- package/dist/commands/bridge.d.ts +1 -1
- package/dist/commands/bridge.js +4 -4
- package/dist/commands/funds.d.ts +1 -1
- package/dist/commands/funds.js +18 -68
- package/dist/commands/jobs.d.ts +1 -1
- package/dist/commands/jobs.js +2 -2
- package/dist/commands/manage.d.ts +1 -1
- package/dist/commands/manage.js +12 -84
- package/dist/commands/plan.d.ts +1 -1
- package/dist/commands/plan.js +2 -2
- package/dist/commands/portfolio.d.ts +19 -3
- package/dist/commands/portfolio.js +747 -163
- package/dist/commands/rebalance.d.ts +1 -1
- package/dist/commands/rebalance.js +18 -12
- package/dist/commands/risk.js +2 -6
- package/dist/commands/settings.js +30 -53
- package/dist/commands/trade.js +16 -15
- package/dist/commands/wallet.d.ts +2 -1
- package/dist/commands/wallet.js +756 -28
- package/dist/errors.d.ts +64 -0
- package/dist/errors.js +82 -2
- package/dist/event-stream.js +12 -1
- package/dist/exchanges/aster-typed-data.d.ts +156 -0
- package/dist/exchanges/aster-typed-data.js +189 -0
- package/dist/exchanges/aster.d.ts +60 -11
- package/dist/exchanges/aster.js +278 -136
- package/dist/exchanges/hyperliquid.d.ts +14 -0
- package/dist/exchanges/hyperliquid.js +84 -34
- package/dist/exchanges/lighter-spot.js +14 -3
- package/dist/exchanges/lighter.d.ts +66 -0
- package/dist/exchanges/lighter.js +199 -27
- package/dist/exchanges/pacifica-typed-data.d.ts +75 -0
- package/dist/exchanges/pacifica-typed-data.js +74 -0
- package/dist/exchanges/pacifica.d.ts +29 -1
- package/dist/exchanges/pacifica.js +94 -6
- package/dist/funding/rates.js +59 -21
- package/dist/guardrail/perp-guardrail.js +9 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +268 -360
- package/dist/jobs.js +3 -1
- package/dist/mcp-server.js +85 -83
- package/dist/position-history.js +47 -4
- package/dist/settings.d.ts +67 -3
- package/dist/settings.js +1 -2
- package/dist/signer/ows-evm.d.ts +14 -0
- package/dist/signer/ows-evm.js +28 -9
- package/dist/smart-order.d.ts +8 -2
- package/dist/smart-order.js +46 -11
- package/dist/strategies/funding-arb.js +29 -8
- package/dist/trade-validator.js +30 -5
- package/dist/utils.d.ts +10 -0
- package/dist/utils.js +19 -0
- package/package.json +2 -2
- package/skills/perp-cli/SKILL.md +23 -14
- package/skills/perp-cli/references/agent-operations.md +6 -4
- package/skills/perp-cli/references/commands.md +55 -34
- package/skills/perp-cli/scripts/preflight.sh +4 -4
- package/skills/perp-cli/scripts/spot-perp-scan.sh +2 -2
- package/dist/__tests__/alert-logic.test.d.ts +0 -1
- package/dist/__tests__/alert-logic.test.js +0 -107
- package/dist/__tests__/arb-auto-3dex.test.d.ts +0 -1
- package/dist/__tests__/arb-auto-3dex.test.js +0 -397
- package/dist/__tests__/arb-history-stats.test.d.ts +0 -1
- package/dist/__tests__/arb-history-stats.test.js +0 -176
- package/dist/__tests__/arb-logic.test.d.ts +0 -1
- package/dist/__tests__/arb-logic.test.js +0 -84
- package/dist/__tests__/arb-manage.test.d.ts +0 -1
- package/dist/__tests__/arb-manage.test.js +0 -253
- package/dist/__tests__/arb-new-features.test.d.ts +0 -1
- package/dist/__tests__/arb-new-features.test.js +0 -457
- package/dist/__tests__/arb-sizing.test.d.ts +0 -1
- package/dist/__tests__/arb-sizing.test.js +0 -48
- package/dist/__tests__/arb-state.test.d.ts +0 -1
- package/dist/__tests__/arb-state.test.js +0 -284
- package/dist/__tests__/arb-userflow.test.d.ts +0 -1
- package/dist/__tests__/arb-userflow.test.js +0 -945
- package/dist/__tests__/arb-utils.test.d.ts +0 -1
- package/dist/__tests__/arb-utils.test.js +0 -264
- package/dist/__tests__/bot-conditions.test.d.ts +0 -1
- package/dist/__tests__/bot-conditions.test.js +0 -341
- package/dist/__tests__/client-id-tracker.test.d.ts +0 -1
- package/dist/__tests__/client-id-tracker.test.js +0 -137
- package/dist/__tests__/commands/new-atomic-commands.test.d.ts +0 -1
- package/dist/__tests__/commands/new-atomic-commands.test.js +0 -502
- package/dist/__tests__/commands/order-intent.test.d.ts +0 -1
- package/dist/__tests__/commands/order-intent.test.js +0 -600
- package/dist/__tests__/commands/trade-commands.test.d.ts +0 -1
- package/dist/__tests__/commands/trade-commands.test.js +0 -821
- package/dist/__tests__/config.test.d.ts +0 -1
- package/dist/__tests__/config.test.js +0 -86
- package/dist/__tests__/cross-chain-margin.test.d.ts +0 -1
- package/dist/__tests__/cross-chain-margin.test.js +0 -287
- package/dist/__tests__/dex-asset-map.test.d.ts +0 -1
- package/dist/__tests__/dex-asset-map.test.js +0 -191
- package/dist/__tests__/errors.test.d.ts +0 -1
- package/dist/__tests__/errors.test.js +0 -110
- package/dist/__tests__/event-stream.test.d.ts +0 -1
- package/dist/__tests__/event-stream.test.js +0 -276
- package/dist/__tests__/exchanges/interface.test.d.ts +0 -1
- package/dist/__tests__/exchanges/interface.test.js +0 -132
- package/dist/__tests__/exchanges/mock-adapter.d.ts +0 -69
- package/dist/__tests__/exchanges/mock-adapter.js +0 -137
- package/dist/__tests__/execution-log.test.d.ts +0 -1
- package/dist/__tests__/execution-log.test.js +0 -106
- package/dist/__tests__/funding-calc.test.d.ts +0 -1
- package/dist/__tests__/funding-calc.test.js +0 -71
- package/dist/__tests__/funding-history.test.d.ts +0 -1
- package/dist/__tests__/funding-history.test.js +0 -343
- package/dist/__tests__/funding-rates.test.d.ts +0 -1
- package/dist/__tests__/funding-rates.test.js +0 -342
- package/dist/__tests__/funding.test.d.ts +0 -1
- package/dist/__tests__/funding.test.js +0 -173
- package/dist/__tests__/gap-logic.test.d.ts +0 -1
- package/dist/__tests__/gap-logic.test.js +0 -43
- package/dist/__tests__/hip3-dex.test.d.ts +0 -1
- package/dist/__tests__/hip3-dex.test.js +0 -234
- package/dist/__tests__/integration/agent-features.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/agent-features.integration.test.js +0 -553
- package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +0 -13
- package/dist/__tests__/integration/atomic-commands.integration.test.js +0 -246
- package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/bridge-simulation.integration.test.js +0 -453
- package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/bridge-strict.integration.test.js +0 -812
- package/dist/__tests__/integration/bridge.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/bridge.integration.test.js +0 -309
- package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/cli-e2e.integration.test.js +0 -202
- package/dist/__tests__/integration/dex-arb.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/dex-arb.integration.test.js +0 -116
- package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +0 -13
- package/dist/__tests__/integration/envelope-consistency.integration.test.js +0 -205
- package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/hip3-dex.integration.test.js +0 -147
- package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/hyperliquid.integration.test.js +0 -79
- package/dist/__tests__/integration/lighter.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/lighter.integration.test.js +0 -53
- package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +0 -9
- package/dist/__tests__/integration/new-commands-e2e.integration.test.js +0 -236
- package/dist/__tests__/integration/order-verification.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/order-verification.integration.test.js +0 -321
- package/dist/__tests__/integration/pacifica.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/pacifica.integration.test.js +0 -75
- package/dist/__tests__/integration/response-shapes.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/response-shapes.integration.test.js +0 -278
- package/dist/__tests__/liquidity.test.d.ts +0 -1
- package/dist/__tests__/liquidity.test.js +0 -225
- package/dist/__tests__/plan-executor.test.d.ts +0 -1
- package/dist/__tests__/plan-executor.test.js +0 -314
- package/dist/__tests__/position-history.test.d.ts +0 -1
- package/dist/__tests__/position-history.test.js +0 -367
- package/dist/__tests__/retry.test.d.ts +0 -1
- package/dist/__tests__/retry.test.js +0 -310
- package/dist/__tests__/risk-assessment.test.d.ts +0 -1
- package/dist/__tests__/risk-assessment.test.js +0 -288
- package/dist/__tests__/security-adversarial.test.d.ts +0 -1
- package/dist/__tests__/security-adversarial.test.js +0 -574
- package/dist/__tests__/strategies.test.d.ts +0 -1
- package/dist/__tests__/strategies.test.js +0 -539
- package/dist/__tests__/trade-execution.test.d.ts +0 -1
- package/dist/__tests__/trade-execution.test.js +0 -129
- package/dist/__tests__/trade-validator.test.d.ts +0 -1
- package/dist/__tests__/trade-validator.test.js +0 -655
- package/dist/__tests__/utils.test.d.ts +0 -1
- package/dist/__tests__/utils.test.js +0 -76
- package/dist/arb/reconcile.d.ts +0 -28
- package/dist/arb/reconcile.js +0 -162
- package/dist/commands/alert.d.ts +0 -2
- package/dist/commands/alert.js +0 -421
- package/dist/commands/analytics.d.ts +0 -3
- package/dist/commands/analytics.js +0 -311
- package/dist/commands/arb-manage.d.ts +0 -5
- package/dist/commands/arb-manage.js +0 -5
- package/dist/commands/dashboard.d.ts +0 -3
- package/dist/commands/dashboard.js +0 -176
- package/dist/commands/deposit.d.ts +0 -4
- package/dist/commands/deposit.js +0 -573
- package/dist/commands/dex.d.ts +0 -3
- package/dist/commands/dex.js +0 -120
- package/dist/commands/env.d.ts +0 -2
- package/dist/commands/env.js +0 -136
- package/dist/commands/funding.d.ts +0 -3
- package/dist/commands/funding.js +0 -554
- package/dist/commands/gap.d.ts +0 -2
- package/dist/commands/gap.js +0 -305
- package/dist/commands/health.d.ts +0 -2
- package/dist/commands/health.js +0 -67
- package/dist/commands/multileg.d.ts +0 -16
- package/dist/commands/multileg.js +0 -174
- package/dist/commands/ows.d.ts +0 -2
- package/dist/commands/ows.js +0 -887
- package/dist/commands/pnl.d.ts +0 -12
- package/dist/commands/pnl.js +0 -243
- package/dist/commands/run.d.ts +0 -3
- package/dist/commands/run.js +0 -202
- package/dist/commands/stream.d.ts +0 -5
- package/dist/commands/stream.js +0 -200
- package/dist/commands/withdraw.d.ts +0 -3
- package/dist/commands/withdraw.js +0 -187
- package/dist/exchanges/edgex.d.ts +0 -62
- package/dist/exchanges/edgex.js +0 -552
- package/dist/exchanges/nado.d.ts +0 -52
- package/dist/exchanges/nado.js +0 -223
- package/dist/funding/index.d.ts +0 -3
- package/dist/funding/index.js +0 -3
- package/dist/retry.d.ts +0 -74
- package/dist/retry.js +0 -129
- package/dist/ws/market-feed.d.ts +0 -74
- package/dist/ws/market-feed.js +0 -227
package/dist/index.js
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// MUST be first import — installs a stderr filter for --json mode so SDK
|
|
3
|
+
// startup banners don't leak into machine output. ESM hoists all static
|
|
4
|
+
// imports to the top, but executes them in source order, so this side-effect
|
|
5
|
+
// import runs before any DEX SDK is loaded.
|
|
6
|
+
import "./_stderr-filter.js";
|
|
2
7
|
// Save original process.exit before any SDK (Go WASM) can patch it
|
|
3
8
|
const _origExit = process.exit.bind(process);
|
|
4
9
|
import { config } from "dotenv";
|
|
5
10
|
import { resolve } from "path";
|
|
6
11
|
import { createRequire } from "node:module";
|
|
7
|
-
//
|
|
12
|
+
// SSOT Rule #3: load only ~/.perp/.env. The CWD .env auto-load was removed
|
|
13
|
+
// because it caused master-address pollution when run from a project tree
|
|
14
|
+
// containing leftover dev keys (`wallet show` returned env-derived addresses
|
|
15
|
+
// instead of the OWS active wallet's). If you need ad-hoc env overrides,
|
|
16
|
+
// export them in your shell — keystore is the canonical source.
|
|
8
17
|
config({ path: resolve(process.env.HOME || "~", ".perp", ".env") });
|
|
9
|
-
config();
|
|
10
18
|
import { Command } from "commander";
|
|
11
19
|
import chalk from "chalk";
|
|
12
20
|
import { Keypair } from "@solana/web3.js";
|
|
@@ -17,28 +25,26 @@ import { resolveExchangeName } from "./exchanges/registry.js";
|
|
|
17
25
|
import { registerMarketCommands } from "./commands/market.js";
|
|
18
26
|
import { registerAccountCommands } from "./commands/account.js";
|
|
19
27
|
import { registerTradeCommands } from "./commands/trade.js";
|
|
20
|
-
|
|
28
|
+
// manage commands now register under `wallet manage ...` via registerWalletCommands.
|
|
21
29
|
// stream commands removed — WS feeds still used by dashboard/event-stream internally
|
|
22
30
|
import { registerArbCommands } from "./commands/arb.js";
|
|
23
31
|
import { registerWalletCommands } from "./commands/wallet.js";
|
|
24
|
-
|
|
25
|
-
import { registerBridgeCommands } from "./commands/bridge.js";
|
|
26
|
-
// deposit + withdraw merged into funds
|
|
32
|
+
// bridge + rebalance now nested under `funds` — registered inside registerFundsCommands.
|
|
27
33
|
import { registerFundsCommands } from "./commands/funds.js";
|
|
28
34
|
// alert commands removed
|
|
29
35
|
import { registerArbAutoCommands } from "./commands/arb-auto.js";
|
|
30
36
|
import { registerArbManageCommands } from "./commands/arb/index.js";
|
|
31
|
-
|
|
32
|
-
import {
|
|
33
|
-
import { registerBotCommands } from "./commands/bot.js";
|
|
37
|
+
// Agent commands now register under `wallet agent ...` via registerWalletCommands.
|
|
38
|
+
import { registerStrategyCommands } from "./commands/bot.js";
|
|
34
39
|
import { registerRiskCommands } from "./commands/risk.js";
|
|
35
40
|
import { registerHistoryCommands } from "./commands/history.js";
|
|
36
41
|
import { registerSettingsCommands } from "./commands/settings.js";
|
|
37
42
|
// dex commands merged into market (hip3) — use --dex flag for markets/balance
|
|
38
|
-
|
|
43
|
+
// `plan` is no longer top-level — registered as `strategy plan` inside
|
|
44
|
+
// registerStrategyCommands.
|
|
39
45
|
// funding merged into arb.ts
|
|
40
46
|
import { registerBacktestCommands } from "./commands/backtest.js";
|
|
41
|
-
import {
|
|
47
|
+
import { registerPortfolioCommand } from "./commands/portfolio.js";
|
|
42
48
|
import { registerInitCommand } from "./commands/init.js";
|
|
43
49
|
import { registerAlertCommands } from "./commands/alerts.js";
|
|
44
50
|
import { loadSettings, saveSettings } from "./settings.js";
|
|
@@ -63,6 +69,8 @@ program
|
|
|
63
69
|
.option("-w, --wallet <name>", "Use a specific wallet by name (from 'perp wallet list')")
|
|
64
70
|
.option("--ows <name>", "Use an OWS (Open Wallet Standard) wallet by name")
|
|
65
71
|
.option("--ows-key <token>", "OWS API key token (ows_key_...) for policy-gated agent access")
|
|
72
|
+
.option("--no-agent", "Bypass agent wallet routing for this run; falls back to OWS master or PK direct")
|
|
73
|
+
.option("--passphrase <pp>", "Master OWS passphrase (also accepts OWS_PASSPHRASE env or stdin)")
|
|
66
74
|
.option("--dex <name>", "HIP-3 deployed perp dex name (Hyperliquid only)")
|
|
67
75
|
.configureOutput({
|
|
68
76
|
writeErr: (str) => {
|
|
@@ -96,6 +104,9 @@ async function getAdapter() {
|
|
|
96
104
|
const owsName = opts.ows;
|
|
97
105
|
const owsKeyToken = opts.owsKey || process.env.OWS_API_KEY;
|
|
98
106
|
const walletName = owsName ? `ows:${owsName}` : opts.wallet;
|
|
107
|
+
// Commander's `--no-agent` flag sets opts.agent === false (NOT opts.noAgent).
|
|
108
|
+
// Normalize so downstream wiring can use a single boolean consistently.
|
|
109
|
+
const noAgent = opts.agent === false;
|
|
99
110
|
// Skip cache when --wallet/--ows is specified (different wallet = different account)
|
|
100
111
|
if (!walletName && _adapter)
|
|
101
112
|
return _adapter;
|
|
@@ -115,6 +126,27 @@ async function getAdapter() {
|
|
|
115
126
|
const settings = loadSettings();
|
|
116
127
|
const builderCode = process.env.PACIFICA_BUILDER_CODE || settings.referralCodes.pacifica || "PERPCLI";
|
|
117
128
|
_pacificaAdapter = new PacificaAdapter(keypair, pacNetwork, builderCode, !!pk);
|
|
129
|
+
// Tier 2: OWS master if --ows or settings.owsActiveWallet (Phase 2c)
|
|
130
|
+
const pacOwsName = owsName || _settings.owsActiveWallet;
|
|
131
|
+
if (pacOwsName && !pk) {
|
|
132
|
+
const { OwsSolanaSigner } = await import("./signer/ows-solana.js");
|
|
133
|
+
const { resolvePassphrase } = await import("./agent-wallet/passphrase.js");
|
|
134
|
+
const pp = await resolvePassphrase({ flag: opts.passphrase });
|
|
135
|
+
if (pp !== null)
|
|
136
|
+
_pacificaAdapter.setSigner(OwsSolanaSigner.create(pacOwsName, pp));
|
|
137
|
+
}
|
|
138
|
+
// Tier 1: agent if registered (Phase 2c)
|
|
139
|
+
const { getAgent: getPacAgent } = await import("./agent-wallet/store.js");
|
|
140
|
+
const pacAgentMeta = getPacAgent("pacifica");
|
|
141
|
+
if (pacAgentMeta) {
|
|
142
|
+
const { OwsSolanaSigner: PacOwsSolanaSigner } = await import("./signer/ows-solana.js");
|
|
143
|
+
// Pacifica agent wallet was created with empty passphrase (see runPacApproveFlow);
|
|
144
|
+
// open with "" to match. Agent at-rest encryption handled by OWS storage layer.
|
|
145
|
+
const agentSigner = PacOwsSolanaSigner.create(pacAgentMeta.agentWalletName, "");
|
|
146
|
+
_pacificaAdapter.setAgentSigner(pacAgentMeta, agentSigner);
|
|
147
|
+
}
|
|
148
|
+
if (noAgent)
|
|
149
|
+
_pacificaAdapter.setNoAgent(true);
|
|
118
150
|
_adapter = _pacificaAdapter;
|
|
119
151
|
break;
|
|
120
152
|
}
|
|
@@ -125,43 +157,79 @@ async function getAdapter() {
|
|
|
125
157
|
await _hlAdapter.init();
|
|
126
158
|
if (pk) {
|
|
127
159
|
const hlSettings = loadSettings();
|
|
128
|
-
if (
|
|
160
|
+
if (!hlSettings.referralApplied.hyperliquid) {
|
|
129
161
|
const hlRef = process.env.HL_REFERRAL_CODE || hlSettings.referralCodes.hyperliquid;
|
|
130
162
|
if (hlRef) {
|
|
131
163
|
_hlAdapter.autoSetReferrer(hlRef).then(() => {
|
|
132
164
|
const s = loadSettings();
|
|
133
165
|
s.referralApplied.hyperliquid = true;
|
|
134
166
|
saveSettings(s);
|
|
135
|
-
}).catch(() => {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
167
|
+
}).catch((err) => {
|
|
168
|
+
// Per SSOT Rule #2: do NOT mark applied=true on failure.
|
|
169
|
+
// Leave referralApplied[ex]=false so next adapter init retries.
|
|
170
|
+
process.stderr.write(`[hyperliquid] referral apply failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
139
171
|
});
|
|
140
172
|
}
|
|
141
173
|
}
|
|
142
174
|
}
|
|
175
|
+
// Tier 2: OWS master if --ows or settings.owsActiveWallet
|
|
176
|
+
const hlOwsName = owsName || _settings.owsActiveWallet;
|
|
177
|
+
if (hlOwsName && !pk) {
|
|
178
|
+
const { OwsEvmSigner } = await import("./signer/ows-evm.js");
|
|
179
|
+
const { resolvePassphrase } = await import("./agent-wallet/passphrase.js");
|
|
180
|
+
const pp = await resolvePassphrase({ flag: opts.passphrase });
|
|
181
|
+
if (pp !== null)
|
|
182
|
+
_hlAdapter.setSigner(OwsEvmSigner.create(hlOwsName, pp));
|
|
183
|
+
}
|
|
184
|
+
// Tier 1: agent if registered
|
|
185
|
+
const { getAgent: getHlAgent } = await import("./agent-wallet/store.js");
|
|
186
|
+
const hlAgentMeta = getHlAgent("hyperliquid");
|
|
187
|
+
if (hlAgentMeta) {
|
|
188
|
+
const { OwsEvmSigner: HlOwsEvmSigner } = await import("./signer/ows-evm.js");
|
|
189
|
+
// HL agent wallet was created with empty passphrase (see runHlApproveFlow);
|
|
190
|
+
// open with "" to match. Agent at-rest encryption handled by OWS storage layer.
|
|
191
|
+
const agentSigner = HlOwsEvmSigner.create(hlAgentMeta.agentWalletName, "");
|
|
192
|
+
_hlAdapter.setAgentSigner(hlAgentMeta, agentSigner);
|
|
193
|
+
}
|
|
194
|
+
if (noAgent)
|
|
195
|
+
_hlAdapter.setNoAgent(true);
|
|
143
196
|
_adapter = _hlAdapter;
|
|
144
197
|
break;
|
|
145
198
|
}
|
|
146
199
|
case "lighter": {
|
|
147
200
|
const { LighterAdapter } = await import("./exchanges/lighter.js");
|
|
201
|
+
// Phase 2d (SSOT Rule #3): when an agent is registered AND not bypassed,
|
|
202
|
+
// load the agent's L2 secp256k1 key from the encrypted keystore at
|
|
203
|
+
// ~/.perp/lighter-agents/<accountIndex>-<slot>.json BEFORE init() so the
|
|
204
|
+
// WASM client is created with the agent identity. Missing keystore →
|
|
205
|
+
// loadLighterKey throws LIGHTER_KEYSTORE_NOT_FOUND with remediation
|
|
206
|
+
// (no fallback to env per SSOT Rule #2).
|
|
207
|
+
const { getAgent: getLtAgent } = await import("./agent-wallet/store.js");
|
|
208
|
+
const ltAgentMeta = getLtAgent("lighter");
|
|
148
209
|
_lighterAdapter = new LighterAdapter(pk ?? "", isTestnet);
|
|
210
|
+
if (ltAgentMeta && !noAgent) {
|
|
211
|
+
const { loadLighterKey } = await import("./agent-wallet/lighter-keystore.js");
|
|
212
|
+
const ltL2Key = loadLighterKey(ltAgentMeta.accountIndex, ltAgentMeta.apiKeyIndex, "");
|
|
213
|
+
_lighterAdapter.setAgentSigner(ltAgentMeta, ltL2Key);
|
|
214
|
+
}
|
|
215
|
+
if (noAgent)
|
|
216
|
+
_lighterAdapter.setNoAgent(true);
|
|
149
217
|
await _lighterAdapter.init();
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
});
|
|
164
|
-
}
|
|
218
|
+
// LT referral apply is L2-signed (POST /referral/use uses the WASM slot
|
|
219
|
+
// signer's auth token, not the master EVM). Trigger on any active
|
|
220
|
+
// signer tier — agent / OWS master / PK direct — gated by isReadOnly.
|
|
221
|
+
const ltSettings = loadSettings();
|
|
222
|
+
if (!ltSettings.referralApplied.lighter && !_lighterAdapter.isReadOnly) {
|
|
223
|
+
const ltRef = process.env.LIGHTER_REFERRAL_CODE || ltSettings.referralCodes.lighter;
|
|
224
|
+
if (ltRef) {
|
|
225
|
+
_lighterAdapter.useReferralCode(ltRef).then(() => {
|
|
226
|
+
const s = loadSettings();
|
|
227
|
+
s.referralApplied.lighter = true;
|
|
228
|
+
saveSettings(s);
|
|
229
|
+
}).catch((err) => {
|
|
230
|
+
// Per SSOT Rule #2: do NOT mark applied=true on failure.
|
|
231
|
+
process.stderr.write(`[lighter] referral apply failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
232
|
+
});
|
|
165
233
|
}
|
|
166
234
|
}
|
|
167
235
|
_adapter = _lighterAdapter;
|
|
@@ -169,8 +237,27 @@ async function getAdapter() {
|
|
|
169
237
|
}
|
|
170
238
|
case "aster": {
|
|
171
239
|
const { AsterAdapter } = await import("./exchanges/aster.js");
|
|
172
|
-
const ast = new AsterAdapter(
|
|
240
|
+
const ast = new AsterAdapter(pk ?? undefined, isTestnet); // Tier 3 from PK
|
|
173
241
|
await ast.init();
|
|
242
|
+
// Tier 2: OWS master if --ows or settings.owsActiveWallet
|
|
243
|
+
const asterOwsName = owsName || _settings.owsActiveWallet;
|
|
244
|
+
if (asterOwsName) {
|
|
245
|
+
const { OwsEvmSigner } = await import("./signer/ows-evm.js");
|
|
246
|
+
const { resolvePassphrase } = await import("./agent-wallet/passphrase.js");
|
|
247
|
+
const pp = await resolvePassphrase({ flag: opts.passphrase });
|
|
248
|
+
if (pp !== null)
|
|
249
|
+
ast.setMasterSigner(OwsEvmSigner.create(asterOwsName, pp));
|
|
250
|
+
}
|
|
251
|
+
// Tier 1: agent if registered
|
|
252
|
+
const { getAgent: getAsterAgent } = await import("./agent-wallet/store.js");
|
|
253
|
+
const agentMeta = getAsterAgent("aster");
|
|
254
|
+
if (agentMeta) {
|
|
255
|
+
const { agentSigningStrategyFor } = await import("./agent-wallet/signing-strategy.js");
|
|
256
|
+
const strat = agentSigningStrategyFor(agentMeta, owsKeyToken ?? "");
|
|
257
|
+
ast.setAgent(agentMeta, strat);
|
|
258
|
+
}
|
|
259
|
+
if (noAgent)
|
|
260
|
+
ast.setNoAgent(true);
|
|
174
261
|
_adapter = ast;
|
|
175
262
|
break;
|
|
176
263
|
}
|
|
@@ -185,6 +272,8 @@ async function _initWithOws(exchange, owsWalletName, isTestnet, opts, owsKeyToke
|
|
|
185
272
|
const { OwsSolanaSigner } = await import("./signer/ows-solana.js");
|
|
186
273
|
// If an OWS API key token is provided, use it as passphrase → routes through policy engine
|
|
187
274
|
const passphrase = owsKeyToken || "";
|
|
275
|
+
// Commander's `--no-agent` flag sets opts.agent === false (NOT opts.noAgent).
|
|
276
|
+
const noAgent = opts.agent === false;
|
|
188
277
|
switch (exchange) {
|
|
189
278
|
case "pacifica": {
|
|
190
279
|
const pacNetwork = (isTestnet ? "testnet" : "mainnet");
|
|
@@ -193,6 +282,15 @@ async function _initWithOws(exchange, owsWalletName, isTestnet, opts, owsKeyToke
|
|
|
193
282
|
const dummyKeypair = Keypair.generate();
|
|
194
283
|
_pacificaAdapter = new PacificaAdapter(dummyKeypair, pacNetwork, builderCode, true);
|
|
195
284
|
_pacificaAdapter.setSigner(OwsSolanaSigner.create(owsWalletName, passphrase));
|
|
285
|
+
// Tier 1: agent if registered (Phase 2c)
|
|
286
|
+
const { getAgent: getPacAgentOws } = await import("./agent-wallet/store.js");
|
|
287
|
+
const pacAgentMetaOws = getPacAgentOws("pacifica");
|
|
288
|
+
if (pacAgentMetaOws) {
|
|
289
|
+
const agentSignerOws = OwsSolanaSigner.create(pacAgentMetaOws.agentWalletName, "");
|
|
290
|
+
_pacificaAdapter.setAgentSigner(pacAgentMetaOws, agentSignerOws);
|
|
291
|
+
}
|
|
292
|
+
if (noAgent)
|
|
293
|
+
_pacificaAdapter.setNoAgent(true);
|
|
196
294
|
_adapter = _pacificaAdapter;
|
|
197
295
|
return _adapter;
|
|
198
296
|
}
|
|
@@ -209,10 +307,40 @@ async function _initWithOws(exchange, owsWalletName, isTestnet, opts, owsKeyToke
|
|
|
209
307
|
const { LighterAdapter } = await import("./exchanges/lighter.js");
|
|
210
308
|
_lighterAdapter = new LighterAdapter("", isTestnet);
|
|
211
309
|
_lighterAdapter.setSigner(OwsEvmSigner.create(owsWalletName, passphrase));
|
|
310
|
+
// Tier 1: agent if registered (mirrors getAdapter / getAdapterForExchange).
|
|
311
|
+
// Without this, _initWithOws would skip Tier 1 and fall through to
|
|
312
|
+
// auto-setup-at-slot-4 which is wrong for agent users (and breaks under
|
|
313
|
+
// SDK 1.0.11+ where signChangePubKey signature changed).
|
|
314
|
+
const { getAgent: getLtAgentOws } = await import("./agent-wallet/store.js");
|
|
315
|
+
const ltAgentMetaOws = getLtAgentOws("lighter");
|
|
316
|
+
if (ltAgentMetaOws && !noAgent) {
|
|
317
|
+
const { loadLighterKey } = await import("./agent-wallet/lighter-keystore.js");
|
|
318
|
+
const ltL2KeyOws = loadLighterKey(ltAgentMetaOws.accountIndex, ltAgentMetaOws.apiKeyIndex, "");
|
|
319
|
+
_lighterAdapter.setAgentSigner(ltAgentMetaOws, ltL2KeyOws);
|
|
320
|
+
}
|
|
321
|
+
if (noAgent)
|
|
322
|
+
_lighterAdapter.setNoAgent(true);
|
|
212
323
|
await _lighterAdapter.init();
|
|
213
324
|
_adapter = _lighterAdapter;
|
|
214
325
|
return _adapter;
|
|
215
326
|
}
|
|
327
|
+
case "aster": {
|
|
328
|
+
const { AsterAdapter } = await import("./exchanges/aster.js");
|
|
329
|
+
const asterOws = new AsterAdapter(undefined, isTestnet);
|
|
330
|
+
await asterOws.init();
|
|
331
|
+
asterOws.setMasterSigner(OwsEvmSigner.create(owsWalletName, passphrase));
|
|
332
|
+
// Optionally also set agent if registered
|
|
333
|
+
const { getAgent: getAsterAgentOws } = await import("./agent-wallet/store.js");
|
|
334
|
+
const asterMeta = getAsterAgentOws("aster");
|
|
335
|
+
if (asterMeta) {
|
|
336
|
+
const { agentSigningStrategyFor } = await import("./agent-wallet/signing-strategy.js");
|
|
337
|
+
asterOws.setAgent(asterMeta, agentSigningStrategyFor(asterMeta, owsKeyToken ?? ""));
|
|
338
|
+
}
|
|
339
|
+
if (noAgent)
|
|
340
|
+
asterOws.setNoAgent(true);
|
|
341
|
+
_adapter = asterOws;
|
|
342
|
+
return _adapter;
|
|
343
|
+
}
|
|
216
344
|
default:
|
|
217
345
|
throw new Error(`Unknown exchange: ${exchange}`);
|
|
218
346
|
}
|
|
@@ -244,13 +372,11 @@ function getHLAdapter() {
|
|
|
244
372
|
registerMarketCommands(program, getAdapter, isJson, getAdapterForExchange);
|
|
245
373
|
registerAccountCommands(program, getAdapter, isJson, getAdapterForExchange);
|
|
246
374
|
registerTradeCommands(program, getAdapter, isJson, isDryRun, getAdapterForExchange);
|
|
247
|
-
|
|
375
|
+
// manage tree wired inside registerWalletCommands below.
|
|
248
376
|
// stream commands removed
|
|
249
377
|
registerArbCommands(program, isJson, getAdapterForExchange);
|
|
250
|
-
registerWalletCommands(program, isJson);
|
|
251
|
-
|
|
252
|
-
registerBridgeCommands(program, isJson);
|
|
253
|
-
registerFundsCommands(program, getAdapter, isJson, () => program.opts().network);
|
|
378
|
+
registerWalletCommands(program, isJson, getAdapter, getPacificaAdapter);
|
|
379
|
+
registerFundsCommands(program, getAdapter, isJson, () => program.opts().network, getAdapterForExchange);
|
|
254
380
|
// alert commands removed
|
|
255
381
|
// Helper to get adapter for a specific exchange (used by arb-auto)
|
|
256
382
|
async function getAdapterForExchange(rawExchange) {
|
|
@@ -258,8 +384,19 @@ async function getAdapterForExchange(rawExchange) {
|
|
|
258
384
|
const opts = program.opts();
|
|
259
385
|
const network = opts.network;
|
|
260
386
|
const isTestnet = network === "testnet";
|
|
261
|
-
const
|
|
387
|
+
const owsName = opts.ows;
|
|
388
|
+
const owsKeyToken = opts.owsKey || process.env.OWS_API_KEY;
|
|
389
|
+
const walletName = owsName ? `ows:${owsName}` : opts.wallet;
|
|
390
|
+
// Commander's `--no-agent` flag sets opts.agent === false (NOT opts.noAgent).
|
|
391
|
+
const noAgent = opts.agent === false;
|
|
262
392
|
const pk = await tryLoadPrivateKey(exchange, opts.privateKey, walletName);
|
|
393
|
+
// Route through OWS init when pk is an OWS reference (e.g. "ows:main").
|
|
394
|
+
// Mirrors getAdapter() at the top of this file. Without this, adapters
|
|
395
|
+
// would try to parse "ows:main" as a raw private key and ethers would
|
|
396
|
+
// throw "invalid BytesLike value" / parseSolanaKeypair would fail.
|
|
397
|
+
if (pk && isOwsKey(pk)) {
|
|
398
|
+
return _initWithOws(exchange, getOwsWalletName(pk), isTestnet, opts, owsKeyToken);
|
|
399
|
+
}
|
|
263
400
|
switch (exchange) {
|
|
264
401
|
case "pacifica": {
|
|
265
402
|
if (_pacificaAdapter)
|
|
@@ -269,6 +406,18 @@ async function getAdapterForExchange(rawExchange) {
|
|
|
269
406
|
const s1 = loadSettings();
|
|
270
407
|
const builderCode = process.env.PACIFICA_BUILDER_CODE || s1.referralCodes.pacifica || "PERPCLI";
|
|
271
408
|
_pacificaAdapter = new PacificaAdapter(keypair, pacNetwork, builderCode, !!pk);
|
|
409
|
+
// Tier 1: agent if registered (Phase 2c)
|
|
410
|
+
const { getAgent: getPacAgentEx } = await import("./agent-wallet/store.js");
|
|
411
|
+
const pacAgentMetaEx = getPacAgentEx("pacifica");
|
|
412
|
+
if (pacAgentMetaEx) {
|
|
413
|
+
const { OwsSolanaSigner: PacOwsSolanaSignerEx } = await import("./signer/ows-solana.js");
|
|
414
|
+
// Pacifica agent wallet was created with empty passphrase (see runPacApproveFlow);
|
|
415
|
+
// open with "" to match. Agent at-rest encryption handled by OWS storage layer.
|
|
416
|
+
const agentSignerEx = PacOwsSolanaSignerEx.create(pacAgentMetaEx.agentWalletName, "");
|
|
417
|
+
_pacificaAdapter.setAgentSigner(pacAgentMetaEx, agentSignerEx);
|
|
418
|
+
}
|
|
419
|
+
if (noAgent)
|
|
420
|
+
_pacificaAdapter.setNoAgent(true);
|
|
272
421
|
if (!_adapter)
|
|
273
422
|
_adapter = _pacificaAdapter;
|
|
274
423
|
return _pacificaAdapter;
|
|
@@ -282,21 +431,35 @@ async function getAdapterForExchange(rawExchange) {
|
|
|
282
431
|
await _hlAdapter.init();
|
|
283
432
|
if (pk) {
|
|
284
433
|
const s2 = loadSettings();
|
|
285
|
-
if (
|
|
434
|
+
if (!s2.referralApplied.hyperliquid) {
|
|
286
435
|
const hlRef = process.env.HL_REFERRAL_CODE || s2.referralCodes.hyperliquid;
|
|
287
436
|
if (hlRef) {
|
|
288
437
|
_hlAdapter.autoSetReferrer(hlRef).then(() => {
|
|
289
438
|
const s = loadSettings();
|
|
290
439
|
s.referralApplied.hyperliquid = true;
|
|
291
440
|
saveSettings(s);
|
|
292
|
-
}).catch(() => {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
441
|
+
}).catch((err) => {
|
|
442
|
+
// Per SSOT Rule #2: do NOT mark applied=true on failure.
|
|
443
|
+
// Leave referralApplied[ex]=false so next adapter init retries.
|
|
444
|
+
process.stderr.write(`[hyperliquid] referral apply failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
296
445
|
});
|
|
297
446
|
}
|
|
298
447
|
}
|
|
299
448
|
}
|
|
449
|
+
// Tier 1: agent if registered
|
|
450
|
+
{
|
|
451
|
+
const { getAgent: getHlAgent2 } = await import("./agent-wallet/store.js");
|
|
452
|
+
const hlAgentMeta2 = getHlAgent2("hyperliquid");
|
|
453
|
+
if (hlAgentMeta2) {
|
|
454
|
+
const { OwsEvmSigner: HlOwsEvmSigner2 } = await import("./signer/ows-evm.js");
|
|
455
|
+
// HL agent wallet was created with empty passphrase (see runHlApproveFlow);
|
|
456
|
+
// open with "" to match. Agent at-rest encryption handled by OWS storage layer.
|
|
457
|
+
const agentSigner2 = HlOwsEvmSigner2.create(hlAgentMeta2.agentWalletName, "");
|
|
458
|
+
_hlAdapter.setAgentSigner(hlAgentMeta2, agentSigner2);
|
|
459
|
+
}
|
|
460
|
+
if (noAgent)
|
|
461
|
+
_hlAdapter.setNoAgent(true);
|
|
462
|
+
}
|
|
300
463
|
if (!_adapter)
|
|
301
464
|
_adapter = _hlAdapter;
|
|
302
465
|
return _hlAdapter;
|
|
@@ -305,23 +468,32 @@ async function getAdapterForExchange(rawExchange) {
|
|
|
305
468
|
if (_lighterAdapter)
|
|
306
469
|
return _lighterAdapter;
|
|
307
470
|
const { LighterAdapter } = await import("./exchanges/lighter.js");
|
|
471
|
+
const { getAgent: getLtAgentEx } = await import("./agent-wallet/store.js");
|
|
472
|
+
const ltAgentMetaEx = getLtAgentEx("lighter");
|
|
308
473
|
_lighterAdapter = new LighterAdapter(pk ?? "", isTestnet);
|
|
474
|
+
if (ltAgentMetaEx && !noAgent) {
|
|
475
|
+
// SSOT Rule #3: load agent L2 key from encrypted keystore — no env fallback.
|
|
476
|
+
const { loadLighterKey } = await import("./agent-wallet/lighter-keystore.js");
|
|
477
|
+
const ltL2KeyEx = loadLighterKey(ltAgentMetaEx.accountIndex, ltAgentMetaEx.apiKeyIndex, "");
|
|
478
|
+
_lighterAdapter.setAgentSigner(ltAgentMetaEx, ltL2KeyEx);
|
|
479
|
+
}
|
|
480
|
+
if (noAgent)
|
|
481
|
+
_lighterAdapter.setNoAgent(true);
|
|
309
482
|
await _lighterAdapter.init();
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
483
|
+
// LT referral apply is L2-signed; trigger on any active tier (agent
|
|
484
|
+
// included), gated by isReadOnly. See first LT case for rationale.
|
|
485
|
+
const s3 = loadSettings();
|
|
486
|
+
if (!s3.referralApplied.lighter && !_lighterAdapter.isReadOnly) {
|
|
487
|
+
const ltRef = process.env.LIGHTER_REFERRAL_CODE || s3.referralCodes.lighter;
|
|
488
|
+
if (ltRef) {
|
|
489
|
+
_lighterAdapter.useReferralCode(ltRef).then(() => {
|
|
490
|
+
const s = loadSettings();
|
|
491
|
+
s.referralApplied.lighter = true;
|
|
492
|
+
saveSettings(s);
|
|
493
|
+
}).catch((err) => {
|
|
494
|
+
// Per SSOT Rule #2: do NOT mark applied=true on failure.
|
|
495
|
+
process.stderr.write(`[lighter] referral apply failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
496
|
+
});
|
|
325
497
|
}
|
|
326
498
|
}
|
|
327
499
|
if (!_adapter)
|
|
@@ -330,9 +502,30 @@ async function getAdapterForExchange(rawExchange) {
|
|
|
330
502
|
}
|
|
331
503
|
case "aster": {
|
|
332
504
|
const { AsterAdapter } = await import("./exchanges/aster.js");
|
|
333
|
-
const
|
|
334
|
-
await
|
|
335
|
-
|
|
505
|
+
const astEx = new AsterAdapter(pk ?? undefined, isTestnet);
|
|
506
|
+
await astEx.init();
|
|
507
|
+
// Tier 2: OWS master if --ows or settings.owsActiveWallet
|
|
508
|
+
const asterExOwsName = opts.ows || _settings.owsActiveWallet;
|
|
509
|
+
if (asterExOwsName) {
|
|
510
|
+
const { OwsEvmSigner } = await import("./signer/ows-evm.js");
|
|
511
|
+
const { resolvePassphrase } = await import("./agent-wallet/passphrase.js");
|
|
512
|
+
const pp = await resolvePassphrase({ flag: opts.passphrase });
|
|
513
|
+
if (pp !== null)
|
|
514
|
+
astEx.setMasterSigner(OwsEvmSigner.create(asterExOwsName, pp));
|
|
515
|
+
}
|
|
516
|
+
// Tier 1: agent if registered
|
|
517
|
+
const owsKeyEx = opts.owsKey || process.env.OWS_API_KEY;
|
|
518
|
+
const { getAgent: getAsterAgentEx } = await import("./agent-wallet/store.js");
|
|
519
|
+
const agentMetaEx = getAsterAgentEx("aster");
|
|
520
|
+
if (agentMetaEx) {
|
|
521
|
+
const { agentSigningStrategyFor } = await import("./agent-wallet/signing-strategy.js");
|
|
522
|
+
astEx.setAgent(agentMetaEx, agentSigningStrategyFor(agentMetaEx, owsKeyEx ?? ""));
|
|
523
|
+
}
|
|
524
|
+
if (noAgent)
|
|
525
|
+
astEx.setNoAgent(true);
|
|
526
|
+
if (!_adapter)
|
|
527
|
+
_adapter = astEx;
|
|
528
|
+
return astEx;
|
|
336
529
|
}
|
|
337
530
|
default:
|
|
338
531
|
throw new Error(`Unknown exchange: ${exchange}`);
|
|
@@ -355,312 +548,27 @@ async function getHLAdapterForDex(dex) {
|
|
|
355
548
|
}
|
|
356
549
|
registerArbAutoCommands(program, getAdapterForExchange, isJson, getHLAdapterForDex);
|
|
357
550
|
registerArbManageCommands(program, getAdapterForExchange, isJson);
|
|
358
|
-
|
|
359
|
-
//
|
|
360
|
-
|
|
361
|
-
//
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
551
|
+
// `agent` is no longer top-level — registered as `wallet agent` inside
|
|
552
|
+
// registerWalletCommands above.
|
|
553
|
+
// withdraw merged into funds.
|
|
554
|
+
// `rebalance` is no longer top-level — registered as `funds rebalance` inside
|
|
555
|
+
// registerFundsCommands above.
|
|
556
|
+
// Background processes & strategies
|
|
557
|
+
import { registerBackgroundCommands } from "./commands/jobs.js";
|
|
558
|
+
registerBackgroundCommands(program, isJson);
|
|
559
|
+
registerStrategyCommands(program, getAdapter, getAdapterForExchange, isJson);
|
|
365
560
|
// Agent-friendly commands
|
|
366
561
|
registerRiskCommands(program, getAdapterForExchange, isJson);
|
|
367
562
|
registerHistoryCommands(program, isJson, getAdapterForExchange);
|
|
368
563
|
registerSettingsCommands(program, isJson, getAdapterForExchange);
|
|
369
564
|
// dex commands removed — use 'market hip3' + --dex flag
|
|
370
|
-
|
|
565
|
+
// `plan` is no longer top-level — registered as `strategy plan` inside
|
|
566
|
+
// registerStrategyCommands above.
|
|
371
567
|
// funding merged into arb — registerFundingCommands removed
|
|
372
568
|
registerBacktestCommands(program, isJson);
|
|
373
|
-
|
|
569
|
+
registerPortfolioCommand(program, getAdapterForExchange, isJson, _pkg.version, getHLAdapterForDex);
|
|
374
570
|
registerInitCommand(program);
|
|
375
571
|
registerAlertCommands(program, isJson);
|
|
376
|
-
// Agent discovery: perp api-spec — deprecated, use 'perp agent schema'
|
|
377
|
-
const apiSpecCmd = program
|
|
378
|
-
.command("api-spec")
|
|
379
|
-
.description("Use 'perp agent schema'")
|
|
380
|
-
.action(async () => {
|
|
381
|
-
const { jsonOk, printJson } = await import("./utils.js");
|
|
382
|
-
const { getCliSpec } = await import("./cli-spec.js");
|
|
383
|
-
printJson(jsonOk(getCliSpec(program)));
|
|
384
|
-
});
|
|
385
|
-
apiSpecCmd._hidden = true;
|
|
386
|
-
// Unified dashboard: balances + positions + top arb opportunities
|
|
387
|
-
program.command("status")
|
|
388
|
-
.description("Unified dashboard: balances, positions, and top arb opportunities")
|
|
389
|
-
.option("--health", "Check connectivity only")
|
|
390
|
-
.action(async (opts) => {
|
|
391
|
-
if (opts.health) {
|
|
392
|
-
const { runHealthCheck } = await import("./commands/risk.js");
|
|
393
|
-
return runHealthCheck(isJson);
|
|
394
|
-
}
|
|
395
|
-
const json = isJson();
|
|
396
|
-
const { formatUsd, formatPnl, makeTable, printJson, jsonOk, withJsonErrors } = await import("./utils.js");
|
|
397
|
-
const { fetchAllFundingRates, TOP_SYMBOLS } = await import("./funding-rates.js");
|
|
398
|
-
const { saveFundingSnapshot, getHistoricalRates } = await import("./funding-history.js");
|
|
399
|
-
await withJsonErrors(json, async () => {
|
|
400
|
-
const EX_LIST = ["pacifica", "hyperliquid", "lighter", "aster"];
|
|
401
|
-
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : e === "lighter" ? "LT" : "AST";
|
|
402
|
-
const snapshotPromises = EX_LIST.map(async (ex) => {
|
|
403
|
-
try {
|
|
404
|
-
const adapter = await getAdapterForExchange(ex);
|
|
405
|
-
const [balance, positions, orders] = await Promise.all([
|
|
406
|
-
adapter.getBalance(), adapter.getPositions(), adapter.getOpenOrders(),
|
|
407
|
-
]);
|
|
408
|
-
// Fetch spot balances for HL and LT (Pacifica is perp-only)
|
|
409
|
-
let spotHoldings = [];
|
|
410
|
-
let isUnified = false;
|
|
411
|
-
try {
|
|
412
|
-
if (ex === "hyperliquid") {
|
|
413
|
-
const { HyperliquidSpotAdapter } = await import("./exchanges/hyperliquid-spot.js");
|
|
414
|
-
const hlSpot = new HyperliquidSpotAdapter(adapter);
|
|
415
|
-
await hlSpot.init();
|
|
416
|
-
const [raw, markets] = await Promise.all([hlSpot.getSpotBalances(), hlSpot.getSpotMarkets()]);
|
|
417
|
-
const priceMap = new Map(markets.map(m => [m.baseToken.toUpperCase(), Number(m.markPrice)]));
|
|
418
|
-
const strip = (t) => t.replace(/-SPOT$/i, "").toUpperCase();
|
|
419
|
-
spotHoldings = raw.filter(b => Number(b.total) > 0).map(b => {
|
|
420
|
-
const base = strip(b.token);
|
|
421
|
-
return { ...b, valueUsd: base === "USDC" ? Number(b.total) : (priceMap.get(base) ?? 0) * Number(b.total) };
|
|
422
|
-
});
|
|
423
|
-
isUnified = !adapter.dex;
|
|
424
|
-
}
|
|
425
|
-
else if (ex === "lighter") {
|
|
426
|
-
const { LighterAdapter } = await import("./exchanges/lighter.js");
|
|
427
|
-
const { LighterSpotAdapter } = await import("./exchanges/lighter-spot.js");
|
|
428
|
-
const ltSpot = new LighterSpotAdapter(adapter);
|
|
429
|
-
await ltSpot.init();
|
|
430
|
-
const [raw, markets] = await Promise.all([ltSpot.getSpotBalances(), ltSpot.getSpotMarkets()]);
|
|
431
|
-
const priceMap = new Map(markets.map(m => [m.baseToken.toUpperCase(), Number(m.markPrice)]));
|
|
432
|
-
spotHoldings = raw.filter(b => Number(b.total) > 0).map(b => ({
|
|
433
|
-
...b,
|
|
434
|
-
valueUsd: b.token === "USDC" || b.token === "USDC_SPOT" ? Number(b.total) : (priceMap.get(b.token.toUpperCase()) ?? 0) * Number(b.total),
|
|
435
|
-
}));
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
catch { /* spot not available */ }
|
|
439
|
-
return { exchange: ex, connected: true, balance, positions, openOrders: orders.length, spotHoldings, isUnified, error: undefined };
|
|
440
|
-
}
|
|
441
|
-
catch (err) {
|
|
442
|
-
return { exchange: ex, connected: false, balance: null, positions: [], openOrders: 0, spotHoldings: [], isUnified: false, error: err instanceof Error ? err.message : String(err) };
|
|
443
|
-
}
|
|
444
|
-
});
|
|
445
|
-
const arbPromise = fetchAllFundingRates({ symbols: TOP_SYMBOLS, minSpread: 0 }).catch(() => null);
|
|
446
|
-
const [snapshots, arbSnapshot] = await Promise.all([Promise.all(snapshotPromises), arbPromise]);
|
|
447
|
-
// Save funding snapshot for sparkline history
|
|
448
|
-
if (arbSnapshot) {
|
|
449
|
-
try {
|
|
450
|
-
const allRates = arbSnapshot.symbols.flatMap(s => s.rates);
|
|
451
|
-
if (allRates.length > 0)
|
|
452
|
-
saveFundingSnapshot(allRates);
|
|
453
|
-
}
|
|
454
|
-
catch { /* */ }
|
|
455
|
-
}
|
|
456
|
-
// Build totals (include non-USDC spot value for unified accounts)
|
|
457
|
-
let totalEquity = 0;
|
|
458
|
-
let totalSpotValue = 0;
|
|
459
|
-
const allPositions = [];
|
|
460
|
-
const allSpotHoldings = [];
|
|
461
|
-
for (const s of snapshots) {
|
|
462
|
-
if (s.balance)
|
|
463
|
-
totalEquity += Number(s.balance.equity);
|
|
464
|
-
for (const p of s.positions)
|
|
465
|
-
allPositions.push({ ...p, exchange: s.exchange });
|
|
466
|
-
// For unified accounts (HL), USDC is already in perp equity — only add non-USDC spot
|
|
467
|
-
const displaySpot = s.isUnified
|
|
468
|
-
? s.spotHoldings.filter(b => b.token.replace(/-SPOT$/i, "").toUpperCase() !== "USDC")
|
|
469
|
-
: s.spotHoldings;
|
|
470
|
-
const spotVal = displaySpot.reduce((sum, b) => sum + b.valueUsd, 0);
|
|
471
|
-
totalSpotValue += spotVal;
|
|
472
|
-
totalEquity += spotVal;
|
|
473
|
-
for (const b of displaySpot)
|
|
474
|
-
allSpotHoldings.push({ ...b, exchange: s.exchange });
|
|
475
|
-
}
|
|
476
|
-
// Top arb
|
|
477
|
-
const topArb = arbSnapshot
|
|
478
|
-
? arbSnapshot.symbols.filter(s => s.maxSpreadAnnual >= 5).sort((a, b) => b.maxSpreadAnnual - a.maxSpreadAnnual).slice(0, 5)
|
|
479
|
-
: [];
|
|
480
|
-
// Risk
|
|
481
|
-
const marginUsed = snapshots.reduce((s, x) => s + (x.balance ? Number(x.balance.marginUsed) : 0), 0);
|
|
482
|
-
const marginPct = totalEquity > 0 ? (marginUsed / totalEquity) * 100 : 0;
|
|
483
|
-
const riskLevel = marginPct < 30 ? "LOW" : marginPct < 60 ? "MEDIUM" : "HIGH";
|
|
484
|
-
if (json) {
|
|
485
|
-
const now = new Date();
|
|
486
|
-
const h24 = new Date(now.getTime() - 24 * 3600_000);
|
|
487
|
-
return printJson(jsonOk({
|
|
488
|
-
version: _pkg.version,
|
|
489
|
-
totalEquity,
|
|
490
|
-
riskLevel,
|
|
491
|
-
exchanges: snapshots.map(s => ({
|
|
492
|
-
name: s.exchange, connected: s.connected,
|
|
493
|
-
equity: s.balance ? Number(s.balance.equity) : 0,
|
|
494
|
-
available: s.balance ? Number(s.balance.available) : 0,
|
|
495
|
-
spotHoldings: s.spotHoldings.filter(b => Number(b.total) > 0).map(b => ({
|
|
496
|
-
token: b.token, total: b.total, available: b.available, valueUsd: b.valueUsd,
|
|
497
|
-
})),
|
|
498
|
-
})),
|
|
499
|
-
totalSpotValue,
|
|
500
|
-
positions: allPositions,
|
|
501
|
-
topArbOpportunities: await (async () => {
|
|
502
|
-
const { getHistoricalAverages: getAvgs } = await import("./funding-history.js");
|
|
503
|
-
const syms = topArb.map(a => a.symbol);
|
|
504
|
-
const avgs = syms.length > 0 ? getAvgs(syms, ["hyperliquid", "pacifica", "lighter"]) : new Map();
|
|
505
|
-
return topArb.map(a => {
|
|
506
|
-
const bestEx = a.rates.find(r => r.exchange === "hyperliquid")?.exchange ?? a.rates[0]?.exchange ?? "hyperliquid";
|
|
507
|
-
const avg = avgs.get(`${a.symbol}:${bestEx}`);
|
|
508
|
-
return {
|
|
509
|
-
symbol: a.symbol, spreadAnnual: a.maxSpreadAnnual,
|
|
510
|
-
direction: `${exAbbr(a.shortExchange)}>${exAbbr(a.longExchange)}`,
|
|
511
|
-
avg24h: avg?.avg24h != null ? Math.abs(avg.avg24h) * 8760 * 100 : null,
|
|
512
|
-
avg7d: avg?.avg7d != null ? Math.abs(avg.avg7d) * 8760 * 100 : null,
|
|
513
|
-
rateHistory: getHistoricalRates(a.symbol, bestEx, h24, now).map(h => ({ ts: h.ts, hourlyRate: h.hourlyRate })),
|
|
514
|
-
};
|
|
515
|
-
});
|
|
516
|
-
})(),
|
|
517
|
-
}));
|
|
518
|
-
}
|
|
519
|
-
// ── Terminal UI ──
|
|
520
|
-
console.log(chalk.cyan.bold(`\n perp-cli v${_pkg.version}`) + chalk.gray(` — ${snapshots.filter(s => s.connected).length} exchanges connected\n`));
|
|
521
|
-
// Balance bars + Top Arb side by side
|
|
522
|
-
const now = new Date();
|
|
523
|
-
const h24 = new Date(now.getTime() - 24 * 3600_000);
|
|
524
|
-
// Left column: Balances (perp + spot per exchange)
|
|
525
|
-
// Compute per-exchange totals: perp equity + non-USDC spot (unified) or all spot
|
|
526
|
-
const exTotals = snapshots.map(s => {
|
|
527
|
-
if (!s.connected || !s.balance)
|
|
528
|
-
return { total: 0, margin: 0, equity: 0 };
|
|
529
|
-
const equity = Number(s.balance.equity);
|
|
530
|
-
const margin = Number(s.balance.marginUsed);
|
|
531
|
-
const displaySpot = s.isUnified
|
|
532
|
-
? s.spotHoldings.filter(b => b.token.replace(/-SPOT$/i, "").toUpperCase() !== "USDC")
|
|
533
|
-
: s.spotHoldings;
|
|
534
|
-
const spotVal = displaySpot.reduce((sum, b) => sum + b.valueUsd, 0);
|
|
535
|
-
return { total: equity + spotVal, margin, equity };
|
|
536
|
-
});
|
|
537
|
-
const balLines = [];
|
|
538
|
-
balLines.push(chalk.white.bold(" Balances"));
|
|
539
|
-
for (let i = 0; i < snapshots.length; i++) {
|
|
540
|
-
const s = snapshots[i];
|
|
541
|
-
if (!s.connected) {
|
|
542
|
-
balLines.push(` ${chalk.gray(exAbbr(s.exchange).padEnd(4))} ${chalk.red("disconnected")}`);
|
|
543
|
-
continue;
|
|
544
|
-
}
|
|
545
|
-
const { total, margin, equity } = exTotals[i];
|
|
546
|
-
const usagePct = equity > 0 ? (margin / equity) * 100 : 0;
|
|
547
|
-
const barFull = Math.min(20, Math.round(usagePct / 5));
|
|
548
|
-
const usageColor = usagePct < 30 ? chalk.green : usagePct < 60 ? chalk.yellow : chalk.red;
|
|
549
|
-
const bar = usageColor("\u2588".repeat(barFull)) + chalk.gray("\u2591".repeat(20 - barFull));
|
|
550
|
-
balLines.push(` ${chalk.white.bold(exAbbr(s.exchange).padEnd(4))} $${formatUsd(total).padEnd(9)} ${bar} ${usagePct.toFixed(0)}% used`);
|
|
551
|
-
}
|
|
552
|
-
// Spot holdings (non-USDC tokens with value)
|
|
553
|
-
const displaySpotHoldings = allSpotHoldings.filter(b => {
|
|
554
|
-
const tk = b.token.replace(/[-_]SPOT$/i, "").toUpperCase();
|
|
555
|
-
return tk !== "USDC";
|
|
556
|
-
});
|
|
557
|
-
if (displaySpotHoldings.length > 0) {
|
|
558
|
-
balLines.push("");
|
|
559
|
-
balLines.push(chalk.white.bold(" Spot Holdings"));
|
|
560
|
-
for (const b of displaySpotHoldings) {
|
|
561
|
-
const token = b.token.replace(/-SPOT$/i, "");
|
|
562
|
-
const ex = exAbbr(b.exchange);
|
|
563
|
-
balLines.push(` ${chalk.gray(ex.padEnd(4))} ${chalk.white.bold(token.padEnd(6))} ${b.total.padEnd(12)} ${chalk.gray(`$${formatUsd(b.valueUsd)}`)}`);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
const riskColor = riskLevel === "LOW" ? chalk.green : riskLevel === "MEDIUM" ? chalk.yellow : chalk.red;
|
|
567
|
-
balLines.push(` ${"─".repeat(44)}`);
|
|
568
|
-
balLines.push(` ${chalk.cyan.bold("Total")} ${chalk.cyan.bold(`$${formatUsd(totalEquity)}`.padEnd(9))} Risk: ${riskColor(riskLevel)}`);
|
|
569
|
-
// Right column: Top Arb Opportunities (with 24h/7d averages from local history — 0 API calls)
|
|
570
|
-
const { getHistoricalAverages } = await import("./funding-history.js");
|
|
571
|
-
const arbSymbols = topArb.map(a => a.symbol);
|
|
572
|
-
const arbExchanges = ["hyperliquid", "pacifica", "lighter"];
|
|
573
|
-
const histAvgs = arbSymbols.length > 0 ? getHistoricalAverages(arbSymbols, arbExchanges) : new Map();
|
|
574
|
-
const arbLines = [];
|
|
575
|
-
arbLines.push(chalk.white.bold(" Top Arb Opportunities"));
|
|
576
|
-
if (topArb.length > 0) {
|
|
577
|
-
// SYMBOL SPREAD 24h TREND 7d DIR
|
|
578
|
-
arbLines.push(chalk.gray(" " + "".padEnd(8) + "now".padEnd(9) + "24h".padEnd(7) + "".padEnd(9) + "7d".padEnd(7)));
|
|
579
|
-
for (const a of topArb) {
|
|
580
|
-
const spreadColor = a.maxSpreadAnnual >= 100 ? chalk.green.bold : a.maxSpreadAnnual >= 30 ? chalk.green : chalk.yellow;
|
|
581
|
-
const dir = `${exAbbr(a.shortExchange)}>${exAbbr(a.longExchange)}`;
|
|
582
|
-
const bestEx = a.rates.find(r => r.exchange === "hyperliquid")?.exchange ?? a.rates[0]?.exchange ?? "hyperliquid";
|
|
583
|
-
// 24h trend arrow
|
|
584
|
-
const history = getHistoricalRates(a.symbol, bestEx, h24, now);
|
|
585
|
-
let trend = "";
|
|
586
|
-
if (history.length >= 2) {
|
|
587
|
-
const oldAnn = Math.abs(history[0].hourlyRate) * 8760 * 100;
|
|
588
|
-
const newAnn = Math.abs(history[history.length - 1].hourlyRate) * 8760 * 100;
|
|
589
|
-
const delta = newAnn - oldAnn;
|
|
590
|
-
const abs = Math.abs(delta);
|
|
591
|
-
trend = abs < 1 ? chalk.gray(`\u2500${abs.toFixed(0)}%`)
|
|
592
|
-
: delta > 0 ? chalk.red(`\u25B2+${abs.toFixed(0)}%`)
|
|
593
|
-
: chalk.green(`\u25BC-${abs.toFixed(0)}%`);
|
|
594
|
-
}
|
|
595
|
-
// 24h and 7d averages (annualized from hourly)
|
|
596
|
-
const avg = histAvgs.get(`${a.symbol}:${bestEx}`);
|
|
597
|
-
const avg24h = avg?.avg24h != null ? `${(Math.abs(avg.avg24h) * 8760 * 100).toFixed(0)}%` : "-";
|
|
598
|
-
const avg7d = avg?.avg7d != null ? `${(Math.abs(avg.avg7d) * 8760 * 100).toFixed(0)}%` : "-";
|
|
599
|
-
arbLines.push(` ${chalk.white.bold(a.symbol.padEnd(8))} ${spreadColor(`${a.maxSpreadAnnual.toFixed(1)}%`.padEnd(9))}${chalk.gray(avg24h.padEnd(7))}${trend.padEnd(9)}${chalk.gray(avg7d.padEnd(7))}${chalk.gray(dir)}`);
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
else {
|
|
603
|
-
arbLines.push(chalk.gray(" No opportunities above 5%"));
|
|
604
|
-
}
|
|
605
|
-
// Print layout — side-by-side if terminal is wide enough, else stacked
|
|
606
|
-
const stripAnsi = (s) => s.replace(/\u001b\[[0-9;]*m/g, "");
|
|
607
|
-
const termW = process.stdout.columns || 80;
|
|
608
|
-
const SIDE_BY_SIDE_MIN = 100; // 2 indent + 1 + 46 + 1 + 48 + 1
|
|
609
|
-
if (termW >= SIDE_BY_SIDE_MIN) {
|
|
610
|
-
const LEFT_W = 46;
|
|
611
|
-
const RIGHT_W = 48;
|
|
612
|
-
const maxLines = Math.max(balLines.length, arbLines.length);
|
|
613
|
-
console.log(` ${"┌"}${"─".repeat(LEFT_W)}${"┬"}${"─".repeat(RIGHT_W)}${"┐"}`);
|
|
614
|
-
for (let i = 0; i < maxLines; i++) {
|
|
615
|
-
const left = balLines[i] ?? "";
|
|
616
|
-
const right = arbLines[i] ?? "";
|
|
617
|
-
const leftPad = LEFT_W - stripAnsi(left).length;
|
|
618
|
-
const rightPad = RIGHT_W - stripAnsi(right).length;
|
|
619
|
-
console.log(` \u2502${left}${" ".repeat(Math.max(0, leftPad))}\u2502${right}${" ".repeat(Math.max(0, rightPad))}\u2502`);
|
|
620
|
-
}
|
|
621
|
-
console.log(` ${"└"}${"─".repeat(LEFT_W)}${"┴"}${"─".repeat(RIGHT_W)}${"┘"}`);
|
|
622
|
-
}
|
|
623
|
-
else {
|
|
624
|
-
// Stacked layout for narrow terminals
|
|
625
|
-
const boxW = Math.min(termW - 4, 74); // 2 indent + 2 borders
|
|
626
|
-
const printBox = (lines) => {
|
|
627
|
-
console.log(` ${"┌"}${"─".repeat(boxW)}${"┐"}`);
|
|
628
|
-
for (const line of lines) {
|
|
629
|
-
const pad = boxW - stripAnsi(line).length;
|
|
630
|
-
console.log(` \u2502${line}${" ".repeat(Math.max(0, pad))}\u2502`);
|
|
631
|
-
}
|
|
632
|
-
console.log(` ${"└"}${"─".repeat(boxW)}${"┘"}`);
|
|
633
|
-
};
|
|
634
|
-
printBox(balLines);
|
|
635
|
-
console.log();
|
|
636
|
-
printBox(arbLines);
|
|
637
|
-
}
|
|
638
|
-
// Positions
|
|
639
|
-
if (allPositions.length > 0) {
|
|
640
|
-
console.log(chalk.white.bold("\n Positions"));
|
|
641
|
-
const posRows = allPositions.map(p => {
|
|
642
|
-
const sideColor = p.side === "long" ? chalk.green : chalk.red;
|
|
643
|
-
const notional = Math.abs(Number(p.size) * Number(p.markPrice));
|
|
644
|
-
const levStr = p.leverage > 0 ? `${p.leverage}x` : "-";
|
|
645
|
-
const warn = p.leverage >= 5 ? chalk.red(" \u26A0") : "";
|
|
646
|
-
return [
|
|
647
|
-
chalk.white.bold(p.symbol.replace("-PERP", "")),
|
|
648
|
-
chalk.gray(exAbbr(p.exchange)),
|
|
649
|
-
sideColor(p.side.toUpperCase()),
|
|
650
|
-
p.size,
|
|
651
|
-
`$${formatUsd(p.entryPrice)}\u2192$${formatUsd(p.markPrice)}`,
|
|
652
|
-
formatPnl(p.unrealizedPnl),
|
|
653
|
-
`$${formatUsd(notional)}`,
|
|
654
|
-
levStr + warn,
|
|
655
|
-
];
|
|
656
|
-
});
|
|
657
|
-
console.log(makeTable(["Symbol", "Ex", "Side", "Size", "Entry\u2192Mark", "uPnL", "Notional", "Lev"], posRows));
|
|
658
|
-
}
|
|
659
|
-
else {
|
|
660
|
-
console.log(chalk.gray("\n No open positions.\n"));
|
|
661
|
-
}
|
|
662
|
-
});
|
|
663
|
-
});
|
|
664
572
|
// Switch shared API URLs if --network testnet is used
|
|
665
573
|
program.hook("preAction", () => {
|
|
666
574
|
const network = program.opts().network;
|
|
@@ -777,8 +685,8 @@ if (rawArgs.length === 0 || (!hasSubcommand && !rawArgs.includes("-h") && !rawAr
|
|
|
777
685
|
console.log(` ${chalk.green("perp portfolio")} balances + positions + risk`);
|
|
778
686
|
console.log(` ${chalk.green("perp arb scan")} funding arbitrage opportunities`);
|
|
779
687
|
console.log(` ${chalk.green("perp arb status")} open arb positions + PnL`);
|
|
780
|
-
console.log(` ${chalk.green("perp
|
|
781
|
-
console.log(` ${chalk.green("perp
|
|
688
|
+
console.log(` ${chalk.green("perp portfolio --arb")} full dashboard (balances + arb top 5)`);
|
|
689
|
+
console.log(` ${chalk.green("perp portfolio --serve")} live web monitoring`);
|
|
782
690
|
console.log(` ${chalk.green("perp --help")} all commands\n`);
|
|
783
691
|
}
|
|
784
692
|
}
|