httpcat-cli 0.3.0 → 0.3.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 (322) hide show
  1. package/.github/workflows/ci.yml +3 -0
  2. package/.github/workflows/rc-publish.yml +6 -0
  3. package/.github/workflows/release.yml +102 -0
  4. package/.github/workflows/sync-version.yml +31 -2
  5. package/README.md +1408 -109
  6. package/additions.txt +3 -0
  7. package/bun.lock +260 -25
  8. package/dist/agent/autonomous-trader.d.ts.map +1 -0
  9. package/dist/agent/autonomous-trader.js +362 -0
  10. package/dist/agent/autonomous-trader.js.map +1 -0
  11. package/dist/agent/ax-agent.d.ts.map +1 -1
  12. package/dist/agent/ax-agent.js +356 -18
  13. package/dist/agent/ax-agent.js.map +1 -1
  14. package/dist/agent/event-client.d.ts.map +1 -0
  15. package/dist/agent/event-client.js +82 -0
  16. package/dist/agent/event-client.js.map +1 -0
  17. package/dist/agent/log-stream.d.ts.map +1 -0
  18. package/dist/agent/log-stream.js +95 -0
  19. package/dist/agent/log-stream.js.map +1 -0
  20. package/dist/agent/memory/conversation-session.d.ts.map +1 -0
  21. package/dist/agent/memory/conversation-session.js +232 -0
  22. package/dist/agent/memory/conversation-session.js.map +1 -0
  23. package/dist/agent/memory/conversation-store.d.ts.map +1 -0
  24. package/dist/agent/memory/conversation-store.js +214 -0
  25. package/dist/agent/memory/conversation-store.js.map +1 -0
  26. package/dist/agent/memory/database-schema.d.ts.map +1 -0
  27. package/dist/agent/memory/database-schema.js +355 -0
  28. package/dist/agent/memory/database-schema.js.map +1 -0
  29. package/dist/agent/memory/decision-tracker.d.ts.map +1 -0
  30. package/dist/agent/memory/decision-tracker.js +274 -0
  31. package/dist/agent/memory/decision-tracker.js.map +1 -0
  32. package/dist/agent/memory/memory-manager.d.ts.map +1 -0
  33. package/dist/agent/memory/memory-manager.js +187 -0
  34. package/dist/agent/memory/memory-manager.js.map +1 -0
  35. package/dist/agent/memory/types.d.ts.map +1 -0
  36. package/dist/agent/memory/types.js +5 -0
  37. package/dist/agent/memory/types.js.map +1 -0
  38. package/dist/agent/message-formatter.d.ts.map +1 -0
  39. package/dist/agent/message-formatter.js +76 -0
  40. package/dist/agent/message-formatter.js.map +1 -0
  41. package/dist/agent/position-db.d.ts.map +1 -0
  42. package/dist/agent/position-db.js +154 -0
  43. package/dist/agent/position-db.js.map +1 -0
  44. package/dist/agent/simple-chat-ui-static.d.ts.map +1 -0
  45. package/dist/agent/simple-chat-ui-static.js +129 -0
  46. package/dist/agent/simple-chat-ui-static.js.map +1 -0
  47. package/dist/agent/simple-chat-ui.d.ts.map +1 -0
  48. package/dist/agent/simple-chat-ui.js +90 -0
  49. package/dist/agent/simple-chat-ui.js.map +1 -0
  50. package/dist/agent/tools.d.ts.map +1 -1
  51. package/dist/agent/tools.js +297 -4
  52. package/dist/agent/tools.js.map +1 -1
  53. package/dist/agent/ui.d.ts.map +1 -0
  54. package/dist/agent/ui.js +84 -0
  55. package/dist/agent/ui.js.map +1 -0
  56. package/dist/agent/unified-runtime.d.ts.map +1 -0
  57. package/dist/agent/unified-runtime.js +397 -0
  58. package/dist/agent/unified-runtime.js.map +1 -0
  59. package/dist/client.d.ts.map +1 -1
  60. package/dist/client.js +272 -21
  61. package/dist/client.js.map +1 -1
  62. package/dist/commands/account.d.ts.map +1 -1
  63. package/dist/commands/account.js +187 -33
  64. package/dist/commands/account.js.map +1 -1
  65. package/dist/commands/agent.d.ts.map +1 -0
  66. package/dist/commands/agent.js +125 -0
  67. package/dist/commands/agent.js.map +1 -0
  68. package/dist/commands/approve.d.ts.map +1 -0
  69. package/dist/commands/approve.js +505 -0
  70. package/dist/commands/approve.js.map +1 -0
  71. package/dist/commands/automation.d.ts.map +1 -0
  72. package/dist/commands/automation.js +346 -0
  73. package/dist/commands/automation.js.map +1 -0
  74. package/dist/commands/balances.d.ts.map +1 -1
  75. package/dist/commands/balances.js +226 -73
  76. package/dist/commands/balances.js.map +1 -1
  77. package/dist/commands/buy.d.ts.map +1 -1
  78. package/dist/commands/buy.js +149 -146
  79. package/dist/commands/buy.js.map +1 -1
  80. package/dist/commands/call.d.ts.map +1 -0
  81. package/dist/commands/call.js +51 -0
  82. package/dist/commands/call.js.map +1 -0
  83. package/dist/commands/cex.d.ts.map +1 -0
  84. package/dist/commands/cex.js +958 -0
  85. package/dist/commands/cex.js.map +1 -0
  86. package/dist/commands/chat.d.ts.map +1 -1
  87. package/dist/commands/chat.js +169 -411
  88. package/dist/commands/chat.js.map +1 -1
  89. package/dist/commands/claim.d.ts.map +1 -1
  90. package/dist/commands/claim.js +313 -29
  91. package/dist/commands/claim.js.map +1 -1
  92. package/dist/commands/create.d.ts.map +1 -1
  93. package/dist/commands/create.js +151 -43
  94. package/dist/commands/create.js.map +1 -1
  95. package/dist/commands/gasless-swap.d.ts.map +1 -0
  96. package/dist/commands/gasless-swap.js +232 -0
  97. package/dist/commands/gasless-swap.js.map +1 -0
  98. package/dist/commands/health.d.ts.map +1 -1
  99. package/dist/commands/health.js +63 -7
  100. package/dist/commands/health.js.map +1 -1
  101. package/dist/commands/info.d.ts.map +1 -1
  102. package/dist/commands/info.js +131 -47
  103. package/dist/commands/info.js.map +1 -1
  104. package/dist/commands/launchpad.d.ts.map +1 -0
  105. package/dist/commands/launchpad.js +708 -0
  106. package/dist/commands/launchpad.js.map +1 -0
  107. package/dist/commands/list.d.ts.map +1 -1
  108. package/dist/commands/list.js +57 -23
  109. package/dist/commands/list.js.map +1 -1
  110. package/dist/commands/market.d.ts.map +1 -0
  111. package/dist/commands/market.js +960 -0
  112. package/dist/commands/market.js.map +1 -0
  113. package/dist/commands/mcp-install.d.ts.map +1 -0
  114. package/dist/commands/mcp-install.js +387 -0
  115. package/dist/commands/mcp-install.js.map +1 -0
  116. package/dist/commands/opps.d.ts.map +1 -0
  117. package/dist/commands/opps.js +409 -0
  118. package/dist/commands/opps.js.map +1 -0
  119. package/dist/commands/perps.d.ts.map +1 -0
  120. package/dist/commands/perps.js +248 -0
  121. package/dist/commands/perps.js.map +1 -0
  122. package/dist/commands/portfolio.d.ts.map +1 -0
  123. package/dist/commands/portfolio.js +679 -0
  124. package/dist/commands/portfolio.js.map +1 -0
  125. package/dist/commands/positions.d.ts.map +1 -1
  126. package/dist/commands/positions.js +76 -47
  127. package/dist/commands/positions.js.map +1 -1
  128. package/dist/commands/predict.d.ts.map +1 -0
  129. package/dist/commands/predict.js +280 -0
  130. package/dist/commands/predict.js.map +1 -0
  131. package/dist/commands/predictions.d.ts.map +1 -0
  132. package/dist/commands/predictions.js +486 -0
  133. package/dist/commands/predictions.js.map +1 -0
  134. package/dist/commands/risk.d.ts.map +1 -0
  135. package/dist/commands/risk.js +225 -0
  136. package/dist/commands/risk.js.map +1 -0
  137. package/dist/commands/security.d.ts.map +1 -0
  138. package/dist/commands/security.js +244 -0
  139. package/dist/commands/security.js.map +1 -0
  140. package/dist/commands/sell.d.ts.map +1 -1
  141. package/dist/commands/sell.js +67 -34
  142. package/dist/commands/sell.js.map +1 -1
  143. package/dist/commands/send.d.ts.map +1 -0
  144. package/dist/commands/send.js +733 -0
  145. package/dist/commands/send.js.map +1 -0
  146. package/dist/commands/sign.d.ts.map +1 -0
  147. package/dist/commands/sign.js +1048 -0
  148. package/dist/commands/sign.js.map +1 -0
  149. package/dist/commands/swap.d.ts.map +1 -0
  150. package/dist/commands/swap.js +744 -0
  151. package/dist/commands/swap.js.map +1 -0
  152. package/dist/commands/system.d.ts.map +1 -0
  153. package/dist/commands/system.js +417 -0
  154. package/dist/commands/system.js.map +1 -0
  155. package/dist/commands/tools/index.d.ts.map +1 -0
  156. package/dist/commands/tools/index.js +2040 -0
  157. package/dist/commands/tools/index.js.map +1 -0
  158. package/dist/commands/trade.d.ts.map +1 -0
  159. package/dist/commands/trade.js +237 -0
  160. package/dist/commands/trade.js.map +1 -0
  161. package/dist/commands/transactions.d.ts.map +1 -1
  162. package/dist/commands/transactions.js +29 -17
  163. package/dist/commands/transactions.js.map +1 -1
  164. package/dist/commands/update.d.ts.map +1 -0
  165. package/dist/commands/update.js +429 -0
  166. package/dist/commands/update.js.map +1 -0
  167. package/dist/config.d.ts.map +1 -1
  168. package/dist/config.js +351 -40
  169. package/dist/config.js.map +1 -1
  170. package/dist/index.js +4524 -924
  171. package/dist/index.js.map +1 -1
  172. package/dist/interactive/art.d.ts.map +1 -1
  173. package/dist/interactive/art.js +33 -1
  174. package/dist/interactive/art.js.map +1 -1
  175. package/dist/interactive/shell.d.ts.map +1 -1
  176. package/dist/interactive/shell.js +467 -2652
  177. package/dist/interactive/shell.js.map +1 -1
  178. package/dist/mcp/context.d.ts.map +1 -0
  179. package/dist/mcp/context.js +211 -0
  180. package/dist/mcp/context.js.map +1 -0
  181. package/dist/mcp/onboarding.d.ts.map +1 -0
  182. package/dist/mcp/onboarding.js +266 -0
  183. package/dist/mcp/onboarding.js.map +1 -0
  184. package/dist/mcp/resources.d.ts.map +1 -0
  185. package/dist/mcp/resources.js +222 -0
  186. package/dist/mcp/resources.js.map +1 -0
  187. package/dist/mcp/server.d.ts.map +1 -1
  188. package/dist/mcp/server.js +51 -1
  189. package/dist/mcp/server.js.map +1 -1
  190. package/dist/mcp/tools.d.ts.map +1 -1
  191. package/dist/mcp/tools.js +4119 -169
  192. package/dist/mcp/tools.js.map +1 -1
  193. package/dist/mcp/types.d.ts.map +1 -1
  194. package/dist/types/agent-info.d.ts.map +1 -0
  195. package/dist/types/agent-info.js +11 -0
  196. package/dist/types/agent-info.js.map +1 -0
  197. package/dist/ui/components/ScrollableList.d.ts.map +1 -0
  198. package/dist/ui/components/ScrollableList.js +72 -0
  199. package/dist/ui/components/ScrollableList.js.map +1 -0
  200. package/dist/ui/components/ThemeProvider.d.ts.map +1 -0
  201. package/dist/ui/components/ThemeProvider.js +87 -0
  202. package/dist/ui/components/ThemeProvider.js.map +1 -0
  203. package/dist/ui/components/ThemedBox.d.ts.map +1 -0
  204. package/dist/ui/components/ThemedBox.js +24 -0
  205. package/dist/ui/components/ThemedBox.js.map +1 -0
  206. package/dist/ui/components/agent/ChatHeader.d.ts.map +1 -0
  207. package/dist/ui/components/agent/ChatHeader.js +39 -0
  208. package/dist/ui/components/agent/ChatHeader.js.map +1 -0
  209. package/dist/ui/components/agent/Header.d.ts.map +1 -0
  210. package/dist/ui/components/agent/Header.js +14 -0
  211. package/dist/ui/components/agent/Header.js.map +1 -0
  212. package/dist/ui/components/agent/Input.d.ts.map +1 -0
  213. package/dist/ui/components/agent/Input.js +23 -0
  214. package/dist/ui/components/agent/Input.js.map +1 -0
  215. package/dist/ui/components/agent/Output.d.ts.map +1 -0
  216. package/dist/ui/components/agent/Output.js +23 -0
  217. package/dist/ui/components/agent/Output.js.map +1 -0
  218. package/dist/ui/components/chat/TokenChatUI.d.ts.map +1 -0
  219. package/dist/ui/components/chat/TokenChatUI.js +133 -0
  220. package/dist/ui/components/chat/TokenChatUI.js.map +1 -0
  221. package/dist/ui/components/shell/ShellHeader.d.ts.map +1 -0
  222. package/dist/ui/components/shell/ShellHeader.js +31 -0
  223. package/dist/ui/components/shell/ShellHeader.js.map +1 -0
  224. package/dist/ui/components/shell/ShellInput.d.ts.map +1 -0
  225. package/dist/ui/components/shell/ShellInput.js +151 -0
  226. package/dist/ui/components/shell/ShellInput.js.map +1 -0
  227. package/dist/ui/components/shell/ShellOutput.d.ts.map +1 -0
  228. package/dist/ui/components/shell/ShellOutput.js +8 -0
  229. package/dist/ui/components/shell/ShellOutput.js.map +1 -0
  230. package/dist/ui/hooks/useChatWebSocket.d.ts.map +1 -0
  231. package/dist/ui/hooks/useChatWebSocket.js +76 -0
  232. package/dist/ui/hooks/useChatWebSocket.js.map +1 -0
  233. package/dist/ui/hooks/useCommandHistory.d.ts.map +1 -0
  234. package/dist/ui/hooks/useCommandHistory.js +70 -0
  235. package/dist/ui/hooks/useCommandHistory.js.map +1 -0
  236. package/dist/ui/hooks/useDebounce.d.ts.map +1 -0
  237. package/dist/ui/hooks/useDebounce.js +17 -0
  238. package/dist/ui/hooks/useDebounce.js.map +1 -0
  239. package/dist/ui/hooks/useLogStream.d.ts.map +1 -0
  240. package/dist/ui/hooks/useLogStream.js +20 -0
  241. package/dist/ui/hooks/useLogStream.js.map +1 -0
  242. package/dist/ui/hooks/useVirtualScroll.d.ts.map +1 -0
  243. package/dist/ui/hooks/useVirtualScroll.js +70 -0
  244. package/dist/ui/hooks/useVirtualScroll.js.map +1 -0
  245. package/dist/utils/admin.d.ts.map +1 -0
  246. package/dist/utils/admin.js +144 -0
  247. package/dist/utils/admin.js.map +1 -0
  248. package/dist/utils/autoSetup.d.ts.map +1 -0
  249. package/dist/utils/autoSetup.js +252 -0
  250. package/dist/utils/autoSetup.js.map +1 -0
  251. package/dist/utils/build-constants.d.ts.map +1 -0
  252. package/dist/utils/build-constants.js +10 -0
  253. package/dist/utils/build-constants.js.map +1 -0
  254. package/dist/utils/constants.d.ts.map +1 -1
  255. package/dist/utils/errors.d.ts.map +1 -1
  256. package/dist/utils/errors.js +10 -1
  257. package/dist/utils/errors.js.map +1 -1
  258. package/dist/utils/formatting.d.ts.map +1 -1
  259. package/dist/utils/formatting.js +46 -9
  260. package/dist/utils/formatting.js.map +1 -1
  261. package/dist/utils/llm-cli-config.d.ts.map +1 -0
  262. package/dist/utils/llm-cli-config.js +963 -0
  263. package/dist/utils/llm-cli-config.js.map +1 -0
  264. package/dist/utils/llm-cli-detector.d.ts.map +1 -0
  265. package/dist/utils/llm-cli-detector.js +202 -0
  266. package/dist/utils/llm-cli-detector.js.map +1 -0
  267. package/dist/utils/loading.d.ts.map +1 -1
  268. package/dist/utils/loading.js +25 -3
  269. package/dist/utils/loading.js.map +1 -1
  270. package/dist/utils/maintenance.d.ts.map +1 -0
  271. package/dist/utils/maintenance.js +17 -0
  272. package/dist/utils/maintenance.js.map +1 -0
  273. package/dist/utils/mcp-config.d.ts.map +1 -0
  274. package/dist/utils/mcp-config.js +77 -0
  275. package/dist/utils/mcp-config.js.map +1 -0
  276. package/dist/utils/privateKeyPrompt.d.ts.map +1 -1
  277. package/dist/utils/privateKeyPrompt.js +308 -129
  278. package/dist/utils/privateKeyPrompt.js.map +1 -1
  279. package/dist/utils/process-cleanup.d.ts.map +1 -0
  280. package/dist/utils/process-cleanup.js +136 -0
  281. package/dist/utils/process-cleanup.js.map +1 -0
  282. package/dist/utils/retry.d.ts.map +1 -0
  283. package/dist/utils/retry.js +56 -0
  284. package/dist/utils/retry.js.map +1 -0
  285. package/dist/utils/rpc-helpers.d.ts.map +1 -0
  286. package/dist/utils/rpc-helpers.js +70 -0
  287. package/dist/utils/rpc-helpers.js.map +1 -0
  288. package/dist/utils/rpc-transport.d.ts.map +1 -0
  289. package/dist/utils/rpc-transport.js +87 -0
  290. package/dist/utils/rpc-transport.js.map +1 -0
  291. package/dist/utils/shell-setup.d.ts.map +1 -0
  292. package/dist/utils/shell-setup.js +531 -0
  293. package/dist/utils/shell-setup.js.map +1 -0
  294. package/dist/utils/status.d.ts.map +1 -1
  295. package/dist/utils/status.js +34 -5
  296. package/dist/utils/status.js.map +1 -1
  297. package/dist/utils/token-resolver.d.ts.map +1 -1
  298. package/dist/utils/token-resolver.js +51 -8
  299. package/dist/utils/token-resolver.js.map +1 -1
  300. package/dist/utils/x402-caller.d.ts.map +1 -0
  301. package/dist/utils/x402-caller.js +17 -0
  302. package/dist/utils/x402-caller.js.map +1 -0
  303. package/docs/README.md +28 -0
  304. package/docs/agent/README.md +18 -0
  305. package/docs/api/README.md +41 -0
  306. package/docs/cli/README.md +42 -0
  307. package/docs/guides/README.md +26 -0
  308. package/docs/implementation/README.md +18 -0
  309. package/docs/planning/README.md +19 -0
  310. package/docs/testing/README.md +15 -0
  311. package/docs/ux/README.md +16 -0
  312. package/issues.txt +2 -0
  313. package/package.json +24 -9
  314. package/scripts/cat-spin.sh +417 -0
  315. package/scripts/deprecate-rc-versions.js +58 -0
  316. package/scripts/inject-build-constants.js +43 -0
  317. package/scripts/monitor-foobar.js +117 -0
  318. package/swap.logs +61 -0
  319. package/swapping.txt +108 -0
  320. package/test.txt +12 -0
  321. package/tests/fixtures/test-data.json +16 -0
  322. package/Screenshot 2025-12-21 at 8.56.02/342/200/257PM.png +0 -0
