perp-cli 0.12.17 → 0.14.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 +247 -27
- package/dist/_stderr-filter.js +0 -1
- package/dist/agent-wallet/lighter-keystore.d.ts +11 -0
- package/dist/agent-wallet/lighter-keystore.js +92 -10
- package/dist/agent-wallet/passphrase.d.ts +1 -1
- package/dist/agent-wallet/passphrase.js +16 -3
- package/dist/agent-wallet/store.d.ts +37 -4
- package/dist/agent-wallet/store.js +132 -0
- package/dist/agent-wallet/venue-keystore.d.ts +14 -0
- package/dist/agent-wallet/venue-keystore.js +121 -0
- package/dist/api/public/hyperliquid.js +4 -4
- package/dist/api/public/lighter.js +5 -5
- package/dist/api/public/pacifica.d.ts +0 -1
- package/dist/api/public/pacifica.js +4 -13
- package/dist/api/public/urls.d.ts +9 -1
- package/dist/api/public/urls.js +35 -13
- package/dist/arb/file-lock.d.ts +11 -0
- package/dist/arb/file-lock.js +74 -0
- package/dist/arb/index.d.ts +2 -2
- package/dist/arb/index.js +2 -2
- package/dist/arb/pending-entry.d.ts +32 -0
- package/dist/arb/pending-entry.js +77 -0
- package/dist/arb/sizing.d.ts +6 -13
- package/dist/arb/sizing.js +12 -51
- package/dist/arb/state.d.ts +45 -12
- package/dist/arb/state.js +214 -42
- package/dist/arb/utils.d.ts +20 -6
- package/dist/arb/utils.js +43 -39
- package/dist/bot/conditions.d.ts +4 -4
- package/dist/bot/conditions.js +11 -6
- package/dist/bot/config.d.ts +17 -1
- package/dist/bot/config.js +48 -12
- package/dist/bot/engine.d.ts +61 -2
- package/dist/bot/engine.js +610 -144
- package/dist/bot/fill-size.d.ts +7 -0
- package/dist/bot/fill-size.js +18 -0
- package/dist/bot/index.d.ts +1 -1
- package/dist/bot/index.js +1 -1
- package/dist/bot/perp-order.d.ts +99 -0
- package/dist/bot/perp-order.js +299 -0
- package/dist/bot/presets.d.ts +7 -0
- package/dist/bot/presets.js +17 -0
- package/dist/bot/quarantine.d.ts +75 -0
- package/dist/bot/quarantine.js +190 -0
- package/dist/bot/quote-rules.d.ts +23 -0
- package/dist/bot/quote-rules.js +69 -0
- package/dist/bot/strategies/avellaneda-mm.d.ts +32 -2
- package/dist/bot/strategies/avellaneda-mm.js +72 -70
- package/dist/bot/strategies/basis-arb.js +2 -0
- package/dist/bot/strategies/dca-strategy.d.ts +2 -0
- package/dist/bot/strategies/dca-strategy.js +5 -0
- package/dist/bot/strategies/funding-arb-strategy.d.ts +43 -3
- package/dist/bot/strategies/funding-arb-strategy.js +571 -380
- package/dist/bot/strategies/funding-arb-utils.d.ts +48 -23
- package/dist/bot/strategies/funding-arb-utils.js +159 -195
- package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
- package/dist/bot/strategies/funding-arb-v2.js +258 -47
- package/dist/bot/strategies/funding-auto.js +215 -194
- package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +14 -0
- package/dist/bot/strategies/spot-perp-arb-strategy.js +217 -50
- package/dist/bot/strategies/spot-perp-executor.d.ts +73 -19
- package/dist/bot/strategies/spot-perp-executor.js +319 -120
- package/dist/bot/strategies/spot-perp-state.d.ts +43 -0
- package/dist/bot/strategies/spot-perp-state.js +70 -0
- package/dist/bot/strategy-types.d.ts +6 -0
- package/dist/bot/tui/BotDashboard.d.ts +7 -2
- package/dist/bot/tui/BotDashboard.js +6 -3
- package/dist/bridge-engine.d.ts +120 -19
- package/dist/bridge-engine.js +710 -422
- package/dist/bridge-signers.d.ts +54 -0
- package/dist/bridge-signers.js +161 -0
- package/dist/cache.d.ts +4 -1
- package/dist/cache.js +11 -0
- package/dist/cli-flags.d.ts +50 -0
- package/dist/cli-flags.js +104 -0
- package/dist/cli-program.d.ts +35 -0
- package/dist/cli-program.js +286 -0
- package/dist/cli-spec.js +1 -1
- package/dist/client-id-tracker.js +11 -2
- package/dist/commands/account.d.ts +7 -1
- package/dist/commands/account.js +70 -92
- package/dist/commands/agent-import.d.ts +114 -0
- package/dist/commands/agent-import.js +794 -0
- package/dist/commands/agent.d.ts +232 -2
- package/dist/commands/agent.js +1858 -955
- package/dist/commands/alerts.d.ts +1 -0
- package/dist/commands/alerts.js +31 -28
- package/dist/commands/arb/index.d.ts +60 -1
- package/dist/commands/arb/index.js +381 -384
- package/dist/commands/arb-auto.d.ts +132 -4
- package/dist/commands/arb-auto.js +1474 -967
- package/dist/commands/arb.d.ts +3 -1
- package/dist/commands/arb.js +62 -30
- package/dist/commands/bot.js +127 -47
- package/dist/commands/bridge.d.ts +2 -1
- package/dist/commands/bridge.js +339 -192
- package/dist/commands/builder.d.ts +55 -0
- package/dist/commands/builder.js +387 -0
- package/dist/commands/funds-onchain.d.ts +32 -0
- package/dist/commands/funds-onchain.js +286 -0
- package/dist/commands/funds.d.ts +25 -0
- package/dist/commands/funds.js +939 -245
- package/dist/commands/history.js +72 -45
- package/dist/commands/init.d.ts +107 -15
- package/dist/commands/init.js +1116 -249
- package/dist/commands/jobs.d.ts +2 -1
- package/dist/commands/jobs.js +77 -15
- package/dist/commands/manage.js +136 -91
- package/dist/commands/market.d.ts +4 -1
- package/dist/commands/market.js +35 -32
- package/dist/commands/outcome.d.ts +10 -0
- package/dist/commands/outcome.js +396 -0
- package/dist/commands/plan.js +52 -12
- package/dist/commands/portfolio.d.ts +1 -0
- package/dist/commands/portfolio.js +60 -72
- package/dist/commands/rebalance.d.ts +2 -1
- package/dist/commands/rebalance.js +33 -43
- package/dist/commands/risk.js +46 -19
- package/dist/commands/settings.d.ts +1 -0
- package/dist/commands/settings.js +246 -51
- package/dist/commands/trade.d.ts +50 -0
- package/dist/commands/trade.js +1943 -695
- package/dist/commands/wallet.d.ts +30 -1
- package/dist/commands/wallet.js +316 -337
- package/dist/config.d.ts +60 -9
- package/dist/config.js +252 -82
- package/dist/constants.d.ts +29 -4
- package/dist/constants.js +62 -20
- package/dist/dashboard/server.d.ts +2 -0
- package/dist/dashboard/server.js +9 -6
- package/dist/dashboard/ui.js +6 -4
- package/dist/dashboard/ws-feeds.js +20 -8
- package/dist/dex-asset-map.js +5 -3
- package/dist/errors.d.ts +122 -0
- package/dist/errors.js +194 -0
- package/dist/event-stream.js +75 -19
- package/dist/evm-master-tx.d.ts +59 -0
- package/dist/evm-master-tx.js +154 -0
- package/dist/exchanges/adapter-network.d.ts +10 -0
- package/dist/exchanges/adapter-network.js +10 -0
- package/dist/exchanges/arcus/adapter.d.ts +196 -0
- package/dist/exchanges/arcus/adapter.js +1092 -0
- package/dist/exchanges/arcus/agent.d.ts +55 -0
- package/dist/exchanges/arcus/agent.js +188 -0
- package/dist/exchanges/arcus/client.d.ts +63 -0
- package/dist/exchanges/arcus/client.js +285 -0
- package/dist/exchanges/arcus/public.d.ts +65 -0
- package/dist/exchanges/arcus/public.js +138 -0
- package/dist/exchanges/arcus/signing.d.ts +176 -0
- package/dist/exchanges/arcus/signing.js +288 -0
- package/dist/exchanges/arcus/spot-chain.d.ts +60 -0
- package/dist/exchanges/arcus/spot-chain.js +241 -0
- package/dist/exchanges/arcus/spot-router.d.ts +177 -0
- package/dist/exchanges/arcus/spot-router.js +183 -0
- package/dist/exchanges/arcus/spot.d.ts +109 -0
- package/dist/exchanges/arcus/spot.js +645 -0
- package/dist/exchanges/aster-funds.d.ts +127 -0
- package/dist/exchanges/aster-funds.js +252 -0
- package/dist/exchanges/aster-nonce.d.ts +2 -0
- package/dist/exchanges/aster-nonce.js +107 -0
- package/dist/exchanges/aster-spot.d.ts +117 -0
- package/dist/exchanges/aster-spot.js +686 -0
- package/dist/exchanges/aster-typed-data.d.ts +133 -7
- package/dist/exchanges/aster-typed-data.js +121 -7
- package/dist/exchanges/aster.d.ts +219 -6
- package/dist/exchanges/aster.js +1023 -153
- package/dist/exchanges/builder-fee.d.ts +254 -0
- package/dist/exchanges/builder-fee.js +421 -0
- package/dist/exchanges/canonical-symbols.d.ts +32 -0
- package/dist/exchanges/canonical-symbols.js +96 -0
- package/dist/exchanges/capabilities.d.ts +7 -1
- package/dist/exchanges/capabilities.js +7 -0
- package/dist/exchanges/hl-signing.d.ts +165 -0
- package/dist/exchanges/hl-signing.js +158 -0
- package/dist/exchanges/hyperliquid-outcome.d.ts +211 -0
- package/dist/exchanges/hyperliquid-outcome.js +646 -0
- package/dist/exchanges/hyperliquid-spot.d.ts +44 -5
- package/dist/exchanges/hyperliquid-spot.js +308 -117
- package/dist/exchanges/hyperliquid.d.ts +462 -57
- package/dist/exchanges/hyperliquid.js +1575 -449
- package/dist/exchanges/index.d.ts +6 -0
- package/dist/exchanges/index.js +4 -0
- package/dist/exchanges/interface.d.ts +63 -10
- package/dist/exchanges/interface.js +18 -3
- package/dist/exchanges/ioc-outcome.d.ts +44 -0
- package/dist/exchanges/ioc-outcome.js +57 -0
- package/dist/exchanges/legacy-trading-v2.d.ts +39 -0
- package/dist/exchanges/legacy-trading-v2.js +940 -0
- package/dist/exchanges/lighter-public.d.ts +43 -0
- package/dist/exchanges/lighter-public.js +276 -0
- package/dist/exchanges/lighter-rh-deposit.d.ts +62 -0
- package/dist/exchanges/lighter-rh-deposit.js +185 -0
- package/dist/exchanges/lighter-signer.d.ts +123 -0
- package/dist/exchanges/lighter-signer.js +215 -0
- package/dist/exchanges/lighter-spot.d.ts +91 -13
- package/dist/exchanges/lighter-spot.js +595 -233
- package/dist/exchanges/lighter.d.ts +611 -39
- package/dist/exchanges/lighter.js +1890 -649
- package/dist/exchanges/ondo/adapter.d.ts +166 -0
- package/dist/exchanges/ondo/adapter.js +835 -0
- package/dist/exchanges/ondo/agent.d.ts +31 -0
- package/dist/exchanges/ondo/agent.js +101 -0
- package/dist/exchanges/ondo/client.d.ts +60 -0
- package/dist/exchanges/ondo/client.js +297 -0
- package/dist/exchanges/ondo/funds.d.ts +122 -0
- package/dist/exchanges/ondo/funds.js +290 -0
- package/dist/exchanges/ondo/public.d.ts +95 -0
- package/dist/exchanges/ondo/public.js +214 -0
- package/dist/exchanges/ondo/spot.d.ts +127 -0
- package/dist/exchanges/ondo/spot.js +392 -0
- package/dist/exchanges/outcome-interface.d.ts +140 -0
- package/dist/exchanges/outcome-interface.js +16 -0
- package/dist/exchanges/pacifica-spot.d.ts +83 -0
- package/dist/exchanges/pacifica-spot.js +324 -0
- package/dist/exchanges/pacifica-typed-data.d.ts +30 -13
- package/dist/exchanges/pacifica-typed-data.js +57 -9
- package/dist/exchanges/pacifica.d.ts +288 -4
- package/dist/exchanges/pacifica.js +1013 -76
- package/dist/exchanges/precision.d.ts +47 -0
- package/dist/exchanges/precision.js +209 -0
- package/dist/exchanges/qfex/adapter.d.ts +341 -0
- package/dist/exchanges/qfex/adapter.js +1383 -0
- package/dist/exchanges/qfex/agent.d.ts +58 -0
- package/dist/exchanges/qfex/agent.js +188 -0
- package/dist/exchanges/qfex/client.d.ts +54 -0
- package/dist/exchanges/qfex/client.js +138 -0
- package/dist/exchanges/qfex/errors.d.ts +70 -0
- package/dist/exchanges/qfex/errors.js +236 -0
- package/dist/exchanges/qfex/public.d.ts +122 -0
- package/dist/exchanges/qfex/public.js +268 -0
- package/dist/exchanges/qfex/signing.d.ts +160 -0
- package/dist/exchanges/qfex/signing.js +283 -0
- package/dist/exchanges/qfex/ws-transport.d.ts +294 -0
- package/dist/exchanges/qfex/ws-transport.js +798 -0
- package/dist/exchanges/rate-limiter.d.ts +12 -0
- package/dist/exchanges/rate-limiter.js +64 -0
- package/dist/exchanges/registry.d.ts +14 -2
- package/dist/exchanges/registry.js +20 -32
- package/dist/exchanges/slippage.d.ts +37 -0
- package/dist/exchanges/slippage.js +70 -0
- package/dist/exchanges/spot-factory.d.ts +166 -0
- package/dist/exchanges/spot-factory.js +431 -0
- package/dist/exchanges/spot-interface.d.ts +98 -4
- package/dist/exchanges/spot-interface.js +27 -1
- package/dist/exchanges/spot-result.d.ts +79 -0
- package/dist/exchanges/spot-result.js +273 -0
- package/dist/exchanges/trading-v2.d.ts +117 -0
- package/dist/exchanges/trading-v2.js +301 -0
- package/dist/exchanges/venue-types.d.ts +231 -0
- package/dist/exchanges/venue-types.js +87 -0
- package/dist/exchanges/venue.d.ts +211 -0
- package/dist/exchanges/venue.js +132 -0
- package/dist/exchanges/venues/arcus.d.ts +8 -0
- package/dist/exchanges/venues/arcus.js +109 -0
- package/dist/exchanges/venues/aster.d.ts +49 -0
- package/dist/exchanges/venues/aster.js +176 -0
- package/dist/exchanges/venues/create-helpers.d.ts +18 -0
- package/dist/exchanges/venues/create-helpers.js +199 -0
- package/dist/exchanges/venues/hyperliquid.d.ts +23 -0
- package/dist/exchanges/venues/hyperliquid.js +126 -0
- package/dist/exchanges/venues/index.d.ts +16 -0
- package/dist/exchanges/venues/index.js +26 -0
- package/dist/exchanges/venues/lighter-family.d.ts +92 -0
- package/dist/exchanges/venues/lighter-family.js +166 -0
- package/dist/exchanges/venues/lighter-rh.d.ts +7 -0
- package/dist/exchanges/venues/lighter-rh.js +58 -0
- package/dist/exchanges/venues/lighter.d.ts +6 -0
- package/dist/exchanges/venues/lighter.js +55 -0
- package/dist/exchanges/venues/ondo.d.ts +34 -0
- package/dist/exchanges/venues/ondo.js +140 -0
- package/dist/exchanges/venues/pacifica.d.ts +8 -0
- package/dist/exchanges/venues/pacifica.js +95 -0
- package/dist/exchanges/venues/qfex.d.ts +28 -0
- package/dist/exchanges/venues/qfex.js +132 -0
- package/dist/execution-log.js +10 -2
- package/dist/funding/legacy-public.d.ts +31 -0
- package/dist/funding/legacy-public.js +369 -0
- package/dist/funding/normalize.d.ts +28 -16
- package/dist/funding/normalize.js +61 -29
- package/dist/funding/rates.d.ts +46 -11
- package/dist/funding/rates.js +228 -277
- package/dist/guardrail/perp-guardrail.js +0 -0
- package/dist/index.js +156 -684
- package/dist/jobs.d.ts +84 -6
- package/dist/jobs.js +239 -41
- package/dist/landing.d.ts +7 -2
- package/dist/landing.js +19 -3
- package/dist/mcp-server.d.ts +14 -4
- package/dist/mcp-server.js +820 -402
- package/dist/mcp-trade-guard.d.ts +16 -0
- package/dist/mcp-trade-guard.js +59 -0
- package/dist/pacifica/client.d.ts +41 -10
- package/dist/pacifica/client.js +167 -63
- package/dist/pacifica/constants.d.ts +0 -1
- package/dist/pacifica/constants.js +0 -5
- package/dist/pacifica/deposit.d.ts +30 -3
- package/dist/pacifica/deposit.js +119 -2
- package/dist/pacifica/faucet.d.ts +29 -0
- package/dist/pacifica/faucet.js +100 -0
- package/dist/pacifica/sign-tx.d.ts +15 -0
- package/dist/pacifica/sign-tx.js +41 -0
- package/dist/pacifica/types/account.d.ts +28 -4
- package/dist/pacifica/types/lake.d.ts +20 -5
- package/dist/pacifica/types/lake.js +4 -0
- package/dist/pacifica/types/market.d.ts +5 -0
- package/dist/pacifica/types/order.d.ts +4 -1
- package/dist/pacifica/types/position.d.ts +9 -1
- package/dist/plan-executor.d.ts +10 -5
- package/dist/plan-executor.js +165 -54
- package/dist/position-history.js +13 -0
- package/dist/rebalance.d.ts +26 -2
- package/dist/rebalance.js +86 -7
- package/dist/relay-swap.d.ts +232 -0
- package/dist/relay-swap.js +630 -0
- package/dist/risk.js +5 -1
- package/dist/secret-env.d.ts +15 -0
- package/dist/secret-env.js +36 -0
- package/dist/settings.d.ts +169 -6
- package/dist/settings.js +219 -38
- package/dist/signer/ows-evm.d.ts +13 -0
- package/dist/signer/ows-evm.js +22 -0
- package/dist/smart-order.d.ts +9 -0
- package/dist/smart-order.js +53 -8
- package/dist/strategies/dca.d.ts +6 -2
- package/dist/strategies/dca.js +66 -26
- package/dist/strategies/funding-arb.d.ts +11 -1
- package/dist/strategies/funding-arb.js +202 -89
- package/dist/strategies/grid.d.ts +31 -0
- package/dist/strategies/grid.js +89 -11
- package/dist/strategies/pending-stop.d.ts +8 -0
- package/dist/strategies/pending-stop.js +12 -0
- package/dist/strategies/split-order.d.ts +45 -17
- package/dist/strategies/split-order.js +157 -91
- package/dist/strategies/trailing-stop.d.ts +2 -1
- package/dist/strategies/trailing-stop.js +52 -6
- package/dist/strategies/twap.d.ts +6 -1
- package/dist/strategies/twap.js +39 -11
- package/dist/trade-validator.d.ts +22 -0
- package/dist/trade-validator.js +119 -15
- package/dist/utils/numeric.d.ts +46 -0
- package/dist/utils/numeric.js +79 -0
- package/dist/utils.d.ts +19 -0
- package/dist/utils.js +96 -19
- package/dist/withdraw-gate.d.ts +49 -0
- package/dist/withdraw-gate.js +131 -0
- package/package.json +15 -6
- package/skills/perp-cli/SKILL.md +142 -55
- package/skills/perp-cli/references/agent-operations.md +110 -56
- package/skills/perp-cli/references/commands.md +266 -71
- package/skills/perp-cli/references/strategies.md +34 -31
- package/skills/perp-cli/scripts/arb-monitor.sh +10 -5
- package/skills/perp-cli/scripts/funding-analysis.sh +14 -9
- package/skills/perp-cli/scripts/preflight.sh +21 -14
- package/skills/perp-cli/scripts/spot-perp-scan.sh +12 -7
- package/skills/perp-cli/scripts/validate-arb.sh +31 -15
- package/vendor/lighter-signer/SHA256SUMS +2 -0
- package/vendor/lighter-signer/VERSION +8 -0
- package/vendor/lighter-signer/lighter-signer.wasm +0 -0
- package/vendor/lighter-signer/wasm_exec.js +575 -0
- package/dist/bot/strategies/funding-arb-v2-strategy.d.ts +0 -42
- package/dist/bot/strategies/funding-arb-v2-strategy.js +0 -864
package/dist/index.js
CHANGED
|
@@ -15,718 +15,190 @@ import { createRequire } from "node:module";
|
|
|
15
15
|
// instead of the OWS active wallet's). If you need ad-hoc env overrides,
|
|
16
16
|
// export them in your shell — keystore is the canonical source.
|
|
17
17
|
config({ path: resolve(process.env.HOME || "~", ".perp", ".env") });
|
|
18
|
-
import { Command } from "commander";
|
|
19
18
|
import chalk from "chalk";
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import { resolveExchangeName } from "./exchanges/registry.js";
|
|
25
|
-
import { registerMarketCommands } from "./commands/market.js";
|
|
26
|
-
import { registerAccountCommands } from "./commands/account.js";
|
|
27
|
-
import { registerTradeCommands } from "./commands/trade.js";
|
|
28
|
-
// manage commands now register under `wallet manage ...` via registerWalletCommands.
|
|
29
|
-
// stream commands removed — WS feeds still used by dashboard/event-stream internally
|
|
30
|
-
import { registerArbCommands } from "./commands/arb.js";
|
|
31
|
-
import { registerWalletCommands } from "./commands/wallet.js";
|
|
32
|
-
// bridge + rebalance now nested under `funds` — registered inside registerFundsCommands.
|
|
33
|
-
import { registerFundsCommands } from "./commands/funds.js";
|
|
34
|
-
// alert commands removed
|
|
35
|
-
import { registerArbAutoCommands } from "./commands/arb-auto.js";
|
|
36
|
-
import { registerArbManageCommands } from "./commands/arb/index.js";
|
|
37
|
-
// Agent commands now register under `wallet agent ...` via registerWalletCommands.
|
|
38
|
-
import { registerStrategyCommands } from "./commands/bot.js";
|
|
39
|
-
import { registerRiskCommands } from "./commands/risk.js";
|
|
40
|
-
import { registerHistoryCommands } from "./commands/history.js";
|
|
41
|
-
import { registerSettingsCommands } from "./commands/settings.js";
|
|
42
|
-
// dex commands merged into market (hip3) — use --dex flag for markets/balance
|
|
43
|
-
// `plan` is no longer top-level — registered as `strategy plan` inside
|
|
44
|
-
// registerStrategyCommands.
|
|
45
|
-
// funding merged into arb.ts
|
|
46
|
-
import { registerBacktestCommands } from "./commands/backtest.js";
|
|
47
|
-
import { registerPortfolioCommand } from "./commands/portfolio.js";
|
|
48
|
-
import { registerInitCommand } from "./commands/init.js";
|
|
49
|
-
import { registerAlertCommands } from "./commands/alerts.js";
|
|
50
|
-
import { loadSettings, saveSettings } from "./settings.js";
|
|
51
|
-
import { setSharedApiNetwork } from "./shared-api.js";
|
|
19
|
+
import { listVenues } from "./exchanges/venue.js";
|
|
20
|
+
import { PerpError } from "./errors.js";
|
|
21
|
+
import { loadSettings } from "./settings.js";
|
|
22
|
+
import { jsonError, jsonOk, printCliError, printJson } from "./utils.js";
|
|
52
23
|
import { LANDING_EXCHANGES, asterAgentMissing as getAsterAgentMissing, renderLandingExchangeLine } from "./landing.js";
|
|
24
|
+
import { buildProgram, landingData, numericNetworkFlagError } from "./cli-program.js";
|
|
53
25
|
const _require = createRequire(import.meta.url);
|
|
54
26
|
const _pkg = _require("../package.json");
|
|
55
|
-
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
.option("-n, --network <network>", "Network: mainnet or testnet", "mainnet")
|
|
65
|
-
.option("-k, --private-key <key>", "Private key")
|
|
66
|
-
.option("--json", "Output raw JSON (for piping)")
|
|
67
|
-
.option("--fields <fields>", "Comma-separated fields to include in JSON output (e.g. totalEquity,positions)")
|
|
68
|
-
.option("--ndjson", "Output newline-delimited JSON (one object per line for streaming)")
|
|
69
|
-
.option("--dry-run", "Simulate trades without executing (log as simulated)")
|
|
70
|
-
.option("-w, --wallet <name>", "Use a specific wallet by name (from 'perp wallet list')")
|
|
71
|
-
.option("--ows <name>", "Use an OWS (Open Wallet Standard) wallet by name")
|
|
72
|
-
.option("--ows-key <token>", "OWS API key token (ows_key_...) for policy-gated agent access")
|
|
73
|
-
.option("--no-agent", "Bypass agent wallet routing for this run; falls back to OWS master or PK direct")
|
|
74
|
-
.option("--passphrase <pp>", "Master OWS passphrase (also accepts OWS_PASSPHRASE env or stdin)")
|
|
75
|
-
.option("--dex <name>", "HIP-3 deployed perp dex name (Hyperliquid only)")
|
|
76
|
-
.configureOutput({
|
|
77
|
-
writeErr: (str) => {
|
|
27
|
+
// Resolve default exchange from settings (fallback: "pacifica").
|
|
28
|
+
// A malformed settings.json is a hard stop (SSOT Rule #2): report it cleanly and exit.
|
|
29
|
+
function loadStartupSettings() {
|
|
30
|
+
try {
|
|
31
|
+
return loadSettings();
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
const s = err instanceof PerpError ? err.structured : null;
|
|
35
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
78
36
|
if (process.argv.includes("--json")) {
|
|
79
|
-
|
|
80
|
-
// Use inline envelope (jsonError import not available in sync context)
|
|
81
|
-
console.log(JSON.stringify({
|
|
82
|
-
ok: false,
|
|
83
|
-
error: { code: "CLI_ERROR", message: msg },
|
|
84
|
-
meta: { timestamp: new Date().toISOString() },
|
|
85
|
-
}));
|
|
37
|
+
console.log(JSON.stringify(jsonError(s?.code ?? "INVALID_PARAMS", msg, { status: s?.status ?? 400, retryable: false, remediation: s?.remediation })));
|
|
86
38
|
}
|
|
87
39
|
else {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
});
|
|
92
|
-
let _adapter = null;
|
|
93
|
-
let _pacificaAdapter = null;
|
|
94
|
-
let _hlAdapter = null;
|
|
95
|
-
let _lighterAdapter = null;
|
|
96
|
-
/** Map short aliases to canonical exchange names. Full names pass through. */
|
|
97
|
-
function resolveExchangeAlias(name) {
|
|
98
|
-
return resolveExchangeName(name);
|
|
99
|
-
}
|
|
100
|
-
function getExchange() {
|
|
101
|
-
return resolveExchangeAlias(program.opts().exchange);
|
|
102
|
-
}
|
|
103
|
-
async function getAdapter() {
|
|
104
|
-
const opts = program.opts();
|
|
105
|
-
const owsName = opts.ows;
|
|
106
|
-
const owsKeyToken = opts.owsKey || process.env.OWS_API_KEY;
|
|
107
|
-
const walletName = owsName ? `ows:${owsName}` : opts.wallet;
|
|
108
|
-
// Commander's `--no-agent` flag sets opts.agent === false (NOT opts.noAgent).
|
|
109
|
-
// Normalize so downstream wiring can use a single boolean consistently.
|
|
110
|
-
const noAgent = opts.agent === false;
|
|
111
|
-
// Skip cache when --wallet/--ows is specified (different wallet = different account)
|
|
112
|
-
if (!walletName && _adapter)
|
|
113
|
-
return _adapter;
|
|
114
|
-
const exchange = resolveExchangeAlias(opts.exchange);
|
|
115
|
-
const network = opts.network;
|
|
116
|
-
const isTestnet = network === "testnet";
|
|
117
|
-
// Try to load key — null means no key configured (read-only mode)
|
|
118
|
-
const pk = await tryLoadPrivateKey(exchange, opts.privateKey, walletName);
|
|
119
|
-
// OWS wallet detected — use OWS signers instead of raw keys
|
|
120
|
-
if (pk && isOwsKey(pk)) {
|
|
121
|
-
return _initWithOws(exchange, getOwsWalletName(pk), isTestnet, opts, owsKeyToken);
|
|
122
|
-
}
|
|
123
|
-
switch (exchange) {
|
|
124
|
-
case "pacifica": {
|
|
125
|
-
const keypair = pk ? parseSolanaKeypair(pk) : Keypair.generate();
|
|
126
|
-
const pacNetwork = (isTestnet ? "testnet" : "mainnet");
|
|
127
|
-
const settings = loadSettings();
|
|
128
|
-
const builderCode = process.env.PACIFICA_BUILDER_CODE || settings.referralCodes.pacifica || "PERPCLI";
|
|
129
|
-
_pacificaAdapter = new PacificaAdapter(keypair, pacNetwork, builderCode, !!pk);
|
|
130
|
-
// Tier 2: OWS master if --ows or settings.owsActiveWallet (Phase 2c)
|
|
131
|
-
const pacOwsName = owsName || _settings.owsActiveWallet;
|
|
132
|
-
if (pacOwsName && !pk) {
|
|
133
|
-
const { OwsSolanaSigner } = await import("./signer/ows-solana.js");
|
|
134
|
-
const { resolvePassphrase } = await import("./agent-wallet/passphrase.js");
|
|
135
|
-
const pp = await resolvePassphrase({ flag: opts.passphrase });
|
|
136
|
-
if (pp !== null)
|
|
137
|
-
_pacificaAdapter.setSigner(OwsSolanaSigner.create(pacOwsName, pp));
|
|
138
|
-
}
|
|
139
|
-
// Tier 1: agent if registered (Phase 2c)
|
|
140
|
-
const { getAgent: getPacAgent } = await import("./agent-wallet/store.js");
|
|
141
|
-
const pacAgentMeta = getPacAgent("pacifica");
|
|
142
|
-
if (pacAgentMeta) {
|
|
143
|
-
const { OwsSolanaSigner: PacOwsSolanaSigner } = await import("./signer/ows-solana.js");
|
|
144
|
-
// Pacifica agent wallet was created with empty passphrase (see runPacApproveFlow);
|
|
145
|
-
// open with "" to match. Agent at-rest encryption handled by OWS storage layer.
|
|
146
|
-
const agentSigner = PacOwsSolanaSigner.create(pacAgentMeta.agentWalletName, "");
|
|
147
|
-
_pacificaAdapter.setAgentSigner(pacAgentMeta, agentSigner);
|
|
148
|
-
}
|
|
149
|
-
if (noAgent)
|
|
150
|
-
_pacificaAdapter.setNoAgent(true);
|
|
151
|
-
_adapter = _pacificaAdapter;
|
|
152
|
-
break;
|
|
153
|
-
}
|
|
154
|
-
case "hyperliquid": {
|
|
155
|
-
_hlAdapter = new HyperliquidAdapter(pk ?? undefined, isTestnet);
|
|
156
|
-
if (opts.dex)
|
|
157
|
-
_hlAdapter.setDex(opts.dex);
|
|
158
|
-
await _hlAdapter.init();
|
|
159
|
-
if (pk) {
|
|
160
|
-
const hlSettings = loadSettings();
|
|
161
|
-
if (!hlSettings.referralApplied.hyperliquid) {
|
|
162
|
-
const hlRef = process.env.HL_REFERRAL_CODE || hlSettings.referralCodes.hyperliquid;
|
|
163
|
-
if (hlRef) {
|
|
164
|
-
_hlAdapter.autoSetReferrer(hlRef).then(() => {
|
|
165
|
-
const s = loadSettings();
|
|
166
|
-
s.referralApplied.hyperliquid = true;
|
|
167
|
-
saveSettings(s);
|
|
168
|
-
}).catch((err) => {
|
|
169
|
-
// Per SSOT Rule #2: do NOT mark applied=true on failure.
|
|
170
|
-
// Leave referralApplied[ex]=false so next adapter init retries.
|
|
171
|
-
process.stderr.write(`[hyperliquid] referral apply failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
// Tier 2: OWS master if --ows or settings.owsActiveWallet
|
|
177
|
-
const hlOwsName = owsName || _settings.owsActiveWallet;
|
|
178
|
-
if (hlOwsName && !pk) {
|
|
179
|
-
const { OwsEvmSigner } = await import("./signer/ows-evm.js");
|
|
180
|
-
const { resolvePassphrase } = await import("./agent-wallet/passphrase.js");
|
|
181
|
-
const pp = await resolvePassphrase({ flag: opts.passphrase });
|
|
182
|
-
if (pp !== null)
|
|
183
|
-
_hlAdapter.setSigner(OwsEvmSigner.create(hlOwsName, pp));
|
|
184
|
-
}
|
|
185
|
-
// Tier 1: agent if registered
|
|
186
|
-
const { getAgent: getHlAgent } = await import("./agent-wallet/store.js");
|
|
187
|
-
const hlAgentMeta = getHlAgent("hyperliquid");
|
|
188
|
-
if (hlAgentMeta) {
|
|
189
|
-
const { OwsEvmSigner: HlOwsEvmSigner } = await import("./signer/ows-evm.js");
|
|
190
|
-
// HL agent wallet was created with empty passphrase (see runHlApproveFlow);
|
|
191
|
-
// open with "" to match. Agent at-rest encryption handled by OWS storage layer.
|
|
192
|
-
const agentSigner = HlOwsEvmSigner.create(hlAgentMeta.agentWalletName, "");
|
|
193
|
-
_hlAdapter.setAgentSigner(hlAgentMeta, agentSigner);
|
|
194
|
-
}
|
|
195
|
-
if (noAgent)
|
|
196
|
-
_hlAdapter.setNoAgent(true);
|
|
197
|
-
_adapter = _hlAdapter;
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
200
|
-
case "lighter": {
|
|
201
|
-
const { LighterAdapter } = await import("./exchanges/lighter.js");
|
|
202
|
-
// Phase 2d (SSOT Rule #3): when an agent is registered AND not bypassed,
|
|
203
|
-
// load the agent's L2 secp256k1 key from the encrypted keystore at
|
|
204
|
-
// ~/.perp/lighter-agents/<accountIndex>-<slot>.json BEFORE init() so the
|
|
205
|
-
// WASM client is created with the agent identity. Missing keystore →
|
|
206
|
-
// loadLighterKey throws LIGHTER_KEYSTORE_NOT_FOUND with remediation
|
|
207
|
-
// (no fallback to env per SSOT Rule #2).
|
|
208
|
-
const { getAgent: getLtAgent } = await import("./agent-wallet/store.js");
|
|
209
|
-
const ltAgentMeta = getLtAgent("lighter");
|
|
210
|
-
_lighterAdapter = new LighterAdapter(pk ?? "", isTestnet);
|
|
211
|
-
if (ltAgentMeta && !noAgent) {
|
|
212
|
-
const { loadLighterKey } = await import("./agent-wallet/lighter-keystore.js");
|
|
213
|
-
const ltL2Key = loadLighterKey(ltAgentMeta.accountIndex, ltAgentMeta.apiKeyIndex, "");
|
|
214
|
-
_lighterAdapter.setAgentSigner(ltAgentMeta, ltL2Key);
|
|
215
|
-
}
|
|
216
|
-
if (noAgent)
|
|
217
|
-
_lighterAdapter.setNoAgent(true);
|
|
218
|
-
await _lighterAdapter.init();
|
|
219
|
-
// LT referral apply is L2-signed (POST /referral/use uses the WASM slot
|
|
220
|
-
// signer's auth token, not the master EVM). Trigger on any active
|
|
221
|
-
// signer tier — agent / OWS master / PK direct — gated by isReadOnly.
|
|
222
|
-
const ltSettings = loadSettings();
|
|
223
|
-
if (!ltSettings.referralApplied.lighter && !_lighterAdapter.isReadOnly) {
|
|
224
|
-
const ltRef = process.env.LIGHTER_REFERRAL_CODE || ltSettings.referralCodes.lighter;
|
|
225
|
-
if (ltRef) {
|
|
226
|
-
_lighterAdapter.useReferralCode(ltRef).then(() => {
|
|
227
|
-
const s = loadSettings();
|
|
228
|
-
s.referralApplied.lighter = true;
|
|
229
|
-
saveSettings(s);
|
|
230
|
-
}).catch((err) => {
|
|
231
|
-
// Per SSOT Rule #2: do NOT mark applied=true on failure.
|
|
232
|
-
process.stderr.write(`[lighter] referral apply failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
_adapter = _lighterAdapter;
|
|
237
|
-
break;
|
|
238
|
-
}
|
|
239
|
-
case "aster": {
|
|
240
|
-
const { AsterAdapter } = await import("./exchanges/aster.js");
|
|
241
|
-
const ast = new AsterAdapter(pk ?? undefined, isTestnet); // Tier 3 from PK
|
|
242
|
-
await ast.init();
|
|
243
|
-
// Tier 2: OWS master if --ows or settings.owsActiveWallet
|
|
244
|
-
const asterOwsName = owsName || _settings.owsActiveWallet;
|
|
245
|
-
if (asterOwsName) {
|
|
246
|
-
const { OwsEvmSigner } = await import("./signer/ows-evm.js");
|
|
247
|
-
const { resolvePassphrase } = await import("./agent-wallet/passphrase.js");
|
|
248
|
-
const pp = await resolvePassphrase({ flag: opts.passphrase });
|
|
249
|
-
if (pp !== null)
|
|
250
|
-
ast.setMasterSigner(OwsEvmSigner.create(asterOwsName, pp));
|
|
251
|
-
}
|
|
252
|
-
// Tier 1: agent if registered
|
|
253
|
-
const { getAgent: getAsterAgent } = await import("./agent-wallet/store.js");
|
|
254
|
-
const agentMeta = getAsterAgent("aster");
|
|
255
|
-
if (agentMeta) {
|
|
256
|
-
const { agentSigningStrategyFor } = await import("./agent-wallet/signing-strategy.js");
|
|
257
|
-
const strat = agentSigningStrategyFor(agentMeta, owsKeyToken ?? "");
|
|
258
|
-
ast.setAgent(agentMeta, strat);
|
|
259
|
-
}
|
|
260
|
-
if (noAgent)
|
|
261
|
-
ast.setNoAgent(true);
|
|
262
|
-
_adapter = ast;
|
|
263
|
-
break;
|
|
264
|
-
}
|
|
265
|
-
default:
|
|
266
|
-
throw new Error(`Unknown exchange: ${exchange}`);
|
|
267
|
-
}
|
|
268
|
-
return _adapter;
|
|
269
|
-
}
|
|
270
|
-
/** Initialize adapter with OWS wallet signer injection. */
|
|
271
|
-
async function _initWithOws(exchange, owsWalletName, isTestnet, opts, owsKeyToken) {
|
|
272
|
-
const { OwsEvmSigner } = await import("./signer/ows-evm.js");
|
|
273
|
-
const { OwsSolanaSigner } = await import("./signer/ows-solana.js");
|
|
274
|
-
// If an OWS API key token is provided, use it as passphrase → routes through policy engine
|
|
275
|
-
const passphrase = owsKeyToken || "";
|
|
276
|
-
// Commander's `--no-agent` flag sets opts.agent === false (NOT opts.noAgent).
|
|
277
|
-
const noAgent = opts.agent === false;
|
|
278
|
-
switch (exchange) {
|
|
279
|
-
case "pacifica": {
|
|
280
|
-
const pacNetwork = (isTestnet ? "testnet" : "mainnet");
|
|
281
|
-
const settings = loadSettings();
|
|
282
|
-
const builderCode = process.env.PACIFICA_BUILDER_CODE || settings.referralCodes.pacifica || "PERPCLI";
|
|
283
|
-
const dummyKeypair = Keypair.generate();
|
|
284
|
-
_pacificaAdapter = new PacificaAdapter(dummyKeypair, pacNetwork, builderCode, true);
|
|
285
|
-
_pacificaAdapter.setSigner(OwsSolanaSigner.create(owsWalletName, passphrase));
|
|
286
|
-
// Tier 1: agent if registered (Phase 2c)
|
|
287
|
-
const { getAgent: getPacAgentOws } = await import("./agent-wallet/store.js");
|
|
288
|
-
const pacAgentMetaOws = getPacAgentOws("pacifica");
|
|
289
|
-
if (pacAgentMetaOws) {
|
|
290
|
-
const agentSignerOws = OwsSolanaSigner.create(pacAgentMetaOws.agentWalletName, "");
|
|
291
|
-
_pacificaAdapter.setAgentSigner(pacAgentMetaOws, agentSignerOws);
|
|
292
|
-
}
|
|
293
|
-
if (noAgent)
|
|
294
|
-
_pacificaAdapter.setNoAgent(true);
|
|
295
|
-
_adapter = _pacificaAdapter;
|
|
296
|
-
return _adapter;
|
|
297
|
-
}
|
|
298
|
-
case "hyperliquid": {
|
|
299
|
-
_hlAdapter = new HyperliquidAdapter(undefined, isTestnet);
|
|
300
|
-
if (opts.dex)
|
|
301
|
-
_hlAdapter.setDex(opts.dex);
|
|
302
|
-
_hlAdapter.setSigner(OwsEvmSigner.create(owsWalletName, passphrase));
|
|
303
|
-
await _hlAdapter.init();
|
|
304
|
-
_adapter = _hlAdapter;
|
|
305
|
-
return _adapter;
|
|
306
|
-
}
|
|
307
|
-
case "lighter": {
|
|
308
|
-
const { LighterAdapter } = await import("./exchanges/lighter.js");
|
|
309
|
-
_lighterAdapter = new LighterAdapter("", isTestnet);
|
|
310
|
-
_lighterAdapter.setSigner(OwsEvmSigner.create(owsWalletName, passphrase));
|
|
311
|
-
// Tier 1: agent if registered (mirrors getAdapter / getAdapterForExchange).
|
|
312
|
-
// Without this, _initWithOws would skip Tier 1 and fall through to
|
|
313
|
-
// auto-setup-at-slot-4 which is wrong for agent users (and breaks under
|
|
314
|
-
// SDK 1.0.11+ where signChangePubKey signature changed).
|
|
315
|
-
const { getAgent: getLtAgentOws } = await import("./agent-wallet/store.js");
|
|
316
|
-
const ltAgentMetaOws = getLtAgentOws("lighter");
|
|
317
|
-
if (ltAgentMetaOws && !noAgent) {
|
|
318
|
-
const { loadLighterKey } = await import("./agent-wallet/lighter-keystore.js");
|
|
319
|
-
const ltL2KeyOws = loadLighterKey(ltAgentMetaOws.accountIndex, ltAgentMetaOws.apiKeyIndex, "");
|
|
320
|
-
_lighterAdapter.setAgentSigner(ltAgentMetaOws, ltL2KeyOws);
|
|
321
|
-
}
|
|
322
|
-
if (noAgent)
|
|
323
|
-
_lighterAdapter.setNoAgent(true);
|
|
324
|
-
await _lighterAdapter.init();
|
|
325
|
-
_adapter = _lighterAdapter;
|
|
326
|
-
return _adapter;
|
|
327
|
-
}
|
|
328
|
-
case "aster": {
|
|
329
|
-
const { AsterAdapter } = await import("./exchanges/aster.js");
|
|
330
|
-
const asterOws = new AsterAdapter(undefined, isTestnet);
|
|
331
|
-
await asterOws.init();
|
|
332
|
-
asterOws.setMasterSigner(OwsEvmSigner.create(owsWalletName, passphrase));
|
|
333
|
-
// Optionally also set agent if registered
|
|
334
|
-
const { getAgent: getAsterAgentOws } = await import("./agent-wallet/store.js");
|
|
335
|
-
const asterMeta = getAsterAgentOws("aster");
|
|
336
|
-
if (asterMeta) {
|
|
337
|
-
const { agentSigningStrategyFor } = await import("./agent-wallet/signing-strategy.js");
|
|
338
|
-
asterOws.setAgent(asterMeta, agentSigningStrategyFor(asterMeta, owsKeyToken ?? ""));
|
|
339
|
-
}
|
|
340
|
-
if (noAgent)
|
|
341
|
-
asterOws.setNoAgent(true);
|
|
342
|
-
_adapter = asterOws;
|
|
343
|
-
return _adapter;
|
|
344
|
-
}
|
|
345
|
-
default:
|
|
346
|
-
throw new Error(`Unknown exchange: ${exchange}`);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
// Sync wrapper for commands that need adapter (lazy init)
|
|
350
|
-
function getAdapterSync() {
|
|
351
|
-
if (!_adapter)
|
|
352
|
-
throw new Error("Adapter not initialized");
|
|
353
|
-
return _adapter;
|
|
354
|
-
}
|
|
355
|
-
function isJson() {
|
|
356
|
-
return !!program.opts().json || process.argv.includes("--ndjson");
|
|
357
|
-
}
|
|
358
|
-
function isDryRun() {
|
|
359
|
-
return !!program.opts().dryRun;
|
|
360
|
-
}
|
|
361
|
-
// Helper to get PacificaAdapter specifically (for Pacifica-only commands)
|
|
362
|
-
function getPacificaAdapter() {
|
|
363
|
-
if (!_pacificaAdapter)
|
|
364
|
-
throw new Error("This command requires --exchange pacifica");
|
|
365
|
-
return _pacificaAdapter;
|
|
366
|
-
}
|
|
367
|
-
function getHLAdapter() {
|
|
368
|
-
if (!_hlAdapter)
|
|
369
|
-
throw new Error("This command requires --exchange hyperliquid");
|
|
370
|
-
return _hlAdapter;
|
|
371
|
-
}
|
|
372
|
-
// Register command groups with async adapter getter
|
|
373
|
-
registerMarketCommands(program, getAdapter, isJson, getAdapterForExchange);
|
|
374
|
-
registerAccountCommands(program, getAdapter, isJson, getAdapterForExchange);
|
|
375
|
-
registerTradeCommands(program, getAdapter, isJson, isDryRun, getAdapterForExchange);
|
|
376
|
-
// manage tree wired inside registerWalletCommands below.
|
|
377
|
-
// stream commands removed
|
|
378
|
-
registerArbCommands(program, isJson, getAdapterForExchange);
|
|
379
|
-
registerWalletCommands(program, isJson, getAdapter, getPacificaAdapter);
|
|
380
|
-
registerFundsCommands(program, getAdapter, isJson, () => program.opts().network, getAdapterForExchange);
|
|
381
|
-
// alert commands removed
|
|
382
|
-
// Helper to get adapter for a specific exchange (used by arb-auto)
|
|
383
|
-
async function getAdapterForExchange(rawExchange) {
|
|
384
|
-
const exchange = resolveExchangeAlias(rawExchange);
|
|
385
|
-
const opts = program.opts();
|
|
386
|
-
const network = opts.network;
|
|
387
|
-
const isTestnet = network === "testnet";
|
|
388
|
-
const owsName = opts.ows;
|
|
389
|
-
const owsKeyToken = opts.owsKey || process.env.OWS_API_KEY;
|
|
390
|
-
const walletName = owsName ? `ows:${owsName}` : opts.wallet;
|
|
391
|
-
// Commander's `--no-agent` flag sets opts.agent === false (NOT opts.noAgent).
|
|
392
|
-
const noAgent = opts.agent === false;
|
|
393
|
-
const pk = await tryLoadPrivateKey(exchange, opts.privateKey, walletName);
|
|
394
|
-
// Route through OWS init when pk is an OWS reference (e.g. "ows:main").
|
|
395
|
-
// Mirrors getAdapter() at the top of this file. Without this, adapters
|
|
396
|
-
// would try to parse "ows:main" as a raw private key and ethers would
|
|
397
|
-
// throw "invalid BytesLike value" / parseSolanaKeypair would fail.
|
|
398
|
-
if (pk && isOwsKey(pk)) {
|
|
399
|
-
return _initWithOws(exchange, getOwsWalletName(pk), isTestnet, opts, owsKeyToken);
|
|
400
|
-
}
|
|
401
|
-
switch (exchange) {
|
|
402
|
-
case "pacifica": {
|
|
403
|
-
if (_pacificaAdapter)
|
|
404
|
-
return _pacificaAdapter;
|
|
405
|
-
const keypair = pk ? parseSolanaKeypair(pk) : Keypair.generate();
|
|
406
|
-
const pacNetwork = (isTestnet ? "testnet" : "mainnet");
|
|
407
|
-
const s1 = loadSettings();
|
|
408
|
-
const builderCode = process.env.PACIFICA_BUILDER_CODE || s1.referralCodes.pacifica || "PERPCLI";
|
|
409
|
-
_pacificaAdapter = new PacificaAdapter(keypair, pacNetwork, builderCode, !!pk);
|
|
410
|
-
// Tier 1: agent if registered (Phase 2c)
|
|
411
|
-
const { getAgent: getPacAgentEx } = await import("./agent-wallet/store.js");
|
|
412
|
-
const pacAgentMetaEx = getPacAgentEx("pacifica");
|
|
413
|
-
if (pacAgentMetaEx) {
|
|
414
|
-
const { OwsSolanaSigner: PacOwsSolanaSignerEx } = await import("./signer/ows-solana.js");
|
|
415
|
-
// Pacifica agent wallet was created with empty passphrase (see runPacApproveFlow);
|
|
416
|
-
// open with "" to match. Agent at-rest encryption handled by OWS storage layer.
|
|
417
|
-
const agentSignerEx = PacOwsSolanaSignerEx.create(pacAgentMetaEx.agentWalletName, "");
|
|
418
|
-
_pacificaAdapter.setAgentSigner(pacAgentMetaEx, agentSignerEx);
|
|
419
|
-
}
|
|
420
|
-
if (noAgent)
|
|
421
|
-
_pacificaAdapter.setNoAgent(true);
|
|
422
|
-
if (!_adapter)
|
|
423
|
-
_adapter = _pacificaAdapter;
|
|
424
|
-
return _pacificaAdapter;
|
|
425
|
-
}
|
|
426
|
-
case "hyperliquid": {
|
|
427
|
-
if (_hlAdapter)
|
|
428
|
-
return _hlAdapter;
|
|
429
|
-
_hlAdapter = new HyperliquidAdapter(pk ?? undefined, isTestnet);
|
|
430
|
-
if (opts.dex)
|
|
431
|
-
_hlAdapter.setDex(opts.dex);
|
|
432
|
-
await _hlAdapter.init();
|
|
433
|
-
if (pk) {
|
|
434
|
-
const s2 = loadSettings();
|
|
435
|
-
if (!s2.referralApplied.hyperliquid) {
|
|
436
|
-
const hlRef = process.env.HL_REFERRAL_CODE || s2.referralCodes.hyperliquid;
|
|
437
|
-
if (hlRef) {
|
|
438
|
-
_hlAdapter.autoSetReferrer(hlRef).then(() => {
|
|
439
|
-
const s = loadSettings();
|
|
440
|
-
s.referralApplied.hyperliquid = true;
|
|
441
|
-
saveSettings(s);
|
|
442
|
-
}).catch((err) => {
|
|
443
|
-
// Per SSOT Rule #2: do NOT mark applied=true on failure.
|
|
444
|
-
// Leave referralApplied[ex]=false so next adapter init retries.
|
|
445
|
-
process.stderr.write(`[hyperliquid] referral apply failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
// Tier 1: agent if registered
|
|
451
|
-
{
|
|
452
|
-
const { getAgent: getHlAgent2 } = await import("./agent-wallet/store.js");
|
|
453
|
-
const hlAgentMeta2 = getHlAgent2("hyperliquid");
|
|
454
|
-
if (hlAgentMeta2) {
|
|
455
|
-
const { OwsEvmSigner: HlOwsEvmSigner2 } = await import("./signer/ows-evm.js");
|
|
456
|
-
// HL agent wallet was created with empty passphrase (see runHlApproveFlow);
|
|
457
|
-
// open with "" to match. Agent at-rest encryption handled by OWS storage layer.
|
|
458
|
-
const agentSigner2 = HlOwsEvmSigner2.create(hlAgentMeta2.agentWalletName, "");
|
|
459
|
-
_hlAdapter.setAgentSigner(hlAgentMeta2, agentSigner2);
|
|
460
|
-
}
|
|
461
|
-
if (noAgent)
|
|
462
|
-
_hlAdapter.setNoAgent(true);
|
|
463
|
-
}
|
|
464
|
-
if (!_adapter)
|
|
465
|
-
_adapter = _hlAdapter;
|
|
466
|
-
return _hlAdapter;
|
|
40
|
+
console.error(chalk.red(`Error: ${msg}`));
|
|
41
|
+
if (s?.remediation)
|
|
42
|
+
console.error(chalk.yellow(` ${s.remediation}`));
|
|
467
43
|
}
|
|
468
|
-
|
|
469
|
-
if (_lighterAdapter)
|
|
470
|
-
return _lighterAdapter;
|
|
471
|
-
const { LighterAdapter } = await import("./exchanges/lighter.js");
|
|
472
|
-
const { getAgent: getLtAgentEx } = await import("./agent-wallet/store.js");
|
|
473
|
-
const ltAgentMetaEx = getLtAgentEx("lighter");
|
|
474
|
-
_lighterAdapter = new LighterAdapter(pk ?? "", isTestnet);
|
|
475
|
-
if (ltAgentMetaEx && !noAgent) {
|
|
476
|
-
// SSOT Rule #3: load agent L2 key from encrypted keystore — no env fallback.
|
|
477
|
-
const { loadLighterKey } = await import("./agent-wallet/lighter-keystore.js");
|
|
478
|
-
const ltL2KeyEx = loadLighterKey(ltAgentMetaEx.accountIndex, ltAgentMetaEx.apiKeyIndex, "");
|
|
479
|
-
_lighterAdapter.setAgentSigner(ltAgentMetaEx, ltL2KeyEx);
|
|
480
|
-
}
|
|
481
|
-
if (noAgent)
|
|
482
|
-
_lighterAdapter.setNoAgent(true);
|
|
483
|
-
await _lighterAdapter.init();
|
|
484
|
-
// LT referral apply is L2-signed; trigger on any active tier (agent
|
|
485
|
-
// included), gated by isReadOnly. See first LT case for rationale.
|
|
486
|
-
const s3 = loadSettings();
|
|
487
|
-
if (!s3.referralApplied.lighter && !_lighterAdapter.isReadOnly) {
|
|
488
|
-
const ltRef = process.env.LIGHTER_REFERRAL_CODE || s3.referralCodes.lighter;
|
|
489
|
-
if (ltRef) {
|
|
490
|
-
_lighterAdapter.useReferralCode(ltRef).then(() => {
|
|
491
|
-
const s = loadSettings();
|
|
492
|
-
s.referralApplied.lighter = true;
|
|
493
|
-
saveSettings(s);
|
|
494
|
-
}).catch((err) => {
|
|
495
|
-
// Per SSOT Rule #2: do NOT mark applied=true on failure.
|
|
496
|
-
process.stderr.write(`[lighter] referral apply failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
if (!_adapter)
|
|
501
|
-
_adapter = _lighterAdapter;
|
|
502
|
-
return _lighterAdapter;
|
|
503
|
-
}
|
|
504
|
-
case "aster": {
|
|
505
|
-
const { AsterAdapter } = await import("./exchanges/aster.js");
|
|
506
|
-
const astEx = new AsterAdapter(pk ?? undefined, isTestnet);
|
|
507
|
-
await astEx.init();
|
|
508
|
-
// Tier 2: OWS master if --ows or settings.owsActiveWallet
|
|
509
|
-
const asterExOwsName = opts.ows || _settings.owsActiveWallet;
|
|
510
|
-
if (asterExOwsName) {
|
|
511
|
-
const { OwsEvmSigner } = await import("./signer/ows-evm.js");
|
|
512
|
-
const { resolvePassphrase } = await import("./agent-wallet/passphrase.js");
|
|
513
|
-
const pp = await resolvePassphrase({ flag: opts.passphrase });
|
|
514
|
-
if (pp !== null)
|
|
515
|
-
astEx.setMasterSigner(OwsEvmSigner.create(asterExOwsName, pp));
|
|
516
|
-
}
|
|
517
|
-
// Tier 1: agent if registered
|
|
518
|
-
const owsKeyEx = opts.owsKey || process.env.OWS_API_KEY;
|
|
519
|
-
const { getAgent: getAsterAgentEx } = await import("./agent-wallet/store.js");
|
|
520
|
-
const agentMetaEx = getAsterAgentEx("aster");
|
|
521
|
-
if (agentMetaEx) {
|
|
522
|
-
const { agentSigningStrategyFor } = await import("./agent-wallet/signing-strategy.js");
|
|
523
|
-
astEx.setAgent(agentMetaEx, agentSigningStrategyFor(agentMetaEx, owsKeyEx ?? ""));
|
|
524
|
-
}
|
|
525
|
-
if (noAgent)
|
|
526
|
-
astEx.setNoAgent(true);
|
|
527
|
-
if (!_adapter)
|
|
528
|
-
_adapter = astEx;
|
|
529
|
-
return astEx;
|
|
530
|
-
}
|
|
531
|
-
default:
|
|
532
|
-
throw new Error(`Unknown exchange: ${exchange}`);
|
|
44
|
+
process.exit(1);
|
|
533
45
|
}
|
|
534
46
|
}
|
|
535
|
-
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
if (_dexAdapters.has(dex))
|
|
539
|
-
return _dexAdapters.get(dex);
|
|
540
|
-
const opts = program.opts();
|
|
541
|
-
const walletName = opts.wallet;
|
|
542
|
-
const pk = await tryLoadPrivateKey("hyperliquid", opts.privateKey, walletName);
|
|
543
|
-
const adapter = new HyperliquidAdapter(pk ?? undefined, opts.network === "testnet");
|
|
544
|
-
if (dex !== "hl")
|
|
545
|
-
adapter.setDex(dex);
|
|
546
|
-
await adapter.init();
|
|
547
|
-
_dexAdapters.set(dex, adapter);
|
|
548
|
-
return adapter;
|
|
549
|
-
}
|
|
550
|
-
registerArbAutoCommands(program, getAdapterForExchange, isJson, getHLAdapterForDex);
|
|
551
|
-
registerArbManageCommands(program, getAdapterForExchange, isJson);
|
|
552
|
-
// `agent` is no longer top-level — registered as `wallet agent` inside
|
|
553
|
-
// registerWalletCommands above.
|
|
554
|
-
// withdraw merged into funds.
|
|
555
|
-
// `rebalance` is no longer top-level — registered as `funds rebalance` inside
|
|
556
|
-
// registerFundsCommands above.
|
|
557
|
-
// Background processes & strategies
|
|
558
|
-
import { registerBackgroundCommands } from "./commands/jobs.js";
|
|
559
|
-
registerBackgroundCommands(program, isJson);
|
|
560
|
-
registerStrategyCommands(program, getAdapter, getAdapterForExchange, isJson);
|
|
561
|
-
// Agent-friendly commands
|
|
562
|
-
registerRiskCommands(program, getAdapterForExchange, isJson);
|
|
563
|
-
registerHistoryCommands(program, isJson, getAdapterForExchange);
|
|
564
|
-
registerSettingsCommands(program, isJson, getAdapterForExchange);
|
|
565
|
-
// dex commands removed — use 'market hip3' + --dex flag
|
|
566
|
-
// `plan` is no longer top-level — registered as `strategy plan` inside
|
|
567
|
-
// registerStrategyCommands above.
|
|
568
|
-
// funding merged into arb — registerFundingCommands removed
|
|
569
|
-
registerBacktestCommands(program, isJson);
|
|
570
|
-
registerPortfolioCommand(program, getAdapterForExchange, isJson, _pkg.version, getHLAdapterForDex);
|
|
571
|
-
registerInitCommand(program);
|
|
572
|
-
registerAlertCommands(program, isJson);
|
|
573
|
-
// Switch shared API URLs if --network testnet is used
|
|
574
|
-
program.hook("preAction", () => {
|
|
575
|
-
const network = program.opts().network;
|
|
576
|
-
if (network === "testnet")
|
|
577
|
-
setSharedApiNetwork("testnet");
|
|
578
|
-
});
|
|
47
|
+
const _settings = loadStartupSettings();
|
|
48
|
+
const _defaultExchange = _settings.defaultExchange || "pacifica";
|
|
49
|
+
const { program, getAdapterForExchange, isJson } = buildProgram({ defaultExchange: _defaultExchange, version: _pkg.version });
|
|
579
50
|
// Smart landing page: `perp` with no subcommand
|
|
580
51
|
const rawArgs = process.argv.slice(2);
|
|
581
|
-
const
|
|
52
|
+
const _venueWords = listVenues().flatMap(v => [v.id, ...v.aliases]);
|
|
53
|
+
const hasSubcommand = rawArgs.some((a) => !a.startsWith("-") && ![..._venueWords, "mainnet", "testnet"].includes(a));
|
|
54
|
+
// `history list -n 5` (old record-count flag) → INVALID_PARAMS with the --limit migration, before
|
|
55
|
+
// Commander turns it into a bare "invalid network" usage error.
|
|
56
|
+
const _numericNetwork = numericNetworkFlagError(rawArgs);
|
|
57
|
+
if (_numericNetwork !== null) {
|
|
58
|
+
printCliError(_numericNetwork, rawArgs.includes("--json"));
|
|
59
|
+
_origExit(1);
|
|
60
|
+
}
|
|
582
61
|
if (rawArgs.length === 0 || (!hasSubcommand && !rawArgs.includes("-h") && !rawArgs.includes("--help") && !rawArgs.includes("-V") && !rawArgs.includes("--version"))) {
|
|
583
|
-
// No subcommand — show smart landing instead of help dump
|
|
584
|
-
(
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
console.log(
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
62
|
+
// No subcommand — show smart landing instead of help dump.
|
|
63
|
+
// Parse root options first so an invalid -n (e.g. "Testnet") is a usage error here too.
|
|
64
|
+
program.parseOptions(rawArgs);
|
|
65
|
+
if (isJson()) {
|
|
66
|
+
// JSON mode: one landing / usage document on stdout — no banner, no venue reads.
|
|
67
|
+
printJson(jsonOk(landingData(program, _pkg.version)));
|
|
68
|
+
setTimeout(() => _origExit(0), 100);
|
|
69
|
+
}
|
|
70
|
+
else
|
|
71
|
+
(async () => {
|
|
72
|
+
try {
|
|
73
|
+
const { getWalletSetupStatus } = await import("./commands/wallet.js");
|
|
74
|
+
const status = getWalletSetupStatus();
|
|
75
|
+
const settings = loadSettings();
|
|
76
|
+
const hasEnvKey = !!(process.env.PRIVATE_KEY || process.env.PACIFICA_PRIVATE_KEY ||
|
|
77
|
+
process.env.HL_PRIVATE_KEY || process.env.HYPERLIQUID_PRIVATE_KEY ||
|
|
78
|
+
process.env.LIGHTER_PRIVATE_KEY || process.env.ASTER_PRIVATE_KEY);
|
|
79
|
+
const hasOwsWallet = !!settings.owsActiveWallet;
|
|
80
|
+
// ── ASCII banner ──
|
|
81
|
+
const banner = [
|
|
82
|
+
"",
|
|
83
|
+
chalk.cyan(" ██████╗ ███████╗██████╗ ██████╗ ██████╗██╗ ██╗"),
|
|
84
|
+
chalk.cyan(" ██╔══██╗██╔════╝██╔══██╗██╔══██╗ ██╔════╝██║ ██║"),
|
|
85
|
+
chalk.cyan(" ██████╔╝█████╗ ██████╔╝██████╔╝ ██║ ██║ ██║"),
|
|
86
|
+
chalk.cyan(" ██╔═══╝ ██╔══╝ ██╔══██╗██╔═══╝ ██║ ██║ ██║"),
|
|
87
|
+
chalk.cyan(" ██║ ███████╗██║ ██║██║ ╚██████╗███████╗██║"),
|
|
88
|
+
chalk.cyan(" ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═════╝╚══════╝╚═╝"),
|
|
89
|
+
"",
|
|
90
|
+
chalk.gray(` ${listVenues().length} exchanges · 19 strategies · v${_pkg.version}`),
|
|
91
|
+
];
|
|
92
|
+
console.log(banner.join("\n"));
|
|
93
|
+
if (!status.hasWallets && !hasEnvKey && !hasOwsWallet && !settings.defaultExchange) {
|
|
94
|
+
// Fresh install — onboarding
|
|
95
|
+
console.log(chalk.yellow.bold("\n ⚡ Get started:\n"));
|
|
96
|
+
console.log(` ${chalk.cyan("perp wallet generate")} create OWS encrypted wallet`);
|
|
97
|
+
console.log(` ${chalk.cyan("perp setup")} setup wizard`);
|
|
98
|
+
console.log(chalk.gray(`\n Explore without a wallet:`));
|
|
99
|
+
console.log(` ${chalk.green("perp market mid BTC")} quick price check`);
|
|
100
|
+
console.log(` ${chalk.green("perp arb scan")} funding rate arbitrage`);
|
|
101
|
+
console.log(` ${chalk.green("perp market list")} available markets`);
|
|
102
|
+
console.log(` ${chalk.green("perp --help")} all commands\n`);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// Configured — show exchange status + balance
|
|
106
|
+
// Ping + balance in parallel (with 5s timeout to keep landing fast)
|
|
107
|
+
const withTimeout = (p, ms) => Promise.race([p, new Promise((_, rej) => setTimeout(() => rej(new Error("timeout")), ms))]);
|
|
108
|
+
// Aster venue ships only signed account endpoints — no public
|
|
109
|
+
// address-based balance query exists (HL/PAC/LT all do). So when
|
|
110
|
+
// a user has an env-PK but no Aster agent, every getBalance()
|
|
111
|
+
// call throws NOT_SUPPORTED at venue. Detect that case here so
|
|
112
|
+
// landing can surface a clear "agent required" hint instead of
|
|
113
|
+
// a generic dash.
|
|
114
|
+
const asterAgentMissing = getAsterAgentMissing();
|
|
115
|
+
const { getVenue } = await import("./exchanges/venue.js");
|
|
116
|
+
const { hasCredentials, NO_CLI_CREDENTIAL_OPTS } = await import("./config.js");
|
|
117
|
+
// Market-data-only venues are listed as such; account venues are shown only when configured.
|
|
118
|
+
const landingVenues = [];
|
|
119
|
+
for (const ex of LANDING_EXCHANGES) {
|
|
120
|
+
const def = getVenue(ex);
|
|
121
|
+
if (def.create === null || await hasCredentials(def.id, NO_CLI_CREDENTIAL_OPTS))
|
|
122
|
+
landingVenues.push(ex);
|
|
123
|
+
}
|
|
124
|
+
const statusResults = await Promise.allSettled(landingVenues.map(async (ex) => {
|
|
125
|
+
if (getVenue(ex).create === null) {
|
|
126
|
+
return { exchange: ex, marketDataOnly: true, ok: false, equity: 0, positions: 0 };
|
|
127
|
+
}
|
|
635
128
|
try {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
129
|
+
const adapter = await withTimeout(getAdapterForExchange(ex), 5000);
|
|
130
|
+
const [balance, positions] = await withTimeout(Promise.all([adapter.getBalance(), adapter.getPositions()]), 5000);
|
|
131
|
+
const posCount = positions.filter(p => Number(p.size) > 0).length;
|
|
132
|
+
// Spot value on top of perp equity for venues with def.spot (§12). Shared balances
|
|
133
|
+
// (already in perp equity) are skipped; unpriced tokens are excluded + warned.
|
|
134
|
+
let spotValue = 0;
|
|
135
|
+
const spotDef = getVenue(ex);
|
|
136
|
+
const warn = (msg) => process.stderr.write(`[warn] ${msg}\n`);
|
|
137
|
+
try {
|
|
138
|
+
const { getSpotAdapter, fetchValuedSpotBalances, SPOT_VALUATION_TIMEOUT_MS } = await import("./exchanges/spot-factory.js");
|
|
139
|
+
const spot = await withTimeout(getSpotAdapter(spotDef.id, adapter), SPOT_VALUATION_TIMEOUT_MS);
|
|
140
|
+
if (spot !== null) {
|
|
141
|
+
for (const b of await fetchValuedSpotBalances(spotDef, spot, warn)) {
|
|
142
|
+
if (!b.shared)
|
|
143
|
+
spotValue += b.valueUsd;
|
|
144
|
+
}
|
|
647
145
|
}
|
|
648
146
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
const { LighterSpotAdapter } = await import("./exchanges/lighter-spot.js");
|
|
652
|
-
const ltSpot = new LighterSpotAdapter(adapter);
|
|
653
|
-
await ltSpot.init();
|
|
654
|
-
const [raw, markets] = await Promise.all([ltSpot.getSpotBalances(), ltSpot.getSpotMarkets()]);
|
|
655
|
-
const priceMap = new Map(markets.map(m => [m.baseToken.toUpperCase(), Number(m.markPrice)]));
|
|
656
|
-
for (const b of raw) {
|
|
657
|
-
if (b.token === "USDC" || b.token === "USDC_SPOT" || Number(b.total) <= 0)
|
|
658
|
-
continue;
|
|
659
|
-
spotValue += (priceMap.get(b.token.toUpperCase()) ?? 0) * Number(b.total);
|
|
660
|
-
}
|
|
147
|
+
catch (e) {
|
|
148
|
+
warn(`${spotDef.displayName} spot balances unavailable: ${e instanceof Error ? e.message : String(e)}`);
|
|
661
149
|
}
|
|
150
|
+
return { exchange: ex, ok: true, equity: Number(balance.equity) + spotValue, positions: posCount };
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
const { PerpError } = await import("./errors.js");
|
|
154
|
+
const errorCode = err instanceof PerpError ? err.structured.code : undefined;
|
|
155
|
+
return { exchange: ex, ok: false, equity: 0, positions: 0, errorCode };
|
|
156
|
+
}
|
|
157
|
+
}));
|
|
158
|
+
console.log(chalk.white.bold("\n Exchanges:"));
|
|
159
|
+
let totalEquity = 0;
|
|
160
|
+
let totalPositions = 0;
|
|
161
|
+
for (const r of statusResults) {
|
|
162
|
+
if (r.status !== "fulfilled")
|
|
163
|
+
continue;
|
|
164
|
+
const s = r.value;
|
|
165
|
+
console.log(renderLandingExchangeLine(s, asterAgentMissing));
|
|
166
|
+
if (s.ok) {
|
|
167
|
+
totalEquity += s.equity;
|
|
168
|
+
totalPositions += s.positions;
|
|
662
169
|
}
|
|
663
|
-
catch { /* spot not available */ }
|
|
664
|
-
return { exchange: ex, ok: true, equity: Number(balance.equity) + spotValue, positions: posCount };
|
|
665
|
-
}
|
|
666
|
-
catch {
|
|
667
|
-
return { exchange: ex, ok: false, equity: 0, positions: 0 };
|
|
668
170
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
let totalPositions = 0;
|
|
673
|
-
for (const r of statusResults) {
|
|
674
|
-
if (r.status !== "fulfilled")
|
|
675
|
-
continue;
|
|
676
|
-
const s = r.value;
|
|
677
|
-
console.log(renderLandingExchangeLine(s, asterAgentMissing));
|
|
678
|
-
if (s.ok) {
|
|
679
|
-
totalEquity += s.equity;
|
|
680
|
-
totalPositions += s.positions;
|
|
171
|
+
if (totalEquity > 0) {
|
|
172
|
+
console.log(chalk.gray(" ─".repeat(20)));
|
|
173
|
+
console.log(` ${chalk.white.bold("Total".padEnd(16))} ${chalk.white.bold(`$${totalEquity.toLocaleString("en", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`)}${totalPositions > 0 ? chalk.yellow(` ${totalPositions} pos`) : ""}`);
|
|
681
174
|
}
|
|
175
|
+
console.log(chalk.white.bold("\n Quick:"));
|
|
176
|
+
console.log(` ${chalk.green("perp portfolio")} balances + positions + risk`);
|
|
177
|
+
console.log(` ${chalk.green("perp arb scan")} funding arbitrage opportunities`);
|
|
178
|
+
console.log(` ${chalk.green("perp arb status")} open arb positions + PnL`);
|
|
179
|
+
console.log(` ${chalk.green("perp portfolio --arb")} full dashboard (balances + arb top 5)`);
|
|
180
|
+
console.log(` ${chalk.green("perp portfolio --serve")} live web monitoring`);
|
|
181
|
+
console.log(` ${chalk.green("perp --help")} all commands\n`);
|
|
682
182
|
}
|
|
683
|
-
if (totalEquity > 0) {
|
|
684
|
-
console.log(chalk.gray(" ─".repeat(20)));
|
|
685
|
-
console.log(` ${chalk.white.bold("Total".padEnd(16))} ${chalk.white.bold(`$${totalEquity.toLocaleString("en", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`)}${totalPositions > 0 ? chalk.yellow(` ${totalPositions} pos`) : ""}`);
|
|
686
|
-
}
|
|
687
|
-
console.log(chalk.white.bold("\n Quick:"));
|
|
688
|
-
console.log(` ${chalk.green("perp portfolio")} balances + positions + risk`);
|
|
689
|
-
console.log(` ${chalk.green("perp arb scan")} funding arbitrage opportunities`);
|
|
690
|
-
console.log(` ${chalk.green("perp arb status")} open arb positions + PnL`);
|
|
691
|
-
console.log(` ${chalk.green("perp portfolio --arb")} full dashboard (balances + arb top 5)`);
|
|
692
|
-
console.log(` ${chalk.green("perp portfolio --serve")} live web monitoring`);
|
|
693
|
-
console.log(` ${chalk.green("perp --help")} all commands\n`);
|
|
694
183
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
})();
|
|
184
|
+
catch {
|
|
185
|
+
program.help();
|
|
186
|
+
}
|
|
187
|
+
setTimeout(() => _origExit(0), 500);
|
|
188
|
+
})();
|
|
701
189
|
}
|
|
702
190
|
else {
|
|
703
191
|
program.parseAsync().then(() => {
|
|
704
192
|
// Allow a short delay for any pending output, then exit cleanly.
|
|
705
193
|
// Without this, HL SDK's WebSocket keeps the process alive indefinitely.
|
|
706
|
-
|
|
194
|
+
// Keep a non-zero exitCode set by a command that printed its own failure (withJsonErrors).
|
|
195
|
+
setTimeout(() => _origExit(Number(process.exitCode ?? 0)), 500);
|
|
707
196
|
}).catch(async (err) => {
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
// catch (Rule #2: stay loud, keep semantic envelope).
|
|
714
|
-
if (err instanceof PerpError) {
|
|
715
|
-
const s = err.structured;
|
|
716
|
-
console.log(JSON.stringify(jsonError(s.code, s.message, {
|
|
717
|
-
status: s.status,
|
|
718
|
-
retryable: s.retryable,
|
|
719
|
-
retryAfterMs: s.retryAfterMs,
|
|
720
|
-
remediation: s.remediation,
|
|
721
|
-
})));
|
|
722
|
-
}
|
|
723
|
-
else {
|
|
724
|
-
console.log(JSON.stringify(jsonError("FATAL", msg)));
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
else {
|
|
728
|
-
console.error(chalk.red(msg));
|
|
729
|
-
}
|
|
197
|
+
// Central printer (utils.printCliError): typed PerpError code / remediation / details
|
|
198
|
+
// are preserved (Rule #2: stay loud, keep semantic envelope); generic Errors go
|
|
199
|
+
// through classifyError; CAPABILITY_UNSUPPORTED prints the uniform §13.6 line.
|
|
200
|
+
const { printCliError } = await import("./utils.js");
|
|
201
|
+
printCliError(err, isJson());
|
|
730
202
|
_origExit(1);
|
|
731
203
|
});
|
|
732
204
|
}
|