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/commands/wallet.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
+
import { Option } from "commander";
|
|
2
|
+
import { PerpError } from "../errors.js";
|
|
1
3
|
import chalk from "chalk";
|
|
2
|
-
import
|
|
4
|
+
import bs58 from "bs58";
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync } from "fs";
|
|
6
|
+
import { writeKeystoreAtomic } from "../agent-wallet/venue-keystore.js";
|
|
3
7
|
import { resolve } from "path";
|
|
4
|
-
import { formatUsd, makeTable, printJson, jsonOk } from "../utils.js";
|
|
5
|
-
import { EXCHANGE_ENV_MAP, validateKey, loadEnvFile,
|
|
8
|
+
import { formatUsd, makeTable, printJson, jsonOk, withJsonErrors } from "../utils.js";
|
|
9
|
+
import { EXCHANGE_ENV_MAP, validateKey, loadEnvFile, ENV_FILE } from "./init.js";
|
|
10
|
+
import { scrubSecretEnv } from "../secret-env.js";
|
|
6
11
|
import { loadSettings, saveSettings } from "../settings.js";
|
|
7
|
-
import { registerWalletAgentCommands } from "./agent.js";
|
|
12
|
+
import { deprecatedPassphraseOption, registerWalletAgentCommands, warnDeprecatedPassphrase } from "./agent.js";
|
|
8
13
|
import { registerWalletManageCommands } from "./manage.js";
|
|
14
|
+
import { registerWalletBuilderCommands } from "./builder.js";
|
|
9
15
|
import { loadOws } from "../signer/ows-loader.js";
|
|
10
16
|
// ── OWS CLI subprocess helper (used by backup/restore/rotate) ──
|
|
11
17
|
//
|
|
@@ -41,7 +47,8 @@ async function runOwsCli(args, timeout = 30000) {
|
|
|
41
47
|
if (!owsBin) {
|
|
42
48
|
throw new Error("OWS CLI not found. Install: curl -fsSL https://docs.openwallet.sh/install.sh | bash");
|
|
43
49
|
}
|
|
44
|
-
|
|
50
|
+
// The OWS CLI child never receives key material (SSOT Rule #3): secret env vars are stripped.
|
|
51
|
+
const owsEnv = { ...scrubSecretEnv(process.env), PATH: `${pathResolve(process.env.HOME || "~", ".ows", "bin")}:${process.env.PATH}` };
|
|
45
52
|
const proc = spawnSync(owsBin, args, { encoding: "utf-8", timeout, env: owsEnv });
|
|
46
53
|
if (proc.error)
|
|
47
54
|
throw new Error(`OWS CLI failed: ${proc.error.message}`);
|
|
@@ -66,7 +73,8 @@ function loadStore() {
|
|
|
66
73
|
}
|
|
67
74
|
function saveStore(store) {
|
|
68
75
|
ensurePerpDir();
|
|
69
|
-
|
|
76
|
+
// Atomic, explicit 0600 (writeFileSync's mode is ignored when the file already exists).
|
|
77
|
+
writeKeystoreAtomic(WALLETS_FILE, JSON.stringify(store, null, 2));
|
|
70
78
|
}
|
|
71
79
|
/** Exported for smart landing page in index.ts */
|
|
72
80
|
export function getWalletSetupStatus() {
|
|
@@ -182,141 +190,114 @@ async function deriveEvmAddress(key) {
|
|
|
182
190
|
const pk = key.startsWith("0x") ? key : `0x${key}`;
|
|
183
191
|
return new ethers.Wallet(pk).address;
|
|
184
192
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
console.log(chalk.yellow.bold("\n [Legacy] Solana Wallet Generated\n"));
|
|
207
|
-
console.log(` Name: ${chalk.white.bold(walletName)}`);
|
|
208
|
-
console.log(` Address: ${chalk.green(address)}`);
|
|
209
|
-
console.log(chalk.gray(` Saved: ~/.perp/.env + wallets.json (unencrypted)`));
|
|
210
|
-
console.log(chalk.yellow(`\n Consider: ${chalk.cyan("perp wallet migrate")} to move to OWS vault\n`));
|
|
193
|
+
/** `--legacy` (raw keys written to ~/.perp/.env + wallets.json) was removed — SSOT Rule #3. */
|
|
194
|
+
function legacyWriteRemoved(cmd) {
|
|
195
|
+
return new PerpError("INVALID_PARAMS", `wallet ${cmd} --legacy was removed: it wrote raw private keys to ~/.perp/.env and wallets.json`, {
|
|
196
|
+
remediation: cmd === "generate"
|
|
197
|
+
? "Run: perp wallet generate [name] (OWS encrypted vault). Existing ~/.perp/.env keys keep working; move them with perp wallet set <exchange>"
|
|
198
|
+
: "Run: perp wallet import --name <name> [--chain evm|solana|dual] (key via hidden prompt or stdin) or perp wallet set <exchange>",
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Solana key normalization: OWS SDK expects 32-byte hex secret. Phantom
|
|
203
|
+
* exports base58 (88 chars, 64-byte keypair = secret + public). Auto-detect
|
|
204
|
+
* base58/JSON-array/hex and normalize to 32-byte hex secret.
|
|
205
|
+
*/
|
|
206
|
+
export function normalizeSolanaKey(input) {
|
|
207
|
+
const s = input.trim();
|
|
208
|
+
// Already hex (with optional 0x prefix)
|
|
209
|
+
const hexNoPrefix = s.startsWith("0x") ? s.slice(2) : s;
|
|
210
|
+
if (/^[0-9a-fA-F]{64}$/.test(hexNoPrefix))
|
|
211
|
+
return hexNoPrefix;
|
|
212
|
+
if (/^[0-9a-fA-F]{128}$/.test(hexNoPrefix)) {
|
|
213
|
+
return hexNoPrefix.slice(0, 64); // 64-byte keypair → first 32 bytes secret
|
|
211
214
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (store.wallets[walletName]) {
|
|
218
|
-
console.error(chalk.red(`\n Wallet "${walletName}" already exists.\n`));
|
|
219
|
-
process.exit(1);
|
|
215
|
+
// JSON byte array
|
|
216
|
+
if (s.startsWith("[")) {
|
|
217
|
+
let arr;
|
|
218
|
+
try {
|
|
219
|
+
arr = JSON.parse(s);
|
|
220
220
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
221
|
+
catch {
|
|
222
|
+
// Never surface the parser message: V8 echoes the input (the secret) in it.
|
|
223
|
+
throw new Error("Invalid Solana JSON byte array (not valid JSON)");
|
|
224
|
+
}
|
|
225
|
+
if (arr.length === 64)
|
|
226
|
+
return Buffer.from(arr.slice(0, 32)).toString("hex");
|
|
227
|
+
if (arr.length === 32)
|
|
228
|
+
return Buffer.from(arr).toString("hex");
|
|
229
|
+
throw new Error(`Invalid Solana JSON byte array length: ${arr.length} (expected 32 or 64)`);
|
|
230
|
+
}
|
|
231
|
+
// base58 (Phantom export — typically 87-88 chars for 64-byte keypair)
|
|
232
|
+
try {
|
|
233
|
+
const decoded = bs58.decode(s);
|
|
234
|
+
if (decoded.length === 64)
|
|
235
|
+
return Buffer.from(decoded.slice(0, 32)).toString("hex");
|
|
236
|
+
if (decoded.length === 32)
|
|
237
|
+
return Buffer.from(decoded).toString("hex");
|
|
238
|
+
throw new Error(`Invalid Solana base58 byte length: ${decoded.length} (expected 32 or 64)`);
|
|
236
239
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
+
catch (err) {
|
|
241
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
242
|
+
throw new Error(`Invalid Solana key — expected hex (64 chars), base58 (Phantom export), or JSON byte array. Inner: ${msg}`);
|
|
240
243
|
}
|
|
241
244
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
address = kp.publicKey.toBase58();
|
|
258
|
-
normalizedKey = bs58.encode(kp.secretKey);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
catch {
|
|
262
|
-
console.error(chalk.red("\n Invalid Solana private key.\n"));
|
|
263
|
-
process.exit(1);
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
const store = loadStore();
|
|
267
|
-
if (store.wallets[name]) {
|
|
268
|
-
console.error(chalk.red(`\n Wallet "${name}" already exists.\n`));
|
|
269
|
-
process.exit(1);
|
|
270
|
-
}
|
|
271
|
-
store.wallets[name] = { name, type: "solana", address, privateKey: normalizedKey, createdAt: new Date().toISOString() };
|
|
272
|
-
if (!store.active.pacifica)
|
|
273
|
-
store.active.pacifica = name;
|
|
274
|
-
saveStore(store);
|
|
275
|
-
setEnvVar("PACIFICA_PRIVATE_KEY", normalizedKey);
|
|
276
|
-
if (isJson())
|
|
277
|
-
return printJson(jsonOk({ name, type: "solana", address, legacy: true }));
|
|
278
|
-
console.log(chalk.yellow.bold("\n [Legacy] Solana Key Imported\n"));
|
|
279
|
-
console.log(` Name: ${chalk.white.bold(name)}`);
|
|
280
|
-
console.log(` Address: ${chalk.green(address)}\n`);
|
|
245
|
+
/**
|
|
246
|
+
* Import key material into the OWS vault as wallet `name` — the single import
|
|
247
|
+
* path shared by `wallet import` and `perp setup`. Never logs key material.
|
|
248
|
+
*/
|
|
249
|
+
/**
|
|
250
|
+
* Strip secret material from an error message before it is shown/logged: the exact secret
|
|
251
|
+
* strings plus any long hex / base58 / byte-array run (keys never reach output — Rule #3).
|
|
252
|
+
*/
|
|
253
|
+
export function redactSecrets(message, secrets) {
|
|
254
|
+
let out = message;
|
|
255
|
+
for (const sec of secrets) {
|
|
256
|
+
const t = sec.trim();
|
|
257
|
+
for (const v of [t, t.replace(/^0x/i, "")])
|
|
258
|
+
if (v.length >= 8)
|
|
259
|
+
out = out.split(v).join("[redacted]");
|
|
281
260
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
store.active.hyperliquid = name;
|
|
302
|
-
if (!store.active.lighter)
|
|
303
|
-
store.active.lighter = name;
|
|
304
|
-
saveStore(store);
|
|
305
|
-
setEnvVar("HL_PRIVATE_KEY", pk);
|
|
306
|
-
setEnvVar("LIGHTER_PRIVATE_KEY", pk);
|
|
307
|
-
if (isJson())
|
|
308
|
-
return printJson(jsonOk({ name, type: "evm", address, legacy: true }));
|
|
309
|
-
console.log(chalk.yellow.bold("\n [Legacy] EVM Key Imported\n"));
|
|
310
|
-
console.log(` Name: ${chalk.white.bold(name)}`);
|
|
311
|
-
console.log(` Address: ${chalk.green(address)}\n`);
|
|
261
|
+
return out
|
|
262
|
+
.replace(/(0x)?[0-9a-fA-F]{32,}/g, "[redacted]")
|
|
263
|
+
.replace(/[1-9A-HJ-NP-Za-km-z]{40,}/g, "[redacted]")
|
|
264
|
+
.replace(/\[\s*\d{1,3}(\s*,\s*\d{1,3}){7,}[^\]]*\]?/g, "[redacted]");
|
|
265
|
+
}
|
|
266
|
+
/** Read all of a piped (non-TTY) stdin. */
|
|
267
|
+
function readAllStdin() {
|
|
268
|
+
return new Promise((resolveRead, reject) => {
|
|
269
|
+
const chunks = [];
|
|
270
|
+
process.stdin.setEncoding("utf-8");
|
|
271
|
+
process.stdin.on("data", (c) => chunks.push(typeof c === "string" ? c : c.toString("utf-8")));
|
|
272
|
+
process.stdin.on("end", () => resolveRead(chunks.join("")));
|
|
273
|
+
process.stdin.on("error", reject);
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
export async function importOwsWallet(name, passphrase, input) {
|
|
277
|
+
const secrets = input.kind === "mnemonic" ? [input.mnemonic] : input.kind === "dual" ? [input.evm, input.solana] : [input.key];
|
|
278
|
+
try {
|
|
279
|
+
return importOwsWalletRaw(name, passphrase, input);
|
|
312
280
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
281
|
+
catch (err) {
|
|
282
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
283
|
+
throw new PerpError("INVALID_PARAMS", `Import failed: ${redactSecrets(msg, secrets)}`, {
|
|
284
|
+
remediation: "Check the key / mnemonic format and retry (the input is never echoed)",
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function importOwsWalletRaw(name, passphrase, input) {
|
|
289
|
+
const ows = loadOws();
|
|
290
|
+
if (input.kind === "mnemonic")
|
|
291
|
+
return ows.importWalletMnemonic(name, input.mnemonic, passphrase);
|
|
292
|
+
if (input.kind === "dual") {
|
|
293
|
+
// Dual-curve: both explicit keys (OWS 1.3+ API — both must be present)
|
|
294
|
+
return ows.importWalletPrivateKey(name, "", // ignored when both secp256k1Key and ed25519Key are provided
|
|
295
|
+
passphrase, undefined, undefined, input.evm, normalizeSolanaKey(input.solana));
|
|
316
296
|
}
|
|
297
|
+
const key = input.chain === "solana" ? normalizeSolanaKey(input.key) : input.key;
|
|
298
|
+
return ows.importWalletPrivateKey(name, key, passphrase, undefined, input.chain);
|
|
317
299
|
}
|
|
318
|
-
|
|
319
|
-
export function registerWalletCommands(program, isJson, getAdapter, getPacificaAdapter) {
|
|
300
|
+
export function registerWalletCommands(program, isJson, getAdapter, getPacificaAdapter, getAdapterForExchange) {
|
|
320
301
|
const wallet = program.command("wallet").description("Wallet management & on-chain balances");
|
|
321
302
|
// ── generate (OWS — creates multi-chain wallet in encrypted vault) ──
|
|
322
303
|
wallet
|
|
@@ -324,12 +305,12 @@ export function registerWalletCommands(program, isJson, getAdapter, getPacificaA
|
|
|
324
305
|
.description("Generate a new wallet (OWS encrypted vault, multi-chain)")
|
|
325
306
|
.option("--words <count>", "Mnemonic word count (12 or 24)", "12")
|
|
326
307
|
.option("--show-mnemonic", "Display the mnemonic phrase (CAUTION)")
|
|
327
|
-
.
|
|
328
|
-
.
|
|
308
|
+
.addOption(deprecatedPassphraseOption()).hook("preAction", warnDeprecatedPassphrase)
|
|
309
|
+
.addOption(new Option("--legacy <chain>", "Removed — refused (raw keys are never written to ~/.perp/.env)").hideHelp())
|
|
329
310
|
.action(async (name, opts, command) => {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
return
|
|
311
|
+
if (opts.legacy !== undefined) {
|
|
312
|
+
await withJsonErrors(isJson(), async () => { throw legacyWriteRemoved("generate"); });
|
|
313
|
+
return;
|
|
333
314
|
}
|
|
334
315
|
const walletName = name || "default";
|
|
335
316
|
try {
|
|
@@ -399,128 +380,73 @@ export function registerWalletCommands(program, isJson, getAdapter, getPacificaA
|
|
|
399
380
|
// Solana key normalization: OWS SDK expects 32-byte hex secret. Phantom
|
|
400
381
|
// exports base58 (88 chars, 64-byte keypair = secret + public). Auto-detect
|
|
401
382
|
// base58/JSON-array/hex and normalize to 32-byte hex secret.
|
|
383
|
+
// SSOT Rule #3: keys are never accepted on argv (shell history / process list) —
|
|
384
|
+
// hidden TTY prompt or stdin pipe only, matching `wallet set`.
|
|
402
385
|
wallet
|
|
403
386
|
.command("import [privateKey]")
|
|
404
|
-
.description("Import a private key into OWS encrypted vault (
|
|
387
|
+
.description("Import a private key or mnemonic into the OWS encrypted vault (key via hidden prompt or stdin — never argv)")
|
|
405
388
|
.option("--name <name>", "Wallet alias name", "imported")
|
|
406
|
-
.option("--chain <chain>", "Key type
|
|
407
|
-
.option("--
|
|
408
|
-
.
|
|
409
|
-
.
|
|
410
|
-
.
|
|
411
|
-
.
|
|
412
|
-
.action(async (
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
}
|
|
421
|
-
console.error(chalk.red(`\n Error: ${msg}\n`));
|
|
422
|
-
process.exit(1);
|
|
423
|
-
}
|
|
424
|
-
return _legacyImport(opts.legacy, privateKey, opts.name, isJson);
|
|
425
|
-
}
|
|
426
|
-
// Validation: input mode is exactly one of (positional | --evm/--solana flags | mnemonic)
|
|
427
|
-
const hasPositional = !!privateKey;
|
|
428
|
-
const hasFlagKeys = !!(opts.evm || opts.solana);
|
|
429
|
-
if (!hasPositional && !hasFlagKeys) {
|
|
430
|
-
const msg = "Provide a <privateKey> argument OR --evm/--solana flag(s)";
|
|
431
|
-
if (isJson()) {
|
|
432
|
-
const { jsonError } = await import("../utils.js");
|
|
433
|
-
return printJson(jsonError("OWS_ERROR", msg));
|
|
434
|
-
}
|
|
435
|
-
console.error(chalk.red(`\n Error: ${msg}\n`));
|
|
436
|
-
process.exit(1);
|
|
437
|
-
}
|
|
438
|
-
if (hasPositional && hasFlagKeys) {
|
|
439
|
-
const msg = "Cannot mix positional <privateKey> with --evm/--solana flags. Use one input style.";
|
|
440
|
-
if (isJson()) {
|
|
441
|
-
const { jsonError } = await import("../utils.js");
|
|
442
|
-
return printJson(jsonError("OWS_ERROR", msg));
|
|
389
|
+
.option("--chain <chain>", "Key type: evm (default), solana, or dual (EVM + Solana keys: two prompts, or two stdin lines EVM first)", "evm")
|
|
390
|
+
.option("--mnemonic", "Import a mnemonic phrase instead of a private key")
|
|
391
|
+
.addOption(deprecatedPassphraseOption()).hook("preAction", warnDeprecatedPassphrase)
|
|
392
|
+
.addOption(new Option("--evm <hex>", "Removed — refused (key on argv); use --chain evm|dual").hideHelp())
|
|
393
|
+
.addOption(new Option("--solana <key>", "Removed — refused (key on argv); use --chain solana|dual").hideHelp())
|
|
394
|
+
.addOption(new Option("--legacy <chain>", "Removed — refused (raw keys are never written to ~/.perp/.env)").hideHelp())
|
|
395
|
+
.action(async (argvKey, opts, command) => {
|
|
396
|
+
await withJsonErrors(isJson(), async () => {
|
|
397
|
+
if (opts.legacy !== undefined)
|
|
398
|
+
throw legacyWriteRemoved("import");
|
|
399
|
+
if (argvKey !== undefined || opts.evm !== undefined || opts.solana !== undefined) {
|
|
400
|
+
throw new PerpError("INVALID_PARAMS", "Refusing a private key / mnemonic on the command line: it is visible in shell history and the process list", {
|
|
401
|
+
remediation: "Enter it at the hidden prompt (perp wallet import --name <name> [--chain evm|solana|dual] [--mnemonic]) or pipe it on stdin (e.g. pbpaste | OWS_PASSPHRASE=... perp wallet import --name <name>). Clear the key from your shell history.",
|
|
402
|
+
});
|
|
443
403
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
if (opts.mnemonic && hasFlagKeys) {
|
|
448
|
-
const msg = "--mnemonic cannot be combined with --evm/--solana";
|
|
449
|
-
if (isJson()) {
|
|
450
|
-
const { jsonError } = await import("../utils.js");
|
|
451
|
-
return printJson(jsonError("OWS_ERROR", msg));
|
|
404
|
+
const chain = opts.chain;
|
|
405
|
+
if (chain !== "evm" && chain !== "solana" && chain !== "dual") {
|
|
406
|
+
throw new PerpError("INVALID_PARAMS", `Unknown --chain "${chain}"`, { remediation: "Use --chain evm, solana, or dual" });
|
|
452
407
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
const ows = loadOws();
|
|
458
|
-
// Auto-normalize Solana keys: accept hex / base58 (Phantom) / JSON byte array
|
|
459
|
-
const bs58 = (await import("bs58")).default;
|
|
460
|
-
const normalizeSolana = (input) => {
|
|
461
|
-
const s = input.trim();
|
|
462
|
-
// Already hex (with optional 0x prefix)
|
|
463
|
-
const hexNoPrefix = s.startsWith("0x") ? s.slice(2) : s;
|
|
464
|
-
if (/^[0-9a-fA-F]{64}$/.test(hexNoPrefix))
|
|
465
|
-
return hexNoPrefix;
|
|
466
|
-
if (/^[0-9a-fA-F]{128}$/.test(hexNoPrefix)) {
|
|
467
|
-
return hexNoPrefix.slice(0, 64); // 64-byte keypair → first 32 bytes secret
|
|
468
|
-
}
|
|
469
|
-
// JSON byte array
|
|
470
|
-
if (s.startsWith("[")) {
|
|
471
|
-
const arr = JSON.parse(s);
|
|
472
|
-
if (arr.length === 64)
|
|
473
|
-
return Buffer.from(arr.slice(0, 32)).toString("hex");
|
|
474
|
-
if (arr.length === 32)
|
|
475
|
-
return Buffer.from(arr).toString("hex");
|
|
476
|
-
throw new Error(`Invalid Solana JSON byte array length: ${arr.length} (expected 32 or 64)`);
|
|
477
|
-
}
|
|
478
|
-
// base58 (Phantom export — typically 87-88 chars for 64-byte keypair)
|
|
479
|
-
try {
|
|
480
|
-
const decoded = bs58.decode(s);
|
|
481
|
-
if (decoded.length === 64)
|
|
482
|
-
return Buffer.from(decoded.slice(0, 32)).toString("hex");
|
|
483
|
-
if (decoded.length === 32)
|
|
484
|
-
return Buffer.from(decoded).toString("hex");
|
|
485
|
-
throw new Error(`Invalid Solana base58 byte length: ${decoded.length} (expected 32 or 64)`);
|
|
486
|
-
}
|
|
487
|
-
catch (err) {
|
|
488
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
489
|
-
throw new Error(`Invalid Solana key — expected hex (64 chars), base58 (Phantom export), or JSON byte array. Inner: ${msg}`);
|
|
490
|
-
}
|
|
491
|
-
};
|
|
492
|
-
// Resolve vault passphrase via the standard 3-path resolver (--passphrase
|
|
493
|
-
// > OWS_PASSPHRASE env > stdin pipe). When stdin is a TTY and nothing was
|
|
494
|
-
// provided, the resolver returns null; fall back to "" to preserve the
|
|
495
|
-
// historical "no passphrase" default for interactive use.
|
|
496
|
-
//
|
|
497
|
-
// optsWithGlobals() merges parent + subcommand options — see the
|
|
498
|
-
// wallet generate action for context. Without it, the parent program's
|
|
499
|
-
// --passphrase shadows the subcommand's same-named flag.
|
|
500
|
-
const merged = command.optsWithGlobals();
|
|
501
|
-
const { resolvePassphrase } = await import("../agent-wallet/passphrase.js");
|
|
502
|
-
const pp = (await resolvePassphrase({ flag: merged.passphrase ?? opts.passphrase })) ?? "";
|
|
503
|
-
let w;
|
|
504
|
-
if (opts.mnemonic) {
|
|
505
|
-
w = ows.importWalletMnemonic(opts.name, privateKey, pp);
|
|
408
|
+
if (opts.mnemonic && chain !== "evm") {
|
|
409
|
+
throw new PerpError("INVALID_PARAMS", "--mnemonic derives every chain itself and cannot be combined with --chain solana|dual", {
|
|
410
|
+
remediation: "Drop --chain: perp wallet import --mnemonic --name <name>",
|
|
411
|
+
});
|
|
506
412
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
413
|
+
const { promptHidden } = await import("./agent.js");
|
|
414
|
+
const piped = process.stdin.isTTY !== true;
|
|
415
|
+
const labels = opts.mnemonic ? ["mnemonic phrase"] : chain === "dual" ? ["EVM private key", "Solana private key"] : [`${chain === "evm" ? "EVM" : "Solana"} private key`];
|
|
416
|
+
let secrets;
|
|
417
|
+
if (piped) {
|
|
418
|
+
const lines = (await readAllStdin()).split(/\r?\n/).map(l => l.trim()).filter(l => l.length > 0);
|
|
419
|
+
secrets = opts.mnemonic ? [lines.join(" ")] : lines;
|
|
511
420
|
}
|
|
512
|
-
else
|
|
513
|
-
|
|
514
|
-
|
|
421
|
+
else {
|
|
422
|
+
secrets = [];
|
|
423
|
+
for (const label of labels)
|
|
424
|
+
secrets.push((await promptHidden(`${label} (hidden): `)).trim());
|
|
515
425
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
426
|
+
if (secrets.length !== labels.length || secrets.some(x => x.length === 0)) {
|
|
427
|
+
throw new PerpError("INVALID_PARAMS", `Expected ${labels.join(" + ")} on stdin / at the prompt (${labels.length === 2 ? "two lines, EVM first" : "one line"}); received ${secrets.filter(x => x.length > 0).length}`, {
|
|
428
|
+
remediation: "Enter it at the hidden prompt, or pipe it: pbpaste | OWS_PASSPHRASE=... perp wallet import --name <name>",
|
|
429
|
+
});
|
|
519
430
|
}
|
|
431
|
+
// Vault passphrase: --passphrase > OWS_PASSPHRASE > hidden prompt. Piped stdin
|
|
432
|
+
// carries the key, so a non-interactive run must name the passphrase explicitly.
|
|
433
|
+
// optsWithGlobals(): the root program's --passphrase shadows the subcommand's.
|
|
434
|
+
const flagPp = command.optsWithGlobals().passphrase;
|
|
435
|
+
let pp;
|
|
436
|
+
if (flagPp !== undefined)
|
|
437
|
+
pp = flagPp;
|
|
438
|
+
else if ("OWS_PASSPHRASE" in process.env)
|
|
439
|
+
pp = process.env.OWS_PASSPHRASE ?? "";
|
|
440
|
+
else if (!piped)
|
|
441
|
+
pp = await promptHidden("OWS vault passphrase (hidden, empty = none): ");
|
|
520
442
|
else {
|
|
521
|
-
|
|
522
|
-
|
|
443
|
+
throw new PerpError("PASSPHRASE_REQUIRED", "stdin carries the key, so the vault passphrase must come from OWS_PASSPHRASE or --passphrase", {
|
|
444
|
+
remediation: `pbpaste | OWS_PASSPHRASE="$PP" perp wallet import --name ${opts.name} (use OWS_PASSPHRASE= for an unencrypted vault wallet)`,
|
|
445
|
+
});
|
|
523
446
|
}
|
|
447
|
+
const w = await importOwsWallet(opts.name, pp, opts.mnemonic ? { kind: "mnemonic", mnemonic: secrets[0] }
|
|
448
|
+
: chain === "dual" ? { kind: "dual", evm: secrets[0], solana: secrets[1] }
|
|
449
|
+
: { kind: "single", key: secrets[0], chain });
|
|
524
450
|
// Set as active wallet if none set
|
|
525
451
|
const { loadSettings: ls, saveSettings: ss } = await import("../settings.js");
|
|
526
452
|
const settings = ls();
|
|
@@ -545,16 +471,7 @@ export function registerWalletCommands(program, isJson, getAdapter, getPacificaA
|
|
|
545
471
|
console.log(chalk.cyan(` Active: yes (used by all exchanges)`));
|
|
546
472
|
}
|
|
547
473
|
console.log();
|
|
548
|
-
}
|
|
549
|
-
catch (e) {
|
|
550
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
551
|
-
if (isJson()) {
|
|
552
|
-
const { jsonError } = await import("../utils.js");
|
|
553
|
-
return printJson(jsonError("OWS_ERROR", msg));
|
|
554
|
-
}
|
|
555
|
-
console.error(chalk.red(`\n Error: ${msg}\n`));
|
|
556
|
-
process.exit(1);
|
|
557
|
-
}
|
|
474
|
+
});
|
|
558
475
|
});
|
|
559
476
|
// ── use (set active wallet — OWS first, legacy fallback) ──
|
|
560
477
|
wallet
|
|
@@ -590,7 +507,7 @@ export function registerWalletCommands(program, isJson, getAdapter, getPacificaA
|
|
|
590
507
|
if (!entry) {
|
|
591
508
|
console.error(chalk.red(`\n Wallet "${name}" not found in OWS vault or legacy store.\n`));
|
|
592
509
|
console.error(chalk.gray(` Create: perp wallet generate ${name}`));
|
|
593
|
-
console.error(chalk.gray(` Import: perp wallet import
|
|
510
|
+
console.error(chalk.gray(` Import: perp wallet import --name ${name} (hidden prompt / stdin)\n`));
|
|
594
511
|
process.exit(1);
|
|
595
512
|
}
|
|
596
513
|
const exchanges = entry.type === "solana" ? ["pacifica"] : ["hyperliquid", "lighter"];
|
|
@@ -604,83 +521,92 @@ export function registerWalletCommands(program, isJson, getAdapter, getPacificaA
|
|
|
604
521
|
console.log(chalk.gray(` Address: ${entry.address}`));
|
|
605
522
|
console.log(chalk.yellow(` Note: consider migrating to OWS: perp wallet migrate\n`));
|
|
606
523
|
});
|
|
607
|
-
// ── set (
|
|
524
|
+
// ── set (import an exchange key into the OWS vault → active wallet) ──
|
|
525
|
+
//
|
|
526
|
+
// SSOT Rule #3: the key goes into the encrypted OWS vault, never ~/.perp/.env,
|
|
527
|
+
// and never via argv (shell history / process list). Input: hidden TTY prompt,
|
|
528
|
+
// or stdin pipe. Keys already in a legacy ~/.perp/.env keep working (read path unchanged).
|
|
608
529
|
wallet
|
|
609
|
-
.command("set <exchange>
|
|
610
|
-
.description("
|
|
530
|
+
.command("set <exchange> [key]")
|
|
531
|
+
.description("Import an exchange private key into the OWS vault and make it the active wallet (key via hidden prompt or stdin — never argv)")
|
|
532
|
+
.option("--name <name>", "OWS wallet name (default: the exchange id, e.g. hyperliquid)")
|
|
611
533
|
.option("--default", "Also set as default exchange")
|
|
612
|
-
.action(async (exchange,
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
if (isJson()) {
|
|
619
|
-
const { jsonError } = await import("../utils.js");
|
|
620
|
-
return printJson(jsonError("INVALID_PARAMS", `Unknown exchange: ${exchange}. Use: pacifica, hyperliquid, lighter, aster (or hl, pac, lt, ast).`));
|
|
621
|
-
}
|
|
622
|
-
console.error(chalk.red(`\n Unknown exchange: ${exchange}`));
|
|
623
|
-
console.error(chalk.gray(` Use: pacifica, hyperliquid, lighter, aster (or hl, pac, lt, ast).\n`));
|
|
624
|
-
process.exit(1);
|
|
625
|
-
}
|
|
626
|
-
const { valid, address } = await validateKey(info.chain, key);
|
|
627
|
-
if (!valid) {
|
|
628
|
-
if (isJson()) {
|
|
629
|
-
const { jsonError } = await import("../utils.js");
|
|
630
|
-
return printJson(jsonError("INVALID_PARAMS", `Invalid ${info.chain} private key`));
|
|
534
|
+
.action(async (exchange, argvKey, opts, command) => {
|
|
535
|
+
await withJsonErrors(isJson(), async () => {
|
|
536
|
+
if (argvKey !== undefined) {
|
|
537
|
+
throw new PerpError("INVALID_PARAMS", "Refusing a private key on the command line: it is visible in shell history and the process list", {
|
|
538
|
+
remediation: `Enter it at the hidden prompt (perp wallet set ${exchange}) or pipe it on stdin (e.g. pbpaste | OWS_PASSPHRASE=... perp wallet set ${exchange}). Clear the key from your shell history.`,
|
|
539
|
+
});
|
|
631
540
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
const settings = loadSettings();
|
|
641
|
-
settings.defaultExchange = resolved;
|
|
642
|
-
saveSettings(settings);
|
|
643
|
-
}
|
|
644
|
-
// Auto-setup Lighter API key if setting lighter PK
|
|
645
|
-
let lighterApiSetup = {};
|
|
646
|
-
if (resolved === "lighter") {
|
|
647
|
-
try {
|
|
648
|
-
const { LighterAdapter } = await import("../exchanges/lighter.js");
|
|
649
|
-
const adapter = new LighterAdapter(normalized);
|
|
650
|
-
await adapter.init();
|
|
651
|
-
const apiKeyIndex = 4;
|
|
652
|
-
const { privateKey: apiKey } = await adapter.setupApiKey(apiKeyIndex);
|
|
653
|
-
// SSOT Rule #3: persist L2 key in encrypted keystore.
|
|
654
|
-
// accountIndex/apiKeyIndex live in settings.agents.lighter and don't
|
|
655
|
-
// need env duplication.
|
|
656
|
-
const { saveLighterKey } = await import("../agent-wallet/lighter-keystore.js");
|
|
657
|
-
saveLighterKey(adapter.accountIndex, apiKeyIndex, apiKey, "");
|
|
658
|
-
lighterApiSetup = { apiKey, accountIndex: adapter.accountIndex };
|
|
541
|
+
// Resolve alias (hl → hyperliquid, pac → pacifica, lt → lighter, ast → aster)
|
|
542
|
+
const aliases = { hl: "hyperliquid", pac: "pacifica", lt: "lighter", ast: "aster" };
|
|
543
|
+
const resolved = aliases[exchange.toLowerCase()] || exchange.toLowerCase();
|
|
544
|
+
const info = EXCHANGE_ENV_MAP[resolved];
|
|
545
|
+
if (!info) {
|
|
546
|
+
throw new PerpError("INVALID_PARAMS", `Unknown exchange: ${exchange}`, {
|
|
547
|
+
remediation: "Use: pacifica, hyperliquid, lighter, aster (or hl, pac, lt, ast). Lighter RH / Ondo / Arcus use: perp wallet agent approve <lighter-rh|ondo|arcus>; QFEX: perp wallet agent import qfex.",
|
|
548
|
+
});
|
|
659
549
|
}
|
|
660
|
-
|
|
661
|
-
|
|
550
|
+
const { promptHidden } = await import("./agent.js");
|
|
551
|
+
const piped = process.stdin.isTTY !== true;
|
|
552
|
+
const key = (piped ? await readAllStdin() : await promptHidden(`${resolved} private key (hidden): `)).trim();
|
|
553
|
+
if (!key) {
|
|
554
|
+
throw new PerpError("INVALID_PARAMS", "No private key received", {
|
|
555
|
+
remediation: `Enter it at the hidden prompt, or pipe it: pbpaste | perp wallet set ${exchange}`,
|
|
556
|
+
});
|
|
662
557
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
}));
|
|
669
|
-
console.log(chalk.green(`\n ${resolved} configured.`));
|
|
670
|
-
console.log(` Address: ${chalk.green(address)}`);
|
|
671
|
-
console.log(` Saved to: ${chalk.gray("~/.perp/.env")}`);
|
|
672
|
-
if (opts.default)
|
|
673
|
-
console.log(` Default: ${chalk.cyan(resolved)}`);
|
|
674
|
-
if (resolved === "lighter") {
|
|
675
|
-
if (lighterApiSetup.apiKey) {
|
|
676
|
-
console.log(chalk.green(` API Key: auto-registered (index: ${lighterApiSetup.accountIndex})`));
|
|
558
|
+
const { valid, address } = await validateKey(info.chain, key);
|
|
559
|
+
if (!valid) {
|
|
560
|
+
throw new PerpError("INVALID_PARAMS", `Invalid ${info.chain} private key`, {
|
|
561
|
+
remediation: info.chain === "evm" ? "Expected 32-byte hex (0x-prefixed or not)" : "Expected a Solana secret key (base58, hex, or JSON byte array)",
|
|
562
|
+
});
|
|
677
563
|
}
|
|
564
|
+
// Vault passphrase: --passphrase > OWS_PASSPHRASE > hidden prompt. Piped stdin
|
|
565
|
+
// carries the key, so a non-interactive run must name the passphrase explicitly.
|
|
566
|
+
const flagPp = command.optsWithGlobals().passphrase;
|
|
567
|
+
let passphrase;
|
|
568
|
+
if (flagPp !== undefined)
|
|
569
|
+
passphrase = flagPp;
|
|
570
|
+
else if ("OWS_PASSPHRASE" in process.env)
|
|
571
|
+
passphrase = process.env.OWS_PASSPHRASE ?? "";
|
|
572
|
+
else if (!piped)
|
|
573
|
+
passphrase = await promptHidden("OWS vault passphrase (hidden, empty = none): ");
|
|
678
574
|
else {
|
|
679
|
-
|
|
680
|
-
|
|
575
|
+
throw new PerpError("PASSPHRASE_REQUIRED", "stdin carries the key, so the vault passphrase must come from OWS_PASSPHRASE or --passphrase", {
|
|
576
|
+
remediation: `pbpaste | OWS_PASSPHRASE="$PP" perp wallet set ${exchange} (use OWS_PASSPHRASE= for an unencrypted vault wallet)`,
|
|
577
|
+
});
|
|
681
578
|
}
|
|
682
|
-
|
|
683
|
-
|
|
579
|
+
const name = opts.name ?? resolved;
|
|
580
|
+
const w = await importOwsWallet(name, passphrase, {
|
|
581
|
+
kind: "single",
|
|
582
|
+
key: info.chain === "evm" && !key.startsWith("0x") ? `0x${key}` : key,
|
|
583
|
+
chain: info.chain,
|
|
584
|
+
});
|
|
585
|
+
const settings = loadSettings();
|
|
586
|
+
const previousActiveWallet = settings.owsActiveWallet || null;
|
|
587
|
+
settings.owsActiveWallet = name;
|
|
588
|
+
if (opts.default)
|
|
589
|
+
settings.defaultExchange = resolved;
|
|
590
|
+
saveSettings(settings);
|
|
591
|
+
// No implicit Lighter API-key registration here (it signed a mainnet ChangePubKey on slot 4
|
|
592
|
+
// and echoed the L2 key): trading needs an explicit `perp wallet agent approve lighter`.
|
|
593
|
+
const lighterNextStep = "perp wallet agent approve lighter";
|
|
594
|
+
if (isJson())
|
|
595
|
+
return printJson(jsonOk({
|
|
596
|
+
exchange: resolved, address, owsWallet: w.name, activeWallet: name, previousActiveWallet, default: !!opts.default,
|
|
597
|
+
...(resolved === "lighter" && { lighterApiKey: { registered: false, nextStep: lighterNextStep } }),
|
|
598
|
+
}));
|
|
599
|
+
console.log(chalk.green(`\n ${resolved} key imported into the OWS vault.`));
|
|
600
|
+
console.log(` Address: ${chalk.green(address)}`);
|
|
601
|
+
console.log(` Wallet: ${chalk.white.bold(w.name)} ${chalk.gray("(encrypted in ~/.ows/)")}`);
|
|
602
|
+
console.log(` Active: ${chalk.cyan(name)} (used by all exchanges)${previousActiveWallet && previousActiveWallet !== name ? chalk.yellow(` — was ${previousActiveWallet}; switch back: perp wallet use ${previousActiveWallet}`) : ""}`);
|
|
603
|
+
if (opts.default)
|
|
604
|
+
console.log(` Default: ${chalk.cyan(resolved)}`);
|
|
605
|
+
if (resolved === "lighter") {
|
|
606
|
+
console.log(chalk.yellow(` API Key: not registered — to trade, run: ${lighterNextStep}`));
|
|
607
|
+
}
|
|
608
|
+
console.log();
|
|
609
|
+
});
|
|
684
610
|
});
|
|
685
611
|
// ── show (show configured exchanges with public addresses) ──
|
|
686
612
|
wallet
|
|
@@ -766,13 +692,17 @@ export function registerWalletCommands(program, isJson, getAdapter, getPacificaA
|
|
|
766
692
|
if (isJson()) {
|
|
767
693
|
return printJson(jsonOk({
|
|
768
694
|
ows: { wallets: owsWallets, active: activeOwsWallet },
|
|
769
|
-
|
|
695
|
+
// Public fields only — wallets.json entries hold raw private keys (never printed).
|
|
696
|
+
legacy: {
|
|
697
|
+
wallets: Object.fromEntries(Object.entries(store.wallets).map(([k, w]) => [k, { name: w.name, type: w.type, address: w.address, createdAt: w.createdAt }])),
|
|
698
|
+
active: store.active,
|
|
699
|
+
},
|
|
770
700
|
}));
|
|
771
701
|
}
|
|
772
702
|
if (owsWallets.length === 0 && legacyEntries.length === 0) {
|
|
773
703
|
console.log(chalk.gray("\n No wallets found."));
|
|
774
704
|
console.log(chalk.gray(` Create: ${chalk.cyan("perp wallet generate [name]")}`));
|
|
775
|
-
console.log(chalk.gray(` Import: ${chalk.cyan("perp wallet import <
|
|
705
|
+
console.log(chalk.gray(` Import: ${chalk.cyan("perp wallet import --name <name>")}\n`));
|
|
776
706
|
return;
|
|
777
707
|
}
|
|
778
708
|
if (owsWallets.length > 0) {
|
|
@@ -929,8 +859,54 @@ export function registerWalletCommands(program, isJson, getAdapter, getPacificaA
|
|
|
929
859
|
const activeEntries = Object.entries(store.active)
|
|
930
860
|
.map(([ex, wn]) => ({ exchange: ex, ...store.wallets[wn] }))
|
|
931
861
|
.filter((e) => e.address);
|
|
932
|
-
//
|
|
862
|
+
// OWS-aware path: vault-based setups have no legacy `active` mapping
|
|
863
|
+
// (it's empty by design — owsActiveWallet replaces per-exchange
|
|
864
|
+
// active wallets). Fetch on-chain balances for both EVM and Solana
|
|
865
|
+
// accounts of the active OWS vault BEFORE falling through to the
|
|
866
|
+
// legacy .env path.
|
|
933
867
|
if (activeEntries.length === 0) {
|
|
868
|
+
const settings = loadSettings();
|
|
869
|
+
if (settings.owsActiveWallet) {
|
|
870
|
+
try {
|
|
871
|
+
const { loadOws } = await import("../signer/ows-loader.js");
|
|
872
|
+
const ows = loadOws();
|
|
873
|
+
const owsWallet = ows.getWallet(settings.owsActiveWallet);
|
|
874
|
+
const owsResults = [];
|
|
875
|
+
for (const acct of owsWallet.accounts) {
|
|
876
|
+
const isEvm = acct.chainId.startsWith("eip155:");
|
|
877
|
+
const isSol = acct.chainId.startsWith("solana:");
|
|
878
|
+
if (!isEvm && !isSol)
|
|
879
|
+
continue;
|
|
880
|
+
const address = acct.address;
|
|
881
|
+
const balances = isSol
|
|
882
|
+
? await getSolanaBalances(address, opts.testnet)
|
|
883
|
+
: await getEvmBalances(address, opts.testnet);
|
|
884
|
+
if (isJson()) {
|
|
885
|
+
owsResults.push({
|
|
886
|
+
wallet: settings.owsActiveWallet,
|
|
887
|
+
chain: isSol ? "solana" : "evm",
|
|
888
|
+
address,
|
|
889
|
+
balances,
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
else {
|
|
893
|
+
console.log(chalk.cyan.bold(`\n ${settings.owsActiveWallet} (${isSol ? "solana" : "evm"} ${address.slice(0, 8)}...)`));
|
|
894
|
+
const rows = balances.map((b) => [
|
|
895
|
+
chalk.white.bold(b.token), b.balance, b.usdValue || chalk.gray("-"),
|
|
896
|
+
]);
|
|
897
|
+
console.log(makeTable(["Token", "Balance", "USD Value"], rows));
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
if (isJson())
|
|
901
|
+
printJson(jsonOk(owsResults));
|
|
902
|
+
else
|
|
903
|
+
console.log();
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
catch {
|
|
907
|
+
// OWS module unavailable or vault missing — fall through to env path
|
|
908
|
+
}
|
|
909
|
+
}
|
|
934
910
|
const stored = loadEnvFile();
|
|
935
911
|
const envEntries = [];
|
|
936
912
|
for (const [exchange, info] of Object.entries(EXCHANGE_ENV_MAP)) {
|
|
@@ -945,10 +921,10 @@ export function registerWalletCommands(program, isJson, getAdapter, getPacificaA
|
|
|
945
921
|
}
|
|
946
922
|
if (envEntries.length === 0) {
|
|
947
923
|
if (isJson()) {
|
|
948
|
-
printJson({ ok: false, error: { code: "INVALID_PARAMS", message: "No wallets configured. Run 'perp setup' or 'perp wallet set <exchange>
|
|
924
|
+
printJson({ ok: false, error: { code: "INVALID_PARAMS", message: "No wallets configured. Run 'perp setup' or 'perp wallet set <exchange>'." }, meta: { timestamp: new Date().toISOString() } });
|
|
949
925
|
}
|
|
950
926
|
else {
|
|
951
|
-
console.error(chalk.gray("\n No wallets configured. Run 'perp setup' or 'perp wallet set <exchange>
|
|
927
|
+
console.error(chalk.gray("\n No wallets configured. Run 'perp setup' or 'perp wallet set <exchange>'.\n"));
|
|
952
928
|
}
|
|
953
929
|
process.exit(1);
|
|
954
930
|
}
|
|
@@ -1071,7 +1047,7 @@ export function registerWalletCommands(program, isJson, getAdapter, getPacificaA
|
|
|
1071
1047
|
results.push({ name: entry.name, status: "migrated" });
|
|
1072
1048
|
}
|
|
1073
1049
|
catch (e) {
|
|
1074
|
-
results.push({ name: entry.name, status: "failed", error: e instanceof Error ? e.message : String(e) });
|
|
1050
|
+
results.push({ name: entry.name, status: "failed", error: redactSecrets(e instanceof Error ? e.message : String(e), [entry.privateKey]) });
|
|
1075
1051
|
}
|
|
1076
1052
|
}
|
|
1077
1053
|
// Set first migrated wallet as active if none set
|
|
@@ -1269,7 +1245,7 @@ export function registerWalletCommands(program, isJson, getAdapter, getPacificaA
|
|
|
1269
1245
|
.requiredOption("--wallets <names>", "Comma-separated OWS wallet names")
|
|
1270
1246
|
.option("--policy <ids>", "Comma-separated policy IDs to enforce")
|
|
1271
1247
|
.option("--expires <iso>", "Key expiry (ISO-8601)")
|
|
1272
|
-
.
|
|
1248
|
+
.addOption(deprecatedPassphraseOption("Vault passphrase", "-p, --passphrase <pass>").default("")).hook("preAction", warnDeprecatedPassphrase)
|
|
1273
1249
|
.action(async (opts, command) => {
|
|
1274
1250
|
try {
|
|
1275
1251
|
const o = loadOws();
|
|
@@ -1391,7 +1367,8 @@ export function registerWalletCommands(program, isJson, getAdapter, getPacificaA
|
|
|
1391
1367
|
}
|
|
1392
1368
|
// Call OWS CLI subprocess for native MoonPay deposit
|
|
1393
1369
|
// OWS CLI writes addresses to stderr, URL to stdout
|
|
1394
|
-
|
|
1370
|
+
// The OWS CLI child never receives key material (SSOT Rule #3): secret env vars are stripped.
|
|
1371
|
+
const owsEnv = { ...scrubSecretEnv(process.env), PATH: `${pathResolve(process.env.HOME || "~", ".ows", "bin")}:${process.env.PATH}` };
|
|
1395
1372
|
try {
|
|
1396
1373
|
const { spawnSync } = await import("child_process");
|
|
1397
1374
|
const proc = spawnSync(owsBin, ["fund", "deposit", "--wallet", walletName, "--chain", chain], {
|
|
@@ -1450,7 +1427,7 @@ export function registerWalletCommands(program, isJson, getAdapter, getPacificaA
|
|
|
1450
1427
|
wallet
|
|
1451
1428
|
.command("setup")
|
|
1452
1429
|
.description("One-click setup: create wallet + guardrail policy + agent API key")
|
|
1453
|
-
.option("
|
|
1430
|
+
.option("--name <name>", "Wallet name", "perp-trading")
|
|
1454
1431
|
.option("--max-tx-usd <amount>", "Max per-transaction USD", "1000")
|
|
1455
1432
|
.option("--max-daily-usd <amount>", "Max daily spending USD", "5000")
|
|
1456
1433
|
.option("--skip-key", "Skip API key creation")
|
|
@@ -1647,6 +1624,8 @@ export function registerWalletCommands(program, isJson, getAdapter, getPacificaA
|
|
|
1647
1624
|
// NOTE: Distinct from `wallet key` (OWS API token) and `wallet policy`
|
|
1648
1625
|
// (OWS guardrail). See runOwsCli comment above for the three-way split.
|
|
1649
1626
|
registerWalletAgentCommands(wallet, isJson);
|
|
1627
|
+
// ── Builder fees (off by default; settings builder.<venue>) ──
|
|
1628
|
+
registerWalletBuilderCommands(wallet, isJson, getAdapterForExchange ?? null);
|
|
1650
1629
|
// ── Manage (per-DEX exchange settings — Pacifica + Lighter) ──────────
|
|
1651
1630
|
// Absorbed from former top-level `perp manage`. Holds exchange-specific
|
|
1652
1631
|
// settings: margin mode, subaccount, lake, builder/referral, API keys.
|