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.
Files changed (240) hide show
  1. package/README.md +30 -25
  2. package/dist/_stderr-filter.d.ts +0 -0
  3. package/dist/_stderr-filter.js +30 -0
  4. package/dist/agent-wallet/expiry.d.ts +15 -0
  5. package/dist/agent-wallet/expiry.js +26 -0
  6. package/dist/agent-wallet/lighter-keystore.d.ts +34 -0
  7. package/dist/agent-wallet/lighter-keystore.js +232 -0
  8. package/dist/agent-wallet/passphrase.d.ts +13 -0
  9. package/dist/agent-wallet/passphrase.js +54 -0
  10. package/dist/agent-wallet/signing-strategy.d.ts +67 -0
  11. package/dist/agent-wallet/signing-strategy.js +51 -0
  12. package/dist/agent-wallet/store.d.ts +37 -0
  13. package/dist/agent-wallet/store.js +172 -0
  14. package/dist/api/public/_http.d.ts +15 -0
  15. package/dist/api/public/_http.js +24 -0
  16. package/dist/api/public/hyperliquid.js +36 -24
  17. package/dist/api/public/lighter.d.ts +7 -1
  18. package/dist/api/public/lighter.js +85 -37
  19. package/dist/api/public/pacifica.js +47 -18
  20. package/dist/bridge-engine.js +102 -49
  21. package/dist/cli-spec.js +1 -2
  22. package/dist/commands/account.js +2 -500
  23. package/dist/commands/agent.d.ts +8 -2
  24. package/dist/commands/agent.js +1903 -366
  25. package/dist/commands/alerts.js +3 -1
  26. package/dist/commands/arb/index.js +37 -7
  27. package/dist/commands/arb-auto.js +44 -11
  28. package/dist/commands/arb.js +83 -34
  29. package/dist/commands/bot.d.ts +1 -1
  30. package/dist/commands/bot.js +18 -14
  31. package/dist/commands/bridge.d.ts +1 -1
  32. package/dist/commands/bridge.js +4 -4
  33. package/dist/commands/funds.d.ts +1 -1
  34. package/dist/commands/funds.js +18 -68
  35. package/dist/commands/jobs.d.ts +1 -1
  36. package/dist/commands/jobs.js +2 -2
  37. package/dist/commands/manage.d.ts +1 -1
  38. package/dist/commands/manage.js +12 -84
  39. package/dist/commands/plan.d.ts +1 -1
  40. package/dist/commands/plan.js +2 -2
  41. package/dist/commands/portfolio.d.ts +19 -3
  42. package/dist/commands/portfolio.js +747 -163
  43. package/dist/commands/rebalance.d.ts +1 -1
  44. package/dist/commands/rebalance.js +18 -12
  45. package/dist/commands/risk.js +2 -6
  46. package/dist/commands/settings.js +30 -53
  47. package/dist/commands/trade.js +16 -15
  48. package/dist/commands/wallet.d.ts +2 -1
  49. package/dist/commands/wallet.js +756 -28
  50. package/dist/errors.d.ts +64 -0
  51. package/dist/errors.js +82 -2
  52. package/dist/event-stream.js +12 -1
  53. package/dist/exchanges/aster-typed-data.d.ts +156 -0
  54. package/dist/exchanges/aster-typed-data.js +189 -0
  55. package/dist/exchanges/aster.d.ts +60 -11
  56. package/dist/exchanges/aster.js +278 -136
  57. package/dist/exchanges/hyperliquid.d.ts +14 -0
  58. package/dist/exchanges/hyperliquid.js +84 -34
  59. package/dist/exchanges/lighter-spot.js +14 -3
  60. package/dist/exchanges/lighter.d.ts +66 -0
  61. package/dist/exchanges/lighter.js +199 -27
  62. package/dist/exchanges/pacifica-typed-data.d.ts +75 -0
  63. package/dist/exchanges/pacifica-typed-data.js +74 -0
  64. package/dist/exchanges/pacifica.d.ts +29 -1
  65. package/dist/exchanges/pacifica.js +94 -6
  66. package/dist/funding/rates.js +59 -21
  67. package/dist/guardrail/perp-guardrail.js +9 -1
  68. package/dist/index.d.ts +1 -1
  69. package/dist/index.js +268 -360
  70. package/dist/jobs.js +3 -1
  71. package/dist/mcp-server.js +85 -83
  72. package/dist/position-history.js +47 -4
  73. package/dist/settings.d.ts +67 -3
  74. package/dist/settings.js +1 -2
  75. package/dist/signer/ows-evm.d.ts +14 -0
  76. package/dist/signer/ows-evm.js +28 -9
  77. package/dist/smart-order.d.ts +8 -2
  78. package/dist/smart-order.js +46 -11
  79. package/dist/strategies/funding-arb.js +29 -8
  80. package/dist/trade-validator.js +30 -5
  81. package/dist/utils.d.ts +10 -0
  82. package/dist/utils.js +19 -0
  83. package/package.json +2 -2
  84. package/skills/perp-cli/SKILL.md +23 -14
  85. package/skills/perp-cli/references/agent-operations.md +6 -4
  86. package/skills/perp-cli/references/commands.md +55 -34
  87. package/skills/perp-cli/scripts/preflight.sh +4 -4
  88. package/skills/perp-cli/scripts/spot-perp-scan.sh +2 -2
  89. package/dist/__tests__/alert-logic.test.d.ts +0 -1
  90. package/dist/__tests__/alert-logic.test.js +0 -107
  91. package/dist/__tests__/arb-auto-3dex.test.d.ts +0 -1
  92. package/dist/__tests__/arb-auto-3dex.test.js +0 -397
  93. package/dist/__tests__/arb-history-stats.test.d.ts +0 -1
  94. package/dist/__tests__/arb-history-stats.test.js +0 -176
  95. package/dist/__tests__/arb-logic.test.d.ts +0 -1
  96. package/dist/__tests__/arb-logic.test.js +0 -84
  97. package/dist/__tests__/arb-manage.test.d.ts +0 -1
  98. package/dist/__tests__/arb-manage.test.js +0 -253
  99. package/dist/__tests__/arb-new-features.test.d.ts +0 -1
  100. package/dist/__tests__/arb-new-features.test.js +0 -457
  101. package/dist/__tests__/arb-sizing.test.d.ts +0 -1
  102. package/dist/__tests__/arb-sizing.test.js +0 -48
  103. package/dist/__tests__/arb-state.test.d.ts +0 -1
  104. package/dist/__tests__/arb-state.test.js +0 -284
  105. package/dist/__tests__/arb-userflow.test.d.ts +0 -1
  106. package/dist/__tests__/arb-userflow.test.js +0 -945
  107. package/dist/__tests__/arb-utils.test.d.ts +0 -1
  108. package/dist/__tests__/arb-utils.test.js +0 -264
  109. package/dist/__tests__/bot-conditions.test.d.ts +0 -1
  110. package/dist/__tests__/bot-conditions.test.js +0 -341
  111. package/dist/__tests__/client-id-tracker.test.d.ts +0 -1
  112. package/dist/__tests__/client-id-tracker.test.js +0 -137
  113. package/dist/__tests__/commands/new-atomic-commands.test.d.ts +0 -1
  114. package/dist/__tests__/commands/new-atomic-commands.test.js +0 -502
  115. package/dist/__tests__/commands/order-intent.test.d.ts +0 -1
  116. package/dist/__tests__/commands/order-intent.test.js +0 -600
  117. package/dist/__tests__/commands/trade-commands.test.d.ts +0 -1
  118. package/dist/__tests__/commands/trade-commands.test.js +0 -821
  119. package/dist/__tests__/config.test.d.ts +0 -1
  120. package/dist/__tests__/config.test.js +0 -86
  121. package/dist/__tests__/cross-chain-margin.test.d.ts +0 -1
  122. package/dist/__tests__/cross-chain-margin.test.js +0 -287
  123. package/dist/__tests__/dex-asset-map.test.d.ts +0 -1
  124. package/dist/__tests__/dex-asset-map.test.js +0 -191
  125. package/dist/__tests__/errors.test.d.ts +0 -1
  126. package/dist/__tests__/errors.test.js +0 -110
  127. package/dist/__tests__/event-stream.test.d.ts +0 -1
  128. package/dist/__tests__/event-stream.test.js +0 -276
  129. package/dist/__tests__/exchanges/interface.test.d.ts +0 -1
  130. package/dist/__tests__/exchanges/interface.test.js +0 -132
  131. package/dist/__tests__/exchanges/mock-adapter.d.ts +0 -69
  132. package/dist/__tests__/exchanges/mock-adapter.js +0 -137
  133. package/dist/__tests__/execution-log.test.d.ts +0 -1
  134. package/dist/__tests__/execution-log.test.js +0 -106
  135. package/dist/__tests__/funding-calc.test.d.ts +0 -1
  136. package/dist/__tests__/funding-calc.test.js +0 -71
  137. package/dist/__tests__/funding-history.test.d.ts +0 -1
  138. package/dist/__tests__/funding-history.test.js +0 -343
  139. package/dist/__tests__/funding-rates.test.d.ts +0 -1
  140. package/dist/__tests__/funding-rates.test.js +0 -342
  141. package/dist/__tests__/funding.test.d.ts +0 -1
  142. package/dist/__tests__/funding.test.js +0 -173
  143. package/dist/__tests__/gap-logic.test.d.ts +0 -1
  144. package/dist/__tests__/gap-logic.test.js +0 -43
  145. package/dist/__tests__/hip3-dex.test.d.ts +0 -1
  146. package/dist/__tests__/hip3-dex.test.js +0 -234
  147. package/dist/__tests__/integration/agent-features.integration.test.d.ts +0 -1
  148. package/dist/__tests__/integration/agent-features.integration.test.js +0 -553
  149. package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +0 -13
  150. package/dist/__tests__/integration/atomic-commands.integration.test.js +0 -246
  151. package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +0 -1
  152. package/dist/__tests__/integration/bridge-simulation.integration.test.js +0 -453
  153. package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +0 -1
  154. package/dist/__tests__/integration/bridge-strict.integration.test.js +0 -812
  155. package/dist/__tests__/integration/bridge.integration.test.d.ts +0 -1
  156. package/dist/__tests__/integration/bridge.integration.test.js +0 -309
  157. package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +0 -1
  158. package/dist/__tests__/integration/cli-e2e.integration.test.js +0 -202
  159. package/dist/__tests__/integration/dex-arb.integration.test.d.ts +0 -1
  160. package/dist/__tests__/integration/dex-arb.integration.test.js +0 -116
  161. package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +0 -13
  162. package/dist/__tests__/integration/envelope-consistency.integration.test.js +0 -205
  163. package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +0 -1
  164. package/dist/__tests__/integration/hip3-dex.integration.test.js +0 -147
  165. package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +0 -1
  166. package/dist/__tests__/integration/hyperliquid.integration.test.js +0 -79
  167. package/dist/__tests__/integration/lighter.integration.test.d.ts +0 -1
  168. package/dist/__tests__/integration/lighter.integration.test.js +0 -53
  169. package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +0 -9
  170. package/dist/__tests__/integration/new-commands-e2e.integration.test.js +0 -236
  171. package/dist/__tests__/integration/order-verification.integration.test.d.ts +0 -1
  172. package/dist/__tests__/integration/order-verification.integration.test.js +0 -321
  173. package/dist/__tests__/integration/pacifica.integration.test.d.ts +0 -1
  174. package/dist/__tests__/integration/pacifica.integration.test.js +0 -75
  175. package/dist/__tests__/integration/response-shapes.integration.test.d.ts +0 -1
  176. package/dist/__tests__/integration/response-shapes.integration.test.js +0 -278
  177. package/dist/__tests__/liquidity.test.d.ts +0 -1
  178. package/dist/__tests__/liquidity.test.js +0 -225
  179. package/dist/__tests__/plan-executor.test.d.ts +0 -1
  180. package/dist/__tests__/plan-executor.test.js +0 -314
  181. package/dist/__tests__/position-history.test.d.ts +0 -1
  182. package/dist/__tests__/position-history.test.js +0 -367
  183. package/dist/__tests__/retry.test.d.ts +0 -1
  184. package/dist/__tests__/retry.test.js +0 -310
  185. package/dist/__tests__/risk-assessment.test.d.ts +0 -1
  186. package/dist/__tests__/risk-assessment.test.js +0 -288
  187. package/dist/__tests__/security-adversarial.test.d.ts +0 -1
  188. package/dist/__tests__/security-adversarial.test.js +0 -574
  189. package/dist/__tests__/strategies.test.d.ts +0 -1
  190. package/dist/__tests__/strategies.test.js +0 -539
  191. package/dist/__tests__/trade-execution.test.d.ts +0 -1
  192. package/dist/__tests__/trade-execution.test.js +0 -129
  193. package/dist/__tests__/trade-validator.test.d.ts +0 -1
  194. package/dist/__tests__/trade-validator.test.js +0 -655
  195. package/dist/__tests__/utils.test.d.ts +0 -1
  196. package/dist/__tests__/utils.test.js +0 -76
  197. package/dist/arb/reconcile.d.ts +0 -28
  198. package/dist/arb/reconcile.js +0 -162
  199. package/dist/commands/alert.d.ts +0 -2
  200. package/dist/commands/alert.js +0 -421
  201. package/dist/commands/analytics.d.ts +0 -3
  202. package/dist/commands/analytics.js +0 -311
  203. package/dist/commands/arb-manage.d.ts +0 -5
  204. package/dist/commands/arb-manage.js +0 -5
  205. package/dist/commands/dashboard.d.ts +0 -3
  206. package/dist/commands/dashboard.js +0 -176
  207. package/dist/commands/deposit.d.ts +0 -4
  208. package/dist/commands/deposit.js +0 -573
  209. package/dist/commands/dex.d.ts +0 -3
  210. package/dist/commands/dex.js +0 -120
  211. package/dist/commands/env.d.ts +0 -2
  212. package/dist/commands/env.js +0 -136
  213. package/dist/commands/funding.d.ts +0 -3
  214. package/dist/commands/funding.js +0 -554
  215. package/dist/commands/gap.d.ts +0 -2
  216. package/dist/commands/gap.js +0 -305
  217. package/dist/commands/health.d.ts +0 -2
  218. package/dist/commands/health.js +0 -67
  219. package/dist/commands/multileg.d.ts +0 -16
  220. package/dist/commands/multileg.js +0 -174
  221. package/dist/commands/ows.d.ts +0 -2
  222. package/dist/commands/ows.js +0 -887
  223. package/dist/commands/pnl.d.ts +0 -12
  224. package/dist/commands/pnl.js +0 -243
  225. package/dist/commands/run.d.ts +0 -3
  226. package/dist/commands/run.js +0 -202
  227. package/dist/commands/stream.d.ts +0 -5
  228. package/dist/commands/stream.js +0 -200
  229. package/dist/commands/withdraw.d.ts +0 -3
  230. package/dist/commands/withdraw.js +0 -187
  231. package/dist/exchanges/edgex.d.ts +0 -62
  232. package/dist/exchanges/edgex.js +0 -552
  233. package/dist/exchanges/nado.d.ts +0 -52
  234. package/dist/exchanges/nado.js +0 -223
  235. package/dist/funding/index.d.ts +0 -3
  236. package/dist/funding/index.js +0 -3
  237. package/dist/retry.d.ts +0 -74
  238. package/dist/retry.js +0 -129
  239. package/dist/ws/market-feed.d.ts +0 -74
  240. package/dist/ws/market-feed.js +0 -227
@@ -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
- }