perp-cli 0.9.8 → 0.12.0
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/bot/engine.js +23 -4
- package/dist/bot/presets.js +3 -3
- package/dist/bot/strategies/funding-arb-strategy.d.ts +0 -23
- package/dist/bot/strategies/funding-arb-strategy.js +41 -176
- package/dist/bot/strategies/funding-auto.js +2 -2
- 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 +20 -16
- 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 +1122 -176
- package/dist/config.d.ts +9 -0
- package/dist/config.js +55 -9
- 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 +88 -34
- package/dist/exchanges/lighter-spot.js +19 -5
- package/dist/exchanges/lighter.d.ts +66 -0
- package/dist/exchanges/lighter.js +213 -31
- 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/contracts.d.ts +14 -0
- package/dist/guardrail/contracts.js +72 -0
- package/dist/guardrail/perp-guardrail.d.ts +21 -0
- package/dist/guardrail/perp-guardrail.js +145 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +307 -362
- package/dist/jobs.js +3 -1
- package/dist/mcp-server.js +92 -88
- package/dist/position-history.js +47 -4
- package/dist/settings.d.ts +69 -3
- package/dist/settings.js +3 -2
- package/dist/signer/index.d.ts +2 -0
- package/dist/signer/index.js +2 -0
- package/dist/signer/ows-evm.d.ts +32 -0
- package/dist/signer/ows-evm.js +86 -0
- package/dist/signer/ows-loader.d.ts +2 -0
- package/dist/signer/ows-loader.js +6 -0
- package/dist/signer/ows-solana.d.ts +25 -0
- package/dist/signer/ows-solana.js +56 -0
- 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 +4 -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/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,43 +1,50 @@
|
|
|
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";
|
|
13
|
-
import { tryLoadPrivateKey, parseSolanaKeypair } from "./config.js";
|
|
21
|
+
import { tryLoadPrivateKey, parseSolanaKeypair, isOwsKey, getOwsWalletName } from "./config.js";
|
|
14
22
|
import { PacificaAdapter } from "./exchanges/pacifica.js";
|
|
15
23
|
import { HyperliquidAdapter } from "./exchanges/hyperliquid.js";
|
|
16
24
|
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
|
-
// deposit + withdraw merged into funds
|
|
32
|
+
// bridge + rebalance now nested under `funds` — registered inside registerFundsCommands.
|
|
26
33
|
import { registerFundsCommands } from "./commands/funds.js";
|
|
27
34
|
// alert commands removed
|
|
28
35
|
import { registerArbAutoCommands } from "./commands/arb-auto.js";
|
|
29
36
|
import { registerArbManageCommands } from "./commands/arb/index.js";
|
|
30
|
-
|
|
31
|
-
import {
|
|
32
|
-
import { registerBotCommands } from "./commands/bot.js";
|
|
37
|
+
// Agent commands now register under `wallet agent ...` via registerWalletCommands.
|
|
38
|
+
import { registerStrategyCommands } from "./commands/bot.js";
|
|
33
39
|
import { registerRiskCommands } from "./commands/risk.js";
|
|
34
40
|
import { registerHistoryCommands } from "./commands/history.js";
|
|
35
41
|
import { registerSettingsCommands } from "./commands/settings.js";
|
|
36
42
|
// dex commands merged into market (hip3) — use --dex flag for markets/balance
|
|
37
|
-
|
|
43
|
+
// `plan` is no longer top-level — registered as `strategy plan` inside
|
|
44
|
+
// registerStrategyCommands.
|
|
38
45
|
// funding merged into arb.ts
|
|
39
46
|
import { registerBacktestCommands } from "./commands/backtest.js";
|
|
40
|
-
import {
|
|
47
|
+
import { registerPortfolioCommand } from "./commands/portfolio.js";
|
|
41
48
|
import { registerInitCommand } from "./commands/init.js";
|
|
42
49
|
import { registerAlertCommands } from "./commands/alerts.js";
|
|
43
50
|
import { loadSettings, saveSettings } from "./settings.js";
|
|
@@ -60,6 +67,10 @@ program
|
|
|
60
67
|
.option("--ndjson", "Output newline-delimited JSON (one object per line for streaming)")
|
|
61
68
|
.option("--dry-run", "Simulate trades without executing (log as simulated)")
|
|
62
69
|
.option("-w, --wallet <name>", "Use a specific wallet by name (from 'perp wallet list')")
|
|
70
|
+
.option("--ows <name>", "Use an OWS (Open Wallet Standard) wallet by name")
|
|
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)")
|
|
63
74
|
.option("--dex <name>", "HIP-3 deployed perp dex name (Hyperliquid only)")
|
|
64
75
|
.configureOutput({
|
|
65
76
|
writeErr: (str) => {
|
|
@@ -90,8 +101,13 @@ function getExchange() {
|
|
|
90
101
|
}
|
|
91
102
|
async function getAdapter() {
|
|
92
103
|
const opts = program.opts();
|
|
93
|
-
const
|
|
94
|
-
|
|
104
|
+
const owsName = opts.ows;
|
|
105
|
+
const owsKeyToken = opts.owsKey || process.env.OWS_API_KEY;
|
|
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;
|
|
110
|
+
// Skip cache when --wallet/--ows is specified (different wallet = different account)
|
|
95
111
|
if (!walletName && _adapter)
|
|
96
112
|
return _adapter;
|
|
97
113
|
const exchange = resolveExchangeAlias(opts.exchange);
|
|
@@ -99,6 +115,10 @@ async function getAdapter() {
|
|
|
99
115
|
const isTestnet = network === "testnet";
|
|
100
116
|
// Try to load key — null means no key configured (read-only mode)
|
|
101
117
|
const pk = await tryLoadPrivateKey(exchange, opts.privateKey, walletName);
|
|
118
|
+
// OWS wallet detected — use OWS signers instead of raw keys
|
|
119
|
+
if (pk && isOwsKey(pk)) {
|
|
120
|
+
return _initWithOws(exchange, getOwsWalletName(pk), isTestnet, opts, owsKeyToken);
|
|
121
|
+
}
|
|
102
122
|
switch (exchange) {
|
|
103
123
|
case "pacifica": {
|
|
104
124
|
const keypair = pk ? parseSolanaKeypair(pk) : Keypair.generate();
|
|
@@ -106,6 +126,27 @@ async function getAdapter() {
|
|
|
106
126
|
const settings = loadSettings();
|
|
107
127
|
const builderCode = process.env.PACIFICA_BUILDER_CODE || settings.referralCodes.pacifica || "PERPCLI";
|
|
108
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);
|
|
109
150
|
_adapter = _pacificaAdapter;
|
|
110
151
|
break;
|
|
111
152
|
}
|
|
@@ -116,43 +157,79 @@ async function getAdapter() {
|
|
|
116
157
|
await _hlAdapter.init();
|
|
117
158
|
if (pk) {
|
|
118
159
|
const hlSettings = loadSettings();
|
|
119
|
-
if (
|
|
160
|
+
if (!hlSettings.referralApplied.hyperliquid) {
|
|
120
161
|
const hlRef = process.env.HL_REFERRAL_CODE || hlSettings.referralCodes.hyperliquid;
|
|
121
162
|
if (hlRef) {
|
|
122
163
|
_hlAdapter.autoSetReferrer(hlRef).then(() => {
|
|
123
164
|
const s = loadSettings();
|
|
124
165
|
s.referralApplied.hyperliquid = true;
|
|
125
166
|
saveSettings(s);
|
|
126
|
-
}).catch(() => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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`);
|
|
130
171
|
});
|
|
131
172
|
}
|
|
132
173
|
}
|
|
133
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);
|
|
134
196
|
_adapter = _hlAdapter;
|
|
135
197
|
break;
|
|
136
198
|
}
|
|
137
199
|
case "lighter": {
|
|
138
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");
|
|
139
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);
|
|
140
217
|
await _lighterAdapter.init();
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
});
|
|
155
|
-
}
|
|
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
|
+
});
|
|
156
233
|
}
|
|
157
234
|
}
|
|
158
235
|
_adapter = _lighterAdapter;
|
|
@@ -160,8 +237,27 @@ async function getAdapter() {
|
|
|
160
237
|
}
|
|
161
238
|
case "aster": {
|
|
162
239
|
const { AsterAdapter } = await import("./exchanges/aster.js");
|
|
163
|
-
const ast = new AsterAdapter(
|
|
240
|
+
const ast = new AsterAdapter(pk ?? undefined, isTestnet); // Tier 3 from PK
|
|
164
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);
|
|
165
261
|
_adapter = ast;
|
|
166
262
|
break;
|
|
167
263
|
}
|
|
@@ -170,6 +266,72 @@ async function getAdapter() {
|
|
|
170
266
|
}
|
|
171
267
|
return _adapter;
|
|
172
268
|
}
|
|
269
|
+
/** Initialize adapter with OWS wallet signer injection. */
|
|
270
|
+
async function _initWithOws(exchange, owsWalletName, isTestnet, opts, owsKeyToken) {
|
|
271
|
+
const { OwsEvmSigner } = await import("./signer/ows-evm.js");
|
|
272
|
+
const { OwsSolanaSigner } = await import("./signer/ows-solana.js");
|
|
273
|
+
// If an OWS API key token is provided, use it as passphrase → routes through policy engine
|
|
274
|
+
const passphrase = owsKeyToken || "";
|
|
275
|
+
// Commander's `--no-agent` flag sets opts.agent === false (NOT opts.noAgent).
|
|
276
|
+
const noAgent = opts.agent === false;
|
|
277
|
+
switch (exchange) {
|
|
278
|
+
case "pacifica": {
|
|
279
|
+
const pacNetwork = (isTestnet ? "testnet" : "mainnet");
|
|
280
|
+
const settings = loadSettings();
|
|
281
|
+
const builderCode = process.env.PACIFICA_BUILDER_CODE || settings.referralCodes.pacifica || "PERPCLI";
|
|
282
|
+
const dummyKeypair = Keypair.generate();
|
|
283
|
+
_pacificaAdapter = new PacificaAdapter(dummyKeypair, pacNetwork, builderCode, true);
|
|
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);
|
|
294
|
+
_adapter = _pacificaAdapter;
|
|
295
|
+
return _adapter;
|
|
296
|
+
}
|
|
297
|
+
case "hyperliquid": {
|
|
298
|
+
_hlAdapter = new HyperliquidAdapter(undefined, isTestnet);
|
|
299
|
+
if (opts.dex)
|
|
300
|
+
_hlAdapter.setDex(opts.dex);
|
|
301
|
+
_hlAdapter.setSigner(OwsEvmSigner.create(owsWalletName, passphrase));
|
|
302
|
+
await _hlAdapter.init();
|
|
303
|
+
_adapter = _hlAdapter;
|
|
304
|
+
return _adapter;
|
|
305
|
+
}
|
|
306
|
+
case "lighter": {
|
|
307
|
+
const { LighterAdapter } = await import("./exchanges/lighter.js");
|
|
308
|
+
_lighterAdapter = new LighterAdapter("", isTestnet);
|
|
309
|
+
_lighterAdapter.setSigner(OwsEvmSigner.create(owsWalletName, passphrase));
|
|
310
|
+
await _lighterAdapter.init();
|
|
311
|
+
_adapter = _lighterAdapter;
|
|
312
|
+
return _adapter;
|
|
313
|
+
}
|
|
314
|
+
case "aster": {
|
|
315
|
+
const { AsterAdapter } = await import("./exchanges/aster.js");
|
|
316
|
+
const asterOws = new AsterAdapter(undefined, isTestnet);
|
|
317
|
+
await asterOws.init();
|
|
318
|
+
asterOws.setMasterSigner(OwsEvmSigner.create(owsWalletName, passphrase));
|
|
319
|
+
// Optionally also set agent if registered
|
|
320
|
+
const { getAgent: getAsterAgentOws } = await import("./agent-wallet/store.js");
|
|
321
|
+
const asterMeta = getAsterAgentOws("aster");
|
|
322
|
+
if (asterMeta) {
|
|
323
|
+
const { agentSigningStrategyFor } = await import("./agent-wallet/signing-strategy.js");
|
|
324
|
+
asterOws.setAgent(asterMeta, agentSigningStrategyFor(asterMeta, owsKeyToken ?? ""));
|
|
325
|
+
}
|
|
326
|
+
if (noAgent)
|
|
327
|
+
asterOws.setNoAgent(true);
|
|
328
|
+
_adapter = asterOws;
|
|
329
|
+
return _adapter;
|
|
330
|
+
}
|
|
331
|
+
default:
|
|
332
|
+
throw new Error(`Unknown exchange: ${exchange}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
173
335
|
// Sync wrapper for commands that need adapter (lazy init)
|
|
174
336
|
function getAdapterSync() {
|
|
175
337
|
if (!_adapter)
|
|
@@ -197,12 +359,11 @@ function getHLAdapter() {
|
|
|
197
359
|
registerMarketCommands(program, getAdapter, isJson, getAdapterForExchange);
|
|
198
360
|
registerAccountCommands(program, getAdapter, isJson, getAdapterForExchange);
|
|
199
361
|
registerTradeCommands(program, getAdapter, isJson, isDryRun, getAdapterForExchange);
|
|
200
|
-
|
|
362
|
+
// manage tree wired inside registerWalletCommands below.
|
|
201
363
|
// stream commands removed
|
|
202
364
|
registerArbCommands(program, isJson, getAdapterForExchange);
|
|
203
|
-
registerWalletCommands(program, isJson);
|
|
204
|
-
|
|
205
|
-
registerFundsCommands(program, getAdapter, isJson, () => program.opts().network);
|
|
365
|
+
registerWalletCommands(program, isJson, getAdapter, getPacificaAdapter);
|
|
366
|
+
registerFundsCommands(program, getAdapter, isJson, () => program.opts().network, getAdapterForExchange);
|
|
206
367
|
// alert commands removed
|
|
207
368
|
// Helper to get adapter for a specific exchange (used by arb-auto)
|
|
208
369
|
async function getAdapterForExchange(rawExchange) {
|
|
@@ -210,8 +371,19 @@ async function getAdapterForExchange(rawExchange) {
|
|
|
210
371
|
const opts = program.opts();
|
|
211
372
|
const network = opts.network;
|
|
212
373
|
const isTestnet = network === "testnet";
|
|
213
|
-
const
|
|
374
|
+
const owsName = opts.ows;
|
|
375
|
+
const owsKeyToken = opts.owsKey || process.env.OWS_API_KEY;
|
|
376
|
+
const walletName = owsName ? `ows:${owsName}` : opts.wallet;
|
|
377
|
+
// Commander's `--no-agent` flag sets opts.agent === false (NOT opts.noAgent).
|
|
378
|
+
const noAgent = opts.agent === false;
|
|
214
379
|
const pk = await tryLoadPrivateKey(exchange, opts.privateKey, walletName);
|
|
380
|
+
// Route through OWS init when pk is an OWS reference (e.g. "ows:main").
|
|
381
|
+
// Mirrors getAdapter() at the top of this file. Without this, adapters
|
|
382
|
+
// would try to parse "ows:main" as a raw private key and ethers would
|
|
383
|
+
// throw "invalid BytesLike value" / parseSolanaKeypair would fail.
|
|
384
|
+
if (pk && isOwsKey(pk)) {
|
|
385
|
+
return _initWithOws(exchange, getOwsWalletName(pk), isTestnet, opts, owsKeyToken);
|
|
386
|
+
}
|
|
215
387
|
switch (exchange) {
|
|
216
388
|
case "pacifica": {
|
|
217
389
|
if (_pacificaAdapter)
|
|
@@ -221,6 +393,18 @@ async function getAdapterForExchange(rawExchange) {
|
|
|
221
393
|
const s1 = loadSettings();
|
|
222
394
|
const builderCode = process.env.PACIFICA_BUILDER_CODE || s1.referralCodes.pacifica || "PERPCLI";
|
|
223
395
|
_pacificaAdapter = new PacificaAdapter(keypair, pacNetwork, builderCode, !!pk);
|
|
396
|
+
// Tier 1: agent if registered (Phase 2c)
|
|
397
|
+
const { getAgent: getPacAgentEx } = await import("./agent-wallet/store.js");
|
|
398
|
+
const pacAgentMetaEx = getPacAgentEx("pacifica");
|
|
399
|
+
if (pacAgentMetaEx) {
|
|
400
|
+
const { OwsSolanaSigner: PacOwsSolanaSignerEx } = await import("./signer/ows-solana.js");
|
|
401
|
+
// Pacifica agent wallet was created with empty passphrase (see runPacApproveFlow);
|
|
402
|
+
// open with "" to match. Agent at-rest encryption handled by OWS storage layer.
|
|
403
|
+
const agentSignerEx = PacOwsSolanaSignerEx.create(pacAgentMetaEx.agentWalletName, "");
|
|
404
|
+
_pacificaAdapter.setAgentSigner(pacAgentMetaEx, agentSignerEx);
|
|
405
|
+
}
|
|
406
|
+
if (noAgent)
|
|
407
|
+
_pacificaAdapter.setNoAgent(true);
|
|
224
408
|
if (!_adapter)
|
|
225
409
|
_adapter = _pacificaAdapter;
|
|
226
410
|
return _pacificaAdapter;
|
|
@@ -234,21 +418,35 @@ async function getAdapterForExchange(rawExchange) {
|
|
|
234
418
|
await _hlAdapter.init();
|
|
235
419
|
if (pk) {
|
|
236
420
|
const s2 = loadSettings();
|
|
237
|
-
if (
|
|
421
|
+
if (!s2.referralApplied.hyperliquid) {
|
|
238
422
|
const hlRef = process.env.HL_REFERRAL_CODE || s2.referralCodes.hyperliquid;
|
|
239
423
|
if (hlRef) {
|
|
240
424
|
_hlAdapter.autoSetReferrer(hlRef).then(() => {
|
|
241
425
|
const s = loadSettings();
|
|
242
426
|
s.referralApplied.hyperliquid = true;
|
|
243
427
|
saveSettings(s);
|
|
244
|
-
}).catch(() => {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
428
|
+
}).catch((err) => {
|
|
429
|
+
// Per SSOT Rule #2: do NOT mark applied=true on failure.
|
|
430
|
+
// Leave referralApplied[ex]=false so next adapter init retries.
|
|
431
|
+
process.stderr.write(`[hyperliquid] referral apply failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
248
432
|
});
|
|
249
433
|
}
|
|
250
434
|
}
|
|
251
435
|
}
|
|
436
|
+
// Tier 1: agent if registered
|
|
437
|
+
{
|
|
438
|
+
const { getAgent: getHlAgent2 } = await import("./agent-wallet/store.js");
|
|
439
|
+
const hlAgentMeta2 = getHlAgent2("hyperliquid");
|
|
440
|
+
if (hlAgentMeta2) {
|
|
441
|
+
const { OwsEvmSigner: HlOwsEvmSigner2 } = await import("./signer/ows-evm.js");
|
|
442
|
+
// HL agent wallet was created with empty passphrase (see runHlApproveFlow);
|
|
443
|
+
// open with "" to match. Agent at-rest encryption handled by OWS storage layer.
|
|
444
|
+
const agentSigner2 = HlOwsEvmSigner2.create(hlAgentMeta2.agentWalletName, "");
|
|
445
|
+
_hlAdapter.setAgentSigner(hlAgentMeta2, agentSigner2);
|
|
446
|
+
}
|
|
447
|
+
if (noAgent)
|
|
448
|
+
_hlAdapter.setNoAgent(true);
|
|
449
|
+
}
|
|
252
450
|
if (!_adapter)
|
|
253
451
|
_adapter = _hlAdapter;
|
|
254
452
|
return _hlAdapter;
|
|
@@ -257,23 +455,32 @@ async function getAdapterForExchange(rawExchange) {
|
|
|
257
455
|
if (_lighterAdapter)
|
|
258
456
|
return _lighterAdapter;
|
|
259
457
|
const { LighterAdapter } = await import("./exchanges/lighter.js");
|
|
458
|
+
const { getAgent: getLtAgentEx } = await import("./agent-wallet/store.js");
|
|
459
|
+
const ltAgentMetaEx = getLtAgentEx("lighter");
|
|
260
460
|
_lighterAdapter = new LighterAdapter(pk ?? "", isTestnet);
|
|
461
|
+
if (ltAgentMetaEx && !noAgent) {
|
|
462
|
+
// SSOT Rule #3: load agent L2 key from encrypted keystore — no env fallback.
|
|
463
|
+
const { loadLighterKey } = await import("./agent-wallet/lighter-keystore.js");
|
|
464
|
+
const ltL2KeyEx = loadLighterKey(ltAgentMetaEx.accountIndex, ltAgentMetaEx.apiKeyIndex, "");
|
|
465
|
+
_lighterAdapter.setAgentSigner(ltAgentMetaEx, ltL2KeyEx);
|
|
466
|
+
}
|
|
467
|
+
if (noAgent)
|
|
468
|
+
_lighterAdapter.setNoAgent(true);
|
|
261
469
|
await _lighterAdapter.init();
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
470
|
+
// LT referral apply is L2-signed; trigger on any active tier (agent
|
|
471
|
+
// included), gated by isReadOnly. See first LT case for rationale.
|
|
472
|
+
const s3 = loadSettings();
|
|
473
|
+
if (!s3.referralApplied.lighter && !_lighterAdapter.isReadOnly) {
|
|
474
|
+
const ltRef = process.env.LIGHTER_REFERRAL_CODE || s3.referralCodes.lighter;
|
|
475
|
+
if (ltRef) {
|
|
476
|
+
_lighterAdapter.useReferralCode(ltRef).then(() => {
|
|
477
|
+
const s = loadSettings();
|
|
478
|
+
s.referralApplied.lighter = true;
|
|
479
|
+
saveSettings(s);
|
|
480
|
+
}).catch((err) => {
|
|
481
|
+
// Per SSOT Rule #2: do NOT mark applied=true on failure.
|
|
482
|
+
process.stderr.write(`[lighter] referral apply failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
483
|
+
});
|
|
277
484
|
}
|
|
278
485
|
}
|
|
279
486
|
if (!_adapter)
|
|
@@ -282,9 +489,30 @@ async function getAdapterForExchange(rawExchange) {
|
|
|
282
489
|
}
|
|
283
490
|
case "aster": {
|
|
284
491
|
const { AsterAdapter } = await import("./exchanges/aster.js");
|
|
285
|
-
const
|
|
286
|
-
await
|
|
287
|
-
|
|
492
|
+
const astEx = new AsterAdapter(pk ?? undefined, isTestnet);
|
|
493
|
+
await astEx.init();
|
|
494
|
+
// Tier 2: OWS master if --ows or settings.owsActiveWallet
|
|
495
|
+
const asterExOwsName = opts.ows || _settings.owsActiveWallet;
|
|
496
|
+
if (asterExOwsName) {
|
|
497
|
+
const { OwsEvmSigner } = await import("./signer/ows-evm.js");
|
|
498
|
+
const { resolvePassphrase } = await import("./agent-wallet/passphrase.js");
|
|
499
|
+
const pp = await resolvePassphrase({ flag: opts.passphrase });
|
|
500
|
+
if (pp !== null)
|
|
501
|
+
astEx.setMasterSigner(OwsEvmSigner.create(asterExOwsName, pp));
|
|
502
|
+
}
|
|
503
|
+
// Tier 1: agent if registered
|
|
504
|
+
const owsKeyEx = opts.owsKey || process.env.OWS_API_KEY;
|
|
505
|
+
const { getAgent: getAsterAgentEx } = await import("./agent-wallet/store.js");
|
|
506
|
+
const agentMetaEx = getAsterAgentEx("aster");
|
|
507
|
+
if (agentMetaEx) {
|
|
508
|
+
const { agentSigningStrategyFor } = await import("./agent-wallet/signing-strategy.js");
|
|
509
|
+
astEx.setAgent(agentMetaEx, agentSigningStrategyFor(agentMetaEx, owsKeyEx ?? ""));
|
|
510
|
+
}
|
|
511
|
+
if (noAgent)
|
|
512
|
+
astEx.setNoAgent(true);
|
|
513
|
+
if (!_adapter)
|
|
514
|
+
_adapter = astEx;
|
|
515
|
+
return astEx;
|
|
288
516
|
}
|
|
289
517
|
default:
|
|
290
518
|
throw new Error(`Unknown exchange: ${exchange}`);
|
|
@@ -307,312 +535,27 @@ async function getHLAdapterForDex(dex) {
|
|
|
307
535
|
}
|
|
308
536
|
registerArbAutoCommands(program, getAdapterForExchange, isJson, getHLAdapterForDex);
|
|
309
537
|
registerArbManageCommands(program, getAdapterForExchange, isJson);
|
|
310
|
-
|
|
311
|
-
//
|
|
312
|
-
|
|
313
|
-
//
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
538
|
+
// `agent` is no longer top-level — registered as `wallet agent` inside
|
|
539
|
+
// registerWalletCommands above.
|
|
540
|
+
// withdraw merged into funds.
|
|
541
|
+
// `rebalance` is no longer top-level — registered as `funds rebalance` inside
|
|
542
|
+
// registerFundsCommands above.
|
|
543
|
+
// Background processes & strategies
|
|
544
|
+
import { registerBackgroundCommands } from "./commands/jobs.js";
|
|
545
|
+
registerBackgroundCommands(program, isJson);
|
|
546
|
+
registerStrategyCommands(program, getAdapter, getAdapterForExchange, isJson);
|
|
317
547
|
// Agent-friendly commands
|
|
318
548
|
registerRiskCommands(program, getAdapterForExchange, isJson);
|
|
319
549
|
registerHistoryCommands(program, isJson, getAdapterForExchange);
|
|
320
550
|
registerSettingsCommands(program, isJson, getAdapterForExchange);
|
|
321
551
|
// dex commands removed — use 'market hip3' + --dex flag
|
|
322
|
-
|
|
552
|
+
// `plan` is no longer top-level — registered as `strategy plan` inside
|
|
553
|
+
// registerStrategyCommands above.
|
|
323
554
|
// funding merged into arb — registerFundingCommands removed
|
|
324
555
|
registerBacktestCommands(program, isJson);
|
|
325
|
-
|
|
556
|
+
registerPortfolioCommand(program, getAdapterForExchange, isJson, _pkg.version, getHLAdapterForDex);
|
|
326
557
|
registerInitCommand(program);
|
|
327
558
|
registerAlertCommands(program, isJson);
|
|
328
|
-
// Agent discovery: perp api-spec — deprecated, use 'perp agent schema'
|
|
329
|
-
const apiSpecCmd = program
|
|
330
|
-
.command("api-spec")
|
|
331
|
-
.description("Use 'perp agent schema'")
|
|
332
|
-
.action(async () => {
|
|
333
|
-
const { jsonOk, printJson } = await import("./utils.js");
|
|
334
|
-
const { getCliSpec } = await import("./cli-spec.js");
|
|
335
|
-
printJson(jsonOk(getCliSpec(program)));
|
|
336
|
-
});
|
|
337
|
-
apiSpecCmd._hidden = true;
|
|
338
|
-
// Unified dashboard: balances + positions + top arb opportunities
|
|
339
|
-
program.command("status")
|
|
340
|
-
.description("Unified dashboard: balances, positions, and top arb opportunities")
|
|
341
|
-
.option("--health", "Check connectivity only")
|
|
342
|
-
.action(async (opts) => {
|
|
343
|
-
if (opts.health) {
|
|
344
|
-
const { runHealthCheck } = await import("./commands/risk.js");
|
|
345
|
-
return runHealthCheck(isJson);
|
|
346
|
-
}
|
|
347
|
-
const json = isJson();
|
|
348
|
-
const { formatUsd, formatPnl, makeTable, printJson, jsonOk, withJsonErrors } = await import("./utils.js");
|
|
349
|
-
const { fetchAllFundingRates, TOP_SYMBOLS } = await import("./funding-rates.js");
|
|
350
|
-
const { saveFundingSnapshot, getHistoricalRates } = await import("./funding-history.js");
|
|
351
|
-
await withJsonErrors(json, async () => {
|
|
352
|
-
const EX_LIST = ["pacifica", "hyperliquid", "lighter", "aster"];
|
|
353
|
-
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : e === "lighter" ? "LT" : "AST";
|
|
354
|
-
const snapshotPromises = EX_LIST.map(async (ex) => {
|
|
355
|
-
try {
|
|
356
|
-
const adapter = await getAdapterForExchange(ex);
|
|
357
|
-
const [balance, positions, orders] = await Promise.all([
|
|
358
|
-
adapter.getBalance(), adapter.getPositions(), adapter.getOpenOrders(),
|
|
359
|
-
]);
|
|
360
|
-
// Fetch spot balances for HL and LT (Pacifica is perp-only)
|
|
361
|
-
let spotHoldings = [];
|
|
362
|
-
let isUnified = false;
|
|
363
|
-
try {
|
|
364
|
-
if (ex === "hyperliquid") {
|
|
365
|
-
const { HyperliquidSpotAdapter } = await import("./exchanges/hyperliquid-spot.js");
|
|
366
|
-
const hlSpot = new HyperliquidSpotAdapter(adapter);
|
|
367
|
-
await hlSpot.init();
|
|
368
|
-
const [raw, markets] = await Promise.all([hlSpot.getSpotBalances(), hlSpot.getSpotMarkets()]);
|
|
369
|
-
const priceMap = new Map(markets.map(m => [m.baseToken.toUpperCase(), Number(m.markPrice)]));
|
|
370
|
-
const strip = (t) => t.replace(/-SPOT$/i, "").toUpperCase();
|
|
371
|
-
spotHoldings = raw.filter(b => Number(b.total) > 0).map(b => {
|
|
372
|
-
const base = strip(b.token);
|
|
373
|
-
return { ...b, valueUsd: base === "USDC" ? Number(b.total) : (priceMap.get(base) ?? 0) * Number(b.total) };
|
|
374
|
-
});
|
|
375
|
-
isUnified = !adapter.dex;
|
|
376
|
-
}
|
|
377
|
-
else if (ex === "lighter") {
|
|
378
|
-
const { LighterAdapter } = await import("./exchanges/lighter.js");
|
|
379
|
-
const { LighterSpotAdapter } = await import("./exchanges/lighter-spot.js");
|
|
380
|
-
const ltSpot = new LighterSpotAdapter(adapter);
|
|
381
|
-
await ltSpot.init();
|
|
382
|
-
const [raw, markets] = await Promise.all([ltSpot.getSpotBalances(), ltSpot.getSpotMarkets()]);
|
|
383
|
-
const priceMap = new Map(markets.map(m => [m.baseToken.toUpperCase(), Number(m.markPrice)]));
|
|
384
|
-
spotHoldings = raw.filter(b => Number(b.total) > 0).map(b => ({
|
|
385
|
-
...b,
|
|
386
|
-
valueUsd: b.token === "USDC" || b.token === "USDC_SPOT" ? Number(b.total) : (priceMap.get(b.token.toUpperCase()) ?? 0) * Number(b.total),
|
|
387
|
-
}));
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
catch { /* spot not available */ }
|
|
391
|
-
return { exchange: ex, connected: true, balance, positions, openOrders: orders.length, spotHoldings, isUnified, error: undefined };
|
|
392
|
-
}
|
|
393
|
-
catch (err) {
|
|
394
|
-
return { exchange: ex, connected: false, balance: null, positions: [], openOrders: 0, spotHoldings: [], isUnified: false, error: err instanceof Error ? err.message : String(err) };
|
|
395
|
-
}
|
|
396
|
-
});
|
|
397
|
-
const arbPromise = fetchAllFundingRates({ symbols: TOP_SYMBOLS, minSpread: 0 }).catch(() => null);
|
|
398
|
-
const [snapshots, arbSnapshot] = await Promise.all([Promise.all(snapshotPromises), arbPromise]);
|
|
399
|
-
// Save funding snapshot for sparkline history
|
|
400
|
-
if (arbSnapshot) {
|
|
401
|
-
try {
|
|
402
|
-
const allRates = arbSnapshot.symbols.flatMap(s => s.rates);
|
|
403
|
-
if (allRates.length > 0)
|
|
404
|
-
saveFundingSnapshot(allRates);
|
|
405
|
-
}
|
|
406
|
-
catch { /* */ }
|
|
407
|
-
}
|
|
408
|
-
// Build totals (include non-USDC spot value for unified accounts)
|
|
409
|
-
let totalEquity = 0;
|
|
410
|
-
let totalSpotValue = 0;
|
|
411
|
-
const allPositions = [];
|
|
412
|
-
const allSpotHoldings = [];
|
|
413
|
-
for (const s of snapshots) {
|
|
414
|
-
if (s.balance)
|
|
415
|
-
totalEquity += Number(s.balance.equity);
|
|
416
|
-
for (const p of s.positions)
|
|
417
|
-
allPositions.push({ ...p, exchange: s.exchange });
|
|
418
|
-
// For unified accounts (HL), USDC is already in perp equity — only add non-USDC spot
|
|
419
|
-
const displaySpot = s.isUnified
|
|
420
|
-
? s.spotHoldings.filter(b => b.token.replace(/-SPOT$/i, "").toUpperCase() !== "USDC")
|
|
421
|
-
: s.spotHoldings;
|
|
422
|
-
const spotVal = displaySpot.reduce((sum, b) => sum + b.valueUsd, 0);
|
|
423
|
-
totalSpotValue += spotVal;
|
|
424
|
-
totalEquity += spotVal;
|
|
425
|
-
for (const b of displaySpot)
|
|
426
|
-
allSpotHoldings.push({ ...b, exchange: s.exchange });
|
|
427
|
-
}
|
|
428
|
-
// Top arb
|
|
429
|
-
const topArb = arbSnapshot
|
|
430
|
-
? arbSnapshot.symbols.filter(s => s.maxSpreadAnnual >= 5).sort((a, b) => b.maxSpreadAnnual - a.maxSpreadAnnual).slice(0, 5)
|
|
431
|
-
: [];
|
|
432
|
-
// Risk
|
|
433
|
-
const marginUsed = snapshots.reduce((s, x) => s + (x.balance ? Number(x.balance.marginUsed) : 0), 0);
|
|
434
|
-
const marginPct = totalEquity > 0 ? (marginUsed / totalEquity) * 100 : 0;
|
|
435
|
-
const riskLevel = marginPct < 30 ? "LOW" : marginPct < 60 ? "MEDIUM" : "HIGH";
|
|
436
|
-
if (json) {
|
|
437
|
-
const now = new Date();
|
|
438
|
-
const h24 = new Date(now.getTime() - 24 * 3600_000);
|
|
439
|
-
return printJson(jsonOk({
|
|
440
|
-
version: _pkg.version,
|
|
441
|
-
totalEquity,
|
|
442
|
-
riskLevel,
|
|
443
|
-
exchanges: snapshots.map(s => ({
|
|
444
|
-
name: s.exchange, connected: s.connected,
|
|
445
|
-
equity: s.balance ? Number(s.balance.equity) : 0,
|
|
446
|
-
available: s.balance ? Number(s.balance.available) : 0,
|
|
447
|
-
spotHoldings: s.spotHoldings.filter(b => Number(b.total) > 0).map(b => ({
|
|
448
|
-
token: b.token, total: b.total, available: b.available, valueUsd: b.valueUsd,
|
|
449
|
-
})),
|
|
450
|
-
})),
|
|
451
|
-
totalSpotValue,
|
|
452
|
-
positions: allPositions,
|
|
453
|
-
topArbOpportunities: await (async () => {
|
|
454
|
-
const { getHistoricalAverages: getAvgs } = await import("./funding-history.js");
|
|
455
|
-
const syms = topArb.map(a => a.symbol);
|
|
456
|
-
const avgs = syms.length > 0 ? getAvgs(syms, ["hyperliquid", "pacifica", "lighter"]) : new Map();
|
|
457
|
-
return topArb.map(a => {
|
|
458
|
-
const bestEx = a.rates.find(r => r.exchange === "hyperliquid")?.exchange ?? a.rates[0]?.exchange ?? "hyperliquid";
|
|
459
|
-
const avg = avgs.get(`${a.symbol}:${bestEx}`);
|
|
460
|
-
return {
|
|
461
|
-
symbol: a.symbol, spreadAnnual: a.maxSpreadAnnual,
|
|
462
|
-
direction: `${exAbbr(a.shortExchange)}>${exAbbr(a.longExchange)}`,
|
|
463
|
-
avg24h: avg?.avg24h != null ? Math.abs(avg.avg24h) * 8760 * 100 : null,
|
|
464
|
-
avg7d: avg?.avg7d != null ? Math.abs(avg.avg7d) * 8760 * 100 : null,
|
|
465
|
-
rateHistory: getHistoricalRates(a.symbol, bestEx, h24, now).map(h => ({ ts: h.ts, hourlyRate: h.hourlyRate })),
|
|
466
|
-
};
|
|
467
|
-
});
|
|
468
|
-
})(),
|
|
469
|
-
}));
|
|
470
|
-
}
|
|
471
|
-
// ── Terminal UI ──
|
|
472
|
-
console.log(chalk.cyan.bold(`\n perp-cli v${_pkg.version}`) + chalk.gray(` — ${snapshots.filter(s => s.connected).length} exchanges connected\n`));
|
|
473
|
-
// Balance bars + Top Arb side by side
|
|
474
|
-
const now = new Date();
|
|
475
|
-
const h24 = new Date(now.getTime() - 24 * 3600_000);
|
|
476
|
-
// Left column: Balances (perp + spot per exchange)
|
|
477
|
-
// Compute per-exchange totals: perp equity + non-USDC spot (unified) or all spot
|
|
478
|
-
const exTotals = snapshots.map(s => {
|
|
479
|
-
if (!s.connected || !s.balance)
|
|
480
|
-
return { total: 0, margin: 0, equity: 0 };
|
|
481
|
-
const equity = Number(s.balance.equity);
|
|
482
|
-
const margin = Number(s.balance.marginUsed);
|
|
483
|
-
const displaySpot = s.isUnified
|
|
484
|
-
? s.spotHoldings.filter(b => b.token.replace(/-SPOT$/i, "").toUpperCase() !== "USDC")
|
|
485
|
-
: s.spotHoldings;
|
|
486
|
-
const spotVal = displaySpot.reduce((sum, b) => sum + b.valueUsd, 0);
|
|
487
|
-
return { total: equity + spotVal, margin, equity };
|
|
488
|
-
});
|
|
489
|
-
const balLines = [];
|
|
490
|
-
balLines.push(chalk.white.bold(" Balances"));
|
|
491
|
-
for (let i = 0; i < snapshots.length; i++) {
|
|
492
|
-
const s = snapshots[i];
|
|
493
|
-
if (!s.connected) {
|
|
494
|
-
balLines.push(` ${chalk.gray(exAbbr(s.exchange).padEnd(4))} ${chalk.red("disconnected")}`);
|
|
495
|
-
continue;
|
|
496
|
-
}
|
|
497
|
-
const { total, margin, equity } = exTotals[i];
|
|
498
|
-
const usagePct = equity > 0 ? (margin / equity) * 100 : 0;
|
|
499
|
-
const barFull = Math.min(20, Math.round(usagePct / 5));
|
|
500
|
-
const usageColor = usagePct < 30 ? chalk.green : usagePct < 60 ? chalk.yellow : chalk.red;
|
|
501
|
-
const bar = usageColor("\u2588".repeat(barFull)) + chalk.gray("\u2591".repeat(20 - barFull));
|
|
502
|
-
balLines.push(` ${chalk.white.bold(exAbbr(s.exchange).padEnd(4))} $${formatUsd(total).padEnd(9)} ${bar} ${usagePct.toFixed(0)}% used`);
|
|
503
|
-
}
|
|
504
|
-
// Spot holdings (non-USDC tokens with value)
|
|
505
|
-
const displaySpotHoldings = allSpotHoldings.filter(b => {
|
|
506
|
-
const tk = b.token.replace(/[-_]SPOT$/i, "").toUpperCase();
|
|
507
|
-
return tk !== "USDC";
|
|
508
|
-
});
|
|
509
|
-
if (displaySpotHoldings.length > 0) {
|
|
510
|
-
balLines.push("");
|
|
511
|
-
balLines.push(chalk.white.bold(" Spot Holdings"));
|
|
512
|
-
for (const b of displaySpotHoldings) {
|
|
513
|
-
const token = b.token.replace(/-SPOT$/i, "");
|
|
514
|
-
const ex = exAbbr(b.exchange);
|
|
515
|
-
balLines.push(` ${chalk.gray(ex.padEnd(4))} ${chalk.white.bold(token.padEnd(6))} ${b.total.padEnd(12)} ${chalk.gray(`$${formatUsd(b.valueUsd)}`)}`);
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
const riskColor = riskLevel === "LOW" ? chalk.green : riskLevel === "MEDIUM" ? chalk.yellow : chalk.red;
|
|
519
|
-
balLines.push(` ${"─".repeat(44)}`);
|
|
520
|
-
balLines.push(` ${chalk.cyan.bold("Total")} ${chalk.cyan.bold(`$${formatUsd(totalEquity)}`.padEnd(9))} Risk: ${riskColor(riskLevel)}`);
|
|
521
|
-
// Right column: Top Arb Opportunities (with 24h/7d averages from local history — 0 API calls)
|
|
522
|
-
const { getHistoricalAverages } = await import("./funding-history.js");
|
|
523
|
-
const arbSymbols = topArb.map(a => a.symbol);
|
|
524
|
-
const arbExchanges = ["hyperliquid", "pacifica", "lighter"];
|
|
525
|
-
const histAvgs = arbSymbols.length > 0 ? getHistoricalAverages(arbSymbols, arbExchanges) : new Map();
|
|
526
|
-
const arbLines = [];
|
|
527
|
-
arbLines.push(chalk.white.bold(" Top Arb Opportunities"));
|
|
528
|
-
if (topArb.length > 0) {
|
|
529
|
-
// SYMBOL SPREAD 24h TREND 7d DIR
|
|
530
|
-
arbLines.push(chalk.gray(" " + "".padEnd(8) + "now".padEnd(9) + "24h".padEnd(7) + "".padEnd(9) + "7d".padEnd(7)));
|
|
531
|
-
for (const a of topArb) {
|
|
532
|
-
const spreadColor = a.maxSpreadAnnual >= 100 ? chalk.green.bold : a.maxSpreadAnnual >= 30 ? chalk.green : chalk.yellow;
|
|
533
|
-
const dir = `${exAbbr(a.shortExchange)}>${exAbbr(a.longExchange)}`;
|
|
534
|
-
const bestEx = a.rates.find(r => r.exchange === "hyperliquid")?.exchange ?? a.rates[0]?.exchange ?? "hyperliquid";
|
|
535
|
-
// 24h trend arrow
|
|
536
|
-
const history = getHistoricalRates(a.symbol, bestEx, h24, now);
|
|
537
|
-
let trend = "";
|
|
538
|
-
if (history.length >= 2) {
|
|
539
|
-
const oldAnn = Math.abs(history[0].hourlyRate) * 8760 * 100;
|
|
540
|
-
const newAnn = Math.abs(history[history.length - 1].hourlyRate) * 8760 * 100;
|
|
541
|
-
const delta = newAnn - oldAnn;
|
|
542
|
-
const abs = Math.abs(delta);
|
|
543
|
-
trend = abs < 1 ? chalk.gray(`\u2500${abs.toFixed(0)}%`)
|
|
544
|
-
: delta > 0 ? chalk.red(`\u25B2+${abs.toFixed(0)}%`)
|
|
545
|
-
: chalk.green(`\u25BC-${abs.toFixed(0)}%`);
|
|
546
|
-
}
|
|
547
|
-
// 24h and 7d averages (annualized from hourly)
|
|
548
|
-
const avg = histAvgs.get(`${a.symbol}:${bestEx}`);
|
|
549
|
-
const avg24h = avg?.avg24h != null ? `${(Math.abs(avg.avg24h) * 8760 * 100).toFixed(0)}%` : "-";
|
|
550
|
-
const avg7d = avg?.avg7d != null ? `${(Math.abs(avg.avg7d) * 8760 * 100).toFixed(0)}%` : "-";
|
|
551
|
-
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)}`);
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
else {
|
|
555
|
-
arbLines.push(chalk.gray(" No opportunities above 5%"));
|
|
556
|
-
}
|
|
557
|
-
// Print layout — side-by-side if terminal is wide enough, else stacked
|
|
558
|
-
const stripAnsi = (s) => s.replace(/\u001b\[[0-9;]*m/g, "");
|
|
559
|
-
const termW = process.stdout.columns || 80;
|
|
560
|
-
const SIDE_BY_SIDE_MIN = 100; // 2 indent + 1 + 46 + 1 + 48 + 1
|
|
561
|
-
if (termW >= SIDE_BY_SIDE_MIN) {
|
|
562
|
-
const LEFT_W = 46;
|
|
563
|
-
const RIGHT_W = 48;
|
|
564
|
-
const maxLines = Math.max(balLines.length, arbLines.length);
|
|
565
|
-
console.log(` ${"┌"}${"─".repeat(LEFT_W)}${"┬"}${"─".repeat(RIGHT_W)}${"┐"}`);
|
|
566
|
-
for (let i = 0; i < maxLines; i++) {
|
|
567
|
-
const left = balLines[i] ?? "";
|
|
568
|
-
const right = arbLines[i] ?? "";
|
|
569
|
-
const leftPad = LEFT_W - stripAnsi(left).length;
|
|
570
|
-
const rightPad = RIGHT_W - stripAnsi(right).length;
|
|
571
|
-
console.log(` \u2502${left}${" ".repeat(Math.max(0, leftPad))}\u2502${right}${" ".repeat(Math.max(0, rightPad))}\u2502`);
|
|
572
|
-
}
|
|
573
|
-
console.log(` ${"└"}${"─".repeat(LEFT_W)}${"┴"}${"─".repeat(RIGHT_W)}${"┘"}`);
|
|
574
|
-
}
|
|
575
|
-
else {
|
|
576
|
-
// Stacked layout for narrow terminals
|
|
577
|
-
const boxW = Math.min(termW - 4, 74); // 2 indent + 2 borders
|
|
578
|
-
const printBox = (lines) => {
|
|
579
|
-
console.log(` ${"┌"}${"─".repeat(boxW)}${"┐"}`);
|
|
580
|
-
for (const line of lines) {
|
|
581
|
-
const pad = boxW - stripAnsi(line).length;
|
|
582
|
-
console.log(` \u2502${line}${" ".repeat(Math.max(0, pad))}\u2502`);
|
|
583
|
-
}
|
|
584
|
-
console.log(` ${"└"}${"─".repeat(boxW)}${"┘"}`);
|
|
585
|
-
};
|
|
586
|
-
printBox(balLines);
|
|
587
|
-
console.log();
|
|
588
|
-
printBox(arbLines);
|
|
589
|
-
}
|
|
590
|
-
// Positions
|
|
591
|
-
if (allPositions.length > 0) {
|
|
592
|
-
console.log(chalk.white.bold("\n Positions"));
|
|
593
|
-
const posRows = allPositions.map(p => {
|
|
594
|
-
const sideColor = p.side === "long" ? chalk.green : chalk.red;
|
|
595
|
-
const notional = Math.abs(Number(p.size) * Number(p.markPrice));
|
|
596
|
-
const levStr = p.leverage > 0 ? `${p.leverage}x` : "-";
|
|
597
|
-
const warn = p.leverage >= 5 ? chalk.red(" \u26A0") : "";
|
|
598
|
-
return [
|
|
599
|
-
chalk.white.bold(p.symbol.replace("-PERP", "")),
|
|
600
|
-
chalk.gray(exAbbr(p.exchange)),
|
|
601
|
-
sideColor(p.side.toUpperCase()),
|
|
602
|
-
p.size,
|
|
603
|
-
`$${formatUsd(p.entryPrice)}\u2192$${formatUsd(p.markPrice)}`,
|
|
604
|
-
formatPnl(p.unrealizedPnl),
|
|
605
|
-
`$${formatUsd(notional)}`,
|
|
606
|
-
levStr + warn,
|
|
607
|
-
];
|
|
608
|
-
});
|
|
609
|
-
console.log(makeTable(["Symbol", "Ex", "Side", "Size", "Entry\u2192Mark", "uPnL", "Notional", "Lev"], posRows));
|
|
610
|
-
}
|
|
611
|
-
else {
|
|
612
|
-
console.log(chalk.gray("\n No open positions.\n"));
|
|
613
|
-
}
|
|
614
|
-
});
|
|
615
|
-
});
|
|
616
559
|
// Switch shared API URLs if --network testnet is used
|
|
617
560
|
program.hook("preAction", () => {
|
|
618
561
|
const network = program.opts().network;
|
|
@@ -632,6 +575,7 @@ if (rawArgs.length === 0 || (!hasSubcommand && !rawArgs.includes("-h") && !rawAr
|
|
|
632
575
|
const hasEnvKey = !!(process.env.PRIVATE_KEY || process.env.PACIFICA_PRIVATE_KEY ||
|
|
633
576
|
process.env.HL_PRIVATE_KEY || process.env.HYPERLIQUID_PRIVATE_KEY ||
|
|
634
577
|
process.env.LIGHTER_PRIVATE_KEY);
|
|
578
|
+
const hasOwsWallet = !!settings.owsActiveWallet;
|
|
635
579
|
// ── ASCII banner ──
|
|
636
580
|
const banner = [
|
|
637
581
|
"",
|
|
@@ -645,9 +589,10 @@ if (rawArgs.length === 0 || (!hasSubcommand && !rawArgs.includes("-h") && !rawAr
|
|
|
645
589
|
chalk.gray(` 4 exchanges · 19 strategies · v${_pkg.version}`),
|
|
646
590
|
];
|
|
647
591
|
console.log(banner.join("\n"));
|
|
648
|
-
if (!status.hasWallets && !hasEnvKey && !settings.defaultExchange) {
|
|
592
|
+
if (!status.hasWallets && !hasEnvKey && !hasOwsWallet && !settings.defaultExchange) {
|
|
649
593
|
// Fresh install — onboarding
|
|
650
594
|
console.log(chalk.yellow.bold("\n ⚡ Get started:\n"));
|
|
595
|
+
console.log(` ${chalk.cyan("perp wallet generate")} create OWS encrypted wallet`);
|
|
651
596
|
console.log(` ${chalk.cyan("perp setup")} setup wizard`);
|
|
652
597
|
console.log(chalk.gray(`\n Explore without a wallet:`));
|
|
653
598
|
console.log(` ${chalk.green("perp market mid BTC")} quick price check`);
|
|
@@ -727,8 +672,8 @@ if (rawArgs.length === 0 || (!hasSubcommand && !rawArgs.includes("-h") && !rawAr
|
|
|
727
672
|
console.log(` ${chalk.green("perp portfolio")} balances + positions + risk`);
|
|
728
673
|
console.log(` ${chalk.green("perp arb scan")} funding arbitrage opportunities`);
|
|
729
674
|
console.log(` ${chalk.green("perp arb status")} open arb positions + PnL`);
|
|
730
|
-
console.log(` ${chalk.green("perp
|
|
731
|
-
console.log(` ${chalk.green("perp
|
|
675
|
+
console.log(` ${chalk.green("perp portfolio --arb")} full dashboard (balances + arb top 5)`);
|
|
676
|
+
console.log(` ${chalk.green("perp portfolio --serve")} live web monitoring`);
|
|
732
677
|
console.log(` ${chalk.green("perp --help")} all commands\n`);
|
|
733
678
|
}
|
|
734
679
|
}
|