perp-cli 0.11.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -25
- package/dist/_stderr-filter.d.ts +0 -0
- package/dist/_stderr-filter.js +30 -0
- package/dist/agent-wallet/expiry.d.ts +15 -0
- package/dist/agent-wallet/expiry.js +26 -0
- package/dist/agent-wallet/lighter-keystore.d.ts +34 -0
- package/dist/agent-wallet/lighter-keystore.js +232 -0
- package/dist/agent-wallet/passphrase.d.ts +13 -0
- package/dist/agent-wallet/passphrase.js +54 -0
- package/dist/agent-wallet/signing-strategy.d.ts +67 -0
- package/dist/agent-wallet/signing-strategy.js +51 -0
- package/dist/agent-wallet/store.d.ts +37 -0
- package/dist/agent-wallet/store.js +172 -0
- package/dist/api/public/_http.d.ts +15 -0
- package/dist/api/public/_http.js +24 -0
- package/dist/api/public/hyperliquid.js +36 -24
- package/dist/api/public/lighter.d.ts +7 -1
- package/dist/api/public/lighter.js +85 -37
- package/dist/api/public/pacifica.js +47 -18
- package/dist/bridge-engine.js +102 -49
- package/dist/cli-spec.js +1 -2
- package/dist/commands/account.js +2 -500
- package/dist/commands/agent.d.ts +8 -2
- package/dist/commands/agent.js +1903 -366
- package/dist/commands/alerts.js +3 -1
- package/dist/commands/arb/index.js +37 -7
- package/dist/commands/arb-auto.js +44 -11
- package/dist/commands/arb.js +83 -34
- package/dist/commands/bot.d.ts +1 -1
- package/dist/commands/bot.js +18 -14
- package/dist/commands/bridge.d.ts +1 -1
- package/dist/commands/bridge.js +4 -4
- package/dist/commands/funds.d.ts +1 -1
- package/dist/commands/funds.js +18 -68
- package/dist/commands/jobs.d.ts +1 -1
- package/dist/commands/jobs.js +2 -2
- package/dist/commands/manage.d.ts +1 -1
- package/dist/commands/manage.js +12 -84
- package/dist/commands/plan.d.ts +1 -1
- package/dist/commands/plan.js +2 -2
- package/dist/commands/portfolio.d.ts +19 -3
- package/dist/commands/portfolio.js +747 -163
- package/dist/commands/rebalance.d.ts +1 -1
- package/dist/commands/rebalance.js +18 -12
- package/dist/commands/risk.js +2 -6
- package/dist/commands/settings.js +30 -53
- package/dist/commands/trade.js +16 -15
- package/dist/commands/wallet.d.ts +2 -1
- package/dist/commands/wallet.js +756 -28
- package/dist/errors.d.ts +64 -0
- package/dist/errors.js +82 -2
- package/dist/event-stream.js +12 -1
- package/dist/exchanges/aster-typed-data.d.ts +156 -0
- package/dist/exchanges/aster-typed-data.js +189 -0
- package/dist/exchanges/aster.d.ts +60 -11
- package/dist/exchanges/aster.js +278 -136
- package/dist/exchanges/hyperliquid.d.ts +14 -0
- package/dist/exchanges/hyperliquid.js +84 -34
- package/dist/exchanges/lighter-spot.js +14 -3
- package/dist/exchanges/lighter.d.ts +66 -0
- package/dist/exchanges/lighter.js +199 -27
- package/dist/exchanges/pacifica-typed-data.d.ts +75 -0
- package/dist/exchanges/pacifica-typed-data.js +74 -0
- package/dist/exchanges/pacifica.d.ts +29 -1
- package/dist/exchanges/pacifica.js +94 -6
- package/dist/funding/rates.js +59 -21
- package/dist/guardrail/perp-guardrail.js +9 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +268 -360
- package/dist/jobs.js +3 -1
- package/dist/mcp-server.js +85 -83
- package/dist/position-history.js +47 -4
- package/dist/settings.d.ts +67 -3
- package/dist/settings.js +1 -2
- package/dist/signer/ows-evm.d.ts +14 -0
- package/dist/signer/ows-evm.js +28 -9
- package/dist/smart-order.d.ts +8 -2
- package/dist/smart-order.js +46 -11
- package/dist/strategies/funding-arb.js +29 -8
- package/dist/trade-validator.js +30 -5
- package/dist/utils.d.ts +10 -0
- package/dist/utils.js +19 -0
- package/package.json +2 -2
- package/skills/perp-cli/SKILL.md +23 -14
- package/skills/perp-cli/references/agent-operations.md +6 -4
- package/skills/perp-cli/references/commands.md +55 -34
- package/skills/perp-cli/scripts/preflight.sh +4 -4
- package/skills/perp-cli/scripts/spot-perp-scan.sh +2 -2
- package/dist/__tests__/alert-logic.test.d.ts +0 -1
- package/dist/__tests__/alert-logic.test.js +0 -107
- package/dist/__tests__/arb-auto-3dex.test.d.ts +0 -1
- package/dist/__tests__/arb-auto-3dex.test.js +0 -397
- package/dist/__tests__/arb-history-stats.test.d.ts +0 -1
- package/dist/__tests__/arb-history-stats.test.js +0 -176
- package/dist/__tests__/arb-logic.test.d.ts +0 -1
- package/dist/__tests__/arb-logic.test.js +0 -84
- package/dist/__tests__/arb-manage.test.d.ts +0 -1
- package/dist/__tests__/arb-manage.test.js +0 -253
- package/dist/__tests__/arb-new-features.test.d.ts +0 -1
- package/dist/__tests__/arb-new-features.test.js +0 -457
- package/dist/__tests__/arb-sizing.test.d.ts +0 -1
- package/dist/__tests__/arb-sizing.test.js +0 -48
- package/dist/__tests__/arb-state.test.d.ts +0 -1
- package/dist/__tests__/arb-state.test.js +0 -284
- package/dist/__tests__/arb-userflow.test.d.ts +0 -1
- package/dist/__tests__/arb-userflow.test.js +0 -945
- package/dist/__tests__/arb-utils.test.d.ts +0 -1
- package/dist/__tests__/arb-utils.test.js +0 -264
- package/dist/__tests__/bot-conditions.test.d.ts +0 -1
- package/dist/__tests__/bot-conditions.test.js +0 -341
- package/dist/__tests__/client-id-tracker.test.d.ts +0 -1
- package/dist/__tests__/client-id-tracker.test.js +0 -137
- package/dist/__tests__/commands/new-atomic-commands.test.d.ts +0 -1
- package/dist/__tests__/commands/new-atomic-commands.test.js +0 -502
- package/dist/__tests__/commands/order-intent.test.d.ts +0 -1
- package/dist/__tests__/commands/order-intent.test.js +0 -600
- package/dist/__tests__/commands/trade-commands.test.d.ts +0 -1
- package/dist/__tests__/commands/trade-commands.test.js +0 -821
- package/dist/__tests__/config.test.d.ts +0 -1
- package/dist/__tests__/config.test.js +0 -86
- package/dist/__tests__/cross-chain-margin.test.d.ts +0 -1
- package/dist/__tests__/cross-chain-margin.test.js +0 -287
- package/dist/__tests__/dex-asset-map.test.d.ts +0 -1
- package/dist/__tests__/dex-asset-map.test.js +0 -191
- package/dist/__tests__/errors.test.d.ts +0 -1
- package/dist/__tests__/errors.test.js +0 -110
- package/dist/__tests__/event-stream.test.d.ts +0 -1
- package/dist/__tests__/event-stream.test.js +0 -276
- package/dist/__tests__/exchanges/interface.test.d.ts +0 -1
- package/dist/__tests__/exchanges/interface.test.js +0 -132
- package/dist/__tests__/exchanges/mock-adapter.d.ts +0 -69
- package/dist/__tests__/exchanges/mock-adapter.js +0 -137
- package/dist/__tests__/execution-log.test.d.ts +0 -1
- package/dist/__tests__/execution-log.test.js +0 -106
- package/dist/__tests__/funding-calc.test.d.ts +0 -1
- package/dist/__tests__/funding-calc.test.js +0 -71
- package/dist/__tests__/funding-history.test.d.ts +0 -1
- package/dist/__tests__/funding-history.test.js +0 -343
- package/dist/__tests__/funding-rates.test.d.ts +0 -1
- package/dist/__tests__/funding-rates.test.js +0 -342
- package/dist/__tests__/funding.test.d.ts +0 -1
- package/dist/__tests__/funding.test.js +0 -173
- package/dist/__tests__/gap-logic.test.d.ts +0 -1
- package/dist/__tests__/gap-logic.test.js +0 -43
- package/dist/__tests__/hip3-dex.test.d.ts +0 -1
- package/dist/__tests__/hip3-dex.test.js +0 -234
- package/dist/__tests__/integration/agent-features.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/agent-features.integration.test.js +0 -553
- package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +0 -13
- package/dist/__tests__/integration/atomic-commands.integration.test.js +0 -246
- package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/bridge-simulation.integration.test.js +0 -453
- package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/bridge-strict.integration.test.js +0 -812
- package/dist/__tests__/integration/bridge.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/bridge.integration.test.js +0 -309
- package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/cli-e2e.integration.test.js +0 -202
- package/dist/__tests__/integration/dex-arb.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/dex-arb.integration.test.js +0 -116
- package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +0 -13
- package/dist/__tests__/integration/envelope-consistency.integration.test.js +0 -205
- package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/hip3-dex.integration.test.js +0 -147
- package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/hyperliquid.integration.test.js +0 -79
- package/dist/__tests__/integration/lighter.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/lighter.integration.test.js +0 -53
- package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +0 -9
- package/dist/__tests__/integration/new-commands-e2e.integration.test.js +0 -236
- package/dist/__tests__/integration/order-verification.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/order-verification.integration.test.js +0 -321
- package/dist/__tests__/integration/pacifica.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/pacifica.integration.test.js +0 -75
- package/dist/__tests__/integration/response-shapes.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/response-shapes.integration.test.js +0 -278
- package/dist/__tests__/liquidity.test.d.ts +0 -1
- package/dist/__tests__/liquidity.test.js +0 -225
- package/dist/__tests__/plan-executor.test.d.ts +0 -1
- package/dist/__tests__/plan-executor.test.js +0 -314
- package/dist/__tests__/position-history.test.d.ts +0 -1
- package/dist/__tests__/position-history.test.js +0 -367
- package/dist/__tests__/retry.test.d.ts +0 -1
- package/dist/__tests__/retry.test.js +0 -310
- package/dist/__tests__/risk-assessment.test.d.ts +0 -1
- package/dist/__tests__/risk-assessment.test.js +0 -288
- package/dist/__tests__/security-adversarial.test.d.ts +0 -1
- package/dist/__tests__/security-adversarial.test.js +0 -574
- package/dist/__tests__/strategies.test.d.ts +0 -1
- package/dist/__tests__/strategies.test.js +0 -539
- package/dist/__tests__/trade-execution.test.d.ts +0 -1
- package/dist/__tests__/trade-execution.test.js +0 -129
- package/dist/__tests__/trade-validator.test.d.ts +0 -1
- package/dist/__tests__/trade-validator.test.js +0 -655
- package/dist/__tests__/utils.test.d.ts +0 -1
- package/dist/__tests__/utils.test.js +0 -76
- package/dist/arb/reconcile.d.ts +0 -28
- package/dist/arb/reconcile.js +0 -162
- package/dist/commands/alert.d.ts +0 -2
- package/dist/commands/alert.js +0 -421
- package/dist/commands/analytics.d.ts +0 -3
- package/dist/commands/analytics.js +0 -311
- package/dist/commands/arb-manage.d.ts +0 -5
- package/dist/commands/arb-manage.js +0 -5
- package/dist/commands/dashboard.d.ts +0 -3
- package/dist/commands/dashboard.js +0 -176
- package/dist/commands/deposit.d.ts +0 -4
- package/dist/commands/deposit.js +0 -573
- package/dist/commands/dex.d.ts +0 -3
- package/dist/commands/dex.js +0 -120
- package/dist/commands/env.d.ts +0 -2
- package/dist/commands/env.js +0 -136
- package/dist/commands/funding.d.ts +0 -3
- package/dist/commands/funding.js +0 -554
- package/dist/commands/gap.d.ts +0 -2
- package/dist/commands/gap.js +0 -305
- package/dist/commands/health.d.ts +0 -2
- package/dist/commands/health.js +0 -67
- package/dist/commands/multileg.d.ts +0 -16
- package/dist/commands/multileg.js +0 -174
- package/dist/commands/ows.d.ts +0 -2
- package/dist/commands/ows.js +0 -887
- package/dist/commands/pnl.d.ts +0 -12
- package/dist/commands/pnl.js +0 -243
- package/dist/commands/run.d.ts +0 -3
- package/dist/commands/run.js +0 -202
- package/dist/commands/stream.d.ts +0 -5
- package/dist/commands/stream.js +0 -200
- package/dist/commands/withdraw.d.ts +0 -3
- package/dist/commands/withdraw.js +0 -187
- package/dist/exchanges/edgex.d.ts +0 -62
- package/dist/exchanges/edgex.js +0 -552
- package/dist/exchanges/nado.d.ts +0 -52
- package/dist/exchanges/nado.js +0 -223
- package/dist/funding/index.d.ts +0 -3
- package/dist/funding/index.js +0 -3
- package/dist/retry.d.ts +0 -74
- package/dist/retry.js +0 -129
- package/dist/ws/market-feed.d.ts +0 -74
- package/dist/ws/market-feed.js +0 -227
package/dist/commands/ows.js
DELETED
|
@@ -1,887 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
|
-
import { formatUsd, makeTable, printJson, jsonOk } from "../utils.js";
|
|
4
|
-
const _require = createRequire(import.meta.url);
|
|
5
|
-
function loadOws() {
|
|
6
|
-
return _require("@open-wallet-standard/core");
|
|
7
|
-
}
|
|
8
|
-
// ── Balance helpers (reused from wallet module) ──
|
|
9
|
-
async function getSolanaBalances(address, isTestnet) {
|
|
10
|
-
const { Connection, PublicKey } = await import("@solana/web3.js");
|
|
11
|
-
const rpcUrl = isTestnet ? "https://api.devnet.solana.com" : "https://api.mainnet-beta.solana.com";
|
|
12
|
-
const connection = new Connection(rpcUrl);
|
|
13
|
-
const pubkey = new PublicKey(address);
|
|
14
|
-
const balances = [];
|
|
15
|
-
const solLamports = await connection.getBalance(pubkey);
|
|
16
|
-
const solBalance = solLamports / 1e9;
|
|
17
|
-
balances.push({ token: "SOL", balance: solBalance.toFixed(6), usdValue: "" });
|
|
18
|
-
const usdcMint = isTestnet ? "USDPqRbLidFGufty2s3oizmDEKdqx7ePTqzDMbf5ZKM" : "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
19
|
-
try {
|
|
20
|
-
const tokenAccounts = await connection.getParsedTokenAccountsByOwner(pubkey, { mint: new PublicKey(usdcMint) });
|
|
21
|
-
let usdcBalance = 0;
|
|
22
|
-
for (const { account } of tokenAccounts.value) {
|
|
23
|
-
const parsed = account.data.parsed?.info;
|
|
24
|
-
if (parsed)
|
|
25
|
-
usdcBalance += Number(parsed.tokenAmount?.uiAmount ?? 0);
|
|
26
|
-
}
|
|
27
|
-
balances.push({ token: isTestnet ? "USDP" : "USDC", balance: usdcBalance.toFixed(2), usdValue: `$${formatUsd(usdcBalance)}` });
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
balances.push({ token: isTestnet ? "USDP" : "USDC", balance: "0.00", usdValue: "$0.00" });
|
|
31
|
-
}
|
|
32
|
-
try {
|
|
33
|
-
const res = await fetch("https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd");
|
|
34
|
-
const json = (await res.json());
|
|
35
|
-
balances[0].usdValue = `$${formatUsd(solBalance * (json.solana?.usd ?? 0))}`;
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
balances[0].usdValue = "-";
|
|
39
|
-
}
|
|
40
|
-
return balances;
|
|
41
|
-
}
|
|
42
|
-
async function getEvmBalances(address, isTestnet) {
|
|
43
|
-
const { ethers } = await import("ethers");
|
|
44
|
-
const rpcUrl = isTestnet ? "https://sepolia-rollup.arbitrum.io/rpc" : "https://arb1.arbitrum.io/rpc";
|
|
45
|
-
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
46
|
-
const balances = [];
|
|
47
|
-
const ethBal = await provider.getBalance(address);
|
|
48
|
-
const ethBalance = Number(ethers.formatEther(ethBal));
|
|
49
|
-
balances.push({ token: "ETH", balance: ethBalance.toFixed(6), usdValue: "" });
|
|
50
|
-
const usdcAddr = isTestnet ? "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d" : "0xaf88d065e77c8cC2239327C5EDb3A432268e5831";
|
|
51
|
-
try {
|
|
52
|
-
const usdc = new ethers.Contract(usdcAddr, ["function balanceOf(address) view returns (uint256)"], provider);
|
|
53
|
-
const rawBal = await usdc.balanceOf(address);
|
|
54
|
-
const usdcBalance = Number(ethers.formatUnits(rawBal, 6));
|
|
55
|
-
balances.push({ token: "USDC", balance: usdcBalance.toFixed(2), usdValue: `$${formatUsd(usdcBalance)}` });
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
balances.push({ token: "USDC", balance: "0.00", usdValue: "$0.00" });
|
|
59
|
-
}
|
|
60
|
-
try {
|
|
61
|
-
const res = await fetch("https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd");
|
|
62
|
-
const json = (await res.json());
|
|
63
|
-
balances[0].usdValue = `$${formatUsd(ethBalance * (json.ethereum?.usd ?? 0))}`;
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
balances[0].usdValue = "-";
|
|
67
|
-
}
|
|
68
|
-
return balances;
|
|
69
|
-
}
|
|
70
|
-
// ── OWS CLI subprocess helper ──
|
|
71
|
-
async function runOwsCli(args, timeout = 30000) {
|
|
72
|
-
const { resolve: pathResolve } = await import("path");
|
|
73
|
-
const { existsSync } = await import("fs");
|
|
74
|
-
const { spawnSync } = await import("child_process");
|
|
75
|
-
const owsBin = [
|
|
76
|
-
pathResolve(process.env.HOME || "~", ".ows", "bin", "ows"),
|
|
77
|
-
"/usr/local/bin/ows",
|
|
78
|
-
].find(p => existsSync(p));
|
|
79
|
-
if (!owsBin) {
|
|
80
|
-
throw new Error("OWS CLI not found. Install: curl -fsSL https://docs.openwallet.sh/install.sh | bash");
|
|
81
|
-
}
|
|
82
|
-
const owsEnv = { ...process.env, PATH: `${pathResolve(process.env.HOME || "~", ".ows", "bin")}:${process.env.PATH}` };
|
|
83
|
-
const proc = spawnSync(owsBin, args, { encoding: "utf-8", timeout, env: owsEnv });
|
|
84
|
-
if (proc.error)
|
|
85
|
-
throw new Error(`OWS CLI failed: ${proc.error.message}`);
|
|
86
|
-
return { stdout: (proc.stdout || "").trim(), stderr: (proc.stderr || "").trim() };
|
|
87
|
-
}
|
|
88
|
-
// ── Register OWS commands ──
|
|
89
|
-
export function registerOwsCommands(program, isJson) {
|
|
90
|
-
const ows = program.command("ows").description("Open Wallet Standard — encrypted vault, policy engine, agent access");
|
|
91
|
-
// ── Wallet CRUD ──
|
|
92
|
-
ows
|
|
93
|
-
.command("create <name>")
|
|
94
|
-
.description("Create a new OWS wallet (multi-chain: EVM + Solana)")
|
|
95
|
-
.option("--words <count>", "Mnemonic word count (12 or 24)", "12")
|
|
96
|
-
.action(async (name, opts) => {
|
|
97
|
-
try {
|
|
98
|
-
const o = loadOws();
|
|
99
|
-
const w = o.createWallet(name, "", parseInt(opts.words));
|
|
100
|
-
if (isJson())
|
|
101
|
-
return printJson(jsonOk({ id: w.id, name: w.name, accounts: w.accounts, createdAt: w.createdAt }));
|
|
102
|
-
console.log(chalk.cyan.bold("\n OWS Wallet Created\n"));
|
|
103
|
-
console.log(` Name: ${chalk.white.bold(w.name)}`);
|
|
104
|
-
console.log(` ID: ${chalk.gray(w.id)}`);
|
|
105
|
-
console.log();
|
|
106
|
-
for (const acct of w.accounts) {
|
|
107
|
-
const chain = acct.chainId.split(":")[0];
|
|
108
|
-
console.log(` ${chalk.cyan(chain.padEnd(10))} ${chalk.green(acct.address)}`);
|
|
109
|
-
}
|
|
110
|
-
console.log(chalk.gray(`\n Vault: ~/.ows/`));
|
|
111
|
-
console.log(chalk.cyan(`\n Usage: perp --ows ${name} -e hl account balance\n`));
|
|
112
|
-
}
|
|
113
|
-
catch (e) {
|
|
114
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
115
|
-
if (isJson()) {
|
|
116
|
-
const { jsonError } = await import("../utils.js");
|
|
117
|
-
return printJson(jsonError("OWS_ERROR", msg));
|
|
118
|
-
}
|
|
119
|
-
console.error(chalk.red(`\n OWS error: ${msg}\n`));
|
|
120
|
-
process.exit(1);
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
ows
|
|
124
|
-
.command("list")
|
|
125
|
-
.description("List all OWS wallets in the vault")
|
|
126
|
-
.action(async () => {
|
|
127
|
-
try {
|
|
128
|
-
const o = loadOws();
|
|
129
|
-
const wallets = o.listWallets();
|
|
130
|
-
if (isJson())
|
|
131
|
-
return printJson(jsonOk({ wallets }));
|
|
132
|
-
if (wallets.length === 0) {
|
|
133
|
-
console.log(chalk.gray("\n No OWS wallets found."));
|
|
134
|
-
console.log(chalk.gray(` Create one: ${chalk.cyan("perp ows create <name>")}\n`));
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
console.log(chalk.cyan.bold("\n OWS Vault Wallets\n"));
|
|
138
|
-
const rows = wallets.map((w) => {
|
|
139
|
-
const evmAddr = w.accounts.find((a) => a.chainId.startsWith("eip155:"))?.address ?? "-";
|
|
140
|
-
const solAddr = w.accounts.find((a) => a.chainId.startsWith("solana:"))?.address ?? "-";
|
|
141
|
-
return [
|
|
142
|
-
chalk.white.bold(w.name),
|
|
143
|
-
chalk.green(evmAddr.slice(0, 10) + "..." + evmAddr.slice(-4)),
|
|
144
|
-
chalk.green(solAddr.slice(0, 6) + "..." + solAddr.slice(-4)),
|
|
145
|
-
chalk.gray(w.createdAt.split("T")[0]),
|
|
146
|
-
];
|
|
147
|
-
});
|
|
148
|
-
console.log(makeTable(["Name", "EVM Address", "Solana Address", "Created"], rows));
|
|
149
|
-
console.log(chalk.gray(`\n Usage: perp --ows <name> -e <exchange> <command>\n`));
|
|
150
|
-
}
|
|
151
|
-
catch (e) {
|
|
152
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
153
|
-
if (isJson()) {
|
|
154
|
-
const { jsonError } = await import("../utils.js");
|
|
155
|
-
return printJson(jsonError("OWS_ERROR", msg));
|
|
156
|
-
}
|
|
157
|
-
console.error(chalk.red(`\n OWS error: ${msg}\n`));
|
|
158
|
-
process.exit(1);
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
ows
|
|
162
|
-
.command("info <name>")
|
|
163
|
-
.description("Show detailed OWS wallet info")
|
|
164
|
-
.action(async (name) => {
|
|
165
|
-
try {
|
|
166
|
-
const o = loadOws();
|
|
167
|
-
const w = o.getWallet(name);
|
|
168
|
-
if (isJson())
|
|
169
|
-
return printJson(jsonOk(w));
|
|
170
|
-
console.log(chalk.cyan.bold(`\n OWS Wallet: ${w.name}\n`));
|
|
171
|
-
console.log(` ID: ${chalk.gray(w.id)}`);
|
|
172
|
-
console.log(` Created: ${chalk.gray(w.createdAt)}`);
|
|
173
|
-
console.log();
|
|
174
|
-
for (const acct of w.accounts) {
|
|
175
|
-
console.log(` ${chalk.cyan(acct.chainId.padEnd(40))} ${chalk.green(acct.address)}`);
|
|
176
|
-
console.log(` ${chalk.gray(" ".repeat(40) + acct.derivationPath)}`);
|
|
177
|
-
}
|
|
178
|
-
console.log();
|
|
179
|
-
}
|
|
180
|
-
catch (e) {
|
|
181
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
182
|
-
if (isJson()) {
|
|
183
|
-
const { jsonError } = await import("../utils.js");
|
|
184
|
-
return printJson(jsonError("OWS_ERROR", msg));
|
|
185
|
-
}
|
|
186
|
-
console.error(chalk.red(`\n OWS error: ${msg}\n`));
|
|
187
|
-
process.exit(1);
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
ows
|
|
191
|
-
.command("delete <name>")
|
|
192
|
-
.description("Delete an OWS wallet from the vault")
|
|
193
|
-
.action(async (name) => {
|
|
194
|
-
try {
|
|
195
|
-
const o = loadOws();
|
|
196
|
-
const w = o.getWallet(name);
|
|
197
|
-
o.deleteWallet(name);
|
|
198
|
-
if (isJson())
|
|
199
|
-
return printJson(jsonOk({ deleted: name, id: w.id }));
|
|
200
|
-
console.log(chalk.yellow(`\n OWS wallet "${name}" deleted from vault.\n`));
|
|
201
|
-
}
|
|
202
|
-
catch (e) {
|
|
203
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
204
|
-
if (isJson()) {
|
|
205
|
-
const { jsonError } = await import("../utils.js");
|
|
206
|
-
return printJson(jsonError("OWS_ERROR", msg));
|
|
207
|
-
}
|
|
208
|
-
console.error(chalk.red(`\n OWS error: ${msg}\n`));
|
|
209
|
-
process.exit(1);
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
// ── Policy Engine ──
|
|
213
|
-
const policyCmd = ows.command("policy").description("Policy engine — guardrails for agent signing");
|
|
214
|
-
policyCmd
|
|
215
|
-
.command("create")
|
|
216
|
-
.description("Create a signing policy")
|
|
217
|
-
.requiredOption("--id <id>", "Unique policy ID")
|
|
218
|
-
.requiredOption("--name <name>", "Human-readable policy name")
|
|
219
|
-
.option("--chains <chains>", "Comma-separated allowed CAIP-2 chain IDs")
|
|
220
|
-
.option("--expires <iso>", "Policy expiry (ISO-8601 timestamp)")
|
|
221
|
-
.option("--executable <path>", "Path to custom policy executable")
|
|
222
|
-
.option("--perp-defaults", "Auto-configure with perp-cli guardrail (DEX contracts + spending limits)")
|
|
223
|
-
.option("--max-tx-usd <amount>", "Max per-transaction USD", "1000")
|
|
224
|
-
.option("--max-daily-usd <amount>", "Max daily spending USD", "5000")
|
|
225
|
-
.option("--max-withdraw-usd <amount>", "Max per-withdrawal USD", "500")
|
|
226
|
-
.option("--max-daily-withdraw-usd <amount>", "Max daily withdrawal USD", "2000")
|
|
227
|
-
.option("--block-approve", "Block approve() and transferFrom() calls")
|
|
228
|
-
.action(async (opts) => {
|
|
229
|
-
try {
|
|
230
|
-
const o = loadOws();
|
|
231
|
-
const rules = [];
|
|
232
|
-
if (opts.perpDefaults) {
|
|
233
|
-
const { ALLOWED_CHAINS } = await import("../guardrail/contracts.js");
|
|
234
|
-
rules.push({ type: "allowed_chains", chain_ids: ALLOWED_CHAINS });
|
|
235
|
-
}
|
|
236
|
-
else if (opts.chains) {
|
|
237
|
-
rules.push({ type: "allowed_chains", chain_ids: opts.chains.split(",").map(s => s.trim()) });
|
|
238
|
-
}
|
|
239
|
-
if (opts.expires) {
|
|
240
|
-
rules.push({ type: "expires_at", timestamp: opts.expires });
|
|
241
|
-
}
|
|
242
|
-
let executablePath = opts.executable;
|
|
243
|
-
let config;
|
|
244
|
-
if (opts.perpDefaults) {
|
|
245
|
-
const { resolve: pathResolve } = await import("path");
|
|
246
|
-
try {
|
|
247
|
-
const { createRequire: cr } = await import("node:module");
|
|
248
|
-
const req = cr(import.meta.url);
|
|
249
|
-
executablePath = pathResolve(req.resolve("perp-cli/dist/guardrail/perp-guardrail.js"));
|
|
250
|
-
}
|
|
251
|
-
catch {
|
|
252
|
-
executablePath = pathResolve(process.cwd(), "node_modules/.bin/perp-guardrail");
|
|
253
|
-
}
|
|
254
|
-
config = {
|
|
255
|
-
max_tx_usd: parseInt(opts.maxTxUsd || "1000"),
|
|
256
|
-
max_daily_usd: parseInt(opts.maxDailyUsd || "5000"),
|
|
257
|
-
max_withdraw_usd: parseInt(opts.maxWithdrawUsd || "500"),
|
|
258
|
-
max_daily_withdraw_usd: parseInt(opts.maxDailyWithdrawUsd || "2000"),
|
|
259
|
-
...(opts.blockApprove ? { block_selectors: ["0x095ea7b3", "0x23b872dd"] } : {}),
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
const policyDef = {
|
|
263
|
-
id: opts.id, name: opts.name, version: 1, created_at: new Date().toISOString(),
|
|
264
|
-
rules, executable: executablePath || null, config: config || null, action: "deny",
|
|
265
|
-
};
|
|
266
|
-
o.createPolicy(JSON.stringify(policyDef));
|
|
267
|
-
if (isJson())
|
|
268
|
-
return printJson(jsonOk(policyDef));
|
|
269
|
-
console.log(chalk.green(`\n Policy "${opts.name}" created (${opts.id})`));
|
|
270
|
-
if (opts.chains || opts.perpDefaults) {
|
|
271
|
-
const chainRule = rules.find(r => r.type === "allowed_chains");
|
|
272
|
-
if (chainRule)
|
|
273
|
-
console.log(` Chains: ${chalk.cyan(chainRule.chain_ids.length + " chains")}`);
|
|
274
|
-
}
|
|
275
|
-
if (opts.expires)
|
|
276
|
-
console.log(` Expires: ${chalk.yellow(opts.expires)}`);
|
|
277
|
-
if (executablePath)
|
|
278
|
-
console.log(` Executable: ${chalk.gray(executablePath)}`);
|
|
279
|
-
if (config)
|
|
280
|
-
console.log(` Limits: ${chalk.cyan(`$${config.max_tx_usd}/tx, $${config.max_daily_usd}/day`)}`);
|
|
281
|
-
console.log();
|
|
282
|
-
}
|
|
283
|
-
catch (e) {
|
|
284
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
285
|
-
if (isJson()) {
|
|
286
|
-
const { jsonError } = await import("../utils.js");
|
|
287
|
-
return printJson(jsonError("OWS_ERROR", msg));
|
|
288
|
-
}
|
|
289
|
-
console.error(chalk.red(`\n OWS error: ${msg}\n`));
|
|
290
|
-
process.exit(1);
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
policyCmd
|
|
294
|
-
.command("list")
|
|
295
|
-
.description("List all policies")
|
|
296
|
-
.action(async () => {
|
|
297
|
-
try {
|
|
298
|
-
const o = loadOws();
|
|
299
|
-
const policies = o.listPolicies();
|
|
300
|
-
if (isJson())
|
|
301
|
-
return printJson(jsonOk({ policies }));
|
|
302
|
-
if (policies.length === 0) {
|
|
303
|
-
console.log(chalk.gray("\n No policies defined.\n"));
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
console.log(chalk.cyan.bold("\n OWS Policies\n"));
|
|
307
|
-
const rows = policies.map(p => {
|
|
308
|
-
const ruleTypes = p.rules.map(r => r.type).join(", ");
|
|
309
|
-
return [chalk.white.bold(p.id), p.name, chalk.cyan(ruleTypes), chalk.gray(p.created_at?.split("T")[0] ?? "-")];
|
|
310
|
-
});
|
|
311
|
-
console.log(makeTable(["ID", "Name", "Rules", "Created"], rows));
|
|
312
|
-
console.log();
|
|
313
|
-
}
|
|
314
|
-
catch (e) {
|
|
315
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
316
|
-
if (isJson()) {
|
|
317
|
-
const { jsonError } = await import("../utils.js");
|
|
318
|
-
return printJson(jsonError("OWS_ERROR", msg));
|
|
319
|
-
}
|
|
320
|
-
console.error(chalk.red(`\n OWS error: ${msg}\n`));
|
|
321
|
-
process.exit(1);
|
|
322
|
-
}
|
|
323
|
-
});
|
|
324
|
-
policyCmd
|
|
325
|
-
.command("show <id>")
|
|
326
|
-
.description("Show policy details")
|
|
327
|
-
.action(async (id) => {
|
|
328
|
-
try {
|
|
329
|
-
const o = loadOws();
|
|
330
|
-
const p = o.getPolicy(id);
|
|
331
|
-
if (isJson())
|
|
332
|
-
return printJson(jsonOk(p));
|
|
333
|
-
console.log(chalk.cyan.bold(`\n Policy: ${id}\n`));
|
|
334
|
-
console.log(JSON.stringify(p, null, 2));
|
|
335
|
-
console.log();
|
|
336
|
-
}
|
|
337
|
-
catch (e) {
|
|
338
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
339
|
-
if (isJson()) {
|
|
340
|
-
const { jsonError } = await import("../utils.js");
|
|
341
|
-
return printJson(jsonError("OWS_ERROR", msg));
|
|
342
|
-
}
|
|
343
|
-
console.error(chalk.red(`\n OWS error: ${msg}\n`));
|
|
344
|
-
process.exit(1);
|
|
345
|
-
}
|
|
346
|
-
});
|
|
347
|
-
policyCmd
|
|
348
|
-
.command("delete <id>")
|
|
349
|
-
.description("Delete a policy")
|
|
350
|
-
.action(async (id) => {
|
|
351
|
-
try {
|
|
352
|
-
const o = loadOws();
|
|
353
|
-
o.deletePolicy(id);
|
|
354
|
-
if (isJson())
|
|
355
|
-
return printJson(jsonOk({ deleted: id }));
|
|
356
|
-
console.log(chalk.yellow(`\n Policy "${id}" deleted.\n`));
|
|
357
|
-
}
|
|
358
|
-
catch (e) {
|
|
359
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
360
|
-
if (isJson()) {
|
|
361
|
-
const { jsonError } = await import("../utils.js");
|
|
362
|
-
return printJson(jsonError("OWS_ERROR", msg));
|
|
363
|
-
}
|
|
364
|
-
console.error(chalk.red(`\n OWS error: ${msg}\n`));
|
|
365
|
-
process.exit(1);
|
|
366
|
-
}
|
|
367
|
-
});
|
|
368
|
-
// ── API Keys ──
|
|
369
|
-
const keyCmd = ows.command("key").description("API key management — scoped agent access tokens");
|
|
370
|
-
keyCmd
|
|
371
|
-
.command("create")
|
|
372
|
-
.description("Create an API key for policy-gated agent signing")
|
|
373
|
-
.requiredOption("--name <name>", "Key name (e.g. trading-bot)")
|
|
374
|
-
.requiredOption("--wallets <names>", "Comma-separated OWS wallet names")
|
|
375
|
-
.option("--policy <ids>", "Comma-separated policy IDs to enforce")
|
|
376
|
-
.option("--expires <iso>", "Key expiry (ISO-8601)")
|
|
377
|
-
.option("-p, --passphrase <pass>", "Vault passphrase", "")
|
|
378
|
-
.action(async (opts) => {
|
|
379
|
-
try {
|
|
380
|
-
const o = loadOws();
|
|
381
|
-
const walletIds = opts.wallets.split(",").map(s => s.trim());
|
|
382
|
-
const policyIds = opts.policy ? opts.policy.split(",").map(s => s.trim()) : [];
|
|
383
|
-
const result = o.createApiKey(opts.name, walletIds, policyIds, opts.passphrase, opts.expires);
|
|
384
|
-
if (isJson())
|
|
385
|
-
return printJson(jsonOk({ id: result.id, name: result.name, token: result.token, wallets: walletIds, policies: policyIds, expires: opts.expires ?? null }));
|
|
386
|
-
console.log(chalk.green.bold("\n OWS API Key Created\n"));
|
|
387
|
-
console.log(` Name: ${chalk.white.bold(result.name)}`);
|
|
388
|
-
console.log(` ID: ${chalk.gray(result.id)}`);
|
|
389
|
-
console.log(` Wallets: ${chalk.cyan(walletIds.join(", "))}`);
|
|
390
|
-
if (policyIds.length)
|
|
391
|
-
console.log(` Policies: ${chalk.cyan(policyIds.join(", "))}`);
|
|
392
|
-
if (opts.expires)
|
|
393
|
-
console.log(` Expires: ${chalk.yellow(opts.expires)}`);
|
|
394
|
-
console.log();
|
|
395
|
-
console.log(` Token: ${chalk.yellow.bold(result.token)}`);
|
|
396
|
-
console.log(chalk.red("\n Save this token — it will not be shown again!\n"));
|
|
397
|
-
console.log(chalk.gray(" Usage: perp --ows <wallet> --ows-key <token> -e hl trade ..."));
|
|
398
|
-
console.log(chalk.gray(" or: OWS_API_KEY=<token> perp --ows <wallet> -e hl trade ...\n"));
|
|
399
|
-
}
|
|
400
|
-
catch (e) {
|
|
401
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
402
|
-
if (isJson()) {
|
|
403
|
-
const { jsonError } = await import("../utils.js");
|
|
404
|
-
return printJson(jsonError("OWS_ERROR", msg));
|
|
405
|
-
}
|
|
406
|
-
console.error(chalk.red(`\n OWS error: ${msg}\n`));
|
|
407
|
-
process.exit(1);
|
|
408
|
-
}
|
|
409
|
-
});
|
|
410
|
-
keyCmd
|
|
411
|
-
.command("list")
|
|
412
|
-
.description("List all API keys (tokens are never displayed)")
|
|
413
|
-
.action(async () => {
|
|
414
|
-
try {
|
|
415
|
-
const o = loadOws();
|
|
416
|
-
const keys = o.listApiKeys();
|
|
417
|
-
if (isJson())
|
|
418
|
-
return printJson(jsonOk({ keys }));
|
|
419
|
-
if (keys.length === 0) {
|
|
420
|
-
console.log(chalk.gray("\n No API keys found.\n"));
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
console.log(chalk.cyan.bold("\n OWS API Keys\n"));
|
|
424
|
-
const rows = keys.map(k => [
|
|
425
|
-
chalk.white.bold(k.name), chalk.gray(k.id), chalk.gray(k.created_at?.split("T")[0] ?? "-"),
|
|
426
|
-
]);
|
|
427
|
-
console.log(makeTable(["Name", "ID", "Created"], rows));
|
|
428
|
-
console.log();
|
|
429
|
-
}
|
|
430
|
-
catch (e) {
|
|
431
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
432
|
-
if (isJson()) {
|
|
433
|
-
const { jsonError } = await import("../utils.js");
|
|
434
|
-
return printJson(jsonError("OWS_ERROR", msg));
|
|
435
|
-
}
|
|
436
|
-
console.error(chalk.red(`\n OWS error: ${msg}\n`));
|
|
437
|
-
process.exit(1);
|
|
438
|
-
}
|
|
439
|
-
});
|
|
440
|
-
keyCmd
|
|
441
|
-
.command("revoke <id>")
|
|
442
|
-
.description("Revoke an API key (token becomes permanently invalid)")
|
|
443
|
-
.action(async (id) => {
|
|
444
|
-
try {
|
|
445
|
-
const o = loadOws();
|
|
446
|
-
o.revokeApiKey(id);
|
|
447
|
-
if (isJson())
|
|
448
|
-
return printJson(jsonOk({ revoked: id }));
|
|
449
|
-
console.log(chalk.yellow(`\n API key "${id}" revoked.\n`));
|
|
450
|
-
}
|
|
451
|
-
catch (e) {
|
|
452
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
453
|
-
if (isJson()) {
|
|
454
|
-
const { jsonError } = await import("../utils.js");
|
|
455
|
-
return printJson(jsonError("OWS_ERROR", msg));
|
|
456
|
-
}
|
|
457
|
-
console.error(chalk.red(`\n OWS error: ${msg}\n`));
|
|
458
|
-
process.exit(1);
|
|
459
|
-
}
|
|
460
|
-
});
|
|
461
|
-
// ── Fund (MoonPay + balance) ──
|
|
462
|
-
const fundCmd = ows.command("fund").description("Funding — on-ramp & balance via OWS");
|
|
463
|
-
fundCmd
|
|
464
|
-
.command("balance <walletName>")
|
|
465
|
-
.description("Check on-chain token balance for an OWS wallet")
|
|
466
|
-
.requiredOption("-c, --chain <chain>", "Chain: evm, base, arbitrum, solana, etc.")
|
|
467
|
-
.action(async (walletName, opts) => {
|
|
468
|
-
try {
|
|
469
|
-
const o = loadOws();
|
|
470
|
-
const w = o.getWallet(walletName);
|
|
471
|
-
const chainMap = {
|
|
472
|
-
evm: "eip155:", ethereum: "eip155:", base: "eip155:", arbitrum: "eip155:",
|
|
473
|
-
solana: "solana:", bitcoin: "bip122:", cosmos: "cosmos:",
|
|
474
|
-
tron: "tron:", ton: "ton:", sui: "sui:", filecoin: "fil:",
|
|
475
|
-
};
|
|
476
|
-
const prefix = chainMap[opts.chain.toLowerCase()] ?? opts.chain;
|
|
477
|
-
const account = w.accounts.find((a) => a.chainId.startsWith(prefix));
|
|
478
|
-
if (!account)
|
|
479
|
-
throw new Error(`No account found for chain "${opts.chain}" in wallet "${walletName}"`);
|
|
480
|
-
const isSolana = account.chainId.startsWith("solana:");
|
|
481
|
-
const balances = isSolana ? await getSolanaBalances(account.address, false) : await getEvmBalances(account.address, false);
|
|
482
|
-
if (isJson())
|
|
483
|
-
return printJson(jsonOk({ wallet: walletName, chain: account.chainId, address: account.address, balances }));
|
|
484
|
-
console.log(chalk.cyan.bold(`\n ${walletName} — ${account.chainId}\n`));
|
|
485
|
-
console.log(` Address: ${chalk.green(account.address)}\n`);
|
|
486
|
-
if (balances.length === 0) {
|
|
487
|
-
console.log(chalk.gray(" No balances found.\n"));
|
|
488
|
-
return;
|
|
489
|
-
}
|
|
490
|
-
const rows = balances.map((b) => [chalk.white.bold(b.token), b.balance, b.usdValue || chalk.gray("-")]);
|
|
491
|
-
console.log(makeTable(["Token", "Balance", "USD Value"], rows));
|
|
492
|
-
console.log();
|
|
493
|
-
}
|
|
494
|
-
catch (e) {
|
|
495
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
496
|
-
if (isJson()) {
|
|
497
|
-
const { jsonError } = await import("../utils.js");
|
|
498
|
-
return printJson(jsonError("OWS_ERROR", msg));
|
|
499
|
-
}
|
|
500
|
-
console.error(chalk.red(`\n OWS error: ${msg}\n`));
|
|
501
|
-
process.exit(1);
|
|
502
|
-
}
|
|
503
|
-
});
|
|
504
|
-
fundCmd
|
|
505
|
-
.command("deposit <walletName>")
|
|
506
|
-
.description("Create multi-chain deposit via MoonPay (auto-converts to USDC)")
|
|
507
|
-
.option("-c, --chain <chain>", "Target chain for USDC delivery")
|
|
508
|
-
.option("--for <exchange>", "Target exchange (auto-selects chain: hl→arbitrum, pac→solana, lt→ethereum)")
|
|
509
|
-
.action(async (walletName, opts) => {
|
|
510
|
-
try {
|
|
511
|
-
// Resolve chain from exchange if specified
|
|
512
|
-
const exchangeChainMap = {
|
|
513
|
-
hyperliquid: "arbitrum", hl: "arbitrum",
|
|
514
|
-
pacifica: "solana", pac: "solana",
|
|
515
|
-
lighter: "ethereum", lt: "ethereum",
|
|
516
|
-
};
|
|
517
|
-
const forExchange = opts.for;
|
|
518
|
-
const chain = opts.chain || (forExchange ? exchangeChainMap[forExchange.toLowerCase()] : undefined) || "base";
|
|
519
|
-
// Verify wallet exists first
|
|
520
|
-
const o = loadOws();
|
|
521
|
-
o.getWallet(walletName);
|
|
522
|
-
// Find OWS CLI binary
|
|
523
|
-
const { resolve: pathResolve } = await import("path");
|
|
524
|
-
const { existsSync } = await import("fs");
|
|
525
|
-
const owsBin = [
|
|
526
|
-
pathResolve(process.env.HOME || "~", ".ows", "bin", "ows"),
|
|
527
|
-
"/usr/local/bin/ows",
|
|
528
|
-
].find(p => existsSync(p));
|
|
529
|
-
if (!owsBin) {
|
|
530
|
-
throw new Error("OWS CLI not found. Install: curl -fsSL https://docs.openwallet.sh/install.sh | bash");
|
|
531
|
-
}
|
|
532
|
-
// Call OWS CLI subprocess for native MoonPay deposit
|
|
533
|
-
// OWS CLI writes addresses to stderr, URL to stdout
|
|
534
|
-
const owsEnv = { ...process.env, PATH: `${pathResolve(process.env.HOME || "~", ".ows", "bin")}:${process.env.PATH}` };
|
|
535
|
-
try {
|
|
536
|
-
const { spawnSync } = await import("child_process");
|
|
537
|
-
const proc = spawnSync(owsBin, ["fund", "deposit", "--wallet", walletName, "--chain", chain], {
|
|
538
|
-
encoding: "utf-8", timeout: 30000, env: owsEnv,
|
|
539
|
-
});
|
|
540
|
-
const stderrOutput = proc.stderr || "";
|
|
541
|
-
const combined = stderrOutput + "\n" + (proc.stdout || "");
|
|
542
|
-
// Parse deposit addresses
|
|
543
|
-
const addresses = {};
|
|
544
|
-
for (const line of combined.split("\n")) {
|
|
545
|
-
const match = line.match(/^\s+(bitcoin|ethereum|solana|tron)\s+(\S+)/);
|
|
546
|
-
if (match)
|
|
547
|
-
addresses[match[1]] = match[2];
|
|
548
|
-
}
|
|
549
|
-
const urlMatch = combined.match(/(https:\/\/moonpay\S+)/);
|
|
550
|
-
const depositUrl = urlMatch?.[1] ?? null;
|
|
551
|
-
// Find our wallet's target address
|
|
552
|
-
const w = o.getWallet(walletName);
|
|
553
|
-
const evmAddr = w.accounts.find((a) => a.chainId.startsWith("eip155:"))?.address ?? "";
|
|
554
|
-
if (isJson()) {
|
|
555
|
-
return printJson(jsonOk({
|
|
556
|
-
wallet: walletName,
|
|
557
|
-
chain,
|
|
558
|
-
exchange: forExchange || null,
|
|
559
|
-
targetAddress: evmAddr,
|
|
560
|
-
depositAddresses: addresses,
|
|
561
|
-
depositUrl,
|
|
562
|
-
note: `Send any crypto to the deposit addresses. Funds auto-convert to USDC and deliver to ${evmAddr} on ${chain}.`,
|
|
563
|
-
}));
|
|
564
|
-
}
|
|
565
|
-
// Pass through the human-readable output
|
|
566
|
-
if (stderrOutput.trim()) {
|
|
567
|
-
console.log();
|
|
568
|
-
console.log(stderrOutput.trim());
|
|
569
|
-
}
|
|
570
|
-
if (depositUrl)
|
|
571
|
-
console.log(`\n${depositUrl}`);
|
|
572
|
-
console.log();
|
|
573
|
-
return;
|
|
574
|
-
}
|
|
575
|
-
catch (spawnErr) {
|
|
576
|
-
throw new Error(`OWS CLI deposit failed: ${spawnErr instanceof Error ? spawnErr.message : String(spawnErr)}`);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
catch (e) {
|
|
580
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
581
|
-
if (isJson()) {
|
|
582
|
-
const { jsonError } = await import("../utils.js");
|
|
583
|
-
return printJson(jsonError("OWS_ERROR", msg));
|
|
584
|
-
}
|
|
585
|
-
console.error(chalk.red(`\n OWS error: ${msg}\n`));
|
|
586
|
-
process.exit(1);
|
|
587
|
-
}
|
|
588
|
-
});
|
|
589
|
-
// ── x402 Payment (via OWS CLI subprocess) ──
|
|
590
|
-
ows
|
|
591
|
-
.command("pay <url>")
|
|
592
|
-
.description("HTTP request with automatic x402 USDC payment via OWS")
|
|
593
|
-
.requiredOption("--ows-wallet <name>", "OWS wallet name for payment")
|
|
594
|
-
.option("-m, --method <method>", "HTTP method", "GET")
|
|
595
|
-
.option("-b, --body <json>", "Request body (JSON string)")
|
|
596
|
-
.action(async (url, opts) => {
|
|
597
|
-
try {
|
|
598
|
-
// Verify wallet exists
|
|
599
|
-
const o = loadOws();
|
|
600
|
-
o.getWallet(opts.owsWallet);
|
|
601
|
-
// Find OWS CLI binary
|
|
602
|
-
const { resolve: pathResolve } = await import("path");
|
|
603
|
-
const { existsSync } = await import("fs");
|
|
604
|
-
const owsBin = [
|
|
605
|
-
pathResolve(process.env.HOME || "~", ".ows", "bin", "ows"),
|
|
606
|
-
"/usr/local/bin/ows",
|
|
607
|
-
].find(p => existsSync(p));
|
|
608
|
-
if (!owsBin) {
|
|
609
|
-
throw new Error("OWS CLI not found. Install: curl -fsSL https://docs.openwallet.sh/install.sh | bash");
|
|
610
|
-
}
|
|
611
|
-
// Build OWS CLI args
|
|
612
|
-
const args = ["pay", "request", url, "--wallet", opts.owsWallet];
|
|
613
|
-
if (opts.method && opts.method.toUpperCase() !== "GET") {
|
|
614
|
-
args.push("--method", opts.method.toUpperCase());
|
|
615
|
-
}
|
|
616
|
-
if (opts.body) {
|
|
617
|
-
args.push("--body", opts.body);
|
|
618
|
-
}
|
|
619
|
-
// Use --no-passphrase for non-interactive (passphrase-less wallets)
|
|
620
|
-
args.push("--no-passphrase");
|
|
621
|
-
const { spawnSync } = await import("child_process");
|
|
622
|
-
const owsEnv = { ...process.env, PATH: `${pathResolve(process.env.HOME || "~", ".ows", "bin")}:${process.env.PATH}` };
|
|
623
|
-
const proc = spawnSync(owsBin, args, {
|
|
624
|
-
encoding: "utf-8", timeout: 120000, env: owsEnv,
|
|
625
|
-
});
|
|
626
|
-
const stdout = (proc.stdout || "").trim();
|
|
627
|
-
const stderr = (proc.stderr || "").trim();
|
|
628
|
-
const combined = stderr + (stdout ? "\n" + stdout : "");
|
|
629
|
-
if (proc.status !== 0 && !stdout && !stderr) {
|
|
630
|
-
throw new Error("OWS pay request failed with no output");
|
|
631
|
-
}
|
|
632
|
-
if (isJson()) {
|
|
633
|
-
// Try to parse JSON from stdout
|
|
634
|
-
try {
|
|
635
|
-
const parsed = JSON.parse(stdout);
|
|
636
|
-
return printJson(jsonOk({ paid: true, response: parsed }));
|
|
637
|
-
}
|
|
638
|
-
catch {
|
|
639
|
-
return printJson(jsonOk({ paid: !!stdout, response: stdout || stderr }));
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
// Pass through output
|
|
643
|
-
if (combined) {
|
|
644
|
-
console.log();
|
|
645
|
-
console.log(combined);
|
|
646
|
-
console.log();
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
catch (e) {
|
|
650
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
651
|
-
if (isJson()) {
|
|
652
|
-
const { jsonError } = await import("../utils.js");
|
|
653
|
-
return printJson(jsonError("OWS_ERROR", msg));
|
|
654
|
-
}
|
|
655
|
-
console.error(chalk.red(`\n OWS error: ${msg}\n`));
|
|
656
|
-
process.exit(1);
|
|
657
|
-
}
|
|
658
|
-
});
|
|
659
|
-
// ── Setup Wizard ──
|
|
660
|
-
ows
|
|
661
|
-
.command("setup")
|
|
662
|
-
.description("One-click setup: create wallet + guardrail policy + agent API key")
|
|
663
|
-
.option("-n, --name <name>", "Wallet name", "perp-trading")
|
|
664
|
-
.option("--max-tx-usd <amount>", "Max per-transaction USD", "1000")
|
|
665
|
-
.option("--max-daily-usd <amount>", "Max daily spending USD", "5000")
|
|
666
|
-
.option("--skip-key", "Skip API key creation")
|
|
667
|
-
.action(async (opts) => {
|
|
668
|
-
try {
|
|
669
|
-
const o = loadOws();
|
|
670
|
-
// Step 1: Create or reuse wallet
|
|
671
|
-
let w;
|
|
672
|
-
let walletCreated = false;
|
|
673
|
-
try {
|
|
674
|
-
w = o.getWallet(opts.name);
|
|
675
|
-
if (!isJson())
|
|
676
|
-
console.log(chalk.gray(`\n Wallet "${opts.name}" already exists. Using it.`));
|
|
677
|
-
}
|
|
678
|
-
catch {
|
|
679
|
-
w = o.createWallet(opts.name, "", 12);
|
|
680
|
-
walletCreated = true;
|
|
681
|
-
const { loadSettings, saveSettings } = await import("../settings.js");
|
|
682
|
-
const settings = loadSettings();
|
|
683
|
-
if (!settings.owsActiveWallet) {
|
|
684
|
-
settings.owsActiveWallet = opts.name;
|
|
685
|
-
saveSettings(settings);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
const evmAddr = w.accounts.find((a) => a.chainId.startsWith("eip155:"))?.address ?? "-";
|
|
689
|
-
const solAddr = w.accounts.find((a) => a.chainId.startsWith("solana:"))?.address ?? "-";
|
|
690
|
-
if (!isJson() && walletCreated) {
|
|
691
|
-
console.log(chalk.green.bold(`\n 1. Wallet Created: ${opts.name}`));
|
|
692
|
-
console.log(` EVM: ${chalk.green(evmAddr)}`);
|
|
693
|
-
console.log(` Solana: ${chalk.green(solAddr)}`);
|
|
694
|
-
}
|
|
695
|
-
// Step 2: Create guardrail policy
|
|
696
|
-
const policyId = `perp-guardrail-${opts.name}`;
|
|
697
|
-
const { ALLOWED_CHAINS } = await import("../guardrail/contracts.js");
|
|
698
|
-
const { resolve: pathResolve } = await import("path");
|
|
699
|
-
let executablePath;
|
|
700
|
-
try {
|
|
701
|
-
const { createRequire: cr } = await import("node:module");
|
|
702
|
-
const req = cr(import.meta.url);
|
|
703
|
-
executablePath = pathResolve(req.resolve("perp-cli/dist/guardrail/perp-guardrail.js"));
|
|
704
|
-
}
|
|
705
|
-
catch {
|
|
706
|
-
executablePath = pathResolve(process.cwd(), "node_modules/.bin/perp-guardrail");
|
|
707
|
-
}
|
|
708
|
-
const policyDef = {
|
|
709
|
-
id: policyId, name: `perp-cli guardrail (${opts.name})`, version: 1,
|
|
710
|
-
created_at: new Date().toISOString(),
|
|
711
|
-
rules: [{ type: "allowed_chains", chain_ids: ALLOWED_CHAINS }],
|
|
712
|
-
executable: executablePath,
|
|
713
|
-
config: {
|
|
714
|
-
max_tx_usd: parseInt(opts.maxTxUsd),
|
|
715
|
-
max_daily_usd: parseInt(opts.maxDailyUsd),
|
|
716
|
-
max_withdraw_usd: Math.round(parseInt(opts.maxTxUsd) / 2),
|
|
717
|
-
max_daily_withdraw_usd: Math.round(parseInt(opts.maxDailyUsd) / 2.5),
|
|
718
|
-
block_selectors: ["0x095ea7b3", "0x23b872dd"],
|
|
719
|
-
},
|
|
720
|
-
action: "deny",
|
|
721
|
-
};
|
|
722
|
-
try {
|
|
723
|
-
o.deletePolicy(policyId);
|
|
724
|
-
}
|
|
725
|
-
catch { /* doesn't exist yet */ }
|
|
726
|
-
o.createPolicy(JSON.stringify(policyDef));
|
|
727
|
-
if (!isJson()) {
|
|
728
|
-
console.log(chalk.green.bold(`\n 2. Guardrail Policy Created: ${policyId}`));
|
|
729
|
-
console.log(` Limits: ${chalk.cyan(`$${opts.maxTxUsd}/tx, $${opts.maxDailyUsd}/day`)}`);
|
|
730
|
-
console.log(` Chains: ${chalk.cyan(`${ALLOWED_CHAINS.length} chains (all perp-cli exchanges)`)}`);
|
|
731
|
-
}
|
|
732
|
-
// Step 3: Create API key
|
|
733
|
-
let token;
|
|
734
|
-
let keyId;
|
|
735
|
-
if (!opts.skipKey) {
|
|
736
|
-
const keyName = `${opts.name}-agent`;
|
|
737
|
-
const result = o.createApiKey(keyName, [opts.name], [policyId], "", undefined);
|
|
738
|
-
token = result.token;
|
|
739
|
-
keyId = result.id;
|
|
740
|
-
if (!isJson()) {
|
|
741
|
-
console.log(chalk.green.bold(`\n 3. Agent API Key Created: ${keyName}`));
|
|
742
|
-
console.log(` ID: ${chalk.gray(keyId)}`);
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
if (isJson()) {
|
|
746
|
-
return printJson(jsonOk({
|
|
747
|
-
wallet: { name: opts.name, evm: evmAddr, solana: solAddr, created: walletCreated },
|
|
748
|
-
policy: { id: policyId, maxTxUsd: parseInt(opts.maxTxUsd), maxDailyUsd: parseInt(opts.maxDailyUsd) },
|
|
749
|
-
apiKey: token ? { id: keyId, token } : null,
|
|
750
|
-
}));
|
|
751
|
-
}
|
|
752
|
-
console.log(chalk.cyan.bold("\n ─── Setup Complete ───\n"));
|
|
753
|
-
if (token) {
|
|
754
|
-
console.log(` ${chalk.yellow.bold("Agent Token:")} ${chalk.yellow(token)}`);
|
|
755
|
-
console.log(chalk.red(" Save this token — it will not be shown again!\n"));
|
|
756
|
-
console.log(chalk.white.bold(" Usage:\n"));
|
|
757
|
-
console.log(chalk.gray(" # Owner mode (no policy limits):"));
|
|
758
|
-
console.log(` ${chalk.cyan(`perp --ows ${opts.name} -e hl trade buy ETH 0.1`)}\n`);
|
|
759
|
-
console.log(chalk.gray(" # Agent mode (policy enforced):"));
|
|
760
|
-
console.log(` ${chalk.cyan(`perp --ows ${opts.name} --ows-key ${token.slice(0, 16)}... -e hl trade buy ETH 0.1`)}\n`);
|
|
761
|
-
}
|
|
762
|
-
console.log(chalk.gray(" Fund your wallet before trading:"));
|
|
763
|
-
console.log(` ${chalk.cyan(`perp ows fund deposit ${opts.name} --for hl`)} ${chalk.gray("(Hyperliquid → Arbitrum USDC)")}`);
|
|
764
|
-
console.log(` ${chalk.cyan(`perp ows fund deposit ${opts.name} --for pac`)} ${chalk.gray("(Pacifica → Solana USDC)")}`);
|
|
765
|
-
console.log(` ${chalk.cyan(`perp ows fund deposit ${opts.name} --for lt`)} ${chalk.gray("(Lighter → Ethereum USDC)")}\n`);
|
|
766
|
-
}
|
|
767
|
-
catch (e) {
|
|
768
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
769
|
-
if (isJson()) {
|
|
770
|
-
const { jsonError } = await import("../utils.js");
|
|
771
|
-
return printJson(jsonError("OWS_ERROR", msg));
|
|
772
|
-
}
|
|
773
|
-
console.error(chalk.red(`\n OWS setup error: ${msg}\n`));
|
|
774
|
-
process.exit(1);
|
|
775
|
-
}
|
|
776
|
-
});
|
|
777
|
-
// ── Backup / Restore ──
|
|
778
|
-
ows
|
|
779
|
-
.command("backup")
|
|
780
|
-
.description("Backup entire OWS vault (encrypted archive)")
|
|
781
|
-
.option("-o, --output <path>", "Output file path", `ows-backup-${new Date().toISOString().split("T")[0]}.tar.gz.enc`)
|
|
782
|
-
.action(async (opts) => {
|
|
783
|
-
try {
|
|
784
|
-
const { stdout, stderr } = await runOwsCli(["backup", "--output", opts.output]);
|
|
785
|
-
if (isJson())
|
|
786
|
-
return printJson(jsonOk({ backup: opts.output, output: stderr || stdout }));
|
|
787
|
-
if (stderr)
|
|
788
|
-
console.log("\n" + stderr);
|
|
789
|
-
if (stdout)
|
|
790
|
-
console.log(stdout);
|
|
791
|
-
console.log();
|
|
792
|
-
}
|
|
793
|
-
catch (e) {
|
|
794
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
795
|
-
if (isJson()) {
|
|
796
|
-
const { jsonError } = await import("../utils.js");
|
|
797
|
-
return printJson(jsonError("OWS_ERROR", msg));
|
|
798
|
-
}
|
|
799
|
-
console.error(chalk.red(`\n OWS error: ${msg}\n`));
|
|
800
|
-
process.exit(1);
|
|
801
|
-
}
|
|
802
|
-
});
|
|
803
|
-
ows
|
|
804
|
-
.command("restore <file>")
|
|
805
|
-
.description("Restore OWS vault from encrypted backup")
|
|
806
|
-
.action(async (file) => {
|
|
807
|
-
try {
|
|
808
|
-
const { stdout, stderr } = await runOwsCli(["restore", "--input", file]);
|
|
809
|
-
if (isJson())
|
|
810
|
-
return printJson(jsonOk({ restored: file, output: stderr || stdout }));
|
|
811
|
-
if (stderr)
|
|
812
|
-
console.log("\n" + stderr);
|
|
813
|
-
if (stdout)
|
|
814
|
-
console.log(stdout);
|
|
815
|
-
console.log();
|
|
816
|
-
}
|
|
817
|
-
catch (e) {
|
|
818
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
819
|
-
if (isJson()) {
|
|
820
|
-
const { jsonError } = await import("../utils.js");
|
|
821
|
-
return printJson(jsonError("OWS_ERROR", msg));
|
|
822
|
-
}
|
|
823
|
-
console.error(chalk.red(`\n OWS error: ${msg}\n`));
|
|
824
|
-
process.exit(1);
|
|
825
|
-
}
|
|
826
|
-
});
|
|
827
|
-
// ── Key Rotation ──
|
|
828
|
-
ows
|
|
829
|
-
.command("rotate")
|
|
830
|
-
.description("Rotate wallet keys (create new wallet + transfer assets)")
|
|
831
|
-
.requiredOption("--from <name>", "Source wallet name")
|
|
832
|
-
.requiredOption("--to <name>", "Target wallet name (created if not exists)")
|
|
833
|
-
.option("--chain <chain>", "Chain to rotate on", "evm")
|
|
834
|
-
.action(async (opts) => {
|
|
835
|
-
try {
|
|
836
|
-
const chainMap = { evm: "eip155:1", arbitrum: "eip155:42161", base: "eip155:8453", solana: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" };
|
|
837
|
-
const chainArg = chainMap[opts.chain] || opts.chain;
|
|
838
|
-
const { stdout, stderr } = await runOwsCli(["wallet", "rotate", "--from", opts.from, "--to", opts.to, "--chain", chainArg], 120000);
|
|
839
|
-
if (isJson())
|
|
840
|
-
return printJson(jsonOk({ from: opts.from, to: opts.to, chain: chainArg, output: stderr || stdout }));
|
|
841
|
-
if (stderr)
|
|
842
|
-
console.log("\n" + stderr);
|
|
843
|
-
if (stdout)
|
|
844
|
-
console.log(stdout);
|
|
845
|
-
console.log();
|
|
846
|
-
}
|
|
847
|
-
catch (e) {
|
|
848
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
849
|
-
if (isJson()) {
|
|
850
|
-
const { jsonError } = await import("../utils.js");
|
|
851
|
-
return printJson(jsonError("OWS_ERROR", msg));
|
|
852
|
-
}
|
|
853
|
-
console.error(chalk.red(`\n OWS error: ${msg}\n`));
|
|
854
|
-
process.exit(1);
|
|
855
|
-
}
|
|
856
|
-
});
|
|
857
|
-
// ── Pay Discover (x402 service directory) ──
|
|
858
|
-
ows
|
|
859
|
-
.command("discover")
|
|
860
|
-
.description("Discover x402-enabled services (Bazaar directory)")
|
|
861
|
-
.option("-q, --query <search>", "Filter services by keyword")
|
|
862
|
-
.option("--limit <n>", "Max results", "20")
|
|
863
|
-
.action(async (opts) => {
|
|
864
|
-
try {
|
|
865
|
-
const args = ["pay", "discover", "--limit", opts.limit];
|
|
866
|
-
if (opts.query)
|
|
867
|
-
args.push("--query", opts.query);
|
|
868
|
-
const { stdout, stderr } = await runOwsCli(args);
|
|
869
|
-
if (isJson())
|
|
870
|
-
return printJson(jsonOk({ services: stdout || stderr }));
|
|
871
|
-
if (stderr)
|
|
872
|
-
console.log("\n" + stderr);
|
|
873
|
-
if (stdout)
|
|
874
|
-
console.log("\n" + stdout);
|
|
875
|
-
console.log();
|
|
876
|
-
}
|
|
877
|
-
catch (e) {
|
|
878
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
879
|
-
if (isJson()) {
|
|
880
|
-
const { jsonError } = await import("../utils.js");
|
|
881
|
-
return printJson(jsonError("OWS_ERROR", msg));
|
|
882
|
-
}
|
|
883
|
-
console.error(chalk.red(`\n OWS error: ${msg}\n`));
|
|
884
|
-
process.exit(1);
|
|
885
|
-
}
|
|
886
|
-
});
|
|
887
|
-
}
|