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,744 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { printBox, } from "../utils/formatting.js";
|
|
3
|
+
import { printSuccess } from "../interactive/art.js";
|
|
4
|
+
import { createPublicClient, createWalletClient, parseAbi, formatUnits, parseUnits, decodeEventLog, } from "viem";
|
|
5
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
6
|
+
import { getUSDCAddress, getNetworkConfig } from "../utils/constants.js";
|
|
7
|
+
import { resolveTokenId } from "../utils/token-resolver.js";
|
|
8
|
+
import { getTokenInfo } from "./info.js";
|
|
9
|
+
import { createRpcTransport } from "../utils/rpc-transport.js";
|
|
10
|
+
/**
|
|
11
|
+
* Resolve a token identifier (symbol, name, or address) to a contract address
|
|
12
|
+
*/
|
|
13
|
+
async function resolveToAddress(identifier, client, network, silent) {
|
|
14
|
+
const lower = identifier.toLowerCase().trim();
|
|
15
|
+
// Handle USDC specially
|
|
16
|
+
if (lower === "usdc") {
|
|
17
|
+
return getUSDCAddress(network);
|
|
18
|
+
}
|
|
19
|
+
// Check if it's already an address
|
|
20
|
+
const addressRegex = /^0x[0-9a-f]{40}$/i;
|
|
21
|
+
if (addressRegex.test(identifier)) {
|
|
22
|
+
return identifier;
|
|
23
|
+
}
|
|
24
|
+
// Resolve via token_info
|
|
25
|
+
const tokenId = await resolveTokenId(identifier, client, silent);
|
|
26
|
+
// If resolveTokenId returned an address (external token or CAT), use it
|
|
27
|
+
if (addressRegex.test(tokenId)) {
|
|
28
|
+
return tokenId;
|
|
29
|
+
}
|
|
30
|
+
// Otherwise it's a UUID - get the token info to find the address
|
|
31
|
+
const tokenInfo = await getTokenInfo(client, tokenId, undefined, silent);
|
|
32
|
+
if (!tokenInfo.address) {
|
|
33
|
+
throw new Error(`Token ${identifier} has no contract address`);
|
|
34
|
+
}
|
|
35
|
+
return tokenInfo.address;
|
|
36
|
+
}
|
|
37
|
+
const ERC20_ABI = parseAbi([
|
|
38
|
+
"function approve(address spender, uint256 amount) returns (bool)",
|
|
39
|
+
"function allowance(address owner, address spender) view returns (uint256)",
|
|
40
|
+
"function balanceOf(address owner) view returns (uint256)",
|
|
41
|
+
"function decimals() view returns (uint8)",
|
|
42
|
+
"function symbol() view returns (string)",
|
|
43
|
+
"function name() view returns (string)",
|
|
44
|
+
]);
|
|
45
|
+
const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
46
|
+
// Routers that use Permit2 for token transfers
|
|
47
|
+
const PERMIT2_ROUTERS = new Set([
|
|
48
|
+
"0x6ff5693b99212da76ad316178a184ab56d299b43", // Uniswap Universal Router (Base)
|
|
49
|
+
"0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", // Uniswap Universal Router (Mainnet)
|
|
50
|
+
"0x198ef79f1f515f02dfe9e3115ed9fc07183f02fc", // Uniswap Universal Router (Base Sepolia)
|
|
51
|
+
].map((a) => a.toLowerCase()));
|
|
52
|
+
// Routers that require direct ERC20 approve (NOT Permit2)
|
|
53
|
+
const DIRECT_APPROVE_ROUTERS = new Set([
|
|
54
|
+
"0x6352a56caadc4f1e25cd6c75970fa768a3304e64", // OpenOcean Exchange (Base)
|
|
55
|
+
"0xdef1c0ded9bec7f1a1670819833240f027b25eff", // 0x Exchange Proxy
|
|
56
|
+
].map((a) => a.toLowerCase()));
|
|
57
|
+
/**
|
|
58
|
+
* Check if a router uses Permit2 or requires direct ERC20 approval
|
|
59
|
+
*/
|
|
60
|
+
function routerUsesPermit2(routerAddress) {
|
|
61
|
+
const normalized = routerAddress.toLowerCase();
|
|
62
|
+
// Explicitly known Permit2 routers
|
|
63
|
+
if (PERMIT2_ROUTERS.has(normalized)) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
// Explicitly known direct-approve routers
|
|
67
|
+
if (DIRECT_APPROVE_ROUTERS.has(normalized)) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
// Default: assume direct approval for unknown routers (safer)
|
|
71
|
+
// Most DEX aggregators use direct approval
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
const PERMIT2_ABI = parseAbi([
|
|
75
|
+
"function allowance(address owner, address token, address spender) view returns (uint160 amount, uint48 expiration, uint48 nonce)",
|
|
76
|
+
"function approve(address token, address spender, uint160 amount, uint48 expiration)",
|
|
77
|
+
]);
|
|
78
|
+
const ERC20_TRANSFER_EVENT_ABI = parseAbi([
|
|
79
|
+
"event Transfer(address indexed from, address indexed to, uint256 value)",
|
|
80
|
+
]);
|
|
81
|
+
function getErc20NetDeltaFromReceipt(args) {
|
|
82
|
+
const tokenLc = args.token.toLowerCase();
|
|
83
|
+
const userLc = args.user.toLowerCase();
|
|
84
|
+
let delta = 0n;
|
|
85
|
+
const logs = Array.isArray(args.receipt?.logs) ? args.receipt.logs : [];
|
|
86
|
+
for (const log of logs) {
|
|
87
|
+
try {
|
|
88
|
+
const addr = String(log?.address ?? "").toLowerCase();
|
|
89
|
+
if (addr !== tokenLc)
|
|
90
|
+
continue;
|
|
91
|
+
const decoded = decodeEventLog({
|
|
92
|
+
abi: ERC20_TRANSFER_EVENT_ABI,
|
|
93
|
+
data: log.data,
|
|
94
|
+
topics: log.topics,
|
|
95
|
+
});
|
|
96
|
+
if (decoded.eventName !== "Transfer")
|
|
97
|
+
continue;
|
|
98
|
+
const fromLc = decoded.args.from.toLowerCase();
|
|
99
|
+
const toLc = decoded.args.to.toLowerCase();
|
|
100
|
+
const value = BigInt(decoded.args.value);
|
|
101
|
+
if (fromLc === userLc)
|
|
102
|
+
delta -= value;
|
|
103
|
+
if (toLc === userLc)
|
|
104
|
+
delta += value;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// ignore
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return delta;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Ensure direct ERC20 approval to a router (for non-Permit2 routers like OpenOcean)
|
|
114
|
+
*/
|
|
115
|
+
async function ensureDirectApproval(args) {
|
|
116
|
+
const currentAllowance = await args.publicClient.readContract({
|
|
117
|
+
address: args.tokenIn,
|
|
118
|
+
abi: ERC20_ABI,
|
|
119
|
+
functionName: "allowance",
|
|
120
|
+
args: [args.userAddress, args.router],
|
|
121
|
+
});
|
|
122
|
+
if (currentAllowance >= args.amount) {
|
|
123
|
+
if (!args.silent) {
|
|
124
|
+
console.log(chalk.dim(` ✓ Already approved to router`));
|
|
125
|
+
}
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (!args.silent) {
|
|
129
|
+
console.log(chalk.cyan(` Approving token to router...`));
|
|
130
|
+
}
|
|
131
|
+
const maxUint256 = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
|
132
|
+
const approveTx = await args.walletClient.writeContract({
|
|
133
|
+
address: args.tokenIn,
|
|
134
|
+
abi: ERC20_ABI,
|
|
135
|
+
functionName: "approve",
|
|
136
|
+
args: [args.router, maxUint256],
|
|
137
|
+
});
|
|
138
|
+
await args.publicClient.waitForTransactionReceipt({ hash: approveTx });
|
|
139
|
+
}
|
|
140
|
+
async function ensurePermit2Allowance(args) {
|
|
141
|
+
const toBigInt = (v) => typeof v === "bigint" ? v : BigInt(v);
|
|
142
|
+
const normalizeAllowance = (raw) => {
|
|
143
|
+
if (Array.isArray(raw)) {
|
|
144
|
+
const [a, e, n] = raw;
|
|
145
|
+
return {
|
|
146
|
+
amount: toBigInt(a),
|
|
147
|
+
expiration: toBigInt(e),
|
|
148
|
+
nonce: toBigInt(n),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
amount: toBigInt(raw.amount),
|
|
153
|
+
expiration: toBigInt(raw.expiration),
|
|
154
|
+
nonce: toBigInt(raw.nonce),
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
const allowance = normalizeAllowance(await args.publicClient.readContract({
|
|
158
|
+
address: PERMIT2_ADDRESS,
|
|
159
|
+
abi: PERMIT2_ABI,
|
|
160
|
+
functionName: "allowance",
|
|
161
|
+
args: [args.userAddress, args.tokenIn, args.universalRouter],
|
|
162
|
+
}));
|
|
163
|
+
const currentAmount = allowance.amount;
|
|
164
|
+
const expiration = allowance.expiration;
|
|
165
|
+
const now = BigInt(Math.floor(Date.now() / 1000));
|
|
166
|
+
const isExpired = expiration === 0n || expiration <= now;
|
|
167
|
+
if (currentAmount >= args.amount && !isExpired) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (!args.silent) {
|
|
171
|
+
console.log(chalk.cyan(` Setting Permit2 allowance for Universal Router...`));
|
|
172
|
+
}
|
|
173
|
+
const MAX_UINT160 = (1n << 160n) - 1n;
|
|
174
|
+
const MAX_UINT48 = 281474976710655n;
|
|
175
|
+
const approveTx = await args.walletClient.writeContract({
|
|
176
|
+
address: PERMIT2_ADDRESS,
|
|
177
|
+
abi: PERMIT2_ABI,
|
|
178
|
+
functionName: "approve",
|
|
179
|
+
args: [args.tokenIn, args.universalRouter, MAX_UINT160, MAX_UINT48],
|
|
180
|
+
});
|
|
181
|
+
await args.publicClient.waitForTransactionReceipt({ hash: approveTx });
|
|
182
|
+
}
|
|
183
|
+
async function executeSwap(swapData, tokenIn, tokenOut, amountIn, privateKey, chain, silent, httpcatClient) {
|
|
184
|
+
const account = privateKeyToAccount(privateKey);
|
|
185
|
+
const userAddress = account.address;
|
|
186
|
+
if (process.env.DEBUG) {
|
|
187
|
+
console.log(`[executeSwap] Starting swap execution`);
|
|
188
|
+
console.log(`[executeSwap] User: ${userAddress}`);
|
|
189
|
+
console.log(`[executeSwap] TokenIn: ${tokenIn}`);
|
|
190
|
+
console.log(`[executeSwap] TokenOut: ${tokenOut}`);
|
|
191
|
+
console.log(`[executeSwap] AmountIn: ${amountIn.toString()}`);
|
|
192
|
+
console.log(`[executeSwap] Provider: ${swapData.provider}`);
|
|
193
|
+
console.log(`[executeSwap] Route: ${swapData.route}`);
|
|
194
|
+
console.log(`[executeSwap] Expected AmountOut: ${swapData.amountOut}`);
|
|
195
|
+
console.log(`[executeSwap] Min AmountOut: ${swapData.minAmountOut}`);
|
|
196
|
+
console.log(`[executeSwap] Swap target: ${swapData.transaction.to}`);
|
|
197
|
+
console.log(`[executeSwap] Swap value: ${swapData.transaction.value}`);
|
|
198
|
+
console.log(`[executeSwap] Swap data length: ${swapData.transaction.data.length} chars`);
|
|
199
|
+
}
|
|
200
|
+
const publicClient = createPublicClient({
|
|
201
|
+
chain,
|
|
202
|
+
transport: createRpcTransport({
|
|
203
|
+
chainId: chain.id,
|
|
204
|
+
httpcatClient,
|
|
205
|
+
}),
|
|
206
|
+
});
|
|
207
|
+
const walletClient = createWalletClient({
|
|
208
|
+
account,
|
|
209
|
+
chain,
|
|
210
|
+
transport: createRpcTransport({
|
|
211
|
+
chainId: chain.id,
|
|
212
|
+
httpcatClient,
|
|
213
|
+
}),
|
|
214
|
+
});
|
|
215
|
+
const routerAddress = swapData.transaction.to;
|
|
216
|
+
const usesPermit2 = routerUsesPermit2(routerAddress);
|
|
217
|
+
if (usesPermit2) {
|
|
218
|
+
// Permit2 flow: approve token to Permit2, then set Permit2 allowance to router
|
|
219
|
+
if (!silent) {
|
|
220
|
+
console.log(chalk.dim(` Router ${routerAddress.slice(0, 10)}... uses Permit2`));
|
|
221
|
+
}
|
|
222
|
+
const currentAllowance = await publicClient.readContract({
|
|
223
|
+
address: tokenIn,
|
|
224
|
+
abi: ERC20_ABI,
|
|
225
|
+
functionName: "allowance",
|
|
226
|
+
args: [userAddress, PERMIT2_ADDRESS],
|
|
227
|
+
});
|
|
228
|
+
if (currentAllowance < amountIn) {
|
|
229
|
+
if (!silent) {
|
|
230
|
+
console.log(chalk.cyan(` Approving ${tokenIn} to Permit2...`));
|
|
231
|
+
}
|
|
232
|
+
const maxUint256 = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
|
233
|
+
const approveTx = await walletClient.writeContract({
|
|
234
|
+
address: tokenIn,
|
|
235
|
+
abi: ERC20_ABI,
|
|
236
|
+
functionName: "approve",
|
|
237
|
+
args: [PERMIT2_ADDRESS, maxUint256],
|
|
238
|
+
});
|
|
239
|
+
await publicClient.waitForTransactionReceipt({ hash: approveTx });
|
|
240
|
+
}
|
|
241
|
+
else if (!silent) {
|
|
242
|
+
console.log(chalk.dim(` ✓ Already approved ${tokenIn} to Permit2`));
|
|
243
|
+
}
|
|
244
|
+
// Ensure Permit2 allowance to Universal Router
|
|
245
|
+
await ensurePermit2Allowance({
|
|
246
|
+
tokenIn,
|
|
247
|
+
amount: amountIn,
|
|
248
|
+
universalRouter: routerAddress,
|
|
249
|
+
userAddress,
|
|
250
|
+
publicClient,
|
|
251
|
+
walletClient,
|
|
252
|
+
silent,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
// Direct approval flow: approve token directly to router (OpenOcean, 0x, etc.)
|
|
257
|
+
if (!silent) {
|
|
258
|
+
console.log(chalk.dim(` Router ${routerAddress.slice(0, 10)}... uses direct approval`));
|
|
259
|
+
}
|
|
260
|
+
await ensureDirectApproval({
|
|
261
|
+
tokenIn,
|
|
262
|
+
amount: amountIn,
|
|
263
|
+
router: routerAddress,
|
|
264
|
+
userAddress,
|
|
265
|
+
publicClient,
|
|
266
|
+
walletClient,
|
|
267
|
+
silent,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
if (!silent) {
|
|
271
|
+
console.log(chalk.cyan(` Executing swap via ${swapData.provider}...`));
|
|
272
|
+
}
|
|
273
|
+
// Get balances before
|
|
274
|
+
const tokenInBefore = (await publicClient.readContract({
|
|
275
|
+
address: tokenIn,
|
|
276
|
+
abi: ERC20_ABI,
|
|
277
|
+
functionName: "balanceOf",
|
|
278
|
+
args: [userAddress],
|
|
279
|
+
}));
|
|
280
|
+
const tokenOutBefore = (await publicClient.readContract({
|
|
281
|
+
address: tokenOut,
|
|
282
|
+
abi: ERC20_ABI,
|
|
283
|
+
functionName: "balanceOf",
|
|
284
|
+
args: [userAddress],
|
|
285
|
+
}));
|
|
286
|
+
if (process.env.DEBUG) {
|
|
287
|
+
console.log(`[executeSwap] TokenIn balance before: ${tokenInBefore.toString()}`);
|
|
288
|
+
console.log(`[executeSwap] TokenOut balance before: ${tokenOutBefore.toString()}`);
|
|
289
|
+
}
|
|
290
|
+
// Execute swap
|
|
291
|
+
let swapTx;
|
|
292
|
+
try {
|
|
293
|
+
if (process.env.DEBUG) {
|
|
294
|
+
console.log(`[executeSwap] Sending swap transaction...`);
|
|
295
|
+
}
|
|
296
|
+
swapTx = await walletClient.sendTransaction({
|
|
297
|
+
to: swapData.transaction.to,
|
|
298
|
+
data: swapData.transaction.data,
|
|
299
|
+
value: BigInt(swapData.transaction.value),
|
|
300
|
+
});
|
|
301
|
+
if (process.env.DEBUG) {
|
|
302
|
+
console.log(`[executeSwap] Swap tx submitted: ${swapTx}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
const msg = String(error?.message ?? "");
|
|
307
|
+
if (process.env.DEBUG) {
|
|
308
|
+
console.log(`[executeSwap] Transaction estimation error: ${msg}`);
|
|
309
|
+
}
|
|
310
|
+
// Don't retry with manual gas for known failure cases
|
|
311
|
+
const isKnownFailure = msg.toLowerCase().includes("safeerc20") ||
|
|
312
|
+
msg.toLowerCase().includes("low-level call failed") ||
|
|
313
|
+
msg.toLowerCase().includes("insufficient liquidity") ||
|
|
314
|
+
msg.toLowerCase().includes("transfer failed");
|
|
315
|
+
if (msg.toLowerCase().includes("execution reverted") && !isKnownFailure) {
|
|
316
|
+
if (!silent) {
|
|
317
|
+
console.log(chalk.yellow(` ⚠️ Estimation reverted; force-broadcasting with manual gas limit...`));
|
|
318
|
+
}
|
|
319
|
+
if (process.env.DEBUG) {
|
|
320
|
+
console.log(`[executeSwap] Retrying with manual gas limit (1,500,000)...`);
|
|
321
|
+
}
|
|
322
|
+
swapTx = await walletClient.sendTransaction({
|
|
323
|
+
to: swapData.transaction.to,
|
|
324
|
+
data: swapData.transaction.data,
|
|
325
|
+
value: BigInt(swapData.transaction.value),
|
|
326
|
+
gas: 1500000n,
|
|
327
|
+
});
|
|
328
|
+
if (process.env.DEBUG) {
|
|
329
|
+
console.log(`[executeSwap] Swap tx submitted (manual gas): ${swapTx}`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
if (process.env.DEBUG) {
|
|
334
|
+
console.log(`[executeSwap] ${isKnownFailure
|
|
335
|
+
? "Known failure - not retrying"
|
|
336
|
+
: "Fatal error, throwing"}: ${msg}`);
|
|
337
|
+
}
|
|
338
|
+
throw error;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (process.env.DEBUG) {
|
|
342
|
+
console.log(`[executeSwap] Waiting for transaction receipt...`);
|
|
343
|
+
}
|
|
344
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
345
|
+
hash: swapTx,
|
|
346
|
+
});
|
|
347
|
+
if (process.env.DEBUG) {
|
|
348
|
+
console.log(`[executeSwap] Receipt received`);
|
|
349
|
+
console.log(`[executeSwap] Receipt status: ${receipt.status}`);
|
|
350
|
+
console.log(`[executeSwap] Gas used: ${receipt.gasUsed?.toString()}`);
|
|
351
|
+
console.log(`[executeSwap] Block number: ${receipt.blockNumber?.toString()}`);
|
|
352
|
+
console.log(`[executeSwap] Number of logs: ${receipt.logs?.length || 0}`);
|
|
353
|
+
}
|
|
354
|
+
// Check if transaction reverted
|
|
355
|
+
if (receipt.status === "reverted") {
|
|
356
|
+
throw new Error(`Swap transaction reverted. This usually means:\n` +
|
|
357
|
+
` - Insufficient liquidity for this trade\n` +
|
|
358
|
+
` - Slippage tolerance too low\n` +
|
|
359
|
+
` - Price moved unfavorably\n` +
|
|
360
|
+
` - Token approval issue\n\n` +
|
|
361
|
+
`Transaction: ${swapTx}\n` +
|
|
362
|
+
`Try increasing slippage with --slippage (e.g., --slippage 100 for 1%)`);
|
|
363
|
+
}
|
|
364
|
+
// Calculate actual deltas
|
|
365
|
+
if (process.env.DEBUG) {
|
|
366
|
+
console.log(`[executeSwap] Calculating token deltas from receipt logs...`);
|
|
367
|
+
}
|
|
368
|
+
let tokenInDelta = getErc20NetDeltaFromReceipt({
|
|
369
|
+
receipt,
|
|
370
|
+
token: tokenIn,
|
|
371
|
+
user: userAddress,
|
|
372
|
+
});
|
|
373
|
+
let tokenOutDelta = getErc20NetDeltaFromReceipt({
|
|
374
|
+
receipt,
|
|
375
|
+
token: tokenOut,
|
|
376
|
+
user: userAddress,
|
|
377
|
+
});
|
|
378
|
+
if (process.env.DEBUG) {
|
|
379
|
+
console.log(`[executeSwap] TokenIn delta (from logs): ${tokenInDelta.toString()}`);
|
|
380
|
+
console.log(`[executeSwap] TokenOut delta (from logs): ${tokenOutDelta.toString()}`);
|
|
381
|
+
}
|
|
382
|
+
// Fallback to balance comparison
|
|
383
|
+
if (tokenInDelta === 0n || tokenOutDelta === 0n) {
|
|
384
|
+
if (process.env.DEBUG) {
|
|
385
|
+
console.log(`[executeSwap] One or both deltas are 0, checking actual balances...`);
|
|
386
|
+
}
|
|
387
|
+
const tokenInAfter = (await publicClient.readContract({
|
|
388
|
+
address: tokenIn,
|
|
389
|
+
abi: ERC20_ABI,
|
|
390
|
+
functionName: "balanceOf",
|
|
391
|
+
args: [userAddress],
|
|
392
|
+
}));
|
|
393
|
+
const tokenOutAfter = (await publicClient.readContract({
|
|
394
|
+
address: tokenOut,
|
|
395
|
+
abi: ERC20_ABI,
|
|
396
|
+
functionName: "balanceOf",
|
|
397
|
+
args: [userAddress],
|
|
398
|
+
}));
|
|
399
|
+
if (process.env.DEBUG) {
|
|
400
|
+
console.log(`[executeSwap] TokenIn balance after: ${tokenInAfter.toString()}`);
|
|
401
|
+
console.log(`[executeSwap] TokenOut balance after: ${tokenOutAfter.toString()}`);
|
|
402
|
+
}
|
|
403
|
+
if (tokenInDelta === 0n)
|
|
404
|
+
tokenInDelta = tokenInAfter - tokenInBefore;
|
|
405
|
+
if (tokenOutDelta === 0n)
|
|
406
|
+
tokenOutDelta = tokenOutAfter - tokenOutBefore;
|
|
407
|
+
if (process.env.DEBUG) {
|
|
408
|
+
console.log(`[executeSwap] TokenIn delta (from balance): ${tokenInDelta.toString()}`);
|
|
409
|
+
console.log(`[executeSwap] TokenOut delta (from balance): ${tokenOutDelta.toString()}`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
// Final safety check: if both deltas are 0, the swap failed
|
|
413
|
+
if (tokenInDelta === 0n && tokenOutDelta === 0n) {
|
|
414
|
+
if (process.env.DEBUG) {
|
|
415
|
+
console.log(`[executeSwap] ❌ Both deltas are 0 - swap failed!`);
|
|
416
|
+
}
|
|
417
|
+
throw new Error(`Swap failed: No tokens were transferred.\n` +
|
|
418
|
+
`Transaction: ${swapTx}\n` +
|
|
419
|
+
`The transaction was mined but no swap occurred. This usually means:\n` +
|
|
420
|
+
` - Insufficient liquidity\n` +
|
|
421
|
+
` - Slippage tolerance too low\n` +
|
|
422
|
+
` - Invalid route\n\n` +
|
|
423
|
+
`Try increasing slippage with --slippage (e.g., --slippage 100 for 1%)`);
|
|
424
|
+
}
|
|
425
|
+
if (!silent) {
|
|
426
|
+
console.log(chalk.green(` ✅ Swap complete!`));
|
|
427
|
+
let tokenInDecimals = 18;
|
|
428
|
+
let tokenOutDecimals = 18;
|
|
429
|
+
try {
|
|
430
|
+
tokenInDecimals = Number(await publicClient.readContract({
|
|
431
|
+
address: tokenIn,
|
|
432
|
+
abi: ERC20_ABI,
|
|
433
|
+
functionName: "decimals",
|
|
434
|
+
}));
|
|
435
|
+
tokenOutDecimals = Number(await publicClient.readContract({
|
|
436
|
+
address: tokenOut,
|
|
437
|
+
abi: ERC20_ABI,
|
|
438
|
+
functionName: "decimals",
|
|
439
|
+
}));
|
|
440
|
+
}
|
|
441
|
+
catch {
|
|
442
|
+
/* ignore */
|
|
443
|
+
}
|
|
444
|
+
console.log(chalk.dim(` Token in delta: ${formatUnits(tokenInDelta, tokenInDecimals)} (raw=${tokenInDelta.toString()})`));
|
|
445
|
+
console.log(chalk.dim(` Token out delta: ${formatUnits(tokenOutDelta, tokenOutDecimals)} (raw=${tokenOutDelta.toString()})`));
|
|
446
|
+
}
|
|
447
|
+
// Return absolute values (tokenInDelta is negative, tokenOutDelta is positive)
|
|
448
|
+
const result = {
|
|
449
|
+
txHash: swapTx,
|
|
450
|
+
actualAmountIn: tokenInDelta < 0n ? -tokenInDelta : tokenInDelta,
|
|
451
|
+
actualAmountOut: tokenOutDelta < 0n ? -tokenOutDelta : tokenOutDelta,
|
|
452
|
+
};
|
|
453
|
+
if (process.env.DEBUG) {
|
|
454
|
+
console.log(`[executeSwap] ✅ Swap successful!`);
|
|
455
|
+
console.log(`[executeSwap] Final actualAmountIn: ${result.actualAmountIn.toString()}`);
|
|
456
|
+
console.log(`[executeSwap] Final actualAmountOut: ${result.actualAmountOut.toString()}`);
|
|
457
|
+
console.log(`[executeSwap] Tx hash: ${result.txHash}`);
|
|
458
|
+
}
|
|
459
|
+
return result;
|
|
460
|
+
}
|
|
461
|
+
export async function swap(client, tokenInIdentifier, tokenOutIdentifier, amount, slippageBps = 50, silent = false, privateKey) {
|
|
462
|
+
if (!privateKey) {
|
|
463
|
+
throw new Error("Private key required for swaps");
|
|
464
|
+
}
|
|
465
|
+
const account = privateKeyToAccount(privateKey);
|
|
466
|
+
const userAddress = account.address;
|
|
467
|
+
const network = client.getNetwork();
|
|
468
|
+
// Resolve token identifiers to addresses
|
|
469
|
+
if (!silent) {
|
|
470
|
+
console.log(chalk.cyan(`Resolving tokens...`));
|
|
471
|
+
}
|
|
472
|
+
const tokenIn = await resolveToAddress(tokenInIdentifier, client, network, silent);
|
|
473
|
+
const tokenOut = await resolveToAddress(tokenOutIdentifier, client, network, silent);
|
|
474
|
+
// Get network config (chain + USDC address)
|
|
475
|
+
const { chain } = await getNetworkConfig(network);
|
|
476
|
+
// Get token metadata
|
|
477
|
+
const publicClient = createPublicClient({
|
|
478
|
+
chain,
|
|
479
|
+
transport: createRpcTransport({
|
|
480
|
+
chainId: chain.id,
|
|
481
|
+
httpcatClient: client,
|
|
482
|
+
}),
|
|
483
|
+
});
|
|
484
|
+
let tokenInSymbol = "???";
|
|
485
|
+
let tokenOutSymbol = "???";
|
|
486
|
+
let tokenInDecimals = 18;
|
|
487
|
+
let tokenOutDecimals = 18;
|
|
488
|
+
try {
|
|
489
|
+
tokenInSymbol = (await publicClient.readContract({
|
|
490
|
+
address: tokenIn,
|
|
491
|
+
abi: ERC20_ABI,
|
|
492
|
+
functionName: "symbol",
|
|
493
|
+
}));
|
|
494
|
+
tokenInDecimals = Number(await publicClient.readContract({
|
|
495
|
+
address: tokenIn,
|
|
496
|
+
abi: ERC20_ABI,
|
|
497
|
+
functionName: "decimals",
|
|
498
|
+
}));
|
|
499
|
+
}
|
|
500
|
+
catch {
|
|
501
|
+
/* ignore */
|
|
502
|
+
}
|
|
503
|
+
try {
|
|
504
|
+
tokenOutSymbol = (await publicClient.readContract({
|
|
505
|
+
address: tokenOut,
|
|
506
|
+
abi: ERC20_ABI,
|
|
507
|
+
functionName: "symbol",
|
|
508
|
+
}));
|
|
509
|
+
tokenOutDecimals = Number(await publicClient.readContract({
|
|
510
|
+
address: tokenOut,
|
|
511
|
+
abi: ERC20_ABI,
|
|
512
|
+
functionName: "decimals",
|
|
513
|
+
}));
|
|
514
|
+
}
|
|
515
|
+
catch {
|
|
516
|
+
/* ignore */
|
|
517
|
+
}
|
|
518
|
+
// Convert human-readable amount to smallest units
|
|
519
|
+
const amountInSmallestUnits = parseUnits(amount, tokenInDecimals);
|
|
520
|
+
if (!silent) {
|
|
521
|
+
console.log(chalk.cyan(`Swapping ${amount} ${tokenInSymbol} → ${tokenOutSymbol}...`));
|
|
522
|
+
console.log(chalk.dim(` tokenIn: ${tokenIn}`));
|
|
523
|
+
console.log(chalk.dim(` tokenOut: ${tokenOut}`));
|
|
524
|
+
console.log(chalk.dim(` amount (raw): ${amountInSmallestUnits.toString()}`));
|
|
525
|
+
}
|
|
526
|
+
// Call the universal_swap endpoint
|
|
527
|
+
const input = {
|
|
528
|
+
tokenIn,
|
|
529
|
+
tokenOut,
|
|
530
|
+
amount: amountInSmallestUnits.toString(),
|
|
531
|
+
slippageBps,
|
|
532
|
+
recipient: userAddress,
|
|
533
|
+
};
|
|
534
|
+
const { data, payment } = await client.invoke("swaps/universal", input);
|
|
535
|
+
// Execute the swap on-chain
|
|
536
|
+
const swapResult = await executeSwap(data, tokenIn, tokenOut, amountInSmallestUnits, privateKey, chain, silent, client);
|
|
537
|
+
return {
|
|
538
|
+
...data,
|
|
539
|
+
tokenInSymbol,
|
|
540
|
+
tokenOutSymbol,
|
|
541
|
+
tokenInDecimals,
|
|
542
|
+
tokenOutDecimals,
|
|
543
|
+
txHash: swapResult.txHash,
|
|
544
|
+
paymentTxHash: payment?.txHash,
|
|
545
|
+
// Use actual on-chain amounts instead of quote estimates
|
|
546
|
+
amountIn: swapResult.actualAmountIn.toString(),
|
|
547
|
+
amountOut: swapResult.actualAmountOut.toString(),
|
|
548
|
+
minAmountOut: swapResult.actualAmountOut.toString(), // Actual = min since it already executed
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
export function displaySwapResult(result) {
|
|
552
|
+
printSuccess("Swap completed successfully!", "money");
|
|
553
|
+
console.log();
|
|
554
|
+
const tokenInDecimals = result.tokenInDecimals ?? 18;
|
|
555
|
+
const tokenOutDecimals = result.tokenOutDecimals ?? 18;
|
|
556
|
+
console.log(chalk.magenta.bold("🔄 Swap Summary"));
|
|
557
|
+
console.log();
|
|
558
|
+
const amountInStr = result.amountIn
|
|
559
|
+
? formatUnits(BigInt(result.amountIn), tokenInDecimals) +
|
|
560
|
+
` ${result.tokenInSymbol}`
|
|
561
|
+
: "N/A";
|
|
562
|
+
const amountOutStr = formatUnits(BigInt(result.amountOut), tokenOutDecimals) +
|
|
563
|
+
` ${result.tokenOutSymbol}`;
|
|
564
|
+
const minAmountOutStr = formatUnits(BigInt(result.minAmountOut), tokenOutDecimals) +
|
|
565
|
+
` ${result.tokenOutSymbol}`;
|
|
566
|
+
const boxContent = {
|
|
567
|
+
Swap: chalk.blue(result.tokenInSymbol) +
|
|
568
|
+
chalk.cyan(" → ") +
|
|
569
|
+
chalk.yellow.bold(result.tokenOutSymbol),
|
|
570
|
+
Provider: chalk.cyan(result.provider),
|
|
571
|
+
Route: chalk.yellow(result.route),
|
|
572
|
+
"Amount In": result.amountIn ? chalk.cyan(amountInStr) : chalk.dim("N/A"),
|
|
573
|
+
"Amount Out": chalk.green.bold(amountOutStr),
|
|
574
|
+
"Min Amount Out": chalk.green(minAmountOutStr),
|
|
575
|
+
};
|
|
576
|
+
if (result.txHash) {
|
|
577
|
+
boxContent["Transaction"] = chalk.cyan(result.txHash);
|
|
578
|
+
}
|
|
579
|
+
printBox("Swap Details", boxContent);
|
|
580
|
+
// Display price impact
|
|
581
|
+
if (result.priceImpact !== undefined) {
|
|
582
|
+
console.log();
|
|
583
|
+
if (result.priceImpact < 0.5) {
|
|
584
|
+
console.log(chalk.green(` Price Impact: ${result.priceImpact.toFixed(2)}%`));
|
|
585
|
+
}
|
|
586
|
+
else if (result.priceImpact < 1) {
|
|
587
|
+
console.log(chalk.yellow(` Price Impact: ${result.priceImpact.toFixed(2)}%`));
|
|
588
|
+
}
|
|
589
|
+
else if (result.priceImpact < 3) {
|
|
590
|
+
console.log(chalk.red(` ⚠️ HIGH Price Impact: ${result.priceImpact.toFixed(2)}%`));
|
|
591
|
+
}
|
|
592
|
+
else {
|
|
593
|
+
console.log(chalk.red(` 🚨 VERY HIGH Price Impact: ${result.priceImpact.toFixed(2)}%`));
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
// Display gas estimate
|
|
597
|
+
if (result.gasEstimate) {
|
|
598
|
+
console.log(chalk.dim(` Gas Estimate: ${result.gasEstimate} wei`));
|
|
599
|
+
}
|
|
600
|
+
console.log();
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Execute a MEV-protected swap via UniswapX Dutch auction
|
|
604
|
+
*/
|
|
605
|
+
export async function shieldedSwap(client, tokenInIdentifier, tokenOutIdentifier, amount, slippageBps = 50, privateKey, silent = false) {
|
|
606
|
+
const account = privateKeyToAccount(privateKey);
|
|
607
|
+
const userAddress = account.address;
|
|
608
|
+
const network = client.getNetwork();
|
|
609
|
+
// Resolve token identifiers to addresses
|
|
610
|
+
if (!silent) {
|
|
611
|
+
console.log(chalk.cyan(`Resolving tokens...`));
|
|
612
|
+
}
|
|
613
|
+
const tokenIn = await resolveToAddress(tokenInIdentifier, client, network, silent);
|
|
614
|
+
const tokenOut = await resolveToAddress(tokenOutIdentifier, client, network, silent);
|
|
615
|
+
// Get network config (chain + USDC address)
|
|
616
|
+
const { chain } = await getNetworkConfig(network);
|
|
617
|
+
// Get token metadata
|
|
618
|
+
const publicClient = createPublicClient({
|
|
619
|
+
chain,
|
|
620
|
+
transport: createRpcTransport({
|
|
621
|
+
chainId: chain.id,
|
|
622
|
+
httpcatClient: client,
|
|
623
|
+
}),
|
|
624
|
+
});
|
|
625
|
+
let tokenInSymbol = "???";
|
|
626
|
+
let tokenOutSymbol = "???";
|
|
627
|
+
let tokenInDecimals = 18;
|
|
628
|
+
let tokenOutDecimals = 18;
|
|
629
|
+
try {
|
|
630
|
+
tokenInSymbol = (await publicClient.readContract({
|
|
631
|
+
address: tokenIn,
|
|
632
|
+
abi: ERC20_ABI,
|
|
633
|
+
functionName: "symbol",
|
|
634
|
+
}));
|
|
635
|
+
tokenInDecimals = Number(await publicClient.readContract({
|
|
636
|
+
address: tokenIn,
|
|
637
|
+
abi: ERC20_ABI,
|
|
638
|
+
functionName: "decimals",
|
|
639
|
+
}));
|
|
640
|
+
}
|
|
641
|
+
catch {
|
|
642
|
+
/* ignore */
|
|
643
|
+
}
|
|
644
|
+
try {
|
|
645
|
+
tokenOutSymbol = (await publicClient.readContract({
|
|
646
|
+
address: tokenOut,
|
|
647
|
+
abi: ERC20_ABI,
|
|
648
|
+
functionName: "symbol",
|
|
649
|
+
}));
|
|
650
|
+
tokenOutDecimals = Number(await publicClient.readContract({
|
|
651
|
+
address: tokenOut,
|
|
652
|
+
abi: ERC20_ABI,
|
|
653
|
+
functionName: "decimals",
|
|
654
|
+
}));
|
|
655
|
+
}
|
|
656
|
+
catch {
|
|
657
|
+
/* ignore */
|
|
658
|
+
}
|
|
659
|
+
// Convert human-readable amount to smallest units
|
|
660
|
+
const amountInSmallestUnits = parseUnits(amount, tokenInDecimals);
|
|
661
|
+
if (!silent) {
|
|
662
|
+
console.log(chalk.cyan(`Swapping ${amount} ${tokenInSymbol} → ${tokenOutSymbol} (MEV-protected)...`));
|
|
663
|
+
console.log(chalk.dim(` tokenIn: ${tokenIn}`));
|
|
664
|
+
console.log(chalk.dim(` tokenOut: ${tokenOut}`));
|
|
665
|
+
console.log(chalk.dim(` amount (raw): ${amountInSmallestUnits.toString()}`));
|
|
666
|
+
}
|
|
667
|
+
// Call the shielded swap endpoint
|
|
668
|
+
const input = {
|
|
669
|
+
tokenIn,
|
|
670
|
+
tokenOut,
|
|
671
|
+
amount: amountInSmallestUnits.toString(),
|
|
672
|
+
slippageBps,
|
|
673
|
+
recipient: userAddress,
|
|
674
|
+
};
|
|
675
|
+
const { data, payment } = await client.invoke("swaps/shielded", input);
|
|
676
|
+
// Shielded swaps via UniswapX are handled off-chain, so we may not have a transaction
|
|
677
|
+
// The orderHash indicates the order was placed
|
|
678
|
+
return {
|
|
679
|
+
...data,
|
|
680
|
+
tokenInSymbol,
|
|
681
|
+
tokenOutSymbol,
|
|
682
|
+
tokenInDecimals,
|
|
683
|
+
tokenOutDecimals,
|
|
684
|
+
paymentTxHash: payment?.txHash,
|
|
685
|
+
amountIn: amountInSmallestUnits.toString(),
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
export function displayShieldedSwapResult(result) {
|
|
689
|
+
printSuccess("MEV-protected swap order placed!", "money");
|
|
690
|
+
console.log();
|
|
691
|
+
const tokenInDecimals = result.tokenInDecimals ?? 18;
|
|
692
|
+
const tokenOutDecimals = result.tokenOutDecimals ?? 18;
|
|
693
|
+
console.log(chalk.magenta.bold("🛡️ Shielded Swap Summary"));
|
|
694
|
+
console.log();
|
|
695
|
+
const amountInStr = result.amountIn
|
|
696
|
+
? formatUnits(BigInt(result.amountIn), tokenInDecimals) +
|
|
697
|
+
` ${result.tokenInSymbol}`
|
|
698
|
+
: "N/A";
|
|
699
|
+
const amountOutStr = formatUnits(BigInt(result.amountOut), tokenOutDecimals) +
|
|
700
|
+
` ${result.tokenOutSymbol}`;
|
|
701
|
+
const minAmountOutStr = formatUnits(BigInt(result.minAmountOut), tokenOutDecimals) +
|
|
702
|
+
` ${result.tokenOutSymbol}`;
|
|
703
|
+
const boxContent = {
|
|
704
|
+
Swap: chalk.blue(result.tokenInSymbol) +
|
|
705
|
+
chalk.cyan(" → ") +
|
|
706
|
+
chalk.yellow.bold(result.tokenOutSymbol),
|
|
707
|
+
Provider: chalk.cyan(result.provider),
|
|
708
|
+
Route: chalk.yellow(result.route),
|
|
709
|
+
"Amount In": result.amountIn ? chalk.cyan(amountInStr) : chalk.dim("N/A"),
|
|
710
|
+
"Amount Out": chalk.green.bold(amountOutStr),
|
|
711
|
+
"Min Amount Out": chalk.green(minAmountOutStr),
|
|
712
|
+
};
|
|
713
|
+
if (result.orderHash) {
|
|
714
|
+
boxContent["Order Hash"] = chalk.cyan(result.orderHash);
|
|
715
|
+
}
|
|
716
|
+
if (result.txHash) {
|
|
717
|
+
boxContent["Transaction"] = chalk.cyan(result.txHash);
|
|
718
|
+
}
|
|
719
|
+
printBox("Shielded Swap Details", boxContent);
|
|
720
|
+
// Display price impact
|
|
721
|
+
if (result.priceImpact !== undefined) {
|
|
722
|
+
console.log();
|
|
723
|
+
if (result.priceImpact < 0.5) {
|
|
724
|
+
console.log(chalk.green(` Price Impact: ${result.priceImpact.toFixed(2)}%`));
|
|
725
|
+
}
|
|
726
|
+
else if (result.priceImpact < 1) {
|
|
727
|
+
console.log(chalk.yellow(` Price Impact: ${result.priceImpact.toFixed(2)}%`));
|
|
728
|
+
}
|
|
729
|
+
else if (result.priceImpact < 3) {
|
|
730
|
+
console.log(chalk.red(` ⚠️ HIGH Price Impact: ${result.priceImpact.toFixed(2)}%`));
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
console.log(chalk.red(` 🚨 VERY HIGH Price Impact: ${result.priceImpact.toFixed(2)}%`));
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
// Display gas estimate
|
|
737
|
+
if (result.gasEstimate) {
|
|
738
|
+
console.log(chalk.dim(` Gas Estimate: ${result.gasEstimate} wei`));
|
|
739
|
+
}
|
|
740
|
+
console.log();
|
|
741
|
+
console.log(chalk.cyan("💡 This swap is MEV-protected via UniswapX Dutch auction. The order will be filled by a filler."));
|
|
742
|
+
console.log();
|
|
743
|
+
}
|
|
744
|
+
//# sourceMappingURL=swap.js.map
|