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,2040 @@
|
|
|
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, formatTokenAmount } 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 { callX402Endpoint, displayCallResult } from "../call.js";
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Shared Utilities
|
|
13
|
+
// ============================================================================
|
|
14
|
+
function getPrivateKey(cliPrivateKey, accountIndex) {
|
|
15
|
+
if (cliPrivateKey)
|
|
16
|
+
return cliPrivateKey;
|
|
17
|
+
const envKey = process.env.HTTPCAT_PRIVATE_KEY;
|
|
18
|
+
if (envKey)
|
|
19
|
+
return envKey;
|
|
20
|
+
try {
|
|
21
|
+
const index = accountIndex !== undefined
|
|
22
|
+
? accountIndex
|
|
23
|
+
: config.getActiveAccountIndex();
|
|
24
|
+
return config.getAccountPrivateKey(index);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
return config.get("privateKey");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function isConfigured(cliPrivateKey) {
|
|
31
|
+
if (cliPrivateKey)
|
|
32
|
+
return true;
|
|
33
|
+
return config.isConfigured();
|
|
34
|
+
}
|
|
35
|
+
async function ensureWalletUnlocked() {
|
|
36
|
+
const password = config.getPassword();
|
|
37
|
+
if (!password) {
|
|
38
|
+
config.ensureSessionValid();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (!config.isSessionValid()) {
|
|
42
|
+
const inquirer = (await import("inquirer")).default;
|
|
43
|
+
const answers = await inquirer.prompt([
|
|
44
|
+
{
|
|
45
|
+
type: "password",
|
|
46
|
+
name: "password",
|
|
47
|
+
message: "Enter password to unlock wallet:",
|
|
48
|
+
mask: "•",
|
|
49
|
+
},
|
|
50
|
+
]);
|
|
51
|
+
await config.unlockSession(answers.password);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// Tools Command Group
|
|
56
|
+
// ============================================================================
|
|
57
|
+
export function createToolsCommand() {
|
|
58
|
+
const toolsCommand = new Command("tools")
|
|
59
|
+
.description("EVM development & interaction tools")
|
|
60
|
+
.addHelpText("after", `
|
|
61
|
+
Examples:
|
|
62
|
+
httpcat tools address validate 0x...
|
|
63
|
+
httpcat tools abi encode <abi> transfer <args>
|
|
64
|
+
httpcat tools tx build <tx-json>
|
|
65
|
+
httpcat tools signature eip712 <domain> <types> <message>
|
|
66
|
+
`);
|
|
67
|
+
// ============================================================================
|
|
68
|
+
// Address Utilities
|
|
69
|
+
// ============================================================================
|
|
70
|
+
const addressCommand = toolsCommand
|
|
71
|
+
.command("address")
|
|
72
|
+
.description("Address utilities");
|
|
73
|
+
addressCommand
|
|
74
|
+
.command("validate")
|
|
75
|
+
.description("Validate Ethereum address format ($0.001)")
|
|
76
|
+
.argument("<address>", "Address to validate")
|
|
77
|
+
.action(async (address, options, command) => {
|
|
78
|
+
try {
|
|
79
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
80
|
+
const accountIndex = globalOpts.account;
|
|
81
|
+
await ensureWalletUnlocked();
|
|
82
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
83
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
84
|
+
if (globalOpts.json) {
|
|
85
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
86
|
+
process.exit(2);
|
|
87
|
+
}
|
|
88
|
+
privateKey = await promptForPrivateKey();
|
|
89
|
+
}
|
|
90
|
+
const client = await HttpcatClient.create(privateKey);
|
|
91
|
+
const result = await withLoading(async () => {
|
|
92
|
+
const { data } = await client.invoke("tools/address/validate", {
|
|
93
|
+
address,
|
|
94
|
+
});
|
|
95
|
+
return data;
|
|
96
|
+
}, {
|
|
97
|
+
message: "Validating address...",
|
|
98
|
+
json: globalOpts.json,
|
|
99
|
+
quiet: globalOpts.quiet,
|
|
100
|
+
spinner: "cat",
|
|
101
|
+
clearOnSuccess: true,
|
|
102
|
+
});
|
|
103
|
+
if (globalOpts.json) {
|
|
104
|
+
outputJson("address_validate", result);
|
|
105
|
+
}
|
|
106
|
+
else if (!globalOpts.quiet) {
|
|
107
|
+
console.log();
|
|
108
|
+
console.log(chalk.magenta.bold("✅ Address Validation"));
|
|
109
|
+
console.log();
|
|
110
|
+
const boxData = {
|
|
111
|
+
Address: chalk.cyan(address),
|
|
112
|
+
Valid: result.valid
|
|
113
|
+
? chalk.green.bold("Yes")
|
|
114
|
+
: chalk.red.bold("No"),
|
|
115
|
+
};
|
|
116
|
+
if (result.checksummed) {
|
|
117
|
+
boxData["Checksummed"] = chalk.yellow(result.checksummed);
|
|
118
|
+
}
|
|
119
|
+
printBox("Validation Result", boxData);
|
|
120
|
+
console.log();
|
|
121
|
+
}
|
|
122
|
+
process.exit(0);
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
126
|
+
if (globalOpts.json) {
|
|
127
|
+
outputError("address_validate", error, getExitCode(error));
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
handleError(error, globalOpts.verbose);
|
|
131
|
+
}
|
|
132
|
+
process.exit(getExitCode(error));
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
addressCommand
|
|
136
|
+
.command("checksum")
|
|
137
|
+
.description("Convert address to EIP-55 checksum format ($0.001)")
|
|
138
|
+
.argument("<address>", "Address to checksum")
|
|
139
|
+
.action(async (address, options, command) => {
|
|
140
|
+
try {
|
|
141
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
142
|
+
const accountIndex = globalOpts.account;
|
|
143
|
+
await ensureWalletUnlocked();
|
|
144
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
145
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
146
|
+
if (globalOpts.json) {
|
|
147
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
148
|
+
process.exit(2);
|
|
149
|
+
}
|
|
150
|
+
privateKey = await promptForPrivateKey();
|
|
151
|
+
}
|
|
152
|
+
const client = await HttpcatClient.create(privateKey);
|
|
153
|
+
const result = await withLoading(async () => {
|
|
154
|
+
const { data } = await client.invoke("tools/address/checksum", {
|
|
155
|
+
address,
|
|
156
|
+
});
|
|
157
|
+
return data;
|
|
158
|
+
}, {
|
|
159
|
+
message: "Converting to checksum...",
|
|
160
|
+
json: globalOpts.json,
|
|
161
|
+
quiet: globalOpts.quiet,
|
|
162
|
+
spinner: "cat",
|
|
163
|
+
clearOnSuccess: true,
|
|
164
|
+
});
|
|
165
|
+
if (globalOpts.json) {
|
|
166
|
+
outputJson("address_checksum", result);
|
|
167
|
+
}
|
|
168
|
+
else if (!globalOpts.quiet) {
|
|
169
|
+
console.log();
|
|
170
|
+
console.log(chalk.magenta.bold("🔤 Address Checksum"));
|
|
171
|
+
console.log();
|
|
172
|
+
printBox("Checksum Result", {
|
|
173
|
+
Original: chalk.dim(result.original),
|
|
174
|
+
Checksummed: chalk.green.bold(result.address),
|
|
175
|
+
});
|
|
176
|
+
console.log();
|
|
177
|
+
}
|
|
178
|
+
process.exit(0);
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
182
|
+
if (globalOpts.json) {
|
|
183
|
+
outputError("address_checksum", error, getExitCode(error));
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
handleError(error, globalOpts.verbose);
|
|
187
|
+
}
|
|
188
|
+
process.exit(getExitCode(error));
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
addressCommand
|
|
192
|
+
.command("derive")
|
|
193
|
+
.description("Derive address from private key ($0.001)")
|
|
194
|
+
.argument("<privateKey>", "Private key (0x-prefixed hex)")
|
|
195
|
+
.action(async (privateKey, options, command) => {
|
|
196
|
+
try {
|
|
197
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
198
|
+
const accountIndex = globalOpts.account;
|
|
199
|
+
await ensureWalletUnlocked();
|
|
200
|
+
let pk = getPrivateKey(globalOpts.privateKey, accountIndex) || privateKey;
|
|
201
|
+
if (!isConfigured(globalOpts.privateKey) && !privateKey) {
|
|
202
|
+
if (globalOpts.json) {
|
|
203
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
204
|
+
process.exit(2);
|
|
205
|
+
}
|
|
206
|
+
pk = await promptForPrivateKey();
|
|
207
|
+
}
|
|
208
|
+
const client = await HttpcatClient.create(pk);
|
|
209
|
+
const result = await withLoading(async () => {
|
|
210
|
+
const { data } = await client.invoke("tools/address/derive", {
|
|
211
|
+
privateKey: pk,
|
|
212
|
+
});
|
|
213
|
+
return data;
|
|
214
|
+
}, {
|
|
215
|
+
message: "Deriving address...",
|
|
216
|
+
json: globalOpts.json,
|
|
217
|
+
quiet: globalOpts.quiet,
|
|
218
|
+
spinner: "cat",
|
|
219
|
+
clearOnSuccess: true,
|
|
220
|
+
});
|
|
221
|
+
if (globalOpts.json) {
|
|
222
|
+
outputJson("address_derive", result);
|
|
223
|
+
}
|
|
224
|
+
else if (!globalOpts.quiet) {
|
|
225
|
+
console.log();
|
|
226
|
+
console.log(chalk.magenta.bold("🔑 Address Derivation"));
|
|
227
|
+
console.log();
|
|
228
|
+
printBox("Derived Address", {
|
|
229
|
+
Address: chalk.green.bold(result.address),
|
|
230
|
+
});
|
|
231
|
+
console.log();
|
|
232
|
+
}
|
|
233
|
+
process.exit(0);
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
237
|
+
if (globalOpts.json) {
|
|
238
|
+
outputError("address_derive", error, getExitCode(error));
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
handleError(error, globalOpts.verbose);
|
|
242
|
+
}
|
|
243
|
+
process.exit(getExitCode(error));
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
addressCommand
|
|
247
|
+
.command("balance")
|
|
248
|
+
.description("Get address balance (ETH + tokens) ($0.001)")
|
|
249
|
+
.argument("<address>", "Address (0x format)")
|
|
250
|
+
.option("--tokens <tokens>", "Comma-separated token addresses")
|
|
251
|
+
.action(async (address, options, command) => {
|
|
252
|
+
try {
|
|
253
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
254
|
+
const accountIndex = globalOpts.account;
|
|
255
|
+
await ensureWalletUnlocked();
|
|
256
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
257
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
258
|
+
if (globalOpts.json) {
|
|
259
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
260
|
+
process.exit(2);
|
|
261
|
+
}
|
|
262
|
+
privateKey = await promptForPrivateKey();
|
|
263
|
+
}
|
|
264
|
+
const client = await HttpcatClient.create(privateKey);
|
|
265
|
+
const input = { address };
|
|
266
|
+
if (options.tokens) {
|
|
267
|
+
input.tokens = options.tokens.split(",").map((t) => t.trim());
|
|
268
|
+
}
|
|
269
|
+
const result = await withLoading(async () => {
|
|
270
|
+
const { data } = await client.invoke("tools/address/balance", input);
|
|
271
|
+
return data;
|
|
272
|
+
}, {
|
|
273
|
+
message: "Fetching balance...",
|
|
274
|
+
json: globalOpts.json,
|
|
275
|
+
quiet: globalOpts.quiet,
|
|
276
|
+
spinner: "cat",
|
|
277
|
+
clearOnSuccess: true,
|
|
278
|
+
});
|
|
279
|
+
if (globalOpts.json) {
|
|
280
|
+
outputJson("address_balance", result);
|
|
281
|
+
}
|
|
282
|
+
else if (!globalOpts.quiet) {
|
|
283
|
+
console.log();
|
|
284
|
+
console.log(chalk.magenta.bold("💰 Address Balance"));
|
|
285
|
+
console.log();
|
|
286
|
+
const boxData = {
|
|
287
|
+
Address: chalk.cyan(address),
|
|
288
|
+
"ETH Balance": chalk.green.bold(result.ethBalanceFormatted || formatTokenAmount(result.ethBalance)),
|
|
289
|
+
};
|
|
290
|
+
printBox("Balance", boxData);
|
|
291
|
+
if (result.tokens && result.tokens.length > 0) {
|
|
292
|
+
console.log();
|
|
293
|
+
console.log(chalk.cyan.bold("Token Balances:"));
|
|
294
|
+
for (const token of result.tokens) {
|
|
295
|
+
console.log(` ${formatAddress(token.address, 10)}: ${chalk.green(token.balanceFormatted || formatTokenAmount(token.balance))}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
console.log();
|
|
299
|
+
}
|
|
300
|
+
process.exit(0);
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
304
|
+
if (globalOpts.json) {
|
|
305
|
+
outputError("address_balance", error, getExitCode(error));
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
handleError(error, globalOpts.verbose);
|
|
309
|
+
}
|
|
310
|
+
process.exit(getExitCode(error));
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
// ============================================================================
|
|
314
|
+
// ABI Utilities
|
|
315
|
+
// ============================================================================
|
|
316
|
+
const abiCommand = toolsCommand
|
|
317
|
+
.command("abi")
|
|
318
|
+
.description("ABI encoding/decoding utilities");
|
|
319
|
+
abiCommand
|
|
320
|
+
.command("encode")
|
|
321
|
+
.description("Encode function call from ABI and arguments ($0.001)")
|
|
322
|
+
.requiredOption("--abi <abi>", "ABI JSON (as string or file path)")
|
|
323
|
+
.requiredOption("--function <name>", "Function name")
|
|
324
|
+
.requiredOption("--args <args>", "Arguments as JSON array")
|
|
325
|
+
.action(async (options, command) => {
|
|
326
|
+
try {
|
|
327
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
328
|
+
const accountIndex = globalOpts.account;
|
|
329
|
+
await ensureWalletUnlocked();
|
|
330
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
331
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
332
|
+
if (globalOpts.json) {
|
|
333
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
334
|
+
process.exit(2);
|
|
335
|
+
}
|
|
336
|
+
privateKey = await promptForPrivateKey();
|
|
337
|
+
}
|
|
338
|
+
const client = await HttpcatClient.create(privateKey);
|
|
339
|
+
// Parse ABI (could be file path or JSON string)
|
|
340
|
+
let abi;
|
|
341
|
+
try {
|
|
342
|
+
abi = JSON.parse(options.abi);
|
|
343
|
+
}
|
|
344
|
+
catch {
|
|
345
|
+
// Try as file path
|
|
346
|
+
const fs = await import("fs");
|
|
347
|
+
if (fs.existsSync(options.abi)) {
|
|
348
|
+
abi = JSON.parse(fs.readFileSync(options.abi, "utf-8"));
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
throw new Error("Invalid ABI: must be JSON string or file path");
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
// Parse args
|
|
355
|
+
let args;
|
|
356
|
+
try {
|
|
357
|
+
args = JSON.parse(options.args);
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
throw new Error("Invalid args: must be JSON array");
|
|
361
|
+
}
|
|
362
|
+
const result = await withLoading(async () => {
|
|
363
|
+
const { data } = await client.invoke("tools/abi/encode", {
|
|
364
|
+
abi,
|
|
365
|
+
functionName: options.function,
|
|
366
|
+
args,
|
|
367
|
+
});
|
|
368
|
+
return data;
|
|
369
|
+
}, {
|
|
370
|
+
message: "Encoding function call...",
|
|
371
|
+
json: globalOpts.json,
|
|
372
|
+
quiet: globalOpts.quiet,
|
|
373
|
+
spinner: "cat",
|
|
374
|
+
clearOnSuccess: true,
|
|
375
|
+
});
|
|
376
|
+
if (globalOpts.json) {
|
|
377
|
+
outputJson("abi_encode", result);
|
|
378
|
+
}
|
|
379
|
+
else if (!globalOpts.quiet) {
|
|
380
|
+
console.log();
|
|
381
|
+
console.log(chalk.magenta.bold("📝 ABI Encode"));
|
|
382
|
+
console.log();
|
|
383
|
+
printBox("Encoded Data", {
|
|
384
|
+
Data: chalk.green.bold(result.data),
|
|
385
|
+
});
|
|
386
|
+
console.log();
|
|
387
|
+
}
|
|
388
|
+
process.exit(0);
|
|
389
|
+
}
|
|
390
|
+
catch (error) {
|
|
391
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
392
|
+
if (globalOpts.json) {
|
|
393
|
+
outputError("abi_encode", error, getExitCode(error));
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
handleError(error, globalOpts.verbose);
|
|
397
|
+
}
|
|
398
|
+
process.exit(getExitCode(error));
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
abiCommand
|
|
402
|
+
.command("decode")
|
|
403
|
+
.description("Decode function result or event log ($0.001)")
|
|
404
|
+
.requiredOption("--abi <abi>", "ABI JSON (as string or file path)")
|
|
405
|
+
.requiredOption("--data <data>", "Data to decode (0x-prefixed hex)")
|
|
406
|
+
.requiredOption("--type <type>", "Type: function or event")
|
|
407
|
+
.action(async (options, command) => {
|
|
408
|
+
try {
|
|
409
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
410
|
+
const accountIndex = globalOpts.account;
|
|
411
|
+
await ensureWalletUnlocked();
|
|
412
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
413
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
414
|
+
if (globalOpts.json) {
|
|
415
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
416
|
+
process.exit(2);
|
|
417
|
+
}
|
|
418
|
+
privateKey = await promptForPrivateKey();
|
|
419
|
+
}
|
|
420
|
+
const client = await HttpcatClient.create(privateKey);
|
|
421
|
+
// Parse ABI
|
|
422
|
+
let abi;
|
|
423
|
+
try {
|
|
424
|
+
abi = JSON.parse(options.abi);
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
const fs = await import("fs");
|
|
428
|
+
if (fs.existsSync(options.abi)) {
|
|
429
|
+
abi = JSON.parse(fs.readFileSync(options.abi, "utf-8"));
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
throw new Error("Invalid ABI: must be JSON string or file path");
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
const result = await withLoading(async () => {
|
|
436
|
+
const { data } = await client.invoke("tools/abi/decode", {
|
|
437
|
+
abi,
|
|
438
|
+
data: options.data,
|
|
439
|
+
type: options.type,
|
|
440
|
+
});
|
|
441
|
+
return data;
|
|
442
|
+
}, {
|
|
443
|
+
message: "Decoding data...",
|
|
444
|
+
json: globalOpts.json,
|
|
445
|
+
quiet: globalOpts.quiet,
|
|
446
|
+
spinner: "cat",
|
|
447
|
+
clearOnSuccess: true,
|
|
448
|
+
});
|
|
449
|
+
if (globalOpts.json) {
|
|
450
|
+
outputJson("abi_decode", result);
|
|
451
|
+
}
|
|
452
|
+
else if (!globalOpts.quiet) {
|
|
453
|
+
console.log();
|
|
454
|
+
console.log(chalk.magenta.bold("🔍 ABI Decode"));
|
|
455
|
+
console.log();
|
|
456
|
+
console.log(chalk.cyan("Decoded:"));
|
|
457
|
+
console.log(JSON.stringify(result.decoded, null, 2));
|
|
458
|
+
console.log();
|
|
459
|
+
}
|
|
460
|
+
process.exit(0);
|
|
461
|
+
}
|
|
462
|
+
catch (error) {
|
|
463
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
464
|
+
if (globalOpts.json) {
|
|
465
|
+
outputError("abi_decode", error, getExitCode(error));
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
handleError(error, globalOpts.verbose);
|
|
469
|
+
}
|
|
470
|
+
process.exit(getExitCode(error));
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
abiCommand
|
|
474
|
+
.command("parse")
|
|
475
|
+
.description("Parse ABI JSON and return structured format ($0.001)")
|
|
476
|
+
.requiredOption("--abi <abi>", "ABI JSON (as string or file path)")
|
|
477
|
+
.action(async (options, command) => {
|
|
478
|
+
try {
|
|
479
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
480
|
+
const accountIndex = globalOpts.account;
|
|
481
|
+
await ensureWalletUnlocked();
|
|
482
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
483
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
484
|
+
if (globalOpts.json) {
|
|
485
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
486
|
+
process.exit(2);
|
|
487
|
+
}
|
|
488
|
+
privateKey = await promptForPrivateKey();
|
|
489
|
+
}
|
|
490
|
+
const client = await HttpcatClient.create(privateKey);
|
|
491
|
+
// Parse ABI
|
|
492
|
+
let abi;
|
|
493
|
+
try {
|
|
494
|
+
abi = JSON.parse(options.abi);
|
|
495
|
+
}
|
|
496
|
+
catch {
|
|
497
|
+
const fs = await import("fs");
|
|
498
|
+
if (fs.existsSync(options.abi)) {
|
|
499
|
+
abi = JSON.parse(fs.readFileSync(options.abi, "utf-8"));
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
throw new Error("Invalid ABI: must be JSON string or file path");
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
const result = await withLoading(async () => {
|
|
506
|
+
const { data } = await client.invoke("tools/abi/parse", { abi });
|
|
507
|
+
return data;
|
|
508
|
+
}, {
|
|
509
|
+
message: "Parsing ABI...",
|
|
510
|
+
json: globalOpts.json,
|
|
511
|
+
quiet: globalOpts.quiet,
|
|
512
|
+
spinner: "cat",
|
|
513
|
+
clearOnSuccess: true,
|
|
514
|
+
});
|
|
515
|
+
if (globalOpts.json) {
|
|
516
|
+
outputJson("abi_parse", result);
|
|
517
|
+
}
|
|
518
|
+
else if (!globalOpts.quiet) {
|
|
519
|
+
console.log();
|
|
520
|
+
console.log(chalk.magenta.bold("📋 ABI Parse"));
|
|
521
|
+
console.log();
|
|
522
|
+
const boxData = {
|
|
523
|
+
Functions: chalk.cyan((result.functions?.length || 0).toString()),
|
|
524
|
+
Events: chalk.cyan((result.events?.length || 0).toString()),
|
|
525
|
+
Errors: chalk.cyan((result.errors?.length || 0).toString()),
|
|
526
|
+
};
|
|
527
|
+
printBox("ABI Structure", boxData);
|
|
528
|
+
console.log();
|
|
529
|
+
}
|
|
530
|
+
process.exit(0);
|
|
531
|
+
}
|
|
532
|
+
catch (error) {
|
|
533
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
534
|
+
if (globalOpts.json) {
|
|
535
|
+
outputError("abi_parse", error, getExitCode(error));
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
handleError(error, globalOpts.verbose);
|
|
539
|
+
}
|
|
540
|
+
process.exit(getExitCode(error));
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
// ============================================================================
|
|
544
|
+
// Transaction Building
|
|
545
|
+
// ============================================================================
|
|
546
|
+
const txCommand = toolsCommand
|
|
547
|
+
.command("tx")
|
|
548
|
+
.description("Transaction utilities");
|
|
549
|
+
txCommand
|
|
550
|
+
.command("build")
|
|
551
|
+
.description("Build complete transaction with auto-filled fields ($0.001)")
|
|
552
|
+
.requiredOption("--to <address>", "To address (0x format)")
|
|
553
|
+
.option("--data <data>", "Transaction data (0x-prefixed hex)")
|
|
554
|
+
.option("--value <value>", "Value in wei")
|
|
555
|
+
.option("--from <address>", "From address (0x format)")
|
|
556
|
+
.option("--gas-limit <gas>", "Gas limit")
|
|
557
|
+
.option("--gas-price <price>", "Gas price in wei")
|
|
558
|
+
.option("--nonce <nonce>", "Nonce")
|
|
559
|
+
.action(async (options, command) => {
|
|
560
|
+
try {
|
|
561
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
562
|
+
const accountIndex = globalOpts.account;
|
|
563
|
+
await ensureWalletUnlocked();
|
|
564
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
565
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
566
|
+
if (globalOpts.json) {
|
|
567
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
568
|
+
process.exit(2);
|
|
569
|
+
}
|
|
570
|
+
privateKey = await promptForPrivateKey();
|
|
571
|
+
}
|
|
572
|
+
const client = await HttpcatClient.create(privateKey);
|
|
573
|
+
const input = { to: options.to };
|
|
574
|
+
if (options.data)
|
|
575
|
+
input.data = options.data;
|
|
576
|
+
if (options.value)
|
|
577
|
+
input.value = options.value;
|
|
578
|
+
if (options.from)
|
|
579
|
+
input.from = options.from;
|
|
580
|
+
if (options.gasLimit)
|
|
581
|
+
input.gasLimit = options.gasLimit;
|
|
582
|
+
if (options.gasPrice)
|
|
583
|
+
input.gasPrice = options.gasPrice;
|
|
584
|
+
if (options.nonce)
|
|
585
|
+
input.nonce = parseInt(options.nonce);
|
|
586
|
+
const result = await withLoading(async () => {
|
|
587
|
+
const { data } = await client.invoke("tools/tx/build", input);
|
|
588
|
+
return data;
|
|
589
|
+
}, {
|
|
590
|
+
message: "Building transaction...",
|
|
591
|
+
json: globalOpts.json,
|
|
592
|
+
quiet: globalOpts.quiet,
|
|
593
|
+
spinner: "cat",
|
|
594
|
+
clearOnSuccess: true,
|
|
595
|
+
});
|
|
596
|
+
if (globalOpts.json) {
|
|
597
|
+
outputJson("tx_build", result);
|
|
598
|
+
}
|
|
599
|
+
else if (!globalOpts.quiet) {
|
|
600
|
+
console.log();
|
|
601
|
+
console.log(chalk.magenta.bold("🔨 Transaction Build"));
|
|
602
|
+
console.log();
|
|
603
|
+
console.log(JSON.stringify(result.transaction, null, 2));
|
|
604
|
+
console.log();
|
|
605
|
+
}
|
|
606
|
+
process.exit(0);
|
|
607
|
+
}
|
|
608
|
+
catch (error) {
|
|
609
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
610
|
+
if (globalOpts.json) {
|
|
611
|
+
outputError("tx_build", error, getExitCode(error));
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
handleError(error, globalOpts.verbose);
|
|
615
|
+
}
|
|
616
|
+
process.exit(getExitCode(error));
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
txCommand
|
|
620
|
+
.command("encode")
|
|
621
|
+
.description("Encode transaction to RLP format ($0.001)")
|
|
622
|
+
.requiredOption("--tx <tx>", "Transaction JSON (as string or file path)")
|
|
623
|
+
.action(async (options, command) => {
|
|
624
|
+
try {
|
|
625
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
626
|
+
const accountIndex = globalOpts.account;
|
|
627
|
+
await ensureWalletUnlocked();
|
|
628
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
629
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
630
|
+
if (globalOpts.json) {
|
|
631
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
632
|
+
process.exit(2);
|
|
633
|
+
}
|
|
634
|
+
privateKey = await promptForPrivateKey();
|
|
635
|
+
}
|
|
636
|
+
const client = await HttpcatClient.create(privateKey);
|
|
637
|
+
// Parse transaction
|
|
638
|
+
let transaction;
|
|
639
|
+
try {
|
|
640
|
+
transaction = JSON.parse(options.tx);
|
|
641
|
+
}
|
|
642
|
+
catch {
|
|
643
|
+
const fs = await import("fs");
|
|
644
|
+
if (fs.existsSync(options.tx)) {
|
|
645
|
+
transaction = JSON.parse(fs.readFileSync(options.tx, "utf-8"));
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
throw new Error("Invalid transaction: must be JSON string or file path");
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
const result = await withLoading(async () => {
|
|
652
|
+
const { data } = await client.invoke("tools/tx/encode", {
|
|
653
|
+
transaction,
|
|
654
|
+
});
|
|
655
|
+
return data;
|
|
656
|
+
}, {
|
|
657
|
+
message: "Encoding transaction...",
|
|
658
|
+
json: globalOpts.json,
|
|
659
|
+
quiet: globalOpts.quiet,
|
|
660
|
+
spinner: "cat",
|
|
661
|
+
clearOnSuccess: true,
|
|
662
|
+
});
|
|
663
|
+
if (globalOpts.json) {
|
|
664
|
+
outputJson("tx_encode", result);
|
|
665
|
+
}
|
|
666
|
+
else if (!globalOpts.quiet) {
|
|
667
|
+
console.log();
|
|
668
|
+
console.log(chalk.magenta.bold("📦 Transaction Encode"));
|
|
669
|
+
console.log();
|
|
670
|
+
printBox("RLP Encoded", {
|
|
671
|
+
RLP: chalk.green.bold(result.rlp),
|
|
672
|
+
});
|
|
673
|
+
console.log();
|
|
674
|
+
}
|
|
675
|
+
process.exit(0);
|
|
676
|
+
}
|
|
677
|
+
catch (error) {
|
|
678
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
679
|
+
if (globalOpts.json) {
|
|
680
|
+
outputError("tx_encode", error, getExitCode(error));
|
|
681
|
+
}
|
|
682
|
+
else {
|
|
683
|
+
handleError(error, globalOpts.verbose);
|
|
684
|
+
}
|
|
685
|
+
process.exit(getExitCode(error));
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
txCommand
|
|
689
|
+
.command("decode")
|
|
690
|
+
.description("Decode RLP transaction ($0.001)")
|
|
691
|
+
.requiredOption("--rlp <rlp>", "RLP-encoded transaction (0x-prefixed hex)")
|
|
692
|
+
.action(async (options, command) => {
|
|
693
|
+
try {
|
|
694
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
695
|
+
const accountIndex = globalOpts.account;
|
|
696
|
+
await ensureWalletUnlocked();
|
|
697
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
698
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
699
|
+
if (globalOpts.json) {
|
|
700
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
701
|
+
process.exit(2);
|
|
702
|
+
}
|
|
703
|
+
privateKey = await promptForPrivateKey();
|
|
704
|
+
}
|
|
705
|
+
const client = await HttpcatClient.create(privateKey);
|
|
706
|
+
const result = await withLoading(async () => {
|
|
707
|
+
const { data } = await client.invoke("tools/tx/decode", {
|
|
708
|
+
rlp: options.rlp,
|
|
709
|
+
});
|
|
710
|
+
return data;
|
|
711
|
+
}, {
|
|
712
|
+
message: "Decoding transaction...",
|
|
713
|
+
json: globalOpts.json,
|
|
714
|
+
quiet: globalOpts.quiet,
|
|
715
|
+
spinner: "cat",
|
|
716
|
+
clearOnSuccess: true,
|
|
717
|
+
});
|
|
718
|
+
if (globalOpts.json) {
|
|
719
|
+
outputJson("tx_decode", result);
|
|
720
|
+
}
|
|
721
|
+
else if (!globalOpts.quiet) {
|
|
722
|
+
console.log();
|
|
723
|
+
console.log(chalk.magenta.bold("🔍 Transaction Decode"));
|
|
724
|
+
console.log();
|
|
725
|
+
console.log(JSON.stringify(result.transaction, null, 2));
|
|
726
|
+
console.log();
|
|
727
|
+
}
|
|
728
|
+
process.exit(0);
|
|
729
|
+
}
|
|
730
|
+
catch (error) {
|
|
731
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
732
|
+
if (globalOpts.json) {
|
|
733
|
+
outputError("tx_decode", error, getExitCode(error));
|
|
734
|
+
}
|
|
735
|
+
else {
|
|
736
|
+
handleError(error, globalOpts.verbose);
|
|
737
|
+
}
|
|
738
|
+
process.exit(getExitCode(error));
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
txCommand
|
|
742
|
+
.command("simulate")
|
|
743
|
+
.description("Enhanced transaction simulation with state analysis ($0.001)")
|
|
744
|
+
.requiredOption("--to <address>", "To address (0x format)")
|
|
745
|
+
.requiredOption("--data <data>", "Transaction data (0x-prefixed hex)")
|
|
746
|
+
.requiredOption("--value <value>", "Value in wei")
|
|
747
|
+
.option("--from <address>", "From address (0x format)")
|
|
748
|
+
.action(async (options, command) => {
|
|
749
|
+
try {
|
|
750
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
751
|
+
const accountIndex = globalOpts.account;
|
|
752
|
+
await ensureWalletUnlocked();
|
|
753
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
754
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
755
|
+
if (globalOpts.json) {
|
|
756
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
757
|
+
process.exit(2);
|
|
758
|
+
}
|
|
759
|
+
privateKey = await promptForPrivateKey();
|
|
760
|
+
}
|
|
761
|
+
const client = await HttpcatClient.create(privateKey);
|
|
762
|
+
const input = {
|
|
763
|
+
transaction: {
|
|
764
|
+
to: options.to,
|
|
765
|
+
data: options.data,
|
|
766
|
+
value: options.value,
|
|
767
|
+
},
|
|
768
|
+
};
|
|
769
|
+
if (options.from)
|
|
770
|
+
input.from = options.from;
|
|
771
|
+
const result = await withLoading(async () => {
|
|
772
|
+
const { data } = await client.invoke("tools/tx/simulate", input);
|
|
773
|
+
return data;
|
|
774
|
+
}, {
|
|
775
|
+
message: "Simulating transaction...",
|
|
776
|
+
json: globalOpts.json,
|
|
777
|
+
quiet: globalOpts.quiet,
|
|
778
|
+
spinner: "cat",
|
|
779
|
+
clearOnSuccess: true,
|
|
780
|
+
});
|
|
781
|
+
if (globalOpts.json) {
|
|
782
|
+
outputJson("tx_simulate", result);
|
|
783
|
+
}
|
|
784
|
+
else if (!globalOpts.quiet) {
|
|
785
|
+
console.log();
|
|
786
|
+
console.log(chalk.magenta.bold("🎮 Transaction Simulation"));
|
|
787
|
+
console.log();
|
|
788
|
+
const boxData = {
|
|
789
|
+
Success: result.success
|
|
790
|
+
? chalk.green.bold("Yes")
|
|
791
|
+
: chalk.red.bold("No"),
|
|
792
|
+
"Gas Used": chalk.cyan(result.gasUsed || "N/A"),
|
|
793
|
+
};
|
|
794
|
+
printBox("Simulation Result", boxData);
|
|
795
|
+
if (result.logs && result.logs.length > 0) {
|
|
796
|
+
console.log();
|
|
797
|
+
console.log(chalk.cyan(`Logs (${result.logs.length}):`));
|
|
798
|
+
console.log(JSON.stringify(result.logs, null, 2));
|
|
799
|
+
}
|
|
800
|
+
console.log();
|
|
801
|
+
}
|
|
802
|
+
process.exit(0);
|
|
803
|
+
}
|
|
804
|
+
catch (error) {
|
|
805
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
806
|
+
if (globalOpts.json) {
|
|
807
|
+
outputError("tx_simulate", error, getExitCode(error));
|
|
808
|
+
}
|
|
809
|
+
else {
|
|
810
|
+
handleError(error, globalOpts.verbose);
|
|
811
|
+
}
|
|
812
|
+
process.exit(getExitCode(error));
|
|
813
|
+
}
|
|
814
|
+
});
|
|
815
|
+
txCommand
|
|
816
|
+
.command("estimate-gas")
|
|
817
|
+
.description("Estimate gas for transaction ($0.001)")
|
|
818
|
+
.requiredOption("--to <address>", "To address (0x format)")
|
|
819
|
+
.requiredOption("--data <data>", "Transaction data (0x-prefixed hex)")
|
|
820
|
+
.requiredOption("--value <value>", "Value in wei")
|
|
821
|
+
.option("--from <address>", "From address (0x format)")
|
|
822
|
+
.action(async (options, command) => {
|
|
823
|
+
try {
|
|
824
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
825
|
+
const accountIndex = globalOpts.account;
|
|
826
|
+
await ensureWalletUnlocked();
|
|
827
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
828
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
829
|
+
if (globalOpts.json) {
|
|
830
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
831
|
+
process.exit(2);
|
|
832
|
+
}
|
|
833
|
+
privateKey = await promptForPrivateKey();
|
|
834
|
+
}
|
|
835
|
+
const client = await HttpcatClient.create(privateKey);
|
|
836
|
+
const input = {
|
|
837
|
+
transaction: {
|
|
838
|
+
to: options.to,
|
|
839
|
+
data: options.data,
|
|
840
|
+
value: options.value,
|
|
841
|
+
},
|
|
842
|
+
};
|
|
843
|
+
if (options.from)
|
|
844
|
+
input.from = options.from;
|
|
845
|
+
const result = await withLoading(async () => {
|
|
846
|
+
const { data } = await client.invoke("tools/tx/estimate-gas", input);
|
|
847
|
+
return data;
|
|
848
|
+
}, {
|
|
849
|
+
message: "Estimating gas...",
|
|
850
|
+
json: globalOpts.json,
|
|
851
|
+
quiet: globalOpts.quiet,
|
|
852
|
+
spinner: "cat",
|
|
853
|
+
clearOnSuccess: true,
|
|
854
|
+
});
|
|
855
|
+
if (globalOpts.json) {
|
|
856
|
+
outputJson("tx_estimate_gas", result);
|
|
857
|
+
}
|
|
858
|
+
else if (!globalOpts.quiet) {
|
|
859
|
+
console.log();
|
|
860
|
+
console.log(chalk.magenta.bold("⛽ Gas Estimation"));
|
|
861
|
+
console.log();
|
|
862
|
+
printBox("Gas Estimate", {
|
|
863
|
+
"Gas Estimate": chalk.green.bold(result.gasEstimate),
|
|
864
|
+
"Gas Limit": chalk.yellow(result.gasLimit || result.gasEstimate),
|
|
865
|
+
});
|
|
866
|
+
console.log();
|
|
867
|
+
}
|
|
868
|
+
process.exit(0);
|
|
869
|
+
}
|
|
870
|
+
catch (error) {
|
|
871
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
872
|
+
if (globalOpts.json) {
|
|
873
|
+
outputError("tx_estimate_gas", error, getExitCode(error));
|
|
874
|
+
}
|
|
875
|
+
else {
|
|
876
|
+
handleError(error, globalOpts.verbose);
|
|
877
|
+
}
|
|
878
|
+
process.exit(getExitCode(error));
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
txCommand
|
|
882
|
+
.command("prepare")
|
|
883
|
+
.description("Prepare transaction for signing ($0.001)")
|
|
884
|
+
.requiredOption("--to <address>", "To address (0x format)")
|
|
885
|
+
.requiredOption("--from <address>", "From address (0x format)")
|
|
886
|
+
.option("--data <data>", "Transaction data (0x-prefixed hex)")
|
|
887
|
+
.option("--value <value>", "Value in wei")
|
|
888
|
+
.action(async (options, command) => {
|
|
889
|
+
try {
|
|
890
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
891
|
+
const accountIndex = globalOpts.account;
|
|
892
|
+
await ensureWalletUnlocked();
|
|
893
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
894
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
895
|
+
if (globalOpts.json) {
|
|
896
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
897
|
+
process.exit(2);
|
|
898
|
+
}
|
|
899
|
+
privateKey = await promptForPrivateKey();
|
|
900
|
+
}
|
|
901
|
+
const client = await HttpcatClient.create(privateKey);
|
|
902
|
+
const input = {
|
|
903
|
+
to: options.to,
|
|
904
|
+
from: options.from,
|
|
905
|
+
};
|
|
906
|
+
if (options.data)
|
|
907
|
+
input.data = options.data;
|
|
908
|
+
if (options.value)
|
|
909
|
+
input.value = options.value;
|
|
910
|
+
const result = await withLoading(async () => {
|
|
911
|
+
const { data } = await client.invoke("tools/tx/prepare", input);
|
|
912
|
+
return data;
|
|
913
|
+
}, {
|
|
914
|
+
message: "Preparing transaction...",
|
|
915
|
+
json: globalOpts.json,
|
|
916
|
+
quiet: globalOpts.quiet,
|
|
917
|
+
spinner: "cat",
|
|
918
|
+
clearOnSuccess: true,
|
|
919
|
+
});
|
|
920
|
+
if (globalOpts.json) {
|
|
921
|
+
outputJson("tx_prepare", result);
|
|
922
|
+
}
|
|
923
|
+
else if (!globalOpts.quiet) {
|
|
924
|
+
console.log();
|
|
925
|
+
console.log(chalk.magenta.bold("✍️ Transaction Prepare"));
|
|
926
|
+
console.log();
|
|
927
|
+
console.log(JSON.stringify(result.transaction, null, 2));
|
|
928
|
+
if (result.messageHash) {
|
|
929
|
+
console.log();
|
|
930
|
+
console.log(chalk.cyan("Message Hash:") + " " + chalk.yellow(result.messageHash));
|
|
931
|
+
}
|
|
932
|
+
console.log();
|
|
933
|
+
}
|
|
934
|
+
process.exit(0);
|
|
935
|
+
}
|
|
936
|
+
catch (error) {
|
|
937
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
938
|
+
if (globalOpts.json) {
|
|
939
|
+
outputError("tx_prepare", error, getExitCode(error));
|
|
940
|
+
}
|
|
941
|
+
else {
|
|
942
|
+
handleError(error, globalOpts.verbose);
|
|
943
|
+
}
|
|
944
|
+
process.exit(getExitCode(error));
|
|
945
|
+
}
|
|
946
|
+
});
|
|
947
|
+
// ============================================================================
|
|
948
|
+
// Signature Helpers
|
|
949
|
+
// ============================================================================
|
|
950
|
+
const signatureCommand = toolsCommand
|
|
951
|
+
.command("signature")
|
|
952
|
+
.description("Signature utilities");
|
|
953
|
+
signatureCommand
|
|
954
|
+
.command("eip191")
|
|
955
|
+
.description("Prepare EIP-191 personal sign message ($0.001)")
|
|
956
|
+
.requiredOption("--message <message>", "Message to sign")
|
|
957
|
+
.option("--address <address>", "Address for verification")
|
|
958
|
+
.action(async (options, command) => {
|
|
959
|
+
try {
|
|
960
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
961
|
+
const accountIndex = globalOpts.account;
|
|
962
|
+
await ensureWalletUnlocked();
|
|
963
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
964
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
965
|
+
if (globalOpts.json) {
|
|
966
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
967
|
+
process.exit(2);
|
|
968
|
+
}
|
|
969
|
+
privateKey = await promptForPrivateKey();
|
|
970
|
+
}
|
|
971
|
+
const client = await HttpcatClient.create(privateKey);
|
|
972
|
+
const input = { message: options.message };
|
|
973
|
+
if (options.address)
|
|
974
|
+
input.address = options.address;
|
|
975
|
+
const result = await withLoading(async () => {
|
|
976
|
+
const { data } = await client.invoke("tools/signature/eip191", input);
|
|
977
|
+
return data;
|
|
978
|
+
}, {
|
|
979
|
+
message: "Preparing EIP-191 signature...",
|
|
980
|
+
json: globalOpts.json,
|
|
981
|
+
quiet: globalOpts.quiet,
|
|
982
|
+
spinner: "cat",
|
|
983
|
+
clearOnSuccess: true,
|
|
984
|
+
});
|
|
985
|
+
if (globalOpts.json) {
|
|
986
|
+
outputJson("signature_eip191", result);
|
|
987
|
+
}
|
|
988
|
+
else if (!globalOpts.quiet) {
|
|
989
|
+
console.log();
|
|
990
|
+
console.log(chalk.magenta.bold("✍️ EIP-191 Signature"));
|
|
991
|
+
console.log();
|
|
992
|
+
printBox("Signature Data", {
|
|
993
|
+
Message: chalk.cyan(result.message),
|
|
994
|
+
"Message Hash": chalk.green.bold(result.messageHash),
|
|
995
|
+
Signature: result.signature
|
|
996
|
+
? chalk.yellow(result.signature)
|
|
997
|
+
: chalk.dim("Not signed"),
|
|
998
|
+
});
|
|
999
|
+
console.log();
|
|
1000
|
+
}
|
|
1001
|
+
process.exit(0);
|
|
1002
|
+
}
|
|
1003
|
+
catch (error) {
|
|
1004
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1005
|
+
if (globalOpts.json) {
|
|
1006
|
+
outputError("signature_eip191", error, getExitCode(error));
|
|
1007
|
+
}
|
|
1008
|
+
else {
|
|
1009
|
+
handleError(error, globalOpts.verbose);
|
|
1010
|
+
}
|
|
1011
|
+
process.exit(getExitCode(error));
|
|
1012
|
+
}
|
|
1013
|
+
});
|
|
1014
|
+
signatureCommand
|
|
1015
|
+
.command("eip712")
|
|
1016
|
+
.description("Prepare EIP-712 structured data signature ($0.001)")
|
|
1017
|
+
.requiredOption("--domain <domain>", "Domain JSON (as string or file path)")
|
|
1018
|
+
.requiredOption("--types <types>", "Types JSON (as string or file path)")
|
|
1019
|
+
.requiredOption("--message <message>", "Message JSON (as string or file path)")
|
|
1020
|
+
.action(async (options, command) => {
|
|
1021
|
+
try {
|
|
1022
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1023
|
+
const accountIndex = globalOpts.account;
|
|
1024
|
+
await ensureWalletUnlocked();
|
|
1025
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
1026
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
1027
|
+
if (globalOpts.json) {
|
|
1028
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
1029
|
+
process.exit(2);
|
|
1030
|
+
}
|
|
1031
|
+
privateKey = await promptForPrivateKey();
|
|
1032
|
+
}
|
|
1033
|
+
const client = await HttpcatClient.create(privateKey);
|
|
1034
|
+
// Parse JSON inputs
|
|
1035
|
+
const parseJson = (str) => {
|
|
1036
|
+
try {
|
|
1037
|
+
return JSON.parse(str);
|
|
1038
|
+
}
|
|
1039
|
+
catch {
|
|
1040
|
+
const fs = require("fs");
|
|
1041
|
+
if (fs.existsSync(str)) {
|
|
1042
|
+
return JSON.parse(fs.readFileSync(str, "utf-8"));
|
|
1043
|
+
}
|
|
1044
|
+
throw new Error(`Invalid JSON: ${str}`);
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
const domain = parseJson(options.domain);
|
|
1048
|
+
const types = parseJson(options.types);
|
|
1049
|
+
const message = parseJson(options.message);
|
|
1050
|
+
const result = await withLoading(async () => {
|
|
1051
|
+
const { data } = await client.invoke("tools/signature/eip712", {
|
|
1052
|
+
domain,
|
|
1053
|
+
types,
|
|
1054
|
+
message,
|
|
1055
|
+
});
|
|
1056
|
+
return data;
|
|
1057
|
+
}, {
|
|
1058
|
+
message: "Preparing EIP-712 signature...",
|
|
1059
|
+
json: globalOpts.json,
|
|
1060
|
+
quiet: globalOpts.quiet,
|
|
1061
|
+
spinner: "cat",
|
|
1062
|
+
clearOnSuccess: true,
|
|
1063
|
+
});
|
|
1064
|
+
if (globalOpts.json) {
|
|
1065
|
+
outputJson("signature_eip712", result);
|
|
1066
|
+
}
|
|
1067
|
+
else if (!globalOpts.quiet) {
|
|
1068
|
+
console.log();
|
|
1069
|
+
console.log(chalk.magenta.bold("✍️ EIP-712 Signature"));
|
|
1070
|
+
console.log();
|
|
1071
|
+
printBox("Signature Data", {
|
|
1072
|
+
"Message Hash": chalk.green.bold(result.messageHash),
|
|
1073
|
+
});
|
|
1074
|
+
console.log();
|
|
1075
|
+
}
|
|
1076
|
+
process.exit(0);
|
|
1077
|
+
}
|
|
1078
|
+
catch (error) {
|
|
1079
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1080
|
+
if (globalOpts.json) {
|
|
1081
|
+
outputError("signature_eip712", error, getExitCode(error));
|
|
1082
|
+
}
|
|
1083
|
+
else {
|
|
1084
|
+
handleError(error, globalOpts.verbose);
|
|
1085
|
+
}
|
|
1086
|
+
process.exit(getExitCode(error));
|
|
1087
|
+
}
|
|
1088
|
+
});
|
|
1089
|
+
signatureCommand
|
|
1090
|
+
.command("permit2")
|
|
1091
|
+
.description("Prepare Permit2 signature message ($0.001)")
|
|
1092
|
+
.requiredOption("--token <address>", "Token address (0x format)")
|
|
1093
|
+
.requiredOption("--amount <amount>", "Amount in wei")
|
|
1094
|
+
.requiredOption("--spender <address>", "Spender address (0x format)")
|
|
1095
|
+
.requiredOption("--nonce <nonce>", "Nonce")
|
|
1096
|
+
.requiredOption("--deadline <deadline>", "Deadline (Unix timestamp)")
|
|
1097
|
+
.action(async (options, command) => {
|
|
1098
|
+
try {
|
|
1099
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1100
|
+
const accountIndex = globalOpts.account;
|
|
1101
|
+
await ensureWalletUnlocked();
|
|
1102
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
1103
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
1104
|
+
if (globalOpts.json) {
|
|
1105
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
1106
|
+
process.exit(2);
|
|
1107
|
+
}
|
|
1108
|
+
privateKey = await promptForPrivateKey();
|
|
1109
|
+
}
|
|
1110
|
+
const client = await HttpcatClient.create(privateKey);
|
|
1111
|
+
const result = await withLoading(async () => {
|
|
1112
|
+
const { data } = await client.invoke("tools/signature/permit2", {
|
|
1113
|
+
token: options.token,
|
|
1114
|
+
amount: options.amount,
|
|
1115
|
+
spender: options.spender,
|
|
1116
|
+
nonce: parseInt(options.nonce),
|
|
1117
|
+
deadline: parseInt(options.deadline),
|
|
1118
|
+
});
|
|
1119
|
+
return data;
|
|
1120
|
+
}, {
|
|
1121
|
+
message: "Preparing Permit2 signature...",
|
|
1122
|
+
json: globalOpts.json,
|
|
1123
|
+
quiet: globalOpts.quiet,
|
|
1124
|
+
spinner: "cat",
|
|
1125
|
+
clearOnSuccess: true,
|
|
1126
|
+
});
|
|
1127
|
+
if (globalOpts.json) {
|
|
1128
|
+
outputJson("signature_permit2", result);
|
|
1129
|
+
}
|
|
1130
|
+
else if (!globalOpts.quiet) {
|
|
1131
|
+
console.log();
|
|
1132
|
+
console.log(chalk.magenta.bold("✍️ Permit2 Signature"));
|
|
1133
|
+
console.log();
|
|
1134
|
+
printBox("Signature Data", {
|
|
1135
|
+
"Message Hash": chalk.green.bold(result.messageHash),
|
|
1136
|
+
});
|
|
1137
|
+
console.log();
|
|
1138
|
+
}
|
|
1139
|
+
process.exit(0);
|
|
1140
|
+
}
|
|
1141
|
+
catch (error) {
|
|
1142
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1143
|
+
if (globalOpts.json) {
|
|
1144
|
+
outputError("signature_permit2", error, getExitCode(error));
|
|
1145
|
+
}
|
|
1146
|
+
else {
|
|
1147
|
+
handleError(error, globalOpts.verbose);
|
|
1148
|
+
}
|
|
1149
|
+
process.exit(getExitCode(error));
|
|
1150
|
+
}
|
|
1151
|
+
});
|
|
1152
|
+
// ============================================================================
|
|
1153
|
+
// Multicall Operations
|
|
1154
|
+
// ============================================================================
|
|
1155
|
+
const multicallCommand = toolsCommand
|
|
1156
|
+
.command("multicall")
|
|
1157
|
+
.description("Multicall operations");
|
|
1158
|
+
multicallCommand
|
|
1159
|
+
.command("encode")
|
|
1160
|
+
.description("Encode multicall operation ($0.001)")
|
|
1161
|
+
.requiredOption("--calls <calls>", "Calls JSON array (as string or file path)")
|
|
1162
|
+
.action(async (options, command) => {
|
|
1163
|
+
try {
|
|
1164
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1165
|
+
const accountIndex = globalOpts.account;
|
|
1166
|
+
await ensureWalletUnlocked();
|
|
1167
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
1168
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
1169
|
+
if (globalOpts.json) {
|
|
1170
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
1171
|
+
process.exit(2);
|
|
1172
|
+
}
|
|
1173
|
+
privateKey = await promptForPrivateKey();
|
|
1174
|
+
}
|
|
1175
|
+
const client = await HttpcatClient.create(privateKey);
|
|
1176
|
+
// Parse calls
|
|
1177
|
+
let calls;
|
|
1178
|
+
try {
|
|
1179
|
+
calls = JSON.parse(options.calls);
|
|
1180
|
+
}
|
|
1181
|
+
catch {
|
|
1182
|
+
const fs = await import("fs");
|
|
1183
|
+
if (fs.existsSync(options.calls)) {
|
|
1184
|
+
calls = JSON.parse(fs.readFileSync(options.calls, "utf-8"));
|
|
1185
|
+
}
|
|
1186
|
+
else {
|
|
1187
|
+
throw new Error("Invalid calls: must be JSON array string or file path");
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
const result = await withLoading(async () => {
|
|
1191
|
+
const { data } = await client.invoke("tools/multicall/encode", {
|
|
1192
|
+
calls,
|
|
1193
|
+
});
|
|
1194
|
+
return data;
|
|
1195
|
+
}, {
|
|
1196
|
+
message: "Encoding multicall...",
|
|
1197
|
+
json: globalOpts.json,
|
|
1198
|
+
quiet: globalOpts.quiet,
|
|
1199
|
+
spinner: "cat",
|
|
1200
|
+
clearOnSuccess: true,
|
|
1201
|
+
});
|
|
1202
|
+
if (globalOpts.json) {
|
|
1203
|
+
outputJson("multicall_encode", result);
|
|
1204
|
+
}
|
|
1205
|
+
else if (!globalOpts.quiet) {
|
|
1206
|
+
console.log();
|
|
1207
|
+
console.log(chalk.magenta.bold("📦 Multicall Encode"));
|
|
1208
|
+
console.log();
|
|
1209
|
+
printBox("Encoded Data", {
|
|
1210
|
+
Data: chalk.green.bold(result.data),
|
|
1211
|
+
});
|
|
1212
|
+
console.log();
|
|
1213
|
+
}
|
|
1214
|
+
process.exit(0);
|
|
1215
|
+
}
|
|
1216
|
+
catch (error) {
|
|
1217
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1218
|
+
if (globalOpts.json) {
|
|
1219
|
+
outputError("multicall_encode", error, getExitCode(error));
|
|
1220
|
+
}
|
|
1221
|
+
else {
|
|
1222
|
+
handleError(error, globalOpts.verbose);
|
|
1223
|
+
}
|
|
1224
|
+
process.exit(getExitCode(error));
|
|
1225
|
+
}
|
|
1226
|
+
});
|
|
1227
|
+
multicallCommand
|
|
1228
|
+
.command("decode")
|
|
1229
|
+
.description("Decode multicall results ($0.001)")
|
|
1230
|
+
.requiredOption("--data <data>", "Multicall result data (0x-prefixed hex)")
|
|
1231
|
+
.requiredOption("--calls <calls>", "Original calls JSON array (as string or file path)")
|
|
1232
|
+
.action(async (options, command) => {
|
|
1233
|
+
try {
|
|
1234
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1235
|
+
const accountIndex = globalOpts.account;
|
|
1236
|
+
await ensureWalletUnlocked();
|
|
1237
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
1238
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
1239
|
+
if (globalOpts.json) {
|
|
1240
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
1241
|
+
process.exit(2);
|
|
1242
|
+
}
|
|
1243
|
+
privateKey = await promptForPrivateKey();
|
|
1244
|
+
}
|
|
1245
|
+
const client = await HttpcatClient.create(privateKey);
|
|
1246
|
+
// Parse calls
|
|
1247
|
+
let calls;
|
|
1248
|
+
try {
|
|
1249
|
+
calls = JSON.parse(options.calls);
|
|
1250
|
+
}
|
|
1251
|
+
catch {
|
|
1252
|
+
const fs = await import("fs");
|
|
1253
|
+
if (fs.existsSync(options.calls)) {
|
|
1254
|
+
calls = JSON.parse(fs.readFileSync(options.calls, "utf-8"));
|
|
1255
|
+
}
|
|
1256
|
+
else {
|
|
1257
|
+
throw new Error("Invalid calls: must be JSON array string or file path");
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
const result = await withLoading(async () => {
|
|
1261
|
+
const { data } = await client.invoke("tools/multicall/decode", {
|
|
1262
|
+
data: options.data,
|
|
1263
|
+
calls,
|
|
1264
|
+
});
|
|
1265
|
+
return data;
|
|
1266
|
+
}, {
|
|
1267
|
+
message: "Decoding multicall results...",
|
|
1268
|
+
json: globalOpts.json,
|
|
1269
|
+
quiet: globalOpts.quiet,
|
|
1270
|
+
spinner: "cat",
|
|
1271
|
+
clearOnSuccess: true,
|
|
1272
|
+
});
|
|
1273
|
+
if (globalOpts.json) {
|
|
1274
|
+
outputJson("multicall_decode", result);
|
|
1275
|
+
}
|
|
1276
|
+
else if (!globalOpts.quiet) {
|
|
1277
|
+
console.log();
|
|
1278
|
+
console.log(chalk.magenta.bold("🔍 Multicall Decode"));
|
|
1279
|
+
console.log();
|
|
1280
|
+
console.log(JSON.stringify(result.results, null, 2));
|
|
1281
|
+
console.log();
|
|
1282
|
+
}
|
|
1283
|
+
process.exit(0);
|
|
1284
|
+
}
|
|
1285
|
+
catch (error) {
|
|
1286
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1287
|
+
if (globalOpts.json) {
|
|
1288
|
+
outputError("multicall_decode", error, getExitCode(error));
|
|
1289
|
+
}
|
|
1290
|
+
else {
|
|
1291
|
+
handleError(error, globalOpts.verbose);
|
|
1292
|
+
}
|
|
1293
|
+
process.exit(getExitCode(error));
|
|
1294
|
+
}
|
|
1295
|
+
});
|
|
1296
|
+
// ============================================================================
|
|
1297
|
+
// Nonce Management
|
|
1298
|
+
// ============================================================================
|
|
1299
|
+
const nonceCommand = toolsCommand
|
|
1300
|
+
.command("nonce")
|
|
1301
|
+
.description("Nonce management");
|
|
1302
|
+
nonceCommand
|
|
1303
|
+
.command("get")
|
|
1304
|
+
.description("Get current nonce for address ($0.001)")
|
|
1305
|
+
.argument("<address>", "Address (0x format)")
|
|
1306
|
+
.action(async (address, options, command) => {
|
|
1307
|
+
try {
|
|
1308
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1309
|
+
const accountIndex = globalOpts.account;
|
|
1310
|
+
await ensureWalletUnlocked();
|
|
1311
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
1312
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
1313
|
+
if (globalOpts.json) {
|
|
1314
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
1315
|
+
process.exit(2);
|
|
1316
|
+
}
|
|
1317
|
+
privateKey = await promptForPrivateKey();
|
|
1318
|
+
}
|
|
1319
|
+
const client = await HttpcatClient.create(privateKey);
|
|
1320
|
+
const result = await withLoading(async () => {
|
|
1321
|
+
const { data } = await client.invoke("tools/nonce/get", { address });
|
|
1322
|
+
return data;
|
|
1323
|
+
}, {
|
|
1324
|
+
message: "Fetching nonce...",
|
|
1325
|
+
json: globalOpts.json,
|
|
1326
|
+
quiet: globalOpts.quiet,
|
|
1327
|
+
spinner: "cat",
|
|
1328
|
+
clearOnSuccess: true,
|
|
1329
|
+
});
|
|
1330
|
+
if (globalOpts.json) {
|
|
1331
|
+
outputJson("nonce_get", result);
|
|
1332
|
+
}
|
|
1333
|
+
else if (!globalOpts.quiet) {
|
|
1334
|
+
console.log();
|
|
1335
|
+
console.log(chalk.magenta.bold("🔢 Nonce"));
|
|
1336
|
+
console.log();
|
|
1337
|
+
printBox("Nonce Info", {
|
|
1338
|
+
Address: chalk.cyan(address),
|
|
1339
|
+
"Current Nonce": chalk.green.bold(result.nonce?.toString() || "N/A"),
|
|
1340
|
+
"Pending Nonce": chalk.yellow(result.pendingNonce?.toString() || "N/A"),
|
|
1341
|
+
});
|
|
1342
|
+
console.log();
|
|
1343
|
+
}
|
|
1344
|
+
process.exit(0);
|
|
1345
|
+
}
|
|
1346
|
+
catch (error) {
|
|
1347
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1348
|
+
if (globalOpts.json) {
|
|
1349
|
+
outputError("nonce_get", error, getExitCode(error));
|
|
1350
|
+
}
|
|
1351
|
+
else {
|
|
1352
|
+
handleError(error, globalOpts.verbose);
|
|
1353
|
+
}
|
|
1354
|
+
process.exit(getExitCode(error));
|
|
1355
|
+
}
|
|
1356
|
+
});
|
|
1357
|
+
nonceCommand
|
|
1358
|
+
.command("predict")
|
|
1359
|
+
.description("Predict next nonce accounting for pending transactions ($0.001)")
|
|
1360
|
+
.argument("<address>", "Address (0x format)")
|
|
1361
|
+
.action(async (address, options, command) => {
|
|
1362
|
+
try {
|
|
1363
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1364
|
+
const accountIndex = globalOpts.account;
|
|
1365
|
+
await ensureWalletUnlocked();
|
|
1366
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
1367
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
1368
|
+
if (globalOpts.json) {
|
|
1369
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
1370
|
+
process.exit(2);
|
|
1371
|
+
}
|
|
1372
|
+
privateKey = await promptForPrivateKey();
|
|
1373
|
+
}
|
|
1374
|
+
const client = await HttpcatClient.create(privateKey);
|
|
1375
|
+
const result = await withLoading(async () => {
|
|
1376
|
+
const { data } = await client.invoke("tools/nonce/predict", {
|
|
1377
|
+
address,
|
|
1378
|
+
});
|
|
1379
|
+
return data;
|
|
1380
|
+
}, {
|
|
1381
|
+
message: "Predicting nonce...",
|
|
1382
|
+
json: globalOpts.json,
|
|
1383
|
+
quiet: globalOpts.quiet,
|
|
1384
|
+
spinner: "cat",
|
|
1385
|
+
clearOnSuccess: true,
|
|
1386
|
+
});
|
|
1387
|
+
if (globalOpts.json) {
|
|
1388
|
+
outputJson("nonce_predict", result);
|
|
1389
|
+
}
|
|
1390
|
+
else if (!globalOpts.quiet) {
|
|
1391
|
+
console.log();
|
|
1392
|
+
console.log(chalk.magenta.bold("🔮 Nonce Prediction"));
|
|
1393
|
+
console.log();
|
|
1394
|
+
printBox("Nonce Prediction", {
|
|
1395
|
+
Address: chalk.cyan(address),
|
|
1396
|
+
"Current Nonce": chalk.green(result.currentNonce?.toString() || "N/A"),
|
|
1397
|
+
"Pending Count": chalk.yellow(result.pendingCount?.toString() || "0"),
|
|
1398
|
+
"Predicted Nonce": chalk.green.bold(result.predictedNonce?.toString() || "N/A"),
|
|
1399
|
+
});
|
|
1400
|
+
console.log();
|
|
1401
|
+
}
|
|
1402
|
+
process.exit(0);
|
|
1403
|
+
}
|
|
1404
|
+
catch (error) {
|
|
1405
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1406
|
+
if (globalOpts.json) {
|
|
1407
|
+
outputError("nonce_predict", error, getExitCode(error));
|
|
1408
|
+
}
|
|
1409
|
+
else {
|
|
1410
|
+
handleError(error, globalOpts.verbose);
|
|
1411
|
+
}
|
|
1412
|
+
process.exit(getExitCode(error));
|
|
1413
|
+
}
|
|
1414
|
+
});
|
|
1415
|
+
// ============================================================================
|
|
1416
|
+
// Gas Utilities
|
|
1417
|
+
// ============================================================================
|
|
1418
|
+
const gasCommand = toolsCommand
|
|
1419
|
+
.command("gas")
|
|
1420
|
+
.description("Gas utilities");
|
|
1421
|
+
gasCommand
|
|
1422
|
+
.command("price")
|
|
1423
|
+
.description("Get current gas price ($0.001)")
|
|
1424
|
+
.action(async (options, command) => {
|
|
1425
|
+
try {
|
|
1426
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1427
|
+
const accountIndex = globalOpts.account;
|
|
1428
|
+
await ensureWalletUnlocked();
|
|
1429
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
1430
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
1431
|
+
if (globalOpts.json) {
|
|
1432
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
1433
|
+
process.exit(2);
|
|
1434
|
+
}
|
|
1435
|
+
privateKey = await promptForPrivateKey();
|
|
1436
|
+
}
|
|
1437
|
+
const client = await HttpcatClient.create(privateKey);
|
|
1438
|
+
const result = await withLoading(async () => {
|
|
1439
|
+
const { data } = await client.invoke("tools/gas/price", {});
|
|
1440
|
+
return data;
|
|
1441
|
+
}, {
|
|
1442
|
+
message: "Fetching gas price...",
|
|
1443
|
+
json: globalOpts.json,
|
|
1444
|
+
quiet: globalOpts.quiet,
|
|
1445
|
+
spinner: "cat",
|
|
1446
|
+
clearOnSuccess: true,
|
|
1447
|
+
});
|
|
1448
|
+
if (globalOpts.json) {
|
|
1449
|
+
outputJson("gas_price", result);
|
|
1450
|
+
}
|
|
1451
|
+
else if (!globalOpts.quiet) {
|
|
1452
|
+
console.log();
|
|
1453
|
+
console.log(chalk.magenta.bold("⛽ Gas Price"));
|
|
1454
|
+
console.log();
|
|
1455
|
+
printBox("Gas Prices", {
|
|
1456
|
+
"Gas Price": chalk.green(result.gasPrice || "N/A"),
|
|
1457
|
+
"Max Fee Per Gas": chalk.yellow(result.maxFeePerGas || "N/A"),
|
|
1458
|
+
"Max Priority Fee Per Gas": chalk.cyan(result.maxPriorityFeePerGas || "N/A"),
|
|
1459
|
+
});
|
|
1460
|
+
console.log();
|
|
1461
|
+
}
|
|
1462
|
+
process.exit(0);
|
|
1463
|
+
}
|
|
1464
|
+
catch (error) {
|
|
1465
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1466
|
+
if (globalOpts.json) {
|
|
1467
|
+
outputError("gas_price", error, getExitCode(error));
|
|
1468
|
+
}
|
|
1469
|
+
else {
|
|
1470
|
+
handleError(error, globalOpts.verbose);
|
|
1471
|
+
}
|
|
1472
|
+
process.exit(getExitCode(error));
|
|
1473
|
+
}
|
|
1474
|
+
});
|
|
1475
|
+
gasCommand
|
|
1476
|
+
.command("estimate")
|
|
1477
|
+
.description("Estimate gas for operation ($0.001)")
|
|
1478
|
+
.requiredOption("--to <address>", "To address (0x format)")
|
|
1479
|
+
.requiredOption("--data <data>", "Transaction data (0x-prefixed hex)")
|
|
1480
|
+
.requiredOption("--value <value>", "Value in wei")
|
|
1481
|
+
.action(async (options, command) => {
|
|
1482
|
+
try {
|
|
1483
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1484
|
+
const accountIndex = globalOpts.account;
|
|
1485
|
+
await ensureWalletUnlocked();
|
|
1486
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
1487
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
1488
|
+
if (globalOpts.json) {
|
|
1489
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
1490
|
+
process.exit(2);
|
|
1491
|
+
}
|
|
1492
|
+
privateKey = await promptForPrivateKey();
|
|
1493
|
+
}
|
|
1494
|
+
const client = await HttpcatClient.create(privateKey);
|
|
1495
|
+
const result = await withLoading(async () => {
|
|
1496
|
+
const { data } = await client.invoke("tools/gas/estimate", {
|
|
1497
|
+
transaction: {
|
|
1498
|
+
to: options.to,
|
|
1499
|
+
data: options.data,
|
|
1500
|
+
value: options.value,
|
|
1501
|
+
},
|
|
1502
|
+
});
|
|
1503
|
+
return data;
|
|
1504
|
+
}, {
|
|
1505
|
+
message: "Estimating gas...",
|
|
1506
|
+
json: globalOpts.json,
|
|
1507
|
+
quiet: globalOpts.quiet,
|
|
1508
|
+
spinner: "cat",
|
|
1509
|
+
clearOnSuccess: true,
|
|
1510
|
+
});
|
|
1511
|
+
if (globalOpts.json) {
|
|
1512
|
+
outputJson("gas_estimate", result);
|
|
1513
|
+
}
|
|
1514
|
+
else if (!globalOpts.quiet) {
|
|
1515
|
+
console.log();
|
|
1516
|
+
console.log(chalk.magenta.bold("⛽ Gas Estimate"));
|
|
1517
|
+
console.log();
|
|
1518
|
+
printBox("Gas Estimate", {
|
|
1519
|
+
"Gas Estimate": chalk.green.bold(result.gasEstimate),
|
|
1520
|
+
});
|
|
1521
|
+
console.log();
|
|
1522
|
+
}
|
|
1523
|
+
process.exit(0);
|
|
1524
|
+
}
|
|
1525
|
+
catch (error) {
|
|
1526
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1527
|
+
if (globalOpts.json) {
|
|
1528
|
+
outputError("gas_estimate", error, getExitCode(error));
|
|
1529
|
+
}
|
|
1530
|
+
else {
|
|
1531
|
+
handleError(error, globalOpts.verbose);
|
|
1532
|
+
}
|
|
1533
|
+
process.exit(getExitCode(error));
|
|
1534
|
+
}
|
|
1535
|
+
});
|
|
1536
|
+
gasCommand
|
|
1537
|
+
.command("optimize")
|
|
1538
|
+
.description("Optimize gas settings for transaction ($0.001)")
|
|
1539
|
+
.requiredOption("--to <address>", "To address (0x format)")
|
|
1540
|
+
.requiredOption("--data <data>", "Transaction data (0x-prefixed hex)")
|
|
1541
|
+
.requiredOption("--value <value>", "Value in wei")
|
|
1542
|
+
.option("--priority <priority>", "Priority: low, medium, high", "medium")
|
|
1543
|
+
.action(async (options, command) => {
|
|
1544
|
+
try {
|
|
1545
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1546
|
+
const accountIndex = globalOpts.account;
|
|
1547
|
+
await ensureWalletUnlocked();
|
|
1548
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
1549
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
1550
|
+
if (globalOpts.json) {
|
|
1551
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
1552
|
+
process.exit(2);
|
|
1553
|
+
}
|
|
1554
|
+
privateKey = await promptForPrivateKey();
|
|
1555
|
+
}
|
|
1556
|
+
const client = await HttpcatClient.create(privateKey);
|
|
1557
|
+
const input = {
|
|
1558
|
+
transaction: {
|
|
1559
|
+
to: options.to,
|
|
1560
|
+
data: options.data,
|
|
1561
|
+
value: options.value,
|
|
1562
|
+
},
|
|
1563
|
+
};
|
|
1564
|
+
if (options.priority)
|
|
1565
|
+
input.priority = options.priority;
|
|
1566
|
+
const result = await withLoading(async () => {
|
|
1567
|
+
const { data } = await client.invoke("tools/gas/optimize", input);
|
|
1568
|
+
return data;
|
|
1569
|
+
}, {
|
|
1570
|
+
message: "Optimizing gas settings...",
|
|
1571
|
+
json: globalOpts.json,
|
|
1572
|
+
quiet: globalOpts.quiet,
|
|
1573
|
+
spinner: "cat",
|
|
1574
|
+
clearOnSuccess: true,
|
|
1575
|
+
});
|
|
1576
|
+
if (globalOpts.json) {
|
|
1577
|
+
outputJson("gas_optimize", result);
|
|
1578
|
+
}
|
|
1579
|
+
else if (!globalOpts.quiet) {
|
|
1580
|
+
console.log();
|
|
1581
|
+
console.log(chalk.magenta.bold("⚡ Gas Optimization"));
|
|
1582
|
+
console.log();
|
|
1583
|
+
printBox("Optimized Gas", {
|
|
1584
|
+
"Gas Price": chalk.green(result.gasPrice || "N/A"),
|
|
1585
|
+
"Max Fee Per Gas": chalk.yellow(result.maxFeePerGas || "N/A"),
|
|
1586
|
+
"Max Priority Fee Per Gas": chalk.cyan(result.maxPriorityFeePerGas || "N/A"),
|
|
1587
|
+
"Estimated Cost": chalk.magenta(result.estimatedCost || "N/A"),
|
|
1588
|
+
});
|
|
1589
|
+
console.log();
|
|
1590
|
+
}
|
|
1591
|
+
process.exit(0);
|
|
1592
|
+
}
|
|
1593
|
+
catch (error) {
|
|
1594
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1595
|
+
if (globalOpts.json) {
|
|
1596
|
+
outputError("gas_optimize", error, getExitCode(error));
|
|
1597
|
+
}
|
|
1598
|
+
else {
|
|
1599
|
+
handleError(error, globalOpts.verbose);
|
|
1600
|
+
}
|
|
1601
|
+
process.exit(getExitCode(error));
|
|
1602
|
+
}
|
|
1603
|
+
});
|
|
1604
|
+
// ============================================================================
|
|
1605
|
+
// Key Utilities
|
|
1606
|
+
// ============================================================================
|
|
1607
|
+
const keyCommand = toolsCommand
|
|
1608
|
+
.command("key")
|
|
1609
|
+
.description("Key utilities");
|
|
1610
|
+
keyCommand
|
|
1611
|
+
.command("derive")
|
|
1612
|
+
.description("Derive address from private key ($0.001)")
|
|
1613
|
+
.argument("<privateKey>", "Private key (0x-prefixed hex)")
|
|
1614
|
+
.action(async (privateKey, options, command) => {
|
|
1615
|
+
try {
|
|
1616
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1617
|
+
const accountIndex = globalOpts.account;
|
|
1618
|
+
await ensureWalletUnlocked();
|
|
1619
|
+
let pk = getPrivateKey(globalOpts.privateKey, accountIndex) || privateKey;
|
|
1620
|
+
if (!isConfigured(globalOpts.privateKey) && !privateKey) {
|
|
1621
|
+
if (globalOpts.json) {
|
|
1622
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
1623
|
+
process.exit(2);
|
|
1624
|
+
}
|
|
1625
|
+
pk = await promptForPrivateKey();
|
|
1626
|
+
}
|
|
1627
|
+
const client = await HttpcatClient.create(pk);
|
|
1628
|
+
const result = await withLoading(async () => {
|
|
1629
|
+
const { data } = await client.invoke("tools/key/derive", {
|
|
1630
|
+
privateKey: pk,
|
|
1631
|
+
});
|
|
1632
|
+
return data;
|
|
1633
|
+
}, {
|
|
1634
|
+
message: "Deriving address...",
|
|
1635
|
+
json: globalOpts.json,
|
|
1636
|
+
quiet: globalOpts.quiet,
|
|
1637
|
+
spinner: "cat",
|
|
1638
|
+
clearOnSuccess: true,
|
|
1639
|
+
});
|
|
1640
|
+
if (globalOpts.json) {
|
|
1641
|
+
outputJson("key_derive", result);
|
|
1642
|
+
}
|
|
1643
|
+
else if (!globalOpts.quiet) {
|
|
1644
|
+
console.log();
|
|
1645
|
+
console.log(chalk.magenta.bold("🔑 Key Derivation"));
|
|
1646
|
+
console.log();
|
|
1647
|
+
printBox("Derived Address", {
|
|
1648
|
+
Address: chalk.green.bold(result.address),
|
|
1649
|
+
});
|
|
1650
|
+
console.log();
|
|
1651
|
+
}
|
|
1652
|
+
process.exit(0);
|
|
1653
|
+
}
|
|
1654
|
+
catch (error) {
|
|
1655
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1656
|
+
if (globalOpts.json) {
|
|
1657
|
+
outputError("key_derive", error, getExitCode(error));
|
|
1658
|
+
}
|
|
1659
|
+
else {
|
|
1660
|
+
handleError(error, globalOpts.verbose);
|
|
1661
|
+
}
|
|
1662
|
+
process.exit(getExitCode(error));
|
|
1663
|
+
}
|
|
1664
|
+
});
|
|
1665
|
+
keyCommand
|
|
1666
|
+
.command("validate")
|
|
1667
|
+
.description("Validate private key format ($0.001)")
|
|
1668
|
+
.argument("<privateKey>", "Private key")
|
|
1669
|
+
.action(async (privateKey, options, command) => {
|
|
1670
|
+
try {
|
|
1671
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1672
|
+
const accountIndex = globalOpts.account;
|
|
1673
|
+
await ensureWalletUnlocked();
|
|
1674
|
+
let privateKeyToUse = getPrivateKey(globalOpts.privateKey, accountIndex) || privateKey;
|
|
1675
|
+
if (!isConfigured(globalOpts.privateKey) && !privateKey) {
|
|
1676
|
+
if (globalOpts.json) {
|
|
1677
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
1678
|
+
process.exit(2);
|
|
1679
|
+
}
|
|
1680
|
+
privateKeyToUse = await promptForPrivateKey();
|
|
1681
|
+
}
|
|
1682
|
+
const client = await HttpcatClient.create(privateKeyToUse);
|
|
1683
|
+
const result = await withLoading(async () => {
|
|
1684
|
+
const { data } = await client.invoke("tools/key/validate", {
|
|
1685
|
+
privateKey: privateKeyToUse,
|
|
1686
|
+
});
|
|
1687
|
+
return data;
|
|
1688
|
+
}, {
|
|
1689
|
+
message: "Validating private key...",
|
|
1690
|
+
json: globalOpts.json,
|
|
1691
|
+
quiet: globalOpts.quiet,
|
|
1692
|
+
spinner: "cat",
|
|
1693
|
+
clearOnSuccess: true,
|
|
1694
|
+
});
|
|
1695
|
+
if (globalOpts.json) {
|
|
1696
|
+
outputJson("key_validate", result);
|
|
1697
|
+
}
|
|
1698
|
+
else if (!globalOpts.quiet) {
|
|
1699
|
+
console.log();
|
|
1700
|
+
console.log(chalk.magenta.bold("✅ Key Validation"));
|
|
1701
|
+
console.log();
|
|
1702
|
+
printBox("Validation Result", {
|
|
1703
|
+
Valid: result.valid
|
|
1704
|
+
? chalk.green.bold("Yes")
|
|
1705
|
+
: chalk.red.bold("No"),
|
|
1706
|
+
Format: result.format ? chalk.cyan(result.format) : chalk.dim("N/A"),
|
|
1707
|
+
});
|
|
1708
|
+
console.log();
|
|
1709
|
+
}
|
|
1710
|
+
process.exit(0);
|
|
1711
|
+
}
|
|
1712
|
+
catch (error) {
|
|
1713
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1714
|
+
if (globalOpts.json) {
|
|
1715
|
+
outputError("key_validate", error, getExitCode(error));
|
|
1716
|
+
}
|
|
1717
|
+
else {
|
|
1718
|
+
handleError(error, globalOpts.verbose);
|
|
1719
|
+
}
|
|
1720
|
+
process.exit(getExitCode(error));
|
|
1721
|
+
}
|
|
1722
|
+
});
|
|
1723
|
+
// ============================================================================
|
|
1724
|
+
// RPC Utilities
|
|
1725
|
+
// ============================================================================
|
|
1726
|
+
const rpcCommand = toolsCommand
|
|
1727
|
+
.command("rpc")
|
|
1728
|
+
.description("RPC utilities");
|
|
1729
|
+
rpcCommand
|
|
1730
|
+
.command("proxy")
|
|
1731
|
+
.description("Enhanced JSON-RPC proxy ($0.001)")
|
|
1732
|
+
.requiredOption("--method <method>", "RPC method (e.g., 'eth_getBalance')")
|
|
1733
|
+
.requiredOption("--params <params>", "Params as JSON array")
|
|
1734
|
+
.action(async (options, command) => {
|
|
1735
|
+
try {
|
|
1736
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1737
|
+
const accountIndex = globalOpts.account;
|
|
1738
|
+
await ensureWalletUnlocked();
|
|
1739
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
1740
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
1741
|
+
if (globalOpts.json) {
|
|
1742
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
1743
|
+
process.exit(2);
|
|
1744
|
+
}
|
|
1745
|
+
privateKey = await promptForPrivateKey();
|
|
1746
|
+
}
|
|
1747
|
+
const client = await HttpcatClient.create(privateKey);
|
|
1748
|
+
// Parse params
|
|
1749
|
+
let params;
|
|
1750
|
+
try {
|
|
1751
|
+
params = JSON.parse(options.params);
|
|
1752
|
+
}
|
|
1753
|
+
catch {
|
|
1754
|
+
throw new Error("Invalid params: must be JSON array");
|
|
1755
|
+
}
|
|
1756
|
+
const result = await withLoading(async () => {
|
|
1757
|
+
const { data } = await client.invoke("tools/rpc/proxy", {
|
|
1758
|
+
method: options.method,
|
|
1759
|
+
params,
|
|
1760
|
+
});
|
|
1761
|
+
return data;
|
|
1762
|
+
}, {
|
|
1763
|
+
message: "Calling RPC...",
|
|
1764
|
+
json: globalOpts.json,
|
|
1765
|
+
quiet: globalOpts.quiet,
|
|
1766
|
+
spinner: "cat",
|
|
1767
|
+
clearOnSuccess: true,
|
|
1768
|
+
});
|
|
1769
|
+
if (globalOpts.json) {
|
|
1770
|
+
outputJson("rpc_proxy", result);
|
|
1771
|
+
}
|
|
1772
|
+
else if (!globalOpts.quiet) {
|
|
1773
|
+
console.log();
|
|
1774
|
+
console.log(chalk.magenta.bold("🌐 RPC Proxy"));
|
|
1775
|
+
console.log();
|
|
1776
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1777
|
+
console.log();
|
|
1778
|
+
}
|
|
1779
|
+
process.exit(0);
|
|
1780
|
+
}
|
|
1781
|
+
catch (error) {
|
|
1782
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1783
|
+
if (globalOpts.json) {
|
|
1784
|
+
outputError("rpc_proxy", error, getExitCode(error));
|
|
1785
|
+
}
|
|
1786
|
+
else {
|
|
1787
|
+
handleError(error, globalOpts.verbose);
|
|
1788
|
+
}
|
|
1789
|
+
process.exit(getExitCode(error));
|
|
1790
|
+
}
|
|
1791
|
+
});
|
|
1792
|
+
rpcCommand
|
|
1793
|
+
.command("batch")
|
|
1794
|
+
.description("Batch multiple RPC calls ($0.001)")
|
|
1795
|
+
.requiredOption("--requests <requests>", "Requests JSON array (as string or file path)")
|
|
1796
|
+
.action(async (options, command) => {
|
|
1797
|
+
try {
|
|
1798
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1799
|
+
const accountIndex = globalOpts.account;
|
|
1800
|
+
await ensureWalletUnlocked();
|
|
1801
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
1802
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
1803
|
+
if (globalOpts.json) {
|
|
1804
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
1805
|
+
process.exit(2);
|
|
1806
|
+
}
|
|
1807
|
+
privateKey = await promptForPrivateKey();
|
|
1808
|
+
}
|
|
1809
|
+
const client = await HttpcatClient.create(privateKey);
|
|
1810
|
+
// Parse requests
|
|
1811
|
+
let requests;
|
|
1812
|
+
try {
|
|
1813
|
+
requests = JSON.parse(options.requests);
|
|
1814
|
+
}
|
|
1815
|
+
catch {
|
|
1816
|
+
const fs = await import("fs");
|
|
1817
|
+
if (fs.existsSync(options.requests)) {
|
|
1818
|
+
requests = JSON.parse(fs.readFileSync(options.requests, "utf-8"));
|
|
1819
|
+
}
|
|
1820
|
+
else {
|
|
1821
|
+
throw new Error("Invalid requests: must be JSON array string or file path");
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
const result = await withLoading(async () => {
|
|
1825
|
+
const { data } = await client.invoke("tools/rpc/batch", {
|
|
1826
|
+
requests,
|
|
1827
|
+
});
|
|
1828
|
+
return data;
|
|
1829
|
+
}, {
|
|
1830
|
+
message: "Batching RPC calls...",
|
|
1831
|
+
json: globalOpts.json,
|
|
1832
|
+
quiet: globalOpts.quiet,
|
|
1833
|
+
spinner: "cat",
|
|
1834
|
+
clearOnSuccess: true,
|
|
1835
|
+
});
|
|
1836
|
+
if (globalOpts.json) {
|
|
1837
|
+
outputJson("rpc_batch", result);
|
|
1838
|
+
}
|
|
1839
|
+
else if (!globalOpts.quiet) {
|
|
1840
|
+
console.log();
|
|
1841
|
+
console.log(chalk.magenta.bold("📦 RPC Batch"));
|
|
1842
|
+
console.log();
|
|
1843
|
+
console.log(JSON.stringify(result.results, null, 2));
|
|
1844
|
+
console.log();
|
|
1845
|
+
}
|
|
1846
|
+
process.exit(0);
|
|
1847
|
+
}
|
|
1848
|
+
catch (error) {
|
|
1849
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1850
|
+
if (globalOpts.json) {
|
|
1851
|
+
outputError("rpc_batch", error, getExitCode(error));
|
|
1852
|
+
}
|
|
1853
|
+
else {
|
|
1854
|
+
handleError(error, globalOpts.verbose);
|
|
1855
|
+
}
|
|
1856
|
+
process.exit(getExitCode(error));
|
|
1857
|
+
}
|
|
1858
|
+
});
|
|
1859
|
+
rpcCommand
|
|
1860
|
+
.command("health")
|
|
1861
|
+
.description("Check RPC endpoint health ($0.001)")
|
|
1862
|
+
.action(async (options, command) => {
|
|
1863
|
+
try {
|
|
1864
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1865
|
+
const accountIndex = globalOpts.account;
|
|
1866
|
+
await ensureWalletUnlocked();
|
|
1867
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
1868
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
1869
|
+
if (globalOpts.json) {
|
|
1870
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
1871
|
+
process.exit(2);
|
|
1872
|
+
}
|
|
1873
|
+
privateKey = await promptForPrivateKey();
|
|
1874
|
+
}
|
|
1875
|
+
const client = await HttpcatClient.create(privateKey);
|
|
1876
|
+
const result = await withLoading(async () => {
|
|
1877
|
+
const { data } = await client.invoke("tools/rpc/health", {});
|
|
1878
|
+
return data;
|
|
1879
|
+
}, {
|
|
1880
|
+
message: "Checking RPC health...",
|
|
1881
|
+
json: globalOpts.json,
|
|
1882
|
+
quiet: globalOpts.quiet,
|
|
1883
|
+
spinner: "cat",
|
|
1884
|
+
clearOnSuccess: true,
|
|
1885
|
+
});
|
|
1886
|
+
if (globalOpts.json) {
|
|
1887
|
+
outputJson("rpc_health", result);
|
|
1888
|
+
}
|
|
1889
|
+
else if (!globalOpts.quiet) {
|
|
1890
|
+
console.log();
|
|
1891
|
+
console.log(chalk.magenta.bold("🏥 RPC Health"));
|
|
1892
|
+
console.log();
|
|
1893
|
+
printBox("Health Status", {
|
|
1894
|
+
Healthy: result.healthy
|
|
1895
|
+
? chalk.green.bold("Yes")
|
|
1896
|
+
: chalk.red.bold("No"),
|
|
1897
|
+
Latency: result.latency
|
|
1898
|
+
? chalk.cyan(`${result.latency}ms`)
|
|
1899
|
+
: "N/A",
|
|
1900
|
+
"Chain ID": result.chainId
|
|
1901
|
+
? chalk.yellow(result.chainId.toString())
|
|
1902
|
+
: "N/A",
|
|
1903
|
+
"Block Number": result.blockNumber
|
|
1904
|
+
? chalk.cyan(result.blockNumber.toString())
|
|
1905
|
+
: "N/A",
|
|
1906
|
+
});
|
|
1907
|
+
console.log();
|
|
1908
|
+
}
|
|
1909
|
+
process.exit(0);
|
|
1910
|
+
}
|
|
1911
|
+
catch (error) {
|
|
1912
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1913
|
+
if (globalOpts.json) {
|
|
1914
|
+
outputError("rpc_health", error, getExitCode(error));
|
|
1915
|
+
}
|
|
1916
|
+
else {
|
|
1917
|
+
handleError(error, globalOpts.verbose);
|
|
1918
|
+
}
|
|
1919
|
+
process.exit(getExitCode(error));
|
|
1920
|
+
}
|
|
1921
|
+
});
|
|
1922
|
+
// ============================================================================
|
|
1923
|
+
// x402 Endpoint Call
|
|
1924
|
+
// ============================================================================
|
|
1925
|
+
toolsCommand
|
|
1926
|
+
.command("call")
|
|
1927
|
+
.description("Call an arbitrary x402-protected HTTP endpoint with automatic payment handling")
|
|
1928
|
+
.argument("<url>", "The x402 endpoint URL")
|
|
1929
|
+
.option("-m, --method <method>", "HTTP method (GET, POST, PUT, DELETE, PATCH)", "POST")
|
|
1930
|
+
.option("-b, --body <body>", "Request body as JSON string (or '-' to read from stdin)")
|
|
1931
|
+
.option("-H, --header <header>", "Additional HTTP headers (format: 'Key: Value')", (val, prev) => {
|
|
1932
|
+
const headers = prev || [];
|
|
1933
|
+
headers.push(val);
|
|
1934
|
+
return headers;
|
|
1935
|
+
})
|
|
1936
|
+
.addHelpText("after", `
|
|
1937
|
+
Examples:
|
|
1938
|
+
httpcat tools call https://example.com/api/endpoint --method POST --body '{"key": "value"}'
|
|
1939
|
+
httpcat tools call https://example.com/api/endpoint --method GET
|
|
1940
|
+
echo '{"data": "test"}' | httpcat tools call https://example.com/api/endpoint --body -
|
|
1941
|
+
httpcat tools call https://example.com/api/endpoint --header "X-Custom-Header: value" --header "Authorization: Bearer token"
|
|
1942
|
+
`)
|
|
1943
|
+
.action(async (url, options, command) => {
|
|
1944
|
+
try {
|
|
1945
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
1946
|
+
const accountIndex = globalOpts.account;
|
|
1947
|
+
// Ensure wallet is unlocked
|
|
1948
|
+
await ensureWalletUnlocked();
|
|
1949
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
1950
|
+
// If not configured and not in JSON mode, prompt interactively
|
|
1951
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
1952
|
+
if (globalOpts.json) {
|
|
1953
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
1954
|
+
process.exit(2);
|
|
1955
|
+
}
|
|
1956
|
+
privateKey = await promptForPrivateKey();
|
|
1957
|
+
}
|
|
1958
|
+
const client = await HttpcatClient.create(privateKey);
|
|
1959
|
+
// Parse body
|
|
1960
|
+
let body = undefined;
|
|
1961
|
+
if (options.body !== undefined) {
|
|
1962
|
+
if (options.body === "-") {
|
|
1963
|
+
// Read from stdin
|
|
1964
|
+
const { readFileSync } = await import("fs");
|
|
1965
|
+
const stdin = readFileSync(0, "utf-8").trim();
|
|
1966
|
+
if (stdin) {
|
|
1967
|
+
try {
|
|
1968
|
+
body = JSON.parse(stdin);
|
|
1969
|
+
}
|
|
1970
|
+
catch {
|
|
1971
|
+
body = stdin;
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
else {
|
|
1976
|
+
try {
|
|
1977
|
+
body = JSON.parse(options.body);
|
|
1978
|
+
}
|
|
1979
|
+
catch {
|
|
1980
|
+
body = options.body;
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
// Parse headers
|
|
1985
|
+
const headers = {};
|
|
1986
|
+
if (options.header && Array.isArray(options.header)) {
|
|
1987
|
+
for (const header of options.header) {
|
|
1988
|
+
const colonIndex = header.indexOf(":");
|
|
1989
|
+
if (colonIndex === -1) {
|
|
1990
|
+
throw new Error(`Invalid header format: ${header}. Expected format: "Key: Value"`);
|
|
1991
|
+
}
|
|
1992
|
+
const key = header.substring(0, colonIndex).trim();
|
|
1993
|
+
const value = header.substring(colonIndex + 1).trim();
|
|
1994
|
+
headers[key] = value;
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
else if (options.header) {
|
|
1998
|
+
// Single header
|
|
1999
|
+
const colonIndex = options.header.indexOf(":");
|
|
2000
|
+
if (colonIndex === -1) {
|
|
2001
|
+
throw new Error(`Invalid header format: ${options.header}. Expected format: "Key: Value"`);
|
|
2002
|
+
}
|
|
2003
|
+
const key = options.header.substring(0, colonIndex).trim();
|
|
2004
|
+
const value = options.header.substring(colonIndex + 1).trim();
|
|
2005
|
+
headers[key] = value;
|
|
2006
|
+
}
|
|
2007
|
+
const result = await withLoading(() => callX402Endpoint(client, url, {
|
|
2008
|
+
method: options.method,
|
|
2009
|
+
body,
|
|
2010
|
+
headers,
|
|
2011
|
+
}), {
|
|
2012
|
+
message: "Calling x402 endpoint...",
|
|
2013
|
+
json: globalOpts.json,
|
|
2014
|
+
quiet: globalOpts.quiet,
|
|
2015
|
+
spinner: "cat",
|
|
2016
|
+
clearOnSuccess: true,
|
|
2017
|
+
});
|
|
2018
|
+
if (globalOpts.json) {
|
|
2019
|
+
outputJson("call", result);
|
|
2020
|
+
}
|
|
2021
|
+
else if (!globalOpts.quiet) {
|
|
2022
|
+
displayCallResult(result);
|
|
2023
|
+
}
|
|
2024
|
+
// Exit with non-zero code if status is error
|
|
2025
|
+
process.exit(result.status >= 400 ? 1 : 0);
|
|
2026
|
+
}
|
|
2027
|
+
catch (error) {
|
|
2028
|
+
const globalOpts = command.parent?.parent?.parent?.opts() || {};
|
|
2029
|
+
if (globalOpts.json) {
|
|
2030
|
+
outputError("call", error, getExitCode(error));
|
|
2031
|
+
}
|
|
2032
|
+
else {
|
|
2033
|
+
handleError(error, globalOpts.verbose);
|
|
2034
|
+
}
|
|
2035
|
+
process.exit(getExitCode(error));
|
|
2036
|
+
}
|
|
2037
|
+
});
|
|
2038
|
+
return toolsCommand;
|
|
2039
|
+
}
|
|
2040
|
+
//# sourceMappingURL=index.js.map
|