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.
- package/.github/workflows/ci.yml +3 -0
- package/.github/workflows/rc-publish.yml +6 -0
- package/.github/workflows/release.yml +102 -0
- package/.github/workflows/sync-version.yml +31 -2
- package/README.md +1408 -109
- package/additions.txt +3 -0
- package/bun.lock +260 -25
- package/dist/agent/autonomous-trader.d.ts.map +1 -0
- package/dist/agent/autonomous-trader.js +362 -0
- package/dist/agent/autonomous-trader.js.map +1 -0
- package/dist/agent/ax-agent.d.ts.map +1 -1
- package/dist/agent/ax-agent.js +356 -18
- package/dist/agent/ax-agent.js.map +1 -1
- package/dist/agent/event-client.d.ts.map +1 -0
- package/dist/agent/event-client.js +82 -0
- package/dist/agent/event-client.js.map +1 -0
- package/dist/agent/log-stream.d.ts.map +1 -0
- package/dist/agent/log-stream.js +95 -0
- package/dist/agent/log-stream.js.map +1 -0
- package/dist/agent/memory/conversation-session.d.ts.map +1 -0
- package/dist/agent/memory/conversation-session.js +232 -0
- package/dist/agent/memory/conversation-session.js.map +1 -0
- package/dist/agent/memory/conversation-store.d.ts.map +1 -0
- package/dist/agent/memory/conversation-store.js +214 -0
- package/dist/agent/memory/conversation-store.js.map +1 -0
- package/dist/agent/memory/database-schema.d.ts.map +1 -0
- package/dist/agent/memory/database-schema.js +355 -0
- package/dist/agent/memory/database-schema.js.map +1 -0
- package/dist/agent/memory/decision-tracker.d.ts.map +1 -0
- package/dist/agent/memory/decision-tracker.js +274 -0
- package/dist/agent/memory/decision-tracker.js.map +1 -0
- package/dist/agent/memory/memory-manager.d.ts.map +1 -0
- package/dist/agent/memory/memory-manager.js +187 -0
- package/dist/agent/memory/memory-manager.js.map +1 -0
- package/dist/agent/memory/types.d.ts.map +1 -0
- package/dist/agent/memory/types.js +5 -0
- package/dist/agent/memory/types.js.map +1 -0
- package/dist/agent/message-formatter.d.ts.map +1 -0
- package/dist/agent/message-formatter.js +76 -0
- package/dist/agent/message-formatter.js.map +1 -0
- package/dist/agent/position-db.d.ts.map +1 -0
- package/dist/agent/position-db.js +154 -0
- package/dist/agent/position-db.js.map +1 -0
- package/dist/agent/simple-chat-ui-static.d.ts.map +1 -0
- package/dist/agent/simple-chat-ui-static.js +129 -0
- package/dist/agent/simple-chat-ui-static.js.map +1 -0
- package/dist/agent/simple-chat-ui.d.ts.map +1 -0
- package/dist/agent/simple-chat-ui.js +90 -0
- package/dist/agent/simple-chat-ui.js.map +1 -0
- package/dist/agent/tools.d.ts.map +1 -1
- package/dist/agent/tools.js +297 -4
- package/dist/agent/tools.js.map +1 -1
- package/dist/agent/ui.d.ts.map +1 -0
- package/dist/agent/ui.js +84 -0
- package/dist/agent/ui.js.map +1 -0
- package/dist/agent/unified-runtime.d.ts.map +1 -0
- package/dist/agent/unified-runtime.js +397 -0
- package/dist/agent/unified-runtime.js.map +1 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +272 -21
- package/dist/client.js.map +1 -1
- package/dist/commands/account.d.ts.map +1 -1
- package/dist/commands/account.js +187 -33
- package/dist/commands/account.js.map +1 -1
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +125 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/approve.d.ts.map +1 -0
- package/dist/commands/approve.js +505 -0
- package/dist/commands/approve.js.map +1 -0
- package/dist/commands/automation.d.ts.map +1 -0
- package/dist/commands/automation.js +346 -0
- package/dist/commands/automation.js.map +1 -0
- package/dist/commands/balances.d.ts.map +1 -1
- package/dist/commands/balances.js +226 -73
- package/dist/commands/balances.js.map +1 -1
- package/dist/commands/buy.d.ts.map +1 -1
- package/dist/commands/buy.js +149 -146
- package/dist/commands/buy.js.map +1 -1
- package/dist/commands/call.d.ts.map +1 -0
- package/dist/commands/call.js +51 -0
- package/dist/commands/call.js.map +1 -0
- package/dist/commands/cex.d.ts.map +1 -0
- package/dist/commands/cex.js +958 -0
- package/dist/commands/cex.js.map +1 -0
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +169 -411
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/claim.d.ts.map +1 -1
- package/dist/commands/claim.js +313 -29
- package/dist/commands/claim.js.map +1 -1
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +151 -43
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/gasless-swap.d.ts.map +1 -0
- package/dist/commands/gasless-swap.js +232 -0
- package/dist/commands/gasless-swap.js.map +1 -0
- package/dist/commands/health.d.ts.map +1 -1
- package/dist/commands/health.js +63 -7
- package/dist/commands/health.js.map +1 -1
- package/dist/commands/info.d.ts.map +1 -1
- package/dist/commands/info.js +131 -47
- package/dist/commands/info.js.map +1 -1
- package/dist/commands/launchpad.d.ts.map +1 -0
- package/dist/commands/launchpad.js +708 -0
- package/dist/commands/launchpad.js.map +1 -0
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +57 -23
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/market.d.ts.map +1 -0
- package/dist/commands/market.js +960 -0
- package/dist/commands/market.js.map +1 -0
- package/dist/commands/mcp-install.d.ts.map +1 -0
- package/dist/commands/mcp-install.js +387 -0
- package/dist/commands/mcp-install.js.map +1 -0
- package/dist/commands/opps.d.ts.map +1 -0
- package/dist/commands/opps.js +409 -0
- package/dist/commands/opps.js.map +1 -0
- package/dist/commands/perps.d.ts.map +1 -0
- package/dist/commands/perps.js +248 -0
- package/dist/commands/perps.js.map +1 -0
- package/dist/commands/portfolio.d.ts.map +1 -0
- package/dist/commands/portfolio.js +679 -0
- package/dist/commands/portfolio.js.map +1 -0
- package/dist/commands/positions.d.ts.map +1 -1
- package/dist/commands/positions.js +76 -47
- package/dist/commands/positions.js.map +1 -1
- package/dist/commands/predict.d.ts.map +1 -0
- package/dist/commands/predict.js +280 -0
- package/dist/commands/predict.js.map +1 -0
- package/dist/commands/predictions.d.ts.map +1 -0
- package/dist/commands/predictions.js +486 -0
- package/dist/commands/predictions.js.map +1 -0
- package/dist/commands/risk.d.ts.map +1 -0
- package/dist/commands/risk.js +225 -0
- package/dist/commands/risk.js.map +1 -0
- package/dist/commands/security.d.ts.map +1 -0
- package/dist/commands/security.js +244 -0
- package/dist/commands/security.js.map +1 -0
- package/dist/commands/sell.d.ts.map +1 -1
- package/dist/commands/sell.js +67 -34
- package/dist/commands/sell.js.map +1 -1
- package/dist/commands/send.d.ts.map +1 -0
- package/dist/commands/send.js +733 -0
- package/dist/commands/send.js.map +1 -0
- package/dist/commands/sign.d.ts.map +1 -0
- package/dist/commands/sign.js +1048 -0
- package/dist/commands/sign.js.map +1 -0
- package/dist/commands/swap.d.ts.map +1 -0
- package/dist/commands/swap.js +744 -0
- package/dist/commands/swap.js.map +1 -0
- package/dist/commands/system.d.ts.map +1 -0
- package/dist/commands/system.js +417 -0
- package/dist/commands/system.js.map +1 -0
- package/dist/commands/tools/index.d.ts.map +1 -0
- package/dist/commands/tools/index.js +2040 -0
- package/dist/commands/tools/index.js.map +1 -0
- package/dist/commands/trade.d.ts.map +1 -0
- package/dist/commands/trade.js +237 -0
- package/dist/commands/trade.js.map +1 -0
- package/dist/commands/transactions.d.ts.map +1 -1
- package/dist/commands/transactions.js +29 -17
- package/dist/commands/transactions.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +429 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +351 -40
- package/dist/config.js.map +1 -1
- package/dist/index.js +4524 -924
- package/dist/index.js.map +1 -1
- package/dist/interactive/art.d.ts.map +1 -1
- package/dist/interactive/art.js +33 -1
- package/dist/interactive/art.js.map +1 -1
- package/dist/interactive/shell.d.ts.map +1 -1
- package/dist/interactive/shell.js +467 -2652
- package/dist/interactive/shell.js.map +1 -1
- package/dist/mcp/context.d.ts.map +1 -0
- package/dist/mcp/context.js +211 -0
- package/dist/mcp/context.js.map +1 -0
- package/dist/mcp/onboarding.d.ts.map +1 -0
- package/dist/mcp/onboarding.js +266 -0
- package/dist/mcp/onboarding.js.map +1 -0
- package/dist/mcp/resources.d.ts.map +1 -0
- package/dist/mcp/resources.js +222 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +51 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +4119 -169
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/types/agent-info.d.ts.map +1 -0
- package/dist/types/agent-info.js +11 -0
- package/dist/types/agent-info.js.map +1 -0
- package/dist/ui/components/ScrollableList.d.ts.map +1 -0
- package/dist/ui/components/ScrollableList.js +72 -0
- package/dist/ui/components/ScrollableList.js.map +1 -0
- package/dist/ui/components/ThemeProvider.d.ts.map +1 -0
- package/dist/ui/components/ThemeProvider.js +87 -0
- package/dist/ui/components/ThemeProvider.js.map +1 -0
- package/dist/ui/components/ThemedBox.d.ts.map +1 -0
- package/dist/ui/components/ThemedBox.js +24 -0
- package/dist/ui/components/ThemedBox.js.map +1 -0
- package/dist/ui/components/agent/ChatHeader.d.ts.map +1 -0
- package/dist/ui/components/agent/ChatHeader.js +39 -0
- package/dist/ui/components/agent/ChatHeader.js.map +1 -0
- package/dist/ui/components/agent/Header.d.ts.map +1 -0
- package/dist/ui/components/agent/Header.js +14 -0
- package/dist/ui/components/agent/Header.js.map +1 -0
- package/dist/ui/components/agent/Input.d.ts.map +1 -0
- package/dist/ui/components/agent/Input.js +23 -0
- package/dist/ui/components/agent/Input.js.map +1 -0
- package/dist/ui/components/agent/Output.d.ts.map +1 -0
- package/dist/ui/components/agent/Output.js +23 -0
- package/dist/ui/components/agent/Output.js.map +1 -0
- package/dist/ui/components/chat/TokenChatUI.d.ts.map +1 -0
- package/dist/ui/components/chat/TokenChatUI.js +133 -0
- package/dist/ui/components/chat/TokenChatUI.js.map +1 -0
- package/dist/ui/components/shell/ShellHeader.d.ts.map +1 -0
- package/dist/ui/components/shell/ShellHeader.js +31 -0
- package/dist/ui/components/shell/ShellHeader.js.map +1 -0
- package/dist/ui/components/shell/ShellInput.d.ts.map +1 -0
- package/dist/ui/components/shell/ShellInput.js +151 -0
- package/dist/ui/components/shell/ShellInput.js.map +1 -0
- package/dist/ui/components/shell/ShellOutput.d.ts.map +1 -0
- package/dist/ui/components/shell/ShellOutput.js +8 -0
- package/dist/ui/components/shell/ShellOutput.js.map +1 -0
- package/dist/ui/hooks/useChatWebSocket.d.ts.map +1 -0
- package/dist/ui/hooks/useChatWebSocket.js +76 -0
- package/dist/ui/hooks/useChatWebSocket.js.map +1 -0
- package/dist/ui/hooks/useCommandHistory.d.ts.map +1 -0
- package/dist/ui/hooks/useCommandHistory.js +70 -0
- package/dist/ui/hooks/useCommandHistory.js.map +1 -0
- package/dist/ui/hooks/useDebounce.d.ts.map +1 -0
- package/dist/ui/hooks/useDebounce.js +17 -0
- package/dist/ui/hooks/useDebounce.js.map +1 -0
- package/dist/ui/hooks/useLogStream.d.ts.map +1 -0
- package/dist/ui/hooks/useLogStream.js +20 -0
- package/dist/ui/hooks/useLogStream.js.map +1 -0
- package/dist/ui/hooks/useVirtualScroll.d.ts.map +1 -0
- package/dist/ui/hooks/useVirtualScroll.js +70 -0
- package/dist/ui/hooks/useVirtualScroll.js.map +1 -0
- package/dist/utils/admin.d.ts.map +1 -0
- package/dist/utils/admin.js +144 -0
- package/dist/utils/admin.js.map +1 -0
- package/dist/utils/autoSetup.d.ts.map +1 -0
- package/dist/utils/autoSetup.js +252 -0
- package/dist/utils/autoSetup.js.map +1 -0
- package/dist/utils/build-constants.d.ts.map +1 -0
- package/dist/utils/build-constants.js +10 -0
- package/dist/utils/build-constants.js.map +1 -0
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +10 -1
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/formatting.d.ts.map +1 -1
- package/dist/utils/formatting.js +46 -9
- package/dist/utils/formatting.js.map +1 -1
- package/dist/utils/llm-cli-config.d.ts.map +1 -0
- package/dist/utils/llm-cli-config.js +963 -0
- package/dist/utils/llm-cli-config.js.map +1 -0
- package/dist/utils/llm-cli-detector.d.ts.map +1 -0
- package/dist/utils/llm-cli-detector.js +202 -0
- package/dist/utils/llm-cli-detector.js.map +1 -0
- package/dist/utils/loading.d.ts.map +1 -1
- package/dist/utils/loading.js +25 -3
- package/dist/utils/loading.js.map +1 -1
- package/dist/utils/maintenance.d.ts.map +1 -0
- package/dist/utils/maintenance.js +17 -0
- package/dist/utils/maintenance.js.map +1 -0
- package/dist/utils/mcp-config.d.ts.map +1 -0
- package/dist/utils/mcp-config.js +77 -0
- package/dist/utils/mcp-config.js.map +1 -0
- package/dist/utils/privateKeyPrompt.d.ts.map +1 -1
- package/dist/utils/privateKeyPrompt.js +308 -129
- package/dist/utils/privateKeyPrompt.js.map +1 -1
- package/dist/utils/process-cleanup.d.ts.map +1 -0
- package/dist/utils/process-cleanup.js +136 -0
- package/dist/utils/process-cleanup.js.map +1 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +56 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/utils/rpc-helpers.d.ts.map +1 -0
- package/dist/utils/rpc-helpers.js +70 -0
- package/dist/utils/rpc-helpers.js.map +1 -0
- package/dist/utils/rpc-transport.d.ts.map +1 -0
- package/dist/utils/rpc-transport.js +87 -0
- package/dist/utils/rpc-transport.js.map +1 -0
- package/dist/utils/shell-setup.d.ts.map +1 -0
- package/dist/utils/shell-setup.js +531 -0
- package/dist/utils/shell-setup.js.map +1 -0
- package/dist/utils/status.d.ts.map +1 -1
- package/dist/utils/status.js +34 -5
- package/dist/utils/status.js.map +1 -1
- package/dist/utils/token-resolver.d.ts.map +1 -1
- package/dist/utils/token-resolver.js +51 -8
- package/dist/utils/token-resolver.js.map +1 -1
- package/dist/utils/x402-caller.d.ts.map +1 -0
- package/dist/utils/x402-caller.js +17 -0
- package/dist/utils/x402-caller.js.map +1 -0
- package/docs/README.md +28 -0
- package/docs/agent/README.md +18 -0
- package/docs/api/README.md +41 -0
- package/docs/cli/README.md +42 -0
- package/docs/guides/README.md +26 -0
- package/docs/implementation/README.md +18 -0
- package/docs/planning/README.md +19 -0
- package/docs/testing/README.md +15 -0
- package/docs/ux/README.md +16 -0
- package/issues.txt +2 -0
- package/package.json +24 -9
- package/scripts/cat-spin.sh +417 -0
- package/scripts/deprecate-rc-versions.js +58 -0
- package/scripts/inject-build-constants.js +43 -0
- package/scripts/monitor-foobar.js +117 -0
- package/swap.logs +61 -0
- package/swapping.txt +108 -0
- package/test.txt +12 -0
- package/tests/fixtures/test-data.json +16 -0
- 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
|