@@ -0,0 +1,1048 @@
1
+ import { Command } from "commander";
2
+ import { HttpcatClient } from "../client.js";
3
+ import { config } from "../config.js";
4
+ import chalk from "chalk";
5
+ import { printBox, formatAddress, } from "../utils/formatting.js";
6
+ import { withLoading } from "../utils/loading.js";
7
+ import { outputJson, outputError } from "../headless/json-output.js";
8
+ import { handleError, getExitCode } from "../utils/errors.js";
9
+ import { promptForPrivateKey } from "../utils/privateKeyPrompt.js";
10
+ import { privateKeyToAccount } from "viem/accounts";
11
+ import { recoverMessageAddress, recoverTypedDataAddress, hashMessage, hashTypedData, keccak256, } from "viem";
12
+ import { readFileSync, existsSync } from "fs";
13
+ import { createHash } from "crypto";
14
+ import inquirer from "inquirer";
15
+ // ============================================================================
16
+ // Shared Utilities
17
+ // ============================================================================
18
+ function getPrivateKey(cliPrivateKey, accountIndex) {
19
+ if (cliPrivateKey)
20
+ return cliPrivateKey;
21
+ const envKey = process.env.HTTPCAT_PRIVATE_KEY;
22
+ if (envKey)
23
+ return envKey;
24
+ try {
25
+ const index = accountIndex !== undefined
26
+ ? accountIndex
27
+ : config.getActiveAccountIndex();
28
+ return config.getAccountPrivateKey(index);
29
+ }
30
+ catch (error) {
31
+ return config.get("privateKey");
32
+ }
33
+ }
34
+ function isConfigured(cliPrivateKey) {
35
+ if (cliPrivateKey)
36
+ return true;
37
+ return config.isConfigured();
38
+ }
39
+ async function ensureWalletUnlocked() {
40
+ const password = config.getPassword();
41
+ if (!password) {
42
+ config.ensureSessionValid();
43
+ return;
44
+ }
45
+ if (!config.isSessionValid()) {
46
+ const answers = await inquirer.prompt([
47
+ {
48
+ type: "password",
49
+ name: "password",
50
+ message: "Enter password to unlock wallet:",
51
+ mask: "•",
52
+ },
53
+ ]);
54
+ await config.unlockSession(answers.password);
55
+ }
56
+ }
57
+ /**
58
+ * Parse JSON from string or file path
59
+ */
60
+ function parseJsonInput(input) {
61
+ try {
62
+ return JSON.parse(input);
63
+ }
64
+ catch {
65
+ if (existsSync(input)) {
66
+ return JSON.parse(readFileSync(input, "utf-8"));
67
+ }
68
+ throw new Error(`Invalid JSON: ${input}`);
69
+ }
70
+ }
71
+ /**
72
+ * Read message from stdin if input is "-"
73
+ */
74
+ async function readMessageInput(input) {
75
+ if (input === "-") {
76
+ return new Promise((resolve, reject) => {
77
+ let data = "";
78
+ process.stdin.setEncoding("utf8");
79
+ process.stdin.on("data", (chunk) => {
80
+ data += chunk;
81
+ });
82
+ process.stdin.on("end", () => {
83
+ resolve(data.trim());
84
+ });
85
+ process.stdin.on("error", reject);
86
+ process.stdin.resume();
87
+ });
88
+ }
89
+ return input;
90
+ }
91
+ /**
92
+ * Hash file contents
93
+ */
94
+ function hashFile(filePath, algorithm = "sha256") {
95
+ if (!existsSync(filePath)) {
96
+ throw new Error(`File not found: ${filePath}`);
97
+ }
98
+ const fileContent = readFileSync(filePath);
99
+ if (algorithm === "sha256") {
100
+ return createHash("sha256").update(fileContent).digest("hex");
101
+ }
102
+ else {
103
+ // Keccak-256 (used by Ethereum)
104
+ // Convert Buffer to Hex string first
105
+ const hexString = `0x${fileContent.toString("hex")}`;
106
+ return keccak256(hexString).slice(2); // Remove 0x prefix to match sha256 format
107
+ }
108
+ }
109
+ /**
110
+ * Format signature in different formats
111
+ */
112
+ function formatSignature(signature, format = "hex") {
113
+ if (format === "hex") {
114
+ return signature;
115
+ }
116
+ // RSV format: { r, s, v }
117
+ if (format === "rsv") {
118
+ const r = signature.slice(0, 66);
119
+ const s = "0x" + signature.slice(66, 130);
120
+ const v = parseInt(signature.slice(130, 132), 16);
121
+ return { r, s, v };
122
+ }
123
+ // Compact format: 65 bytes without 0x
124
+ if (format === "compact") {
125
+ return signature.slice(2);
126
+ }
127
+ return signature;
128
+ }
129
+ // ============================================================================
130
+ // Sign Command Group
131
+ // ============================================================================
132
+ export function createSignCommand() {
133
+ const signCommand = new Command("sign")
134
+ .description("Cryptographic signing operations")
135
+ .addHelpText("after", `
136
+ Examples:
137
+ httpcat sign message "Hello, world!"
138
+ httpcat sign file document.pdf
139
+ httpcat sign transaction '{"to":"0x...","value":"1000000000000000000"}'
140
+ httpcat sign eip712 --domain '{"name":"MyApp"}' --types '{}' --message '{}'
141
+ httpcat sign verify 0x... "Hello, world!"
142
+ `);
143
+ // If no subcommand, enter interactive mode
144
+ signCommand.action(async (options, command) => {
145
+ const globalOpts = command.parent?.opts() || {};
146
+ if (globalOpts.json) {
147
+ outputError("sign", new Error("Please specify a subcommand"), 1);
148
+ process.exit(1);
149
+ }
150
+ // Enter interactive mode
151
+ await startInteractiveMode(globalOpts);
152
+ });
153
+ // ============================================================================
154
+ // Message Signing (EIP-191)
155
+ // ============================================================================
156
+ signCommand
157
+ .command("message")
158
+ .description("Sign a message using EIP-191 personal sign")
159
+ .argument("[message]", "Message to sign (or use --file or stdin with -)")
160
+ .option("--file <path>", "Read message from file")
161
+ .option("--stdin", "Read message from stdin")
162
+ .option("--output <file>", "Save signature to file")
163
+ .option("--format <format>", "Signature format: hex, rsv, compact", "hex")
164
+ .option("--verify", "Verify signature after signing")
165
+ .action(async (message, options, command) => {
166
+ try {
167
+ const globalOpts = command.parent?.parent?.opts() || {};
168
+ const accountIndex = globalOpts.account;
169
+ await ensureWalletUnlocked();
170
+ let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
171
+ if (!isConfigured(globalOpts.privateKey)) {
172
+ if (globalOpts.json) {
173
+ console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
174
+ process.exit(2);
175
+ }
176
+ privateKey = await promptForPrivateKey();
177
+ }
178
+ const account = privateKeyToAccount(privateKey);
179
+ // Get message input
180
+ let messageText;
181
+ if (options.file) {
182
+ messageText = readFileSync(options.file, "utf-8");
183
+ }
184
+ else if (options.stdin || message === "-") {
185
+ messageText = await readMessageInput("-");
186
+ }
187
+ else if (message) {
188
+ messageText = message;
189
+ }
190
+ else {
191
+ throw new Error("Message required. Use argument, --file, or --stdin");
192
+ }
193
+ // Compute message hash locally (no API call needed)
194
+ const messageHash = hashMessage(messageText);
195
+ // Sign locally using viem
196
+ const signature = await account.signMessage({ message: messageText });
197
+ const signerAddress = account.address;
198
+ // Verify if requested
199
+ let verified = false;
200
+ if (options.verify) {
201
+ const recovered = await recoverMessageAddress({
202
+ message: messageText,
203
+ signature,
204
+ });
205
+ verified = recovered.toLowerCase() === signerAddress.toLowerCase();
206
+ }
207
+ const formattedSignature = formatSignature(signature, options.format);
208
+ // Save to file if requested
209
+ if (options.output) {
210
+ const outputData = {
211
+ message: messageText,
212
+ messageHash: messageHash,
213
+ signature: signature,
214
+ signer: signerAddress,
215
+ verified,
216
+ format: options.format,
217
+ };
218
+ require("fs").writeFileSync(options.output, JSON.stringify(outputData, null, 2));
219
+ }
220
+ if (globalOpts.json) {
221
+ outputJson("sign_message", {
222
+ type: "eip191",
223
+ message: messageText,
224
+ messageHash: messageHash,
225
+ signature: formattedSignature,
226
+ signer: signerAddress,
227
+ verified,
228
+ });
229
+ }
230
+ else if (!globalOpts.quiet) {
231
+ console.log();
232
+ console.log(chalk.magenta.bold("✍️ Signature Created"));
233
+ console.log();
234
+ const boxData = {
235
+ "Signature Type": chalk.cyan("EIP-191"),
236
+ Message: chalk.yellow(messageText.length > 50 ? messageText.substring(0, 50) + "..." : messageText),
237
+ "Message Hash": chalk.green.bold(messageHash),
238
+ Signature: chalk.yellow(signature),
239
+ Signer: formatAddress(signerAddress),
240
+ };
241
+ if (options.verify) {
242
+ boxData["Verified"] = verified ? chalk.green("✅") : chalk.red("❌");
243
+ }
244
+ printBox("Signature Data", boxData);
245
+ console.log();
246
+ }
247
+ process.exit(0);
248
+ }
249
+ catch (error) {
250
+ const globalOpts = command.parent?.parent?.opts() || {};
251
+ if (globalOpts.json) {
252
+ outputError("sign_message", error, getExitCode(error));
253
+ }
254
+ else {
255
+ handleError(error, globalOpts.verbose);
256
+ }
257
+ process.exit(getExitCode(error));
258
+ }
259
+ });
260
+ // ============================================================================
261
+ // File Signing
262
+ // ============================================================================
263
+ signCommand
264
+ .command("file")
265
+ .description("Sign a file (hash + EIP-191 signature)")
266
+ .argument("<file>", "File path to sign")
267
+ .option("--hash-algorithm <algorithm>", "Hash algorithm: sha256, keccak256", "sha256")
268
+ .option("--output <file>", "Save signature to file")
269
+ .option("--format <format>", "Signature format: hex, rsv, compact", "hex")
270
+ .option("--verify", "Verify signature after signing")
271
+ .action(async (file, options, command) => {
272
+ try {
273
+ const globalOpts = command.parent?.parent?.opts() || {};
274
+ const accountIndex = globalOpts.account;
275
+ await ensureWalletUnlocked();
276
+ let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
277
+ if (!isConfigured(globalOpts.privateKey)) {
278
+ if (globalOpts.json) {
279
+ console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
280
+ process.exit(2);
281
+ }
282
+ privateKey = await promptForPrivateKey();
283
+ }
284
+ const account = privateKeyToAccount(privateKey);
285
+ // Hash file
286
+ const fileHash = hashFile(file, options.hashAlgorithm);
287
+ const fileHashHex = `0x${fileHash}`;
288
+ // Compute message hash locally (no API call needed)
289
+ const messageHash = hashMessage(fileHashHex);
290
+ // Sign the hash locally
291
+ const signature = await account.signMessage({ message: fileHashHex });
292
+ const signerAddress = account.address;
293
+ // Verify if requested
294
+ let verified = false;
295
+ if (options.verify) {
296
+ const recovered = await recoverMessageAddress({
297
+ message: fileHashHex,
298
+ signature,
299
+ });
300
+ verified = recovered.toLowerCase() === signerAddress.toLowerCase();
301
+ }
302
+ const formattedSignature = formatSignature(signature, options.format);
303
+ // Save to file if requested
304
+ if (options.output) {
305
+ const outputData = {
306
+ file,
307
+ fileHash: fileHashHex,
308
+ hashAlgorithm: options.hashAlgorithm,
309
+ messageHash: messageHash,
310
+ signature: signature,
311
+ signer: signerAddress,
312
+ verified,
313
+ format: options.format,
314
+ };
315
+ require("fs").writeFileSync(options.output, JSON.stringify(outputData, null, 2));
316
+ }
317
+ if (globalOpts.json) {
318
+ outputJson("sign_file", {
319
+ type: "file",
320
+ file,
321
+ fileHash: fileHashHex,
322
+ hashAlgorithm: options.hashAlgorithm,
323
+ messageHash: messageHash,
324
+ signature: formattedSignature,
325
+ signer: signerAddress,
326
+ verified,
327
+ });
328
+ }
329
+ else if (!globalOpts.quiet) {
330
+ console.log();
331
+ console.log(chalk.magenta.bold("✍️ File Signature Created"));
332
+ console.log();
333
+ const boxData = {
334
+ File: chalk.cyan(file),
335
+ "Hash Algorithm": chalk.cyan(options.hashAlgorithm),
336
+ "File Hash": chalk.green.bold(fileHashHex),
337
+ "Message Hash": chalk.green.bold(messageHash),
338
+ Signature: chalk.yellow(signature),
339
+ Signer: formatAddress(signerAddress),
340
+ };
341
+ if (options.verify) {
342
+ boxData["Verified"] = verified ? chalk.green("✅") : chalk.red("❌");
343
+ }
344
+ printBox("Signature Data", boxData);
345
+ console.log();
346
+ }
347
+ process.exit(0);
348
+ }
349
+ catch (error) {
350
+ const globalOpts = command.parent?.parent?.opts() || {};
351
+ if (globalOpts.json) {
352
+ outputError("sign_file", error, getExitCode(error));
353
+ }
354
+ else {
355
+ handleError(error, globalOpts.verbose);
356
+ }
357
+ process.exit(getExitCode(error));
358
+ }
359
+ });
360
+ // ============================================================================
361
+ // Transaction Signing
362
+ // ============================================================================
363
+ signCommand
364
+ .command("transaction")
365
+ .description("Sign a transaction (EIP-155)")
366
+ .argument("<tx-json>", "Transaction JSON (string or file path)")
367
+ .option("--output <file>", "Save signed transaction to file")
368
+ .action(async (txJson, options, command) => {
369
+ try {
370
+ const globalOpts = command.parent?.parent?.opts() || {};
371
+ const accountIndex = globalOpts.account;
372
+ await ensureWalletUnlocked();
373
+ let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
374
+ if (!isConfigured(globalOpts.privateKey)) {
375
+ if (globalOpts.json) {
376
+ console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
377
+ process.exit(2);
378
+ }
379
+ privateKey = await promptForPrivateKey();
380
+ }
381
+ const account = privateKeyToAccount(privateKey);
382
+ const client = await HttpcatClient.create(privateKey);
383
+ // Parse transaction JSON
384
+ const tx = parseJsonInput(txJson);
385
+ // Prepare transaction via backend
386
+ // Note: Transaction preparation requires blockchain state (nonce, gas, etc.)
387
+ // so we use the API for this. The actual signing happens locally.
388
+ const prepareResult = await withLoading(async () => {
389
+ const { data } = await client.invoke("tools/tx/prepare", tx);
390
+ return data;
391
+ }, {
392
+ message: "Preparing transaction...",
393
+ json: globalOpts.json,
394
+ quiet: globalOpts.quiet,
395
+ spinner: "cat",
396
+ clearOnSuccess: true,
397
+ });
398
+ // For transaction signing, we prepare the transaction data
399
+ // Full RLP-encoded signed transaction requires additional serialization
400
+ // The prepared transaction contains all necessary fields for signing
401
+ // Note: To actually broadcast, use httpcat send or another transaction execution method
402
+ // Create a signed transaction representation
403
+ // In a full implementation, this would serialize and sign the transaction
404
+ // For now, we return the prepared transaction with signing metadata
405
+ const signedTx = {
406
+ ...prepareResult.transaction,
407
+ from: account.address,
408
+ // The actual signed transaction RLP would be generated here
409
+ // This requires transaction serialization and RLP encoding
410
+ };
411
+ // For display purposes, create a placeholder signed transaction string
412
+ // In production, this would be the actual RLP-encoded signed transaction
413
+ const signedTxHex = `0x${JSON.stringify(signedTx).slice(0, 100)}...`;
414
+ // Save to file if requested
415
+ if (options.output) {
416
+ const outputData = {
417
+ transaction: prepareResult.transaction,
418
+ signedTransaction: signedTx,
419
+ messageHash: prepareResult.messageHash,
420
+ signer: account.address,
421
+ };
422
+ require("fs").writeFileSync(options.output, JSON.stringify(outputData, null, 2));
423
+ }
424
+ if (globalOpts.json) {
425
+ outputJson("sign_transaction", {
426
+ transaction: prepareResult.transaction,
427
+ signedTransaction: signedTx,
428
+ messageHash: prepareResult.messageHash,
429
+ signer: account.address,
430
+ note: "Transaction prepared. Use httpcat send or another method to execute.",
431
+ });
432
+ }
433
+ else if (!globalOpts.quiet) {
434
+ console.log();
435
+ console.log(chalk.magenta.bold("✍️ Transaction Prepared"));
436
+ console.log();
437
+ const boxData = {
438
+ "Transaction Type": chalk.cyan("EIP-155"),
439
+ To: formatAddress(prepareResult.transaction.to || "0x0"),
440
+ Value: chalk.yellow(prepareResult.transaction.value
441
+ ? `${prepareResult.transaction.value} wei`
442
+ : "0 wei"),
443
+ "Message Hash": chalk.green.bold(prepareResult.messageHash),
444
+ Signer: formatAddress(account.address),
445
+ Note: chalk.dim("Transaction prepared. Use 'httpcat send' to execute."),
446
+ };
447
+ printBox("Transaction Data", boxData);
448
+ console.log();
449
+ }
450
+ process.exit(0);
451
+ }
452
+ catch (error) {
453
+ const globalOpts = command.parent?.parent?.opts() || {};
454
+ if (globalOpts.json) {
455
+ outputError("sign_transaction", error, getExitCode(error));
456
+ }
457
+ else {
458
+ handleError(error, globalOpts.verbose);
459
+ }
460
+ process.exit(getExitCode(error));
461
+ }
462
+ });
463
+ // ============================================================================
464
+ // EIP-712 Signing
465
+ // ============================================================================
466
+ signCommand
467
+ .command("eip712")
468
+ .description("Sign EIP-712 structured data")
469
+ .requiredOption("--domain <domain>", "Domain JSON (string or file path)")
470
+ .requiredOption("--types <types>", "Types JSON (string or file path)")
471
+ .requiredOption("--message <message>", "Message JSON (string or file path)")
472
+ .option("--output <file>", "Save signature to file")
473
+ .option("--verify", "Verify signature after signing")
474
+ .action(async (options, command) => {
475
+ try {
476
+ const globalOpts = command.parent?.parent?.opts() || {};
477
+ const accountIndex = globalOpts.account;
478
+ await ensureWalletUnlocked();
479
+ let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
480
+ if (!isConfigured(globalOpts.privateKey)) {
481
+ if (globalOpts.json) {
482
+ console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
483
+ process.exit(2);
484
+ }
485
+ privateKey = await promptForPrivateKey();
486
+ }
487
+ const account = privateKeyToAccount(privateKey);
488
+ // Parse JSON inputs
489
+ const domain = parseJsonInput(options.domain);
490
+ const types = parseJsonInput(options.types);
491
+ const message = parseJsonInput(options.message);
492
+ // Compute message hash locally (no API call needed)
493
+ const primaryType = Object.keys(types).find((k) => k !== "EIP712Domain") || "Message";
494
+ const messageHash = hashTypedData({
495
+ domain: domain,
496
+ types: types,
497
+ primaryType: primaryType,
498
+ message: message,
499
+ });
500
+ // Sign using account's signTypedData method
501
+ const signature = await account.signTypedData({
502
+ domain,
503
+ types: types,
504
+ primaryType,
505
+ message: message,
506
+ });
507
+ const signerAddress = account.address;
508
+ // Verify if requested
509
+ let verified = false;
510
+ if (options.verify) {
511
+ const recovered = await recoverTypedDataAddress({
512
+ domain,
513
+ types: types,
514
+ primaryType: Object.keys(types).find((k) => k !== "EIP712Domain") || "Message",
515
+ message: message,
516
+ signature,
517
+ });
518
+ verified = recovered.toLowerCase() === signerAddress.toLowerCase();
519
+ }
520
+ // Save to file if requested
521
+ if (options.output) {
522
+ const outputData = {
523
+ domain,
524
+ types,
525
+ message,
526
+ messageHash: messageHash,
527
+ signature,
528
+ signer: signerAddress,
529
+ verified,
530
+ };
531
+ require("fs").writeFileSync(options.output, JSON.stringify(outputData, null, 2));
532
+ }
533
+ if (globalOpts.json) {
534
+ outputJson("sign_eip712", {
535
+ type: "eip712",
536
+ domain,
537
+ types,
538
+ message,
539
+ messageHash: messageHash,
540
+ signature,
541
+ signer: signerAddress,
542
+ verified,
543
+ });
544
+ }
545
+ else if (!globalOpts.quiet) {
546
+ console.log();
547
+ console.log(chalk.magenta.bold("✍️ EIP-712 Signature Created"));
548
+ console.log();
549
+ const boxData = {
550
+ "Signature Type": chalk.cyan("EIP-712"),
551
+ Domain: chalk.yellow(JSON.stringify(domain).substring(0, 50) + "..."),
552
+ "Message Hash": chalk.green.bold(messageHash),
553
+ Signature: chalk.yellow(signature),
554
+ Signer: formatAddress(signerAddress),
555
+ };
556
+ if (options.verify) {
557
+ boxData["Verified"] = verified ? chalk.green("✅") : chalk.red("❌");
558
+ }
559
+ printBox("Signature Data", boxData);
560
+ console.log();
561
+ }
562
+ process.exit(0);
563
+ }
564
+ catch (error) {
565
+ const globalOpts = command.parent?.parent?.opts() || {};
566
+ if (globalOpts.json) {
567
+ outputError("sign_eip712", error, getExitCode(error));
568
+ }
569
+ else {
570
+ handleError(error, globalOpts.verbose);
571
+ }
572
+ process.exit(getExitCode(error));
573
+ }
574
+ });
575
+ // ============================================================================
576
+ // Permit2 Signing
577
+ // ============================================================================
578
+ signCommand
579
+ .command("permit2")
580
+ .description("Sign Permit2 token authorization")
581
+ .requiredOption("--token <address>", "Token address (0x format)")
582
+ .requiredOption("--amount <amount>", "Amount in wei")
583
+ .requiredOption("--spender <address>", "Spender address (0x format)")
584
+ .option("--nonce <nonce>", "Nonce (auto-generated if not provided)")
585
+ .option("--deadline <deadline>", "Deadline (Unix timestamp, auto-generated if not provided)")
586
+ .option("--output <file>", "Save signature to file")
587
+ .option("--verify", "Verify signature after signing")
588
+ .action(async (options, command) => {
589
+ try {
590
+ const globalOpts = command.parent?.parent?.opts() || {};
591
+ const accountIndex = globalOpts.account;
592
+ await ensureWalletUnlocked();
593
+ let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
594
+ if (!isConfigured(globalOpts.privateKey)) {
595
+ if (globalOpts.json) {
596
+ console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
597
+ process.exit(2);
598
+ }
599
+ privateKey = await promptForPrivateKey();
600
+ }
601
+ const account = privateKeyToAccount(privateKey);
602
+ // Get network for chain ID
603
+ const { getNetworkConfig } = await import("../utils/constants.js");
604
+ const { config } = await import("../config.js");
605
+ const network = config.get("network") || "eip155:84532";
606
+ const { chain } = await getNetworkConfig(network);
607
+ // Auto-generate nonce and deadline if not provided
608
+ const nonce = options.nonce ? parseInt(options.nonce) : Math.floor(Math.random() * 2 ** 32);
609
+ const deadline = options.deadline
610
+ ? parseInt(options.deadline)
611
+ : Math.floor(Date.now() / 1000) + 3600; // 1 hour from now
612
+ // Construct Permit2 EIP-712 data locally (no API call needed)
613
+ const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
614
+ const domain = {
615
+ name: "Permit2",
616
+ chainId: chain.id,
617
+ verifyingContract: PERMIT2_ADDRESS,
618
+ };
619
+ const types = {
620
+ PermitSingle: [
621
+ { name: "details", type: "PermitDetails" },
622
+ { name: "spender", type: "address" },
623
+ { name: "sigDeadline", type: "uint256" },
624
+ ],
625
+ PermitDetails: [
626
+ { name: "token", type: "address" },
627
+ { name: "amount", type: "uint160" },
628
+ { name: "expiration", type: "uint48" },
629
+ { name: "nonce", type: "uint48" },
630
+ ],
631
+ };
632
+ const message = {
633
+ details: {
634
+ token: options.token,
635
+ amount: BigInt(options.amount) > BigInt(2 ** 160 - 1) ? BigInt(2 ** 160 - 1) : BigInt(options.amount),
636
+ expiration: BigInt(deadline),
637
+ nonce: BigInt(nonce),
638
+ },
639
+ spender: options.spender,
640
+ sigDeadline: BigInt(deadline),
641
+ };
642
+ // Compute message hash locally
643
+ const messageHash = hashTypedData({
644
+ domain,
645
+ types: types,
646
+ primaryType: "PermitSingle",
647
+ message: message,
648
+ });
649
+ // Sign using account's signTypedData method
650
+ const signature = await account.signTypedData({
651
+ domain,
652
+ types: types,
653
+ primaryType: "PermitSingle",
654
+ message: message,
655
+ });
656
+ const signerAddress = account.address;
657
+ // Verify if requested
658
+ let verified = false;
659
+ if (options.verify) {
660
+ const recovered = await recoverTypedDataAddress({
661
+ domain,
662
+ types: types,
663
+ primaryType: "PermitSingle",
664
+ message: message,
665
+ signature,
666
+ });
667
+ verified = recovered.toLowerCase() === signerAddress.toLowerCase();
668
+ }
669
+ // Save to file if requested
670
+ if (options.output) {
671
+ const outputData = {
672
+ token: options.token,
673
+ amount: options.amount,
674
+ spender: options.spender,
675
+ nonce,
676
+ deadline,
677
+ messageHash: messageHash,
678
+ signature,
679
+ signer: signerAddress,
680
+ verified,
681
+ };
682
+ require("fs").writeFileSync(options.output, JSON.stringify(outputData, null, 2));
683
+ }
684
+ if (globalOpts.json) {
685
+ outputJson("sign_permit2", {
686
+ type: "permit2",
687
+ token: options.token,
688
+ amount: options.amount,
689
+ spender: options.spender,
690
+ nonce,
691
+ deadline,
692
+ messageHash: messageHash,
693
+ signature,
694
+ signer: signerAddress,
695
+ verified,
696
+ });
697
+ }
698
+ else if (!globalOpts.quiet) {
699
+ console.log();
700
+ console.log(chalk.magenta.bold("✍️ Permit2 Signature Created"));
701
+ console.log();
702
+ const boxData = {
703
+ "Signature Type": chalk.cyan("Permit2"),
704
+ Token: formatAddress(options.token),
705
+ Amount: chalk.yellow(options.amount),
706
+ Spender: formatAddress(options.spender),
707
+ Nonce: chalk.cyan(nonce.toString()),
708
+ Deadline: chalk.cyan(new Date(deadline * 1000).toISOString()),
709
+ "Message Hash": chalk.green.bold(messageHash),
710
+ Signature: chalk.yellow(signature),
711
+ Signer: formatAddress(signerAddress),
712
+ };
713
+ if (options.verify) {
714
+ boxData["Verified"] = verified ? chalk.green("✅") : chalk.red("❌");
715
+ }
716
+ printBox("Signature Data", boxData);
717
+ console.log();
718
+ }
719
+ process.exit(0);
720
+ }
721
+ catch (error) {
722
+ const globalOpts = command.parent?.parent?.opts() || {};
723
+ if (globalOpts.json) {
724
+ outputError("sign_permit2", error, getExitCode(error));
725
+ }
726
+ else {
727
+ handleError(error, globalOpts.verbose);
728
+ }
729
+ process.exit(getExitCode(error));
730
+ }
731
+ });
732
+ // ============================================================================
733
+ // Batch Signing
734
+ // ============================================================================
735
+ signCommand
736
+ .command("batch")
737
+ .description("Batch sign multiple messages/files")
738
+ .argument("<batch-json>", "Batch JSON (string or file path)")
739
+ .option("--output <file>", "Save all signatures to file")
740
+ .action(async (batchJson, options, command) => {
741
+ try {
742
+ const globalOpts = command.parent?.parent?.opts() || {};
743
+ const accountIndex = globalOpts.account;
744
+ await ensureWalletUnlocked();
745
+ let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
746
+ if (!isConfigured(globalOpts.privateKey)) {
747
+ if (globalOpts.json) {
748
+ console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
749
+ process.exit(2);
750
+ }
751
+ privateKey = await promptForPrivateKey();
752
+ }
753
+ const account = privateKeyToAccount(privateKey);
754
+ // Parse batch JSON
755
+ const batch = parseJsonInput(batchJson);
756
+ if (!Array.isArray(batch) && !batch.messages && !batch.files) {
757
+ throw new Error("Batch JSON must be an array or object with 'messages' and/or 'files' arrays");
758
+ }
759
+ const messages = Array.isArray(batch) ? batch : batch.messages || [];
760
+ const files = Array.isArray(batch) ? [] : batch.files || [];
761
+ const results = [];
762
+ // Sign messages
763
+ for (const msg of messages) {
764
+ const messageText = typeof msg === "string" ? msg : msg.message;
765
+ // Compute message hash locally (no API call needed)
766
+ const messageHash = hashMessage(messageText);
767
+ const signature = await account.signMessage({ message: messageText });
768
+ results.push({
769
+ type: "message",
770
+ message: messageText,
771
+ messageHash: messageHash,
772
+ signature,
773
+ signer: account.address,
774
+ });
775
+ }
776
+ // Sign files
777
+ for (const file of files) {
778
+ const filePath = typeof file === "string" ? file : file.path;
779
+ const hashAlgo = typeof file === "string" ? "sha256" : (file.hashAlgorithm || "sha256");
780
+ const fileHash = hashFile(filePath, hashAlgo);
781
+ const fileHashHex = `0x${fileHash}`;
782
+ // Compute message hash locally (no API call needed)
783
+ const messageHash = hashMessage(fileHashHex);
784
+ const signature = await account.signMessage({ message: fileHashHex });
785
+ results.push({
786
+ type: "file",
787
+ file: filePath,
788
+ fileHash: fileHashHex,
789
+ hashAlgorithm: hashAlgo,
790
+ messageHash: messageHash,
791
+ signature,
792
+ signer: account.address,
793
+ });
794
+ }
795
+ // Save to file if requested
796
+ if (options.output) {
797
+ require("fs").writeFileSync(options.output, JSON.stringify(results, null, 2));
798
+ }
799
+ if (globalOpts.json) {
800
+ outputJson("sign_batch", {
801
+ count: results.length,
802
+ signatures: results,
803
+ });
804
+ }
805
+ else if (!globalOpts.quiet) {
806
+ console.log();
807
+ console.log(chalk.magenta.bold(`✍️ Batch Signature Created (${results.length} items)`));
808
+ console.log();
809
+ for (const result of results) {
810
+ const boxData = {
811
+ Type: chalk.cyan(result.type),
812
+ };
813
+ if (result.type === "message") {
814
+ boxData["Message"] = chalk.yellow(result.message.substring(0, 50) + (result.message.length > 50 ? "..." : ""));
815
+ }
816
+ else {
817
+ boxData["File"] = chalk.cyan(result.file);
818
+ boxData["File Hash"] = chalk.green.bold(result.fileHash);
819
+ }
820
+ boxData["Signature"] = chalk.yellow(result.signature);
821
+ boxData["Signer"] = formatAddress(result.signer);
822
+ printBox(`Signature ${results.indexOf(result) + 1}`, boxData);
823
+ console.log();
824
+ }
825
+ }
826
+ process.exit(0);
827
+ }
828
+ catch (error) {
829
+ const globalOpts = command.parent?.parent?.opts() || {};
830
+ if (globalOpts.json) {
831
+ outputError("sign_batch", error, getExitCode(error));
832
+ }
833
+ else {
834
+ handleError(error, globalOpts.verbose);
835
+ }
836
+ process.exit(getExitCode(error));
837
+ }
838
+ });
839
+ // ============================================================================
840
+ // Signature Verification
841
+ // ============================================================================
842
+ signCommand
843
+ .command("verify")
844
+ .description("Verify a signature")
845
+ .requiredOption("--signature <sig>", "Signature to verify (hex format)")
846
+ .requiredOption("--message <msg>", "Original message")
847
+ .option("--type <type>", "Signature type: eip191, eip712", "eip191")
848
+ .option("--domain <domain>", "EIP-712 domain (required for eip712 type)")
849
+ .option("--types <types>", "EIP-712 types (required for eip712 type)")
850
+ .option("--primary-type <type>", "EIP-712 primary type (required for eip712 type)")
851
+ .action(async (options, command) => {
852
+ try {
853
+ const globalOpts = command.parent?.parent?.opts() || {};
854
+ const signature = options.signature;
855
+ const message = options.message;
856
+ let recoveredAddress;
857
+ let verified = false;
858
+ if (options.type === "eip712") {
859
+ if (!options.domain || !options.types || !options.primaryType) {
860
+ throw new Error("EIP-712 verification requires --domain, --types, and --primary-type");
861
+ }
862
+ const domain = parseJsonInput(options.domain);
863
+ const types = parseJsonInput(options.types);
864
+ const messageObj = parseJsonInput(message);
865
+ recoveredAddress = await recoverTypedDataAddress({
866
+ domain,
867
+ types: types,
868
+ primaryType: options.primaryType,
869
+ message: messageObj,
870
+ signature,
871
+ });
872
+ verified = true; // If recovery succeeds, signature is valid
873
+ }
874
+ else {
875
+ // EIP-191
876
+ recoveredAddress = await recoverMessageAddress({
877
+ message,
878
+ signature,
879
+ });
880
+ verified = true; // If recovery succeeds, signature is valid
881
+ }
882
+ if (globalOpts.json) {
883
+ outputJson("verify_signature", {
884
+ verified,
885
+ recoveredAddress,
886
+ signature,
887
+ message,
888
+ type: options.type,
889
+ });
890
+ }
891
+ else if (!globalOpts.quiet) {
892
+ console.log();
893
+ console.log(chalk.magenta.bold("🔍 Signature Verification"));
894
+ console.log();
895
+ const boxData = {
896
+ "Signature Type": chalk.cyan(options.type.toUpperCase()),
897
+ Verified: verified ? chalk.green("✅ Yes") : chalk.red("❌ No"),
898
+ "Recovered Address": formatAddress(recoveredAddress),
899
+ Signature: chalk.yellow(signature),
900
+ };
901
+ printBox("Verification Result", boxData);
902
+ console.log();
903
+ }
904
+ process.exit(verified ? 0 : 1);
905
+ }
906
+ catch (error) {
907
+ const globalOpts = command.parent?.parent?.opts() || {};
908
+ if (globalOpts.json) {
909
+ outputError("verify_signature", error, getExitCode(error));
910
+ }
911
+ else {
912
+ handleError(error, globalOpts.verbose);
913
+ }
914
+ process.exit(getExitCode(error));
915
+ }
916
+ });
917
+ return signCommand;
918
+ }
919
+ // ============================================================================
920
+ // Interactive Mode
921
+ // ============================================================================
922
+ async function startInteractiveMode(globalOpts) {
923
+ try {
924
+ await ensureWalletUnlocked();
925
+ let privateKey = getPrivateKey(globalOpts.privateKey);
926
+ if (!isConfigured(globalOpts.privateKey)) {
927
+ privateKey = await promptForPrivateKey();
928
+ }
929
+ const account = privateKeyToAccount(privateKey);
930
+ console.log();
931
+ console.log(chalk.magenta.bold("✍️ Interactive Signing Mode"));
932
+ console.log();
933
+ const { signatureType } = await inquirer.prompt([
934
+ {
935
+ type: "list",
936
+ name: "signatureType",
937
+ message: "What would you like to sign?",
938
+ choices: [
939
+ { name: "Message (EIP-191)", value: "message" },
940
+ { name: "File", value: "file" },
941
+ { name: "Transaction", value: "transaction" },
942
+ { name: "EIP-712 Structured Data", value: "eip712" },
943
+ { name: "Permit2 Authorization", value: "permit2" },
944
+ ],
945
+ },
946
+ ]);
947
+ switch (signatureType) {
948
+ case "message": {
949
+ const { message } = await inquirer.prompt([
950
+ {
951
+ type: "input",
952
+ name: "message",
953
+ message: "Enter message to sign:",
954
+ },
955
+ ]);
956
+ const { verify } = await inquirer.prompt([
957
+ {
958
+ type: "confirm",
959
+ name: "verify",
960
+ message: "Verify signature after signing?",
961
+ default: true,
962
+ },
963
+ ]);
964
+ // Execute message signing (no API call needed)
965
+ const messageHash = hashMessage(message);
966
+ const signature = await account.signMessage({ message });
967
+ let verified = false;
968
+ if (verify) {
969
+ const recovered = await recoverMessageAddress({
970
+ message,
971
+ signature,
972
+ });
973
+ verified = recovered.toLowerCase() === account.address.toLowerCase();
974
+ }
975
+ console.log();
976
+ console.log(chalk.magenta.bold("✍️ Signature Created"));
977
+ console.log();
978
+ const boxData = {
979
+ "Signature Type": chalk.cyan("EIP-191"),
980
+ Message: chalk.yellow(message),
981
+ "Message Hash": chalk.green.bold(messageHash),
982
+ Signature: chalk.yellow(signature),
983
+ Signer: formatAddress(account.address),
984
+ };
985
+ if (verify) {
986
+ boxData["Verified"] = verified ? chalk.green("✅") : chalk.red("❌");
987
+ }
988
+ printBox("Signature Data", boxData);
989
+ console.log();
990
+ break;
991
+ }
992
+ case "file": {
993
+ const { filePath } = await inquirer.prompt([
994
+ {
995
+ type: "input",
996
+ name: "filePath",
997
+ message: "Enter file path:",
998
+ validate: (input) => {
999
+ if (!existsSync(input)) {
1000
+ return "File does not exist";
1001
+ }
1002
+ return true;
1003
+ },
1004
+ },
1005
+ ]);
1006
+ const { hashAlgo } = await inquirer.prompt([
1007
+ {
1008
+ type: "list",
1009
+ name: "hashAlgo",
1010
+ message: "Hash algorithm:",
1011
+ choices: [
1012
+ { name: "SHA-256", value: "sha256" },
1013
+ { name: "Keccak-256 (Ethereum)", value: "keccak256" },
1014
+ ],
1015
+ default: "sha256",
1016
+ },
1017
+ ]);
1018
+ const fileHash = hashFile(filePath, hashAlgo);
1019
+ const fileHashHex = `0x${fileHash}`;
1020
+ // Compute message hash locally (no API call needed)
1021
+ const messageHash = hashMessage(fileHashHex);
1022
+ const signature = await account.signMessage({ message: fileHashHex });
1023
+ console.log();
1024
+ console.log(chalk.magenta.bold("✍️ File Signature Created"));
1025
+ console.log();
1026
+ const boxData = {
1027
+ File: chalk.cyan(filePath),
1028
+ "Hash Algorithm": chalk.cyan(hashAlgo),
1029
+ "File Hash": chalk.green.bold(fileHashHex),
1030
+ "Message Hash": chalk.green.bold(messageHash),
1031
+ Signature: chalk.yellow(signature),
1032
+ Signer: formatAddress(account.address),
1033
+ };
1034
+ printBox("Signature Data", boxData);
1035
+ console.log();
1036
+ break;
1037
+ }
1038
+ default:
1039
+ console.log(chalk.yellow(`Interactive mode for ${signatureType} coming soon!`));
1040
+ console.log(chalk.dim(`Use: httpcat sign ${signatureType} --help for CLI usage`));
1041
+ }
1042
+ }
1043
+ catch (error) {
1044
+ handleError(error, globalOpts.verbose);
1045
+ process.exit(getExitCode(error));
1046
+ }
1047
+ }
1048
+ //# sourceMappingURL=sign.js.map