httpcat-cli 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (322) hide show
  1. package/.github/workflows/ci.yml +3 -0
  2. package/.github/workflows/rc-publish.yml +6 -0
  3. package/.github/workflows/release.yml +102 -0
  4. package/.github/workflows/sync-version.yml +31 -2
  5. package/README.md +1408 -109
  6. package/additions.txt +3 -0
  7. package/bun.lock +260 -25
  8. package/dist/agent/autonomous-trader.d.ts.map +1 -0
  9. package/dist/agent/autonomous-trader.js +362 -0
  10. package/dist/agent/autonomous-trader.js.map +1 -0
  11. package/dist/agent/ax-agent.d.ts.map +1 -1
  12. package/dist/agent/ax-agent.js +356 -18
  13. package/dist/agent/ax-agent.js.map +1 -1
  14. package/dist/agent/event-client.d.ts.map +1 -0
  15. package/dist/agent/event-client.js +82 -0
  16. package/dist/agent/event-client.js.map +1 -0
  17. package/dist/agent/log-stream.d.ts.map +1 -0
  18. package/dist/agent/log-stream.js +95 -0
  19. package/dist/agent/log-stream.js.map +1 -0
  20. package/dist/agent/memory/conversation-session.d.ts.map +1 -0
  21. package/dist/agent/memory/conversation-session.js +232 -0
  22. package/dist/agent/memory/conversation-session.js.map +1 -0
  23. package/dist/agent/memory/conversation-store.d.ts.map +1 -0
  24. package/dist/agent/memory/conversation-store.js +214 -0
  25. package/dist/agent/memory/conversation-store.js.map +1 -0
  26. package/dist/agent/memory/database-schema.d.ts.map +1 -0
  27. package/dist/agent/memory/database-schema.js +355 -0
  28. package/dist/agent/memory/database-schema.js.map +1 -0
  29. package/dist/agent/memory/decision-tracker.d.ts.map +1 -0
  30. package/dist/agent/memory/decision-tracker.js +274 -0
  31. package/dist/agent/memory/decision-tracker.js.map +1 -0
  32. package/dist/agent/memory/memory-manager.d.ts.map +1 -0
  33. package/dist/agent/memory/memory-manager.js +187 -0
  34. package/dist/agent/memory/memory-manager.js.map +1 -0
  35. package/dist/agent/memory/types.d.ts.map +1 -0
  36. package/dist/agent/memory/types.js +5 -0
  37. package/dist/agent/memory/types.js.map +1 -0
  38. package/dist/agent/message-formatter.d.ts.map +1 -0
  39. package/dist/agent/message-formatter.js +76 -0
  40. package/dist/agent/message-formatter.js.map +1 -0
  41. package/dist/agent/position-db.d.ts.map +1 -0
  42. package/dist/agent/position-db.js +154 -0
  43. package/dist/agent/position-db.js.map +1 -0
  44. package/dist/agent/simple-chat-ui-static.d.ts.map +1 -0
  45. package/dist/agent/simple-chat-ui-static.js +129 -0
  46. package/dist/agent/simple-chat-ui-static.js.map +1 -0
  47. package/dist/agent/simple-chat-ui.d.ts.map +1 -0
  48. package/dist/agent/simple-chat-ui.js +90 -0
  49. package/dist/agent/simple-chat-ui.js.map +1 -0
  50. package/dist/agent/tools.d.ts.map +1 -1
  51. package/dist/agent/tools.js +297 -4
  52. package/dist/agent/tools.js.map +1 -1
  53. package/dist/agent/ui.d.ts.map +1 -0
  54. package/dist/agent/ui.js +84 -0
  55. package/dist/agent/ui.js.map +1 -0
  56. package/dist/agent/unified-runtime.d.ts.map +1 -0
  57. package/dist/agent/unified-runtime.js +397 -0
  58. package/dist/agent/unified-runtime.js.map +1 -0
  59. package/dist/client.d.ts.map +1 -1
  60. package/dist/client.js +272 -21
  61. package/dist/client.js.map +1 -1
  62. package/dist/commands/account.d.ts.map +1 -1
  63. package/dist/commands/account.js +187 -33
  64. package/dist/commands/account.js.map +1 -1
  65. package/dist/commands/agent.d.ts.map +1 -0
  66. package/dist/commands/agent.js +125 -0
  67. package/dist/commands/agent.js.map +1 -0
  68. package/dist/commands/approve.d.ts.map +1 -0
  69. package/dist/commands/approve.js +505 -0
  70. package/dist/commands/approve.js.map +1 -0
  71. package/dist/commands/automation.d.ts.map +1 -0
  72. package/dist/commands/automation.js +346 -0
  73. package/dist/commands/automation.js.map +1 -0
  74. package/dist/commands/balances.d.ts.map +1 -1
  75. package/dist/commands/balances.js +226 -73
  76. package/dist/commands/balances.js.map +1 -1
  77. package/dist/commands/buy.d.ts.map +1 -1
  78. package/dist/commands/buy.js +149 -146
  79. package/dist/commands/buy.js.map +1 -1
  80. package/dist/commands/call.d.ts.map +1 -0
  81. package/dist/commands/call.js +51 -0
  82. package/dist/commands/call.js.map +1 -0
  83. package/dist/commands/cex.d.ts.map +1 -0
  84. package/dist/commands/cex.js +958 -0
  85. package/dist/commands/cex.js.map +1 -0
  86. package/dist/commands/chat.d.ts.map +1 -1
  87. package/dist/commands/chat.js +169 -411
  88. package/dist/commands/chat.js.map +1 -1
  89. package/dist/commands/claim.d.ts.map +1 -1
  90. package/dist/commands/claim.js +313 -29
  91. package/dist/commands/claim.js.map +1 -1
  92. package/dist/commands/create.d.ts.map +1 -1
  93. package/dist/commands/create.js +151 -43
  94. package/dist/commands/create.js.map +1 -1
  95. package/dist/commands/gasless-swap.d.ts.map +1 -0
  96. package/dist/commands/gasless-swap.js +232 -0
  97. package/dist/commands/gasless-swap.js.map +1 -0
  98. package/dist/commands/health.d.ts.map +1 -1
  99. package/dist/commands/health.js +63 -7
  100. package/dist/commands/health.js.map +1 -1
  101. package/dist/commands/info.d.ts.map +1 -1
  102. package/dist/commands/info.js +131 -47
  103. package/dist/commands/info.js.map +1 -1
  104. package/dist/commands/launchpad.d.ts.map +1 -0
  105. package/dist/commands/launchpad.js +708 -0
  106. package/dist/commands/launchpad.js.map +1 -0
  107. package/dist/commands/list.d.ts.map +1 -1
  108. package/dist/commands/list.js +57 -23
  109. package/dist/commands/list.js.map +1 -1
  110. package/dist/commands/market.d.ts.map +1 -0
  111. package/dist/commands/market.js +960 -0
  112. package/dist/commands/market.js.map +1 -0
  113. package/dist/commands/mcp-install.d.ts.map +1 -0
  114. package/dist/commands/mcp-install.js +387 -0
  115. package/dist/commands/mcp-install.js.map +1 -0
  116. package/dist/commands/opps.d.ts.map +1 -0
  117. package/dist/commands/opps.js +409 -0
  118. package/dist/commands/opps.js.map +1 -0
  119. package/dist/commands/perps.d.ts.map +1 -0
  120. package/dist/commands/perps.js +248 -0
  121. package/dist/commands/perps.js.map +1 -0
  122. package/dist/commands/portfolio.d.ts.map +1 -0
  123. package/dist/commands/portfolio.js +679 -0
  124. package/dist/commands/portfolio.js.map +1 -0
  125. package/dist/commands/positions.d.ts.map +1 -1
  126. package/dist/commands/positions.js +76 -47
  127. package/dist/commands/positions.js.map +1 -1
  128. package/dist/commands/predict.d.ts.map +1 -0
  129. package/dist/commands/predict.js +280 -0
  130. package/dist/commands/predict.js.map +1 -0
  131. package/dist/commands/predictions.d.ts.map +1 -0
  132. package/dist/commands/predictions.js +486 -0
  133. package/dist/commands/predictions.js.map +1 -0
  134. package/dist/commands/risk.d.ts.map +1 -0
  135. package/dist/commands/risk.js +225 -0
  136. package/dist/commands/risk.js.map +1 -0
  137. package/dist/commands/security.d.ts.map +1 -0
  138. package/dist/commands/security.js +244 -0
  139. package/dist/commands/security.js.map +1 -0
  140. package/dist/commands/sell.d.ts.map +1 -1
  141. package/dist/commands/sell.js +67 -34
  142. package/dist/commands/sell.js.map +1 -1
  143. package/dist/commands/send.d.ts.map +1 -0
  144. package/dist/commands/send.js +733 -0
  145. package/dist/commands/send.js.map +1 -0
  146. package/dist/commands/sign.d.ts.map +1 -0
  147. package/dist/commands/sign.js +1048 -0
  148. package/dist/commands/sign.js.map +1 -0
  149. package/dist/commands/swap.d.ts.map +1 -0
  150. package/dist/commands/swap.js +744 -0
  151. package/dist/commands/swap.js.map +1 -0
  152. package/dist/commands/system.d.ts.map +1 -0
  153. package/dist/commands/system.js +417 -0
  154. package/dist/commands/system.js.map +1 -0
  155. package/dist/commands/tools/index.d.ts.map +1 -0
  156. package/dist/commands/tools/index.js +2040 -0
  157. package/dist/commands/tools/index.js.map +1 -0
  158. package/dist/commands/trade.d.ts.map +1 -0
  159. package/dist/commands/trade.js +237 -0
  160. package/dist/commands/trade.js.map +1 -0
  161. package/dist/commands/transactions.d.ts.map +1 -1
  162. package/dist/commands/transactions.js +29 -17
  163. package/dist/commands/transactions.js.map +1 -1
  164. package/dist/commands/update.d.ts.map +1 -0
  165. package/dist/commands/update.js +429 -0
  166. package/dist/commands/update.js.map +1 -0
  167. package/dist/config.d.ts.map +1 -1
  168. package/dist/config.js +351 -40
  169. package/dist/config.js.map +1 -1
  170. package/dist/index.js +4524 -924
  171. package/dist/index.js.map +1 -1
  172. package/dist/interactive/art.d.ts.map +1 -1
  173. package/dist/interactive/art.js +33 -1
  174. package/dist/interactive/art.js.map +1 -1
  175. package/dist/interactive/shell.d.ts.map +1 -1
  176. package/dist/interactive/shell.js +467 -2652
  177. package/dist/interactive/shell.js.map +1 -1
  178. package/dist/mcp/context.d.ts.map +1 -0
  179. package/dist/mcp/context.js +211 -0
  180. package/dist/mcp/context.js.map +1 -0
  181. package/dist/mcp/onboarding.d.ts.map +1 -0
  182. package/dist/mcp/onboarding.js +266 -0
  183. package/dist/mcp/onboarding.js.map +1 -0
  184. package/dist/mcp/resources.d.ts.map +1 -0
  185. package/dist/mcp/resources.js +222 -0
  186. package/dist/mcp/resources.js.map +1 -0
  187. package/dist/mcp/server.d.ts.map +1 -1
  188. package/dist/mcp/server.js +51 -1
  189. package/dist/mcp/server.js.map +1 -1
  190. package/dist/mcp/tools.d.ts.map +1 -1
  191. package/dist/mcp/tools.js +4119 -169
  192. package/dist/mcp/tools.js.map +1 -1
  193. package/dist/mcp/types.d.ts.map +1 -1
  194. package/dist/types/agent-info.d.ts.map +1 -0
  195. package/dist/types/agent-info.js +11 -0
  196. package/dist/types/agent-info.js.map +1 -0
  197. package/dist/ui/components/ScrollableList.d.ts.map +1 -0
  198. package/dist/ui/components/ScrollableList.js +72 -0
  199. package/dist/ui/components/ScrollableList.js.map +1 -0
  200. package/dist/ui/components/ThemeProvider.d.ts.map +1 -0
  201. package/dist/ui/components/ThemeProvider.js +87 -0
  202. package/dist/ui/components/ThemeProvider.js.map +1 -0
  203. package/dist/ui/components/ThemedBox.d.ts.map +1 -0
  204. package/dist/ui/components/ThemedBox.js +24 -0
  205. package/dist/ui/components/ThemedBox.js.map +1 -0
  206. package/dist/ui/components/agent/ChatHeader.d.ts.map +1 -0
  207. package/dist/ui/components/agent/ChatHeader.js +39 -0
  208. package/dist/ui/components/agent/ChatHeader.js.map +1 -0
  209. package/dist/ui/components/agent/Header.d.ts.map +1 -0
  210. package/dist/ui/components/agent/Header.js +14 -0
  211. package/dist/ui/components/agent/Header.js.map +1 -0
  212. package/dist/ui/components/agent/Input.d.ts.map +1 -0
  213. package/dist/ui/components/agent/Input.js +23 -0
  214. package/dist/ui/components/agent/Input.js.map +1 -0
  215. package/dist/ui/components/agent/Output.d.ts.map +1 -0
  216. package/dist/ui/components/agent/Output.js +23 -0
  217. package/dist/ui/components/agent/Output.js.map +1 -0
  218. package/dist/ui/components/chat/TokenChatUI.d.ts.map +1 -0
  219. package/dist/ui/components/chat/TokenChatUI.js +133 -0
  220. package/dist/ui/components/chat/TokenChatUI.js.map +1 -0
  221. package/dist/ui/components/shell/ShellHeader.d.ts.map +1 -0
  222. package/dist/ui/components/shell/ShellHeader.js +31 -0
  223. package/dist/ui/components/shell/ShellHeader.js.map +1 -0
  224. package/dist/ui/components/shell/ShellInput.d.ts.map +1 -0
  225. package/dist/ui/components/shell/ShellInput.js +151 -0
  226. package/dist/ui/components/shell/ShellInput.js.map +1 -0
  227. package/dist/ui/components/shell/ShellOutput.d.ts.map +1 -0
  228. package/dist/ui/components/shell/ShellOutput.js +8 -0
  229. package/dist/ui/components/shell/ShellOutput.js.map +1 -0
  230. package/dist/ui/hooks/useChatWebSocket.d.ts.map +1 -0
  231. package/dist/ui/hooks/useChatWebSocket.js +76 -0
  232. package/dist/ui/hooks/useChatWebSocket.js.map +1 -0
  233. package/dist/ui/hooks/useCommandHistory.d.ts.map +1 -0
  234. package/dist/ui/hooks/useCommandHistory.js +70 -0
  235. package/dist/ui/hooks/useCommandHistory.js.map +1 -0
  236. package/dist/ui/hooks/useDebounce.d.ts.map +1 -0
  237. package/dist/ui/hooks/useDebounce.js +17 -0
  238. package/dist/ui/hooks/useDebounce.js.map +1 -0
  239. package/dist/ui/hooks/useLogStream.d.ts.map +1 -0
  240. package/dist/ui/hooks/useLogStream.js +20 -0
  241. package/dist/ui/hooks/useLogStream.js.map +1 -0
  242. package/dist/ui/hooks/useVirtualScroll.d.ts.map +1 -0
  243. package/dist/ui/hooks/useVirtualScroll.js +70 -0
  244. package/dist/ui/hooks/useVirtualScroll.js.map +1 -0
  245. package/dist/utils/admin.d.ts.map +1 -0
  246. package/dist/utils/admin.js +144 -0
  247. package/dist/utils/admin.js.map +1 -0
  248. package/dist/utils/autoSetup.d.ts.map +1 -0
  249. package/dist/utils/autoSetup.js +252 -0
  250. package/dist/utils/autoSetup.js.map +1 -0
  251. package/dist/utils/build-constants.d.ts.map +1 -0
  252. package/dist/utils/build-constants.js +10 -0
  253. package/dist/utils/build-constants.js.map +1 -0
  254. package/dist/utils/constants.d.ts.map +1 -1
  255. package/dist/utils/errors.d.ts.map +1 -1
  256. package/dist/utils/errors.js +10 -1
  257. package/dist/utils/errors.js.map +1 -1
  258. package/dist/utils/formatting.d.ts.map +1 -1
  259. package/dist/utils/formatting.js +46 -9
  260. package/dist/utils/formatting.js.map +1 -1
  261. package/dist/utils/llm-cli-config.d.ts.map +1 -0
  262. package/dist/utils/llm-cli-config.js +963 -0
  263. package/dist/utils/llm-cli-config.js.map +1 -0
  264. package/dist/utils/llm-cli-detector.d.ts.map +1 -0
  265. package/dist/utils/llm-cli-detector.js +202 -0
  266. package/dist/utils/llm-cli-detector.js.map +1 -0
  267. package/dist/utils/loading.d.ts.map +1 -1
  268. package/dist/utils/loading.js +25 -3
  269. package/dist/utils/loading.js.map +1 -1
  270. package/dist/utils/maintenance.d.ts.map +1 -0
  271. package/dist/utils/maintenance.js +17 -0
  272. package/dist/utils/maintenance.js.map +1 -0
  273. package/dist/utils/mcp-config.d.ts.map +1 -0
  274. package/dist/utils/mcp-config.js +77 -0
  275. package/dist/utils/mcp-config.js.map +1 -0
  276. package/dist/utils/privateKeyPrompt.d.ts.map +1 -1
  277. package/dist/utils/privateKeyPrompt.js +308 -129
  278. package/dist/utils/privateKeyPrompt.js.map +1 -1
  279. package/dist/utils/process-cleanup.d.ts.map +1 -0
  280. package/dist/utils/process-cleanup.js +136 -0
  281. package/dist/utils/process-cleanup.js.map +1 -0
  282. package/dist/utils/retry.d.ts.map +1 -0
  283. package/dist/utils/retry.js +56 -0
  284. package/dist/utils/retry.js.map +1 -0
  285. package/dist/utils/rpc-helpers.d.ts.map +1 -0
  286. package/dist/utils/rpc-helpers.js +70 -0
  287. package/dist/utils/rpc-helpers.js.map +1 -0
  288. package/dist/utils/rpc-transport.d.ts.map +1 -0
  289. package/dist/utils/rpc-transport.js +87 -0
  290. package/dist/utils/rpc-transport.js.map +1 -0
  291. package/dist/utils/shell-setup.d.ts.map +1 -0
  292. package/dist/utils/shell-setup.js +531 -0
  293. package/dist/utils/shell-setup.js.map +1 -0
  294. package/dist/utils/status.d.ts.map +1 -1
  295. package/dist/utils/status.js +34 -5
  296. package/dist/utils/status.js.map +1 -1
  297. package/dist/utils/token-resolver.d.ts.map +1 -1
  298. package/dist/utils/token-resolver.js +51 -8
  299. package/dist/utils/token-resolver.js.map +1 -1
  300. package/dist/utils/x402-caller.d.ts.map +1 -0
  301. package/dist/utils/x402-caller.js +17 -0
  302. package/dist/utils/x402-caller.js.map +1 -0
  303. package/docs/README.md +28 -0
  304. package/docs/agent/README.md +18 -0
  305. package/docs/api/README.md +41 -0
  306. package/docs/cli/README.md +42 -0
  307. package/docs/guides/README.md +26 -0
  308. package/docs/implementation/README.md +18 -0
  309. package/docs/planning/README.md +19 -0
  310. package/docs/testing/README.md +15 -0
  311. package/docs/ux/README.md +16 -0
  312. package/issues.txt +2 -0
  313. package/package.json +24 -9
  314. package/scripts/cat-spin.sh +417 -0
  315. package/scripts/deprecate-rc-versions.js +58 -0
  316. package/scripts/inject-build-constants.js +43 -0
  317. package/scripts/monitor-foobar.js +117 -0
  318. package/swap.logs +61 -0
  319. package/swapping.txt +108 -0
  320. package/test.txt +12 -0
  321. package/tests/fixtures/test-data.json +16 -0
  322. package/Screenshot 2025-12-21 at 8.56.02/342/200/257PM.png +0 -0
@@ -0,0 +1,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