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,733 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { printBox, formatAddress, } from "../utils/formatting.js";
|
|
3
|
+
import { printSuccess } from "../interactive/art.js";
|
|
4
|
+
import { resolveTokenId } from "../utils/token-resolver.js";
|
|
5
|
+
import { createPublicClient, createWalletClient, parseAbi, formatUnits, parseUnits, parseEther, formatEther, isAddress, getAddress, encodeFunctionData, } from "viem";
|
|
6
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
7
|
+
import { config } from "../config.js";
|
|
8
|
+
import { createRpcTransport } from "../utils/rpc-transport.js";
|
|
9
|
+
import { getNetworkConfig } from "../utils/constants.js";
|
|
10
|
+
import inquirer from "inquirer";
|
|
11
|
+
import { readFileSync } from "fs";
|
|
12
|
+
import Table from "cli-table3";
|
|
13
|
+
// ERC20 ABI for token operations
|
|
14
|
+
const ERC20_ABI = parseAbi([
|
|
15
|
+
"function transfer(address to, uint256 amount) returns (bool)",
|
|
16
|
+
"function balanceOf(address owner) view returns (uint256)",
|
|
17
|
+
"function decimals() view returns (uint8)",
|
|
18
|
+
"function symbol() view returns (string)",
|
|
19
|
+
"function name() view returns (string)",
|
|
20
|
+
]);
|
|
21
|
+
/**
|
|
22
|
+
* Validate and checksum an Ethereum address
|
|
23
|
+
*/
|
|
24
|
+
function validateAddress(address) {
|
|
25
|
+
if (!isAddress(address)) {
|
|
26
|
+
throw new Error(`Invalid address format: ${address}`);
|
|
27
|
+
}
|
|
28
|
+
return getAddress(address);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Parse amount string to bigint
|
|
32
|
+
*/
|
|
33
|
+
function parseAmount(amount, decimals = 18) {
|
|
34
|
+
try {
|
|
35
|
+
return parseUnits(amount, decimals);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
throw new Error(`Invalid amount format: ${amount}. ${error instanceof Error ? error.message : ""}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get gas price configuration
|
|
43
|
+
*/
|
|
44
|
+
async function getGasConfig(publicClient, options) {
|
|
45
|
+
const gasConfig = {};
|
|
46
|
+
// Legacy gas price
|
|
47
|
+
if (options.gasPrice) {
|
|
48
|
+
const gwei = parseFloat(options.gasPrice);
|
|
49
|
+
gasConfig.gasPrice = parseUnits(gwei.toString(), 9); // Convert gwei to wei
|
|
50
|
+
}
|
|
51
|
+
// EIP-1559 fees
|
|
52
|
+
if (options.maxFeePerGas || options.maxPriorityFeePerGas) {
|
|
53
|
+
if (options.maxFeePerGas) {
|
|
54
|
+
const gwei = parseFloat(options.maxFeePerGas);
|
|
55
|
+
gasConfig.maxFeePerGas = parseUnits(gwei.toString(), 9);
|
|
56
|
+
}
|
|
57
|
+
if (options.maxPriorityFeePerGas) {
|
|
58
|
+
const gwei = parseFloat(options.maxPriorityFeePerGas);
|
|
59
|
+
gasConfig.maxPriorityFeePerGas = parseUnits(gwei.toString(), 9);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else if (!options.gasPrice) {
|
|
63
|
+
// Auto-estimate EIP-1559 fees if not provided
|
|
64
|
+
try {
|
|
65
|
+
const feeData = await publicClient.estimateFeesPerGas();
|
|
66
|
+
if (feeData) {
|
|
67
|
+
gasConfig.maxFeePerGas = feeData.maxFeePerGas;
|
|
68
|
+
gasConfig.maxPriorityFeePerGas =
|
|
69
|
+
feeData.maxPriorityFeePerGas || feeData.maxFeePerGas / 2n;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Fallback to legacy gas price
|
|
74
|
+
try {
|
|
75
|
+
const gasPrice = await publicClient.getGasPrice();
|
|
76
|
+
gasConfig.gasPrice = gasPrice;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// Use default if all else fails
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Gas limit
|
|
84
|
+
if (options.gasLimit) {
|
|
85
|
+
gasConfig.gas = options.gasLimit;
|
|
86
|
+
}
|
|
87
|
+
return gasConfig;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Prompt for confirmation before sending
|
|
91
|
+
*/
|
|
92
|
+
async function promptConfirmation(summary, options) {
|
|
93
|
+
if (options.noConfirm || options.silent) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
if (options.confirm || !process.stdin.isTTY) {
|
|
97
|
+
// Force confirmation prompt
|
|
98
|
+
const answers = await inquirer.prompt([
|
|
99
|
+
{
|
|
100
|
+
type: "confirm",
|
|
101
|
+
name: "confirm",
|
|
102
|
+
message: `Send ${summary.amount} ${summary.tokenSymbol || "ETH"} to ${summary.to}?`,
|
|
103
|
+
default: false,
|
|
104
|
+
},
|
|
105
|
+
]);
|
|
106
|
+
return answers.confirm;
|
|
107
|
+
}
|
|
108
|
+
// Default: show summary but don't require confirmation in non-interactive mode
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Send native currency (ETH)
|
|
113
|
+
*/
|
|
114
|
+
export async function sendNativeCurrency(client, to, amount, privateKey, options = {}) {
|
|
115
|
+
const account = privateKeyToAccount(privateKey);
|
|
116
|
+
const from = account.address;
|
|
117
|
+
const network = client.getNetwork();
|
|
118
|
+
const { chain } = await getNetworkConfig(network);
|
|
119
|
+
const publicClient = createPublicClient({
|
|
120
|
+
chain,
|
|
121
|
+
transport: createRpcTransport({
|
|
122
|
+
chainId: chain.id,
|
|
123
|
+
httpcatClient: client,
|
|
124
|
+
}),
|
|
125
|
+
});
|
|
126
|
+
const walletClient = createWalletClient({
|
|
127
|
+
account,
|
|
128
|
+
chain,
|
|
129
|
+
transport: createRpcTransport({
|
|
130
|
+
chainId: chain.id,
|
|
131
|
+
httpcatClient: client,
|
|
132
|
+
}),
|
|
133
|
+
});
|
|
134
|
+
// Validate address
|
|
135
|
+
const toAddress = validateAddress(to);
|
|
136
|
+
// Parse amount
|
|
137
|
+
const amountWei = parseEther(amount);
|
|
138
|
+
// Check balance
|
|
139
|
+
const balance = await publicClient.getBalance({ address: from });
|
|
140
|
+
if (balance < amountWei) {
|
|
141
|
+
throw new Error(`Insufficient balance. Have ${formatEther(balance)} ETH, need ${amount} ETH`);
|
|
142
|
+
}
|
|
143
|
+
// Get gas configuration
|
|
144
|
+
const gasConfig = await getGasConfig(publicClient, options);
|
|
145
|
+
// Estimate gas (don't include gas config in estimation)
|
|
146
|
+
let estimatedGas;
|
|
147
|
+
try {
|
|
148
|
+
estimatedGas = await publicClient.estimateGas({
|
|
149
|
+
account: from,
|
|
150
|
+
to: toAddress,
|
|
151
|
+
value: amountWei,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
throw new Error(`Failed to estimate gas: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
156
|
+
}
|
|
157
|
+
// Check if we have enough for gas
|
|
158
|
+
const totalNeeded = amountWei +
|
|
159
|
+
(gasConfig.gas || estimatedGas) *
|
|
160
|
+
(gasConfig.gasPrice || gasConfig.maxFeePerGas || 0n);
|
|
161
|
+
if (balance < totalNeeded) {
|
|
162
|
+
throw new Error(`Insufficient balance for transaction. Need ${formatEther(totalNeeded)} ETH (${amount} ETH + gas)`);
|
|
163
|
+
}
|
|
164
|
+
// Show summary and prompt for confirmation
|
|
165
|
+
const estimatedGasEth = formatEther((gasConfig.gas || estimatedGas) *
|
|
166
|
+
(gasConfig.gasPrice || gasConfig.maxFeePerGas || 0n));
|
|
167
|
+
const confirmed = await promptConfirmation({
|
|
168
|
+
type: "native",
|
|
169
|
+
from,
|
|
170
|
+
to: toAddress,
|
|
171
|
+
amount,
|
|
172
|
+
estimatedGas: estimatedGasEth,
|
|
173
|
+
}, options);
|
|
174
|
+
if (!confirmed) {
|
|
175
|
+
throw new Error("Transaction cancelled by user");
|
|
176
|
+
}
|
|
177
|
+
// Dry run mode
|
|
178
|
+
if (options.dryRun) {
|
|
179
|
+
if (!options.silent) {
|
|
180
|
+
console.log(chalk.yellow("\nš Dry Run Mode - Transaction not broadcast\n"));
|
|
181
|
+
console.log(chalk.cyan(`From: ${from}`));
|
|
182
|
+
console.log(chalk.cyan(`To: ${toAddress}`));
|
|
183
|
+
console.log(chalk.cyan(`Amount: ${amount} ETH`));
|
|
184
|
+
console.log(chalk.cyan(`Estimated Gas: ${estimatedGas.toString()}`));
|
|
185
|
+
console.log(chalk.cyan(`Estimated Gas Cost: ${estimatedGasEth} ETH`));
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
success: true,
|
|
189
|
+
type: "native",
|
|
190
|
+
from,
|
|
191
|
+
to: toAddress,
|
|
192
|
+
amount,
|
|
193
|
+
gasUsed: estimatedGas,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
// Build transaction
|
|
197
|
+
// Note: Don't include 'account' field - walletClient already has it configured
|
|
198
|
+
const txRequest = {
|
|
199
|
+
to: toAddress,
|
|
200
|
+
value: amountWei,
|
|
201
|
+
gas: gasConfig.gas || estimatedGas,
|
|
202
|
+
...gasConfig,
|
|
203
|
+
};
|
|
204
|
+
// Set nonce if provided
|
|
205
|
+
if (options.nonce !== undefined) {
|
|
206
|
+
txRequest.nonce = options.nonce;
|
|
207
|
+
}
|
|
208
|
+
// Send transaction
|
|
209
|
+
if (!options.silent) {
|
|
210
|
+
console.log(chalk.cyan(`\nš¤ Sending ${amount} ETH to ${toAddress}...`));
|
|
211
|
+
}
|
|
212
|
+
const txHash = await walletClient.sendTransaction(txRequest);
|
|
213
|
+
let result = {
|
|
214
|
+
success: true,
|
|
215
|
+
type: "native",
|
|
216
|
+
txHash,
|
|
217
|
+
from,
|
|
218
|
+
to: toAddress,
|
|
219
|
+
amount,
|
|
220
|
+
};
|
|
221
|
+
// Wait for confirmation if requested
|
|
222
|
+
if (options.wait) {
|
|
223
|
+
if (!options.silent) {
|
|
224
|
+
console.log(chalk.cyan(`ā³ Waiting for confirmation...`));
|
|
225
|
+
}
|
|
226
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
227
|
+
hash: txHash,
|
|
228
|
+
timeout: (options.timeout || 300) * 1000,
|
|
229
|
+
});
|
|
230
|
+
result.gasUsed = receipt.gasUsed;
|
|
231
|
+
result.blockNumber = receipt.blockNumber;
|
|
232
|
+
result.confirmations = 1; // At least 1 confirmation
|
|
233
|
+
if (!options.silent) {
|
|
234
|
+
console.log(chalk.green(`ā
Transaction confirmed in block ${receipt.blockNumber.toString()}`));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Send ERC20 token
|
|
241
|
+
*/
|
|
242
|
+
export async function sendToken(client, tokenIdentifier, to, amount, privateKey, options = {}) {
|
|
243
|
+
const account = privateKeyToAccount(privateKey);
|
|
244
|
+
const from = account.address;
|
|
245
|
+
const network = client.getNetwork();
|
|
246
|
+
const { chain } = await getNetworkConfig(network);
|
|
247
|
+
const publicClient = createPublicClient({
|
|
248
|
+
chain,
|
|
249
|
+
transport: createRpcTransport({
|
|
250
|
+
chainId: chain.id,
|
|
251
|
+
httpcatClient: client,
|
|
252
|
+
}),
|
|
253
|
+
});
|
|
254
|
+
const walletClient = createWalletClient({
|
|
255
|
+
account,
|
|
256
|
+
chain,
|
|
257
|
+
transport: createRpcTransport({
|
|
258
|
+
chainId: chain.id,
|
|
259
|
+
httpcatClient: client,
|
|
260
|
+
}),
|
|
261
|
+
});
|
|
262
|
+
// Validate address
|
|
263
|
+
const toAddress = validateAddress(to);
|
|
264
|
+
// Resolve token identifier to address
|
|
265
|
+
let tokenAddress;
|
|
266
|
+
try {
|
|
267
|
+
const resolved = await resolveTokenId(tokenIdentifier, client, options.silent);
|
|
268
|
+
tokenAddress = validateAddress(resolved);
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
// If resolution fails, try treating it as an address directly
|
|
272
|
+
if (isAddress(tokenIdentifier)) {
|
|
273
|
+
tokenAddress = getAddress(tokenIdentifier);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
throw new Error(`Failed to resolve token "${tokenIdentifier}": ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Get token metadata
|
|
280
|
+
let tokenDecimals = options.decimals;
|
|
281
|
+
let tokenSymbol = "TOKEN";
|
|
282
|
+
let tokenName = "Token";
|
|
283
|
+
try {
|
|
284
|
+
if (!tokenDecimals) {
|
|
285
|
+
tokenDecimals = (await publicClient.readContract({
|
|
286
|
+
address: tokenAddress,
|
|
287
|
+
abi: ERC20_ABI,
|
|
288
|
+
functionName: "decimals",
|
|
289
|
+
}));
|
|
290
|
+
}
|
|
291
|
+
tokenSymbol = (await publicClient.readContract({
|
|
292
|
+
address: tokenAddress,
|
|
293
|
+
abi: ERC20_ABI,
|
|
294
|
+
functionName: "symbol",
|
|
295
|
+
}));
|
|
296
|
+
tokenName = (await publicClient.readContract({
|
|
297
|
+
address: tokenAddress,
|
|
298
|
+
abi: ERC20_ABI,
|
|
299
|
+
functionName: "name",
|
|
300
|
+
}));
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
if (!tokenDecimals) {
|
|
304
|
+
throw new Error(`Failed to get token decimals. Use --decimals flag or ensure token contract is valid. ${error instanceof Error ? error.message : ""}`);
|
|
305
|
+
}
|
|
306
|
+
// Use provided decimals if available
|
|
307
|
+
}
|
|
308
|
+
// Parse amount
|
|
309
|
+
const amountWei = parseAmount(amount, tokenDecimals);
|
|
310
|
+
// Check balance
|
|
311
|
+
const balance = (await publicClient.readContract({
|
|
312
|
+
address: tokenAddress,
|
|
313
|
+
abi: ERC20_ABI,
|
|
314
|
+
functionName: "balanceOf",
|
|
315
|
+
args: [from],
|
|
316
|
+
}));
|
|
317
|
+
if (balance < amountWei) {
|
|
318
|
+
throw new Error(`Insufficient token balance. Have ${formatUnits(balance, tokenDecimals)} ${tokenSymbol}, need ${amount} ${tokenSymbol}`);
|
|
319
|
+
}
|
|
320
|
+
// Get gas configuration
|
|
321
|
+
const gasConfig = await getGasConfig(publicClient, options);
|
|
322
|
+
// Estimate gas for transfer (don't include gas config in estimation)
|
|
323
|
+
let estimatedGas;
|
|
324
|
+
try {
|
|
325
|
+
estimatedGas = await publicClient.estimateGas({
|
|
326
|
+
account: from,
|
|
327
|
+
to: tokenAddress,
|
|
328
|
+
data: encodeFunctionData({
|
|
329
|
+
abi: ERC20_ABI,
|
|
330
|
+
functionName: "transfer",
|
|
331
|
+
args: [toAddress, amountWei],
|
|
332
|
+
}),
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
throw new Error(`Failed to estimate gas: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
337
|
+
}
|
|
338
|
+
// Check ETH balance for gas
|
|
339
|
+
const ethBalance = await publicClient.getBalance({ address: from });
|
|
340
|
+
const estimatedGasCost = (gasConfig.gas || estimatedGas) *
|
|
341
|
+
(gasConfig.gasPrice || gasConfig.maxFeePerGas || 0n);
|
|
342
|
+
if (ethBalance < estimatedGasCost) {
|
|
343
|
+
throw new Error(`Insufficient ETH for gas. Need ${formatEther(estimatedGasCost)} ETH`);
|
|
344
|
+
}
|
|
345
|
+
// Show summary and prompt for confirmation
|
|
346
|
+
const estimatedGasEth = formatEther(estimatedGasCost);
|
|
347
|
+
const confirmed = await promptConfirmation({
|
|
348
|
+
type: "token",
|
|
349
|
+
from,
|
|
350
|
+
to: toAddress,
|
|
351
|
+
amount,
|
|
352
|
+
tokenSymbol,
|
|
353
|
+
estimatedGas: estimatedGasEth,
|
|
354
|
+
}, options);
|
|
355
|
+
if (!confirmed) {
|
|
356
|
+
throw new Error("Transaction cancelled by user");
|
|
357
|
+
}
|
|
358
|
+
// Dry run mode
|
|
359
|
+
if (options.dryRun) {
|
|
360
|
+
if (!options.silent) {
|
|
361
|
+
console.log(chalk.yellow("\nš Dry Run Mode - Transaction not broadcast\n"));
|
|
362
|
+
console.log(chalk.cyan(`From: ${from}`));
|
|
363
|
+
console.log(chalk.cyan(`To: ${toAddress}`));
|
|
364
|
+
console.log(chalk.cyan(`Token: ${tokenName} (${tokenSymbol})`));
|
|
365
|
+
console.log(chalk.cyan(`Token Address: ${tokenAddress}`));
|
|
366
|
+
console.log(chalk.cyan(`Amount: ${amount} ${tokenSymbol}`));
|
|
367
|
+
console.log(chalk.cyan(`Estimated Gas: ${estimatedGas.toString()}`));
|
|
368
|
+
console.log(chalk.cyan(`Estimated Gas Cost: ${estimatedGasEth} ETH`));
|
|
369
|
+
}
|
|
370
|
+
return {
|
|
371
|
+
success: true,
|
|
372
|
+
type: "token",
|
|
373
|
+
from,
|
|
374
|
+
to: toAddress,
|
|
375
|
+
amount,
|
|
376
|
+
tokenAddress,
|
|
377
|
+
tokenSymbol,
|
|
378
|
+
gasUsed: estimatedGas,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
// Build transaction
|
|
382
|
+
// Note: Don't include 'account' field - walletClient already has it configured
|
|
383
|
+
const txRequest = {
|
|
384
|
+
to: tokenAddress,
|
|
385
|
+
data: encodeFunctionData({
|
|
386
|
+
abi: ERC20_ABI,
|
|
387
|
+
functionName: "transfer",
|
|
388
|
+
args: [toAddress, amountWei],
|
|
389
|
+
}),
|
|
390
|
+
gas: gasConfig.gas || estimatedGas,
|
|
391
|
+
...gasConfig,
|
|
392
|
+
};
|
|
393
|
+
// Set nonce if provided
|
|
394
|
+
if (options.nonce !== undefined) {
|
|
395
|
+
txRequest.nonce = options.nonce;
|
|
396
|
+
}
|
|
397
|
+
// Send transaction
|
|
398
|
+
if (!options.silent) {
|
|
399
|
+
console.log(chalk.cyan(`\nš¤ Sending ${amount} ${tokenSymbol} to ${toAddress}...`));
|
|
400
|
+
}
|
|
401
|
+
const txHash = await walletClient.sendTransaction(txRequest);
|
|
402
|
+
let result = {
|
|
403
|
+
success: true,
|
|
404
|
+
type: "token",
|
|
405
|
+
txHash,
|
|
406
|
+
from,
|
|
407
|
+
to: toAddress,
|
|
408
|
+
amount,
|
|
409
|
+
tokenAddress,
|
|
410
|
+
tokenSymbol,
|
|
411
|
+
};
|
|
412
|
+
// Wait for confirmation if requested
|
|
413
|
+
if (options.wait) {
|
|
414
|
+
if (!options.silent) {
|
|
415
|
+
console.log(chalk.cyan(`ā³ Waiting for confirmation...`));
|
|
416
|
+
}
|
|
417
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
418
|
+
hash: txHash,
|
|
419
|
+
timeout: (options.timeout || 300) * 1000,
|
|
420
|
+
});
|
|
421
|
+
result.gasUsed = receipt.gasUsed;
|
|
422
|
+
result.blockNumber = receipt.blockNumber;
|
|
423
|
+
result.confirmations = 1;
|
|
424
|
+
if (!options.silent) {
|
|
425
|
+
console.log(chalk.green(`ā
Transaction confirmed in block ${receipt.blockNumber.toString()}`));
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return result;
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Resolve recipient (account index or address) - helper for batch sends
|
|
432
|
+
*/
|
|
433
|
+
function resolveRecipientForBatch(to) {
|
|
434
|
+
// Check if it's a number (account index)
|
|
435
|
+
const accountIndex = parseInt(to, 10);
|
|
436
|
+
if (!isNaN(accountIndex) && to.trim() === accountIndex.toString()) {
|
|
437
|
+
// It's a number - try to resolve to account address
|
|
438
|
+
const accounts = config.getAllAccounts();
|
|
439
|
+
const account = accounts.find((acc) => acc.index === accountIndex);
|
|
440
|
+
if (!account) {
|
|
441
|
+
throw new Error(`Account at index ${accountIndex} not found. Use "httpcat account list" to see available accounts.`);
|
|
442
|
+
}
|
|
443
|
+
return account.address;
|
|
444
|
+
}
|
|
445
|
+
// Not a number - treat as address
|
|
446
|
+
return to;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Send batch transactions
|
|
450
|
+
*/
|
|
451
|
+
export async function sendBatch(client, batchInput, privateKey, options = {}) {
|
|
452
|
+
// Parse batch input if it's a string (file path or JSON)
|
|
453
|
+
let batch;
|
|
454
|
+
if (typeof batchInput === "string") {
|
|
455
|
+
try {
|
|
456
|
+
// Try to read as file first
|
|
457
|
+
try {
|
|
458
|
+
const fileContent = readFileSync(batchInput, "utf-8");
|
|
459
|
+
batch = JSON.parse(fileContent);
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
// If file read fails, try parsing as JSON string
|
|
463
|
+
batch = JSON.parse(batchInput);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
catch (error) {
|
|
467
|
+
throw new Error(`Failed to parse batch input: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
batch = batchInput;
|
|
472
|
+
}
|
|
473
|
+
const results = [];
|
|
474
|
+
// Process native currency sends
|
|
475
|
+
if (batch.native && batch.native.length > 0) {
|
|
476
|
+
if (!options.silent) {
|
|
477
|
+
console.log(chalk.cyan(`\nš¦ Processing ${batch.native.length} native currency send(s)...`));
|
|
478
|
+
}
|
|
479
|
+
for (const item of batch.native) {
|
|
480
|
+
try {
|
|
481
|
+
// Resolve recipient (account index or address)
|
|
482
|
+
const recipientAddress = resolveRecipientForBatch(item.to);
|
|
483
|
+
const result = await sendNativeCurrency(client, recipientAddress, item.amount, privateKey, { ...options, silent: true } // Suppress individual confirmations in batch
|
|
484
|
+
);
|
|
485
|
+
results.push(result);
|
|
486
|
+
if (!options.silent) {
|
|
487
|
+
console.log(chalk.green(` ā
Sent ${item.amount} ETH to ${recipientAddress}`));
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
catch (error) {
|
|
491
|
+
results.push({
|
|
492
|
+
success: false,
|
|
493
|
+
type: "native",
|
|
494
|
+
from: privateKeyToAccount(privateKey).address,
|
|
495
|
+
to: item.to,
|
|
496
|
+
amount: item.amount,
|
|
497
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
498
|
+
});
|
|
499
|
+
if (!options.silent) {
|
|
500
|
+
console.log(chalk.red(` ā Failed to send ${item.amount} ETH to ${item.to}: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
// Process token sends
|
|
506
|
+
if (batch.tokens && batch.tokens.length > 0) {
|
|
507
|
+
if (!options.silent) {
|
|
508
|
+
console.log(chalk.cyan(`\nš¦ Processing ${batch.tokens.length} token send(s)...`));
|
|
509
|
+
}
|
|
510
|
+
for (const item of batch.tokens) {
|
|
511
|
+
if (!item.token) {
|
|
512
|
+
results.push({
|
|
513
|
+
success: false,
|
|
514
|
+
type: "token",
|
|
515
|
+
from: privateKeyToAccount(privateKey).address,
|
|
516
|
+
to: item.to,
|
|
517
|
+
amount: item.amount,
|
|
518
|
+
error: "Token identifier required",
|
|
519
|
+
});
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
try {
|
|
523
|
+
// Resolve recipient (account index or address)
|
|
524
|
+
const recipientAddress = resolveRecipientForBatch(item.to);
|
|
525
|
+
const result = await sendToken(client, item.token, recipientAddress, item.amount, privateKey, { ...options, silent: true } // Suppress individual confirmations in batch
|
|
526
|
+
);
|
|
527
|
+
results.push(result);
|
|
528
|
+
if (!options.silent) {
|
|
529
|
+
console.log(chalk.green(` ā
Sent ${item.amount} ${result.tokenSymbol || "tokens"} to ${recipientAddress}`));
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
catch (error) {
|
|
533
|
+
results.push({
|
|
534
|
+
success: false,
|
|
535
|
+
type: "token",
|
|
536
|
+
from: privateKeyToAccount(privateKey).address,
|
|
537
|
+
to: item.to,
|
|
538
|
+
amount: item.amount,
|
|
539
|
+
tokenAddress: item.token,
|
|
540
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
541
|
+
});
|
|
542
|
+
if (!options.silent) {
|
|
543
|
+
console.log(chalk.red(` ā Failed to send ${item.amount} ${item.token} to ${item.to}: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return results;
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Send custom transaction
|
|
552
|
+
*/
|
|
553
|
+
export async function sendCustomTransaction(client, to, data, privateKey, value = 0n, options = {}) {
|
|
554
|
+
const account = privateKeyToAccount(privateKey);
|
|
555
|
+
const from = account.address;
|
|
556
|
+
const network = client.getNetwork();
|
|
557
|
+
const { chain } = await getNetworkConfig(network);
|
|
558
|
+
const publicClient = createPublicClient({
|
|
559
|
+
chain,
|
|
560
|
+
transport: createRpcTransport({
|
|
561
|
+
chainId: chain.id,
|
|
562
|
+
httpcatClient: client,
|
|
563
|
+
}),
|
|
564
|
+
});
|
|
565
|
+
const walletClient = createWalletClient({
|
|
566
|
+
account,
|
|
567
|
+
chain,
|
|
568
|
+
transport: createRpcTransport({
|
|
569
|
+
chainId: chain.id,
|
|
570
|
+
httpcatClient: client,
|
|
571
|
+
}),
|
|
572
|
+
});
|
|
573
|
+
// Validate address
|
|
574
|
+
const toAddress = validateAddress(to);
|
|
575
|
+
// Validate data
|
|
576
|
+
if (!data || !data.startsWith("0x")) {
|
|
577
|
+
throw new Error("Invalid transaction data. Must be hex string starting with 0x");
|
|
578
|
+
}
|
|
579
|
+
// Get gas configuration
|
|
580
|
+
const gasConfig = await getGasConfig(publicClient, options);
|
|
581
|
+
// Estimate gas (don't include gas config in estimation)
|
|
582
|
+
let estimatedGas;
|
|
583
|
+
try {
|
|
584
|
+
estimatedGas = await publicClient.estimateGas({
|
|
585
|
+
account: from,
|
|
586
|
+
to: toAddress,
|
|
587
|
+
data,
|
|
588
|
+
value,
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
catch (error) {
|
|
592
|
+
throw new Error(`Failed to estimate gas: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
593
|
+
}
|
|
594
|
+
// Check ETH balance for gas + value
|
|
595
|
+
const ethBalance = await publicClient.getBalance({ address: from });
|
|
596
|
+
const estimatedGasCost = (gasConfig.gas || estimatedGas) *
|
|
597
|
+
(gasConfig.gasPrice || gasConfig.maxFeePerGas || 0n);
|
|
598
|
+
const totalNeeded = value + estimatedGasCost;
|
|
599
|
+
if (ethBalance < totalNeeded) {
|
|
600
|
+
throw new Error(`Insufficient ETH. Need ${formatEther(totalNeeded)} ETH (${formatEther(value)} value + ${formatEther(estimatedGasCost)} gas)`);
|
|
601
|
+
}
|
|
602
|
+
// Show summary
|
|
603
|
+
if (!options.silent) {
|
|
604
|
+
console.log(chalk.cyan(`\nš¤ Sending custom transaction to ${toAddress}...`));
|
|
605
|
+
console.log(chalk.dim(`Data: ${data.substring(0, 66)}...`));
|
|
606
|
+
console.log(chalk.dim(`Value: ${formatEther(value)} ETH`));
|
|
607
|
+
console.log(chalk.dim(`Estimated Gas: ${estimatedGas.toString()}`));
|
|
608
|
+
}
|
|
609
|
+
// Dry run mode
|
|
610
|
+
if (options.dryRun) {
|
|
611
|
+
if (!options.silent) {
|
|
612
|
+
console.log(chalk.yellow("\nš Dry Run Mode - Transaction not broadcast\n"));
|
|
613
|
+
}
|
|
614
|
+
return {
|
|
615
|
+
success: true,
|
|
616
|
+
type: "custom",
|
|
617
|
+
from,
|
|
618
|
+
to: toAddress,
|
|
619
|
+
gasUsed: estimatedGas,
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
// Build transaction
|
|
623
|
+
// Note: Don't include 'account' field - walletClient already has it configured
|
|
624
|
+
const txRequest = {
|
|
625
|
+
to: toAddress,
|
|
626
|
+
data,
|
|
627
|
+
value,
|
|
628
|
+
gas: gasConfig.gas || estimatedGas,
|
|
629
|
+
...gasConfig,
|
|
630
|
+
};
|
|
631
|
+
// Set nonce if provided
|
|
632
|
+
if (options.nonce !== undefined) {
|
|
633
|
+
txRequest.nonce = options.nonce;
|
|
634
|
+
}
|
|
635
|
+
const txHash = await walletClient.sendTransaction(txRequest);
|
|
636
|
+
let result = {
|
|
637
|
+
success: true,
|
|
638
|
+
type: "custom",
|
|
639
|
+
txHash,
|
|
640
|
+
from,
|
|
641
|
+
to: toAddress,
|
|
642
|
+
};
|
|
643
|
+
// Wait for confirmation if requested
|
|
644
|
+
if (options.wait) {
|
|
645
|
+
if (!options.silent) {
|
|
646
|
+
console.log(chalk.cyan(`ā³ Waiting for confirmation...`));
|
|
647
|
+
}
|
|
648
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
649
|
+
hash: txHash,
|
|
650
|
+
timeout: (options.timeout || 300) * 1000,
|
|
651
|
+
});
|
|
652
|
+
result.gasUsed = receipt.gasUsed;
|
|
653
|
+
result.blockNumber = receipt.blockNumber;
|
|
654
|
+
result.confirmations = 1;
|
|
655
|
+
if (!options.silent) {
|
|
656
|
+
console.log(chalk.green(`ā
Transaction confirmed in block ${receipt.blockNumber.toString()}`));
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
return result;
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Display send result for interactive mode
|
|
663
|
+
*/
|
|
664
|
+
export function displaySendResult(result) {
|
|
665
|
+
console.log();
|
|
666
|
+
printSuccess(result.success ? "Transaction sent successfully!" : "Transaction failed");
|
|
667
|
+
const boxData = {
|
|
668
|
+
Type: chalk.cyan(result.type.toUpperCase()),
|
|
669
|
+
From: formatAddress(result.from),
|
|
670
|
+
To: formatAddress(result.to),
|
|
671
|
+
};
|
|
672
|
+
if (result.amount) {
|
|
673
|
+
boxData["Amount"] = chalk.green(result.amount + (result.tokenSymbol ? ` ${result.tokenSymbol}` : " ETH"));
|
|
674
|
+
}
|
|
675
|
+
if (result.tokenAddress) {
|
|
676
|
+
boxData["Token"] = formatAddress(result.tokenAddress);
|
|
677
|
+
}
|
|
678
|
+
if (result.txHash) {
|
|
679
|
+
boxData["Transaction"] = chalk.blue(result.txHash);
|
|
680
|
+
}
|
|
681
|
+
if (result.gasUsed) {
|
|
682
|
+
boxData["Gas Used"] = result.gasUsed.toString();
|
|
683
|
+
}
|
|
684
|
+
if (result.blockNumber) {
|
|
685
|
+
boxData["Block"] = result.blockNumber.toString();
|
|
686
|
+
}
|
|
687
|
+
if (result.error) {
|
|
688
|
+
boxData["Error"] = chalk.red(result.error);
|
|
689
|
+
}
|
|
690
|
+
printBox("Send Result", boxData);
|
|
691
|
+
console.log();
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Display batch send results
|
|
695
|
+
*/
|
|
696
|
+
export function displayBatchResults(results) {
|
|
697
|
+
const successful = results.filter((r) => r.success);
|
|
698
|
+
const failed = results.filter((r) => !r.success);
|
|
699
|
+
console.log();
|
|
700
|
+
printSuccess(`Batch send completed: ${successful.length} successful, ${failed.length} failed`);
|
|
701
|
+
console.log(chalk.magenta.bold("š¦ Batch Send Summary"));
|
|
702
|
+
console.log();
|
|
703
|
+
const table = new Table({
|
|
704
|
+
head: [
|
|
705
|
+
chalk.blue.bold("Status"),
|
|
706
|
+
chalk.cyan.bold("Type"),
|
|
707
|
+
chalk.green.bold("To"),
|
|
708
|
+
chalk.yellow.bold("Amount"),
|
|
709
|
+
chalk.magenta.bold("Tx Hash"),
|
|
710
|
+
],
|
|
711
|
+
colAligns: ["left", "left", "left", "right", "left"],
|
|
712
|
+
});
|
|
713
|
+
for (const result of results) {
|
|
714
|
+
const status = result.success ? chalk.green("ā
") : chalk.red("ā");
|
|
715
|
+
const type = result.type.toUpperCase();
|
|
716
|
+
const to = formatAddress(result.to);
|
|
717
|
+
const amount = result.amount
|
|
718
|
+
? result.amount + (result.tokenSymbol ? ` ${result.tokenSymbol}` : " ETH")
|
|
719
|
+
: "-";
|
|
720
|
+
const txHash = result.txHash
|
|
721
|
+
? chalk.blue(result.txHash.substring(0, 20) + "...")
|
|
722
|
+
: chalk.red(result.error || "-");
|
|
723
|
+
table.push([status, type, to, amount, txHash]);
|
|
724
|
+
}
|
|
725
|
+
console.log(table.toString());
|
|
726
|
+
console.log();
|
|
727
|
+
console.log(chalk.green(`ā
Successful: ${successful.length}`));
|
|
728
|
+
if (failed.length > 0) {
|
|
729
|
+
console.log(chalk.red(`ā Failed: ${failed.length}`));
|
|
730
|
+
}
|
|
731
|
+
console.log();
|
|
732
|
+
}
|
|
733
|
+
//# sourceMappingURL=send.js.map
|