perp-cli 0.12.1 → 0.12.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/agent.js +95 -10
- package/dist/commands/init.d.ts +19 -0
- package/dist/commands/init.js +124 -3
- package/dist/commands/wallet.js +15 -5
- package/package.json +2 -2
package/dist/commands/agent.js
CHANGED
|
@@ -1157,6 +1157,56 @@ export function registerWalletAgentCommands(walletCmd, isJson) {
|
|
|
1157
1157
|
}
|
|
1158
1158
|
});
|
|
1159
1159
|
}
|
|
1160
|
+
// ── Pre-venue rollback helpers ───────────────────────────────────────────────
|
|
1161
|
+
//
|
|
1162
|
+
// When `wallet agent approve <dex>` fails BEFORE the venue accepted the agent
|
|
1163
|
+
// (HTTP error, body-code rejection, parse error, network drop), the locally
|
|
1164
|
+
// generated `agent-<dex>-<master>` OWS wallet is orphaned in `~/.ows/wallets/`.
|
|
1165
|
+
// Retry collides with "wallet name already exists". These helpers auto-delete
|
|
1166
|
+
// the orphan and produce an actionable remediation string.
|
|
1167
|
+
//
|
|
1168
|
+
// Post-venue (persist-only) failures must NOT use these — those leave a
|
|
1169
|
+
// status:"partial" record so the user can still revoke venue-side state.
|
|
1170
|
+
/**
|
|
1171
|
+
* Best-effort delete of an orphan local OWS wallet. Returns true on success,
|
|
1172
|
+
* false on failure (also logs the failure to stderr per SSOT Rule #2: rollback
|
|
1173
|
+
* failures must not silently mask the original error). The caller threads the
|
|
1174
|
+
* boolean into the user-visible remediation.
|
|
1175
|
+
*/
|
|
1176
|
+
function tryDeleteOrphanWallet(ows, walletName) {
|
|
1177
|
+
if (!walletName)
|
|
1178
|
+
return false;
|
|
1179
|
+
try {
|
|
1180
|
+
ows.deleteWallet(walletName);
|
|
1181
|
+
return true;
|
|
1182
|
+
}
|
|
1183
|
+
catch (cleanupErr) {
|
|
1184
|
+
// Don't swallow — surface the rollback failure on stderr so the user can
|
|
1185
|
+
// see exactly what's stuck. Original error still propagates from caller.
|
|
1186
|
+
process.stderr.write(`[warn] failed to clean up orphan wallet "${walletName}": ${cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)}\n`);
|
|
1187
|
+
return false;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* Build an actionable remediation message for pre-venue approve failures.
|
|
1192
|
+
* On clean rollback: tells the user to deposit + retry the canonical
|
|
1193
|
+
* `wallet agent approve <dex> --master <master>` command (no manual steps).
|
|
1194
|
+
* On rollback failure: tells the user to manually delete the orphan wallet
|
|
1195
|
+
* before retrying. The agent address is appended for diagnostic purposes
|
|
1196
|
+
* (some users may need it for the venue UI).
|
|
1197
|
+
*/
|
|
1198
|
+
function buildPreVenueRemediation(opts) {
|
|
1199
|
+
const { cleanupOk, dex, masterName, agentAddress, hint } = opts;
|
|
1200
|
+
const retry = `perp wallet agent approve ${dex} --master ${masterName}`;
|
|
1201
|
+
const head = cleanupOk
|
|
1202
|
+
? `Local agent wallet cleaned up. Deposit funds and retry: ${retry}`
|
|
1203
|
+
: `Manual cleanup required — local agent wallet may still exist. Run: perp wallet remove agent-${dex === "hyperliquid" ? "hl" : dex === "pacifica" ? "pac" : "aster"}-${masterName}, then retry: ${retry}`;
|
|
1204
|
+
const parts = [head];
|
|
1205
|
+
if (hint)
|
|
1206
|
+
parts.push(hint);
|
|
1207
|
+
parts.push(`Agent address (diagnostic): ${agentAddress}`);
|
|
1208
|
+
return parts.join(" | ");
|
|
1209
|
+
}
|
|
1160
1210
|
/**
|
|
1161
1211
|
* Core approve flow: Steps 2-12 (OWS wallet + policy + api-key + EIP-712 + Aster POST + persist).
|
|
1162
1212
|
* Caller MUST hold the exchange lock before calling this function.
|
|
@@ -1233,13 +1283,15 @@ async function runApproveFlow(opts) {
|
|
|
1233
1283
|
});
|
|
1234
1284
|
if (!builderRes.ok) {
|
|
1235
1285
|
const errText = await builderRes.text().catch(() => "");
|
|
1236
|
-
// Aster builder POST failed —
|
|
1286
|
+
// Aster builder POST failed — pre-venue (no Aster agent registered yet).
|
|
1287
|
+
// Rollback both: api key + orphan local OWS wallet.
|
|
1237
1288
|
if (owsApiKeyId) {
|
|
1238
1289
|
try {
|
|
1239
1290
|
ows.revokeApiKey(owsApiKeyId);
|
|
1240
1291
|
}
|
|
1241
1292
|
catch { /* best effort */ }
|
|
1242
1293
|
}
|
|
1294
|
+
tryDeleteOrphanWallet(ows, agentWalletName);
|
|
1243
1295
|
throw new PerpError("EXCHANGE_ERROR", `Aster approveBuilder failed (${builderRes.status}): ${errText.slice(0, 200)}`, {
|
|
1244
1296
|
remediation: "Check builder address and max-fee-rate parameters",
|
|
1245
1297
|
});
|
|
@@ -1253,6 +1305,7 @@ async function runApproveFlow(opts) {
|
|
|
1253
1305
|
}
|
|
1254
1306
|
catch { /* best effort */ }
|
|
1255
1307
|
}
|
|
1308
|
+
tryDeleteOrphanWallet(ows, agentWalletName);
|
|
1256
1309
|
const errMsg = builderResp.msg ?? builderResp.message ?? String(builderResp.code);
|
|
1257
1310
|
throw new PerpError("EXCHANGE_ERROR", `Aster approveBuilder failed: ${errMsg}`, {
|
|
1258
1311
|
remediation: "Check builder address and max-fee-rate parameters",
|
|
@@ -1307,30 +1360,37 @@ async function runApproveFlow(opts) {
|
|
|
1307
1360
|
asterPostSucceeded = true;
|
|
1308
1361
|
}
|
|
1309
1362
|
else {
|
|
1310
|
-
// Aster POST body indicated failure —
|
|
1363
|
+
// Aster POST body indicated failure — pre-venue (Aster did not register).
|
|
1364
|
+
// Rollback both api key + orphan local OWS wallet.
|
|
1311
1365
|
if (owsApiKeyId) {
|
|
1312
1366
|
try {
|
|
1313
1367
|
ows.revokeApiKey(owsApiKeyId);
|
|
1314
1368
|
}
|
|
1315
1369
|
catch { /* best effort */ }
|
|
1316
1370
|
}
|
|
1371
|
+
const cleanupOk = tryDeleteOrphanWallet(ows, agentWalletName);
|
|
1317
1372
|
const errMsg = resp.msg ?? resp.message ?? String(resp.code);
|
|
1318
1373
|
throw new PerpError("APPROVE_PARTIAL", `Aster approveAgent failed: ${errMsg}`, {
|
|
1319
|
-
remediation:
|
|
1374
|
+
remediation: buildPreVenueRemediation({
|
|
1375
|
+
cleanupOk, dex: "aster", masterName, agentAddress: agentEvmAddress,
|
|
1376
|
+
}),
|
|
1320
1377
|
});
|
|
1321
1378
|
}
|
|
1322
1379
|
}
|
|
1323
1380
|
else {
|
|
1324
|
-
// HTTP error —
|
|
1381
|
+
// HTTP error — pre-venue (Aster did not register). Rollback both.
|
|
1325
1382
|
if (owsApiKeyId) {
|
|
1326
1383
|
try {
|
|
1327
1384
|
ows.revokeApiKey(owsApiKeyId);
|
|
1328
1385
|
}
|
|
1329
1386
|
catch { /* best effort */ }
|
|
1330
1387
|
}
|
|
1388
|
+
const cleanupOk = tryDeleteOrphanWallet(ows, agentWalletName);
|
|
1331
1389
|
const errText = await agentRes.text().catch(() => "");
|
|
1332
1390
|
throw new PerpError("APPROVE_PARTIAL", `Aster approveAgent failed (${agentRes.status}): ${errText.slice(0, 200)}`, {
|
|
1333
|
-
remediation:
|
|
1391
|
+
remediation: buildPreVenueRemediation({
|
|
1392
|
+
cleanupOk, dex: "aster", masterName, agentAddress: agentEvmAddress,
|
|
1393
|
+
}),
|
|
1334
1394
|
});
|
|
1335
1395
|
}
|
|
1336
1396
|
// Step 12: Persist agent meta
|
|
@@ -1551,13 +1611,26 @@ async function runHlApproveFlow(opts) {
|
|
|
1551
1611
|
resp = JSON.parse(text);
|
|
1552
1612
|
}
|
|
1553
1613
|
catch {
|
|
1614
|
+
// Pre-venue parse failure — Hyperliquid did NOT register the agent.
|
|
1615
|
+
// Roll back the orphan local OWS wallet so retry doesn't collide with
|
|
1616
|
+
// "wallet name already exists". Errors during cleanup must NOT mask the
|
|
1617
|
+
// original network/parse error (SSOT Rule #2).
|
|
1618
|
+
const cleanupOk = tryDeleteOrphanWallet(ows, agentWalletName);
|
|
1554
1619
|
throw new PerpError("EXCHANGE_ERROR", `HL exchange API parse error (${agentRes.status}): ${text.slice(0, 200)}`, {
|
|
1555
|
-
remediation:
|
|
1620
|
+
remediation: buildPreVenueRemediation({
|
|
1621
|
+
cleanupOk, dex: "hyperliquid", masterName, agentAddress: agentEvmAddress,
|
|
1622
|
+
hint: "Check Hyperliquid API status",
|
|
1623
|
+
}),
|
|
1556
1624
|
});
|
|
1557
1625
|
}
|
|
1558
1626
|
if (resp?.status === "err") {
|
|
1627
|
+
// Pre-venue rejection — Hyperliquid did NOT register the agent. Roll
|
|
1628
|
+
// back the orphan local OWS wallet so retry can proceed cleanly.
|
|
1629
|
+
const cleanupOk = tryDeleteOrphanWallet(ows, agentWalletName);
|
|
1559
1630
|
throw new PerpError("APPROVE_PARTIAL", `HL approveAgent failed: ${typeof resp.response === "string" ? resp.response : JSON.stringify(resp)}`, {
|
|
1560
|
-
remediation:
|
|
1631
|
+
remediation: buildPreVenueRemediation({
|
|
1632
|
+
cleanupOk, dex: "hyperliquid", masterName, agentAddress: agentEvmAddress,
|
|
1633
|
+
}),
|
|
1561
1634
|
});
|
|
1562
1635
|
}
|
|
1563
1636
|
// Step 6: Persist
|
|
@@ -1682,24 +1755,36 @@ async function runPacApproveFlow(opts) {
|
|
|
1682
1755
|
// Pacifica REST envelope: success === true OR explicit data field
|
|
1683
1756
|
if (resp.success === false || resp.error) {
|
|
1684
1757
|
const errMsg = typeof resp.error === "string" ? resp.error : JSON.stringify(resp);
|
|
1758
|
+
// Pre-venue rejection — Pacifica did NOT register the agent.
|
|
1759
|
+
const cleanupOk = tryDeleteOrphanWallet(ows, agentWalletName);
|
|
1685
1760
|
throw new PerpError("APPROVE_PARTIAL", `Pacifica bind_agent_wallet failed: ${errMsg}`, {
|
|
1686
|
-
remediation:
|
|
1761
|
+
remediation: buildPreVenueRemediation({
|
|
1762
|
+
cleanupOk, dex: "pacifica", masterName, agentAddress: agentSolanaAddress,
|
|
1763
|
+
}),
|
|
1687
1764
|
});
|
|
1688
1765
|
}
|
|
1689
1766
|
pacPostSucceeded = true;
|
|
1690
1767
|
}
|
|
1691
1768
|
else {
|
|
1692
1769
|
const errText = await httpRes.text().catch(() => "");
|
|
1770
|
+
// Pre-venue rejection — Pacifica did NOT register the agent.
|
|
1771
|
+
const cleanupOk = tryDeleteOrphanWallet(ows, agentWalletName);
|
|
1693
1772
|
throw new PerpError("APPROVE_PARTIAL", `Pacifica bind_agent_wallet failed (${httpRes.status}): ${errText.slice(0, 200)}`, {
|
|
1694
|
-
remediation:
|
|
1773
|
+
remediation: buildPreVenueRemediation({
|
|
1774
|
+
cleanupOk, dex: "pacifica", masterName, agentAddress: agentSolanaAddress,
|
|
1775
|
+
}),
|
|
1695
1776
|
});
|
|
1696
1777
|
}
|
|
1697
1778
|
}
|
|
1698
1779
|
catch (httpErr) {
|
|
1699
1780
|
if (httpErr instanceof PerpError)
|
|
1700
1781
|
throw httpErr;
|
|
1782
|
+
// Network-level failure — Pacifica did NOT register the agent.
|
|
1783
|
+
const cleanupOk = tryDeleteOrphanWallet(ows, agentWalletName);
|
|
1701
1784
|
throw new PerpError("APPROVE_PARTIAL", `Pacifica bind_agent_wallet network error: ${httpErr instanceof Error ? httpErr.message : String(httpErr)}`, {
|
|
1702
|
-
remediation:
|
|
1785
|
+
remediation: buildPreVenueRemediation({
|
|
1786
|
+
cleanupOk, dex: "pacifica", masterName, agentAddress: agentSolanaAddress,
|
|
1787
|
+
}),
|
|
1703
1788
|
});
|
|
1704
1789
|
}
|
|
1705
1790
|
// Step 5: Persist
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -14,3 +14,22 @@ export declare function validateKey(chain: "solana" | "evm" | "apikey", key: str
|
|
|
14
14
|
address: string;
|
|
15
15
|
}>;
|
|
16
16
|
export declare function registerInitCommand(program: Command): void;
|
|
17
|
+
/**
|
|
18
|
+
* Drive `perp setup` end-to-end without TTY. Required:
|
|
19
|
+
* - --passphrase or OWS_PASSPHRASE env (3-path resolver)
|
|
20
|
+
* Optional:
|
|
21
|
+
* - --wallet-name (default: "main")
|
|
22
|
+
* - --default-exchange (must be one of pacifica/hyperliquid/lighter/aster)
|
|
23
|
+
*
|
|
24
|
+
* Side effects:
|
|
25
|
+
* 1. Creates an OWS wallet via createWallet(name, passphrase)
|
|
26
|
+
* 2. Sets settings.owsActiveWallet (if unset) + settings.defaultExchange (if provided)
|
|
27
|
+
*
|
|
28
|
+
* Errors are returned as INVALID_PARAMS / PASSPHRASE_REQUIRED — never via
|
|
29
|
+
* readline. JSON envelope is emitted on stdout when --json is present.
|
|
30
|
+
*/
|
|
31
|
+
export declare function runNonInteractiveSetup(opts: {
|
|
32
|
+
walletName?: string;
|
|
33
|
+
passphrase?: string;
|
|
34
|
+
defaultExchange?: string;
|
|
35
|
+
}): Promise<void>;
|
package/dist/commands/init.js
CHANGED
|
@@ -3,6 +3,10 @@ import { createInterface } from "readline";
|
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
4
4
|
import { resolve } from "path";
|
|
5
5
|
import { loadSettings, saveSettings } from "../settings.js";
|
|
6
|
+
import { PerpError } from "../errors.js";
|
|
7
|
+
import { jsonOk, jsonError, printJson } from "../utils.js";
|
|
8
|
+
import { resolvePassphrase } from "../agent-wallet/passphrase.js";
|
|
9
|
+
import { loadOws } from "../signer/ows-loader.js";
|
|
6
10
|
const PERP_DIR = resolve(process.env.HOME || "~", ".perp");
|
|
7
11
|
const ENV_FILE = resolve(PERP_DIR, ".env");
|
|
8
12
|
// ── .env helpers ──────────────────────────────────────────────
|
|
@@ -109,12 +113,23 @@ export async function validateKey(chain, key) {
|
|
|
109
113
|
}
|
|
110
114
|
// ── Commands ──────────────────────────────────────────────────
|
|
111
115
|
export function registerInitCommand(program) {
|
|
112
|
-
// ── perp setup (interactive) ──
|
|
116
|
+
// ── perp setup (interactive + non-interactive) ──
|
|
113
117
|
program
|
|
114
118
|
.command("setup")
|
|
115
119
|
.alias("init")
|
|
116
|
-
.description("
|
|
117
|
-
.
|
|
120
|
+
.description("Setup wizard — interactive by default, scriptable with --non-interactive")
|
|
121
|
+
.option("--non-interactive", "Run without prompts; requires --passphrase or OWS_PASSPHRASE env")
|
|
122
|
+
.option("--wallet-name <name>", "OWS wallet alias to create (default: 'main')")
|
|
123
|
+
.option("--passphrase <pp>", "OWS vault passphrase (fallback: OWS_PASSPHRASE env / stdin)")
|
|
124
|
+
.option("--default-exchange <name>", "Default exchange (pacifica/hyperliquid/lighter/aster)")
|
|
125
|
+
.action(async (opts) => {
|
|
126
|
+
// Non-interactive mode: skip all readline prompts, drive everything from
|
|
127
|
+
// flags + OWS_PASSPHRASE env. This is the agent-friendly onboarding path
|
|
128
|
+
// (CI / Docker / scripted init) — see plan v3.0 "agent-friendly five
|
|
129
|
+
// guarantees" P1 (no-prompt) and P3 (passphrase 3-path).
|
|
130
|
+
if (opts.nonInteractive) {
|
|
131
|
+
return runNonInteractiveSetup(opts);
|
|
132
|
+
}
|
|
118
133
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
119
134
|
try {
|
|
120
135
|
console.log(chalk.cyan.bold("\n perp-cli Setup Wizard\n"));
|
|
@@ -304,3 +319,109 @@ function parseExchangeChoice(input) {
|
|
|
304
319
|
}
|
|
305
320
|
return [...new Set(exchanges)];
|
|
306
321
|
}
|
|
322
|
+
// ── Non-interactive setup (agent / CI / Docker) ──────────────────────────────
|
|
323
|
+
const VALID_EXCHANGES = ["pacifica", "hyperliquid", "lighter", "aster"];
|
|
324
|
+
/**
|
|
325
|
+
* Test seam: emit JSON envelopes when the global --json flag is present.
|
|
326
|
+
* Mirrors the inline pattern used elsewhere (see src/index.ts:87) instead of
|
|
327
|
+
* wiring isJson() through registerInitCommand for one call site.
|
|
328
|
+
*/
|
|
329
|
+
function setupIsJson() {
|
|
330
|
+
return process.argv.includes("--json");
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Drive `perp setup` end-to-end without TTY. Required:
|
|
334
|
+
* - --passphrase or OWS_PASSPHRASE env (3-path resolver)
|
|
335
|
+
* Optional:
|
|
336
|
+
* - --wallet-name (default: "main")
|
|
337
|
+
* - --default-exchange (must be one of pacifica/hyperliquid/lighter/aster)
|
|
338
|
+
*
|
|
339
|
+
* Side effects:
|
|
340
|
+
* 1. Creates an OWS wallet via createWallet(name, passphrase)
|
|
341
|
+
* 2. Sets settings.owsActiveWallet (if unset) + settings.defaultExchange (if provided)
|
|
342
|
+
*
|
|
343
|
+
* Errors are returned as INVALID_PARAMS / PASSPHRASE_REQUIRED — never via
|
|
344
|
+
* readline. JSON envelope is emitted on stdout when --json is present.
|
|
345
|
+
*/
|
|
346
|
+
export async function runNonInteractiveSetup(opts) {
|
|
347
|
+
const isJson = setupIsJson();
|
|
348
|
+
try {
|
|
349
|
+
// Validate --default-exchange shape early so users hit a precise error
|
|
350
|
+
// before passphrase resolution.
|
|
351
|
+
if (opts.defaultExchange !== undefined &&
|
|
352
|
+
!VALID_EXCHANGES.includes(opts.defaultExchange)) {
|
|
353
|
+
throw new PerpError("INVALID_PARAMS", `Invalid --default-exchange "${opts.defaultExchange}"`, {
|
|
354
|
+
remediation: `Use one of: ${VALID_EXCHANGES.join(", ")}`,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
// Resolve passphrase. resolvePassphrase already throws PASSPHRASE_REQUIRED
|
|
358
|
+
// when stdin is non-TTY and nothing was provided. In TTY mode it returns
|
|
359
|
+
// null — non-interactive mode must NOT fall back to a prompt.
|
|
360
|
+
const resolved = await resolvePassphrase({ flag: opts.passphrase });
|
|
361
|
+
if (resolved === null) {
|
|
362
|
+
throw new PerpError("PASSPHRASE_REQUIRED", "Non-interactive setup requires a passphrase", {
|
|
363
|
+
remediation: "Provide --passphrase <pp> or set OWS_PASSPHRASE env var",
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
const pp = resolved;
|
|
367
|
+
const walletName = opts.walletName ?? "main";
|
|
368
|
+
// Create OWS wallet
|
|
369
|
+
const ows = loadOws();
|
|
370
|
+
const w = ows.createWallet(walletName, pp);
|
|
371
|
+
// Update settings
|
|
372
|
+
const settings = loadSettings();
|
|
373
|
+
if (!settings.owsActiveWallet) {
|
|
374
|
+
settings.owsActiveWallet = walletName;
|
|
375
|
+
}
|
|
376
|
+
if (opts.defaultExchange) {
|
|
377
|
+
settings.defaultExchange = opts.defaultExchange;
|
|
378
|
+
}
|
|
379
|
+
saveSettings(settings);
|
|
380
|
+
if (isJson) {
|
|
381
|
+
printJson(jsonOk({
|
|
382
|
+
walletName: w.name,
|
|
383
|
+
walletId: w.id,
|
|
384
|
+
accounts: w.accounts,
|
|
385
|
+
owsActiveWallet: settings.owsActiveWallet,
|
|
386
|
+
defaultExchange: settings.defaultExchange ?? null,
|
|
387
|
+
mode: "non-interactive",
|
|
388
|
+
}));
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
console.log(chalk.cyan.bold("\n perp-cli Setup (non-interactive) — Done\n"));
|
|
392
|
+
console.log(` Wallet: ${chalk.white.bold(w.name)}`);
|
|
393
|
+
console.log(` Active wallet: ${chalk.cyan(settings.owsActiveWallet)}`);
|
|
394
|
+
if (settings.defaultExchange) {
|
|
395
|
+
console.log(` Default exchange: ${chalk.cyan(settings.defaultExchange)}`);
|
|
396
|
+
}
|
|
397
|
+
console.log(chalk.gray(`\n Vault: ~/.ows/ (AES-256-GCM encrypted)\n`));
|
|
398
|
+
}
|
|
399
|
+
catch (err) {
|
|
400
|
+
if (isJson) {
|
|
401
|
+
if (err instanceof PerpError) {
|
|
402
|
+
printJson(jsonError(err.structured.code, err.structured.message, {
|
|
403
|
+
...(err.structured.remediation
|
|
404
|
+
? { details: { remediation: err.structured.remediation } }
|
|
405
|
+
: {}),
|
|
406
|
+
}));
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
410
|
+
printJson(jsonError("UNKNOWN", msg));
|
|
411
|
+
}
|
|
412
|
+
process.exitCode = 1;
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
if (err instanceof PerpError) {
|
|
416
|
+
console.error(chalk.red(`\n Error [${err.structured.code}]: ${err.structured.message}`));
|
|
417
|
+
if (err.structured.remediation) {
|
|
418
|
+
console.error(chalk.gray(` Remediation: ${err.structured.remediation}\n`));
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
423
|
+
console.error(chalk.red(`\n Error: ${msg}\n`));
|
|
424
|
+
}
|
|
425
|
+
process.exitCode = 1;
|
|
426
|
+
}
|
|
427
|
+
}
|
package/dist/commands/wallet.js
CHANGED
|
@@ -324,6 +324,7 @@ export function registerWalletCommands(program, isJson, getAdapter, getPacificaA
|
|
|
324
324
|
.description("Generate a new wallet (OWS encrypted vault, multi-chain)")
|
|
325
325
|
.option("--words <count>", "Mnemonic word count (12 or 24)", "12")
|
|
326
326
|
.option("--show-mnemonic", "Display the mnemonic phrase (CAUTION)")
|
|
327
|
+
.option("--passphrase <pp>", "Master OWS passphrase (fallback: OWS_PASSPHRASE env / stdin)")
|
|
327
328
|
.option("--legacy <chain>", "Legacy mode: generate solana or evm key (unencrypted)")
|
|
328
329
|
.action(async (name, opts) => {
|
|
329
330
|
// Legacy mode for backward compat
|
|
@@ -332,8 +333,14 @@ export function registerWalletCommands(program, isJson, getAdapter, getPacificaA
|
|
|
332
333
|
}
|
|
333
334
|
const walletName = name || "default";
|
|
334
335
|
try {
|
|
336
|
+
// Resolve vault passphrase via the standard 3-path resolver (--passphrase
|
|
337
|
+
// > OWS_PASSPHRASE env > stdin pipe). When stdin is a TTY and nothing was
|
|
338
|
+
// provided, the resolver returns null; fall back to "" to preserve the
|
|
339
|
+
// historical "no passphrase" default for interactive use.
|
|
340
|
+
const { resolvePassphrase } = await import("../agent-wallet/passphrase.js");
|
|
341
|
+
const pp = (await resolvePassphrase({ flag: opts.passphrase })) ?? "";
|
|
335
342
|
const ows = loadOws();
|
|
336
|
-
const w = ows.createWallet(walletName,
|
|
343
|
+
const w = ows.createWallet(walletName, pp, parseInt(opts.words));
|
|
337
344
|
// Set as active wallet
|
|
338
345
|
const { loadSettings: ls, saveSettings: ss } = await import("../settings.js");
|
|
339
346
|
const settings = ls();
|
|
@@ -393,7 +400,7 @@ export function registerWalletCommands(program, isJson, getAdapter, getPacificaA
|
|
|
393
400
|
.option("--evm <hex>", "EVM (secp256k1) private key — use with --solana to import both curves")
|
|
394
401
|
.option("--solana <key>", "Solana (ed25519) private key — accepts hex (64), base58 (Phantom export), or JSON byte array. Use with --evm to import both curves")
|
|
395
402
|
.option("--mnemonic", "Import as mnemonic phrase instead of private key")
|
|
396
|
-
.option("--passphrase <pp>", "OWS
|
|
403
|
+
.option("--passphrase <pp>", "Master OWS passphrase (fallback: OWS_PASSPHRASE env / stdin)")
|
|
397
404
|
.option("--legacy <chain>", "Legacy mode: import to wallets.json (solana or evm)")
|
|
398
405
|
.action(async (privateKey, opts) => {
|
|
399
406
|
// Legacy mode (positional only)
|
|
@@ -475,9 +482,12 @@ export function registerWalletCommands(program, isJson, getAdapter, getPacificaA
|
|
|
475
482
|
throw new Error(`Invalid Solana key — expected hex (64 chars), base58 (Phantom export), or JSON byte array. Inner: ${msg}`);
|
|
476
483
|
}
|
|
477
484
|
};
|
|
478
|
-
// Resolve vault passphrase
|
|
479
|
-
|
|
480
|
-
|
|
485
|
+
// Resolve vault passphrase via the standard 3-path resolver (--passphrase
|
|
486
|
+
// > OWS_PASSPHRASE env > stdin pipe). When stdin is a TTY and nothing was
|
|
487
|
+
// provided, the resolver returns null; fall back to "" to preserve the
|
|
488
|
+
// historical "no passphrase" default for interactive use.
|
|
489
|
+
const { resolvePassphrase } = await import("../agent-wallet/passphrase.js");
|
|
490
|
+
const pp = (await resolvePassphrase({ flag: opts.passphrase })) ?? "";
|
|
481
491
|
let w;
|
|
482
492
|
if (opts.mnemonic) {
|
|
483
493
|
w = ows.importWalletMnemonic(opts.name, privateKey, pp);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "perp-cli",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.3",
|
|
4
4
|
"description": "Multi-DEX Perpetual Futures CLI - Pacifica, Hyperliquid, Lighter, Aster",
|
|
5
5
|
"bin": {
|
|
6
6
|
"perp": "dist/index.js",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"ink": "^6.8.0",
|
|
60
60
|
"ink-spinner": "^5.0.0",
|
|
61
61
|
"ink-table": "^3.1.0",
|
|
62
|
-
"lighter-ts-sdk": "
|
|
62
|
+
"lighter-ts-sdk": "1.0.10",
|
|
63
63
|
"react": "^19.2.4",
|
|
64
64
|
"tweetnacl": "^1.0.3",
|
|
65
65
|
"ws": "^8.19.0",
|