themaleem

Untitled

Jun 17th, 2025
7
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 32.51 KB | None | 0 0
  1. const fs = require("fs");
  2. const path = require("path");
  3. const { ethers, JsonRpcProvider } = require("ethers");
  4. const axios = require("axios");
  5. const moment = require("moment-timezone");
  6. const blessed = require("blessed");
  7. const contrib = require("blessed-contrib");
  8.  
  9. // Babylon-specific constants
  10. const BABYLON_USDC_ADDRESS =
  11. "bbn1zsrv23akkgxdnwul72sftgv2xjt5khsnt3wwjhp0ffh683hzp5aq5a0h6n";
  12. const BABYLON_CHANNEL_ID = 7;
  13. const BABYLON_RPC_URL = "https://rpc.testnet-5.babylon.union.build";
  14. const BABYLON_WALLET_FILE = path.join(__dirname, "baby_wallet.json");
  15.  
  16. // Helper function for random USDC amounts
  17. function getRandomUSDCAmount() {
  18. const min = 0.007;
  19. const max = 0.04;
  20. return (Math.random() * (max - min) + min).toFixed(6);
  21. }
  22.  
  23. // Create screen
  24. const screen = blessed.screen({
  25. smartCSR: true,
  26. title: "Union Testnet Auto Bot - MeG",
  27. });
  28.  
  29. // Create dashboard grid layout
  30. const grid = new contrib.grid({
  31. rows: 12,
  32. cols: 12,
  33. screen: screen,
  34. });
  35.  
  36. // Create UI components
  37. const transactionLogBox = grid.set(0, 0, 6, 6, contrib.log, {
  38. fg: "green",
  39. selectedFg: "green",
  40. label: "Transaction Logs",
  41. border: { type: "line", fg: "cyan" },
  42. tags: true,
  43. });
  44.  
  45. const walletInfoTable = grid.set(0, 6, 3, 6, contrib.table, {
  46. keys: true,
  47. fg: "white",
  48. selectedFg: "black",
  49. selectedBg: "blue",
  50. interactive: true,
  51. label: "Wallet Information",
  52. border: { type: "line", fg: "cyan" },
  53. columnSpacing: 3,
  54. columnWidth: [12, 40, 14],
  55. });
  56.  
  57. const txLineChart = grid.set(6, 0, 6, 6, contrib.line, {
  58. style: {
  59. line: "yellow",
  60. text: "green",
  61. baseline: "black",
  62. },
  63. xLabelPadding: 3,
  64. xPadding: 5,
  65. showLegend: true,
  66. wholeNumbersOnly: false,
  67. label: "Transaction Performance (Time in ms)",
  68. border: { type: "line", fg: "cyan" },
  69. });
  70.  
  71. const txDonut = grid.set(3, 6, 3, 3, contrib.donut, {
  72. label: "Transaction Status",
  73. radius: 8,
  74. arcWidth: 3,
  75. remainColor: "black",
  76. yPadding: 2,
  77. border: { type: "line", fg: "cyan" },
  78. });
  79.  
  80. const gasUsageGauge = grid.set(3, 9, 3, 3, contrib.gauge, {
  81. label: "Network Usage",
  82. percent: [0, 100],
  83. border: { type: "line", fg: "cyan" },
  84. });
  85.  
  86. const infoBox = grid.set(6, 6, 6, 6, contrib.markdown, {
  87. label: "System Information",
  88. border: { type: "line", fg: "cyan" },
  89. markdownStyles: {
  90. header: { fg: "magenta" },
  91. bold: { fg: "blue" },
  92. italic: { fg: "green" },
  93. link: { fg: "yellow" },
  94. },
  95. });
  96.  
  97. // Helper function for status updates
  98. function updateStatusInfo(destination = "Holesky") {
  99. const now = moment().tz("Asia/Jakarta").format("HH:mm:ss | DD-MM-YYYY");
  100. const networkStatus = Math.floor(Math.random() * 30) + 70;
  101.  
  102. infoBox.setMarkdown(
  103. `# System Status
  104.  
  105. **Time**: ${now}
  106. **Network**: Sepolia to ${destination} Bridge
  107. **Status**: Running
  108. **API Health**: Good
  109. **RPC Provider**: ${currentRpcProviderIndex + 1}/${rpcProviders.length}
  110.  
  111. ## Network Information
  112.  
  113. * Chain ID: 11155111 (Sepolia)
  114. * Gas Price: ~${Math.floor(Math.random() * 15) + 25} Gwei
  115. * Pending Txs: ${Math.floor(Math.random() * 10)}`
  116. );
  117.  
  118. gasUsageGauge.setPercent(networkStatus);
  119. screen.render();
  120. }
  121.  
  122. // Transaction statistics for charts
  123. const txStats = {
  124. success: 0,
  125. failed: 0,
  126. pending: 0,
  127. times: [],
  128. x: Array(30)
  129. .fill(0)
  130. .map((_, i) => i.toString()),
  131. y: Array(30).fill(0),
  132. };
  133.  
  134. function updateCharts() {
  135. txDonut.setData([
  136. { percent: txStats.success, label: "Success", color: "green" },
  137. { percent: txStats.failed, label: "Failed", color: "red" },
  138. { percent: txStats.pending, label: "Pending", color: "yellow" },
  139. ]);
  140.  
  141. if (txStats.times.length > 0) {
  142. txStats.y.shift();
  143. txStats.y.push(txStats.times[txStats.times.length - 1]);
  144. txLineChart.setData([
  145. {
  146. title: "Tx Time",
  147. x: txStats.x,
  148. y: txStats.y,
  149. style: { line: "yellow" },
  150. },
  151. ]);
  152. }
  153. screen.render();
  154. }
  155.  
  156. // Logger functions
  157. const logger = {
  158. info: (msg) => {
  159. transactionLogBox.log(`{green-fg}[ℹ] ${msg}{/green-fg}`);
  160. screen.render();
  161. },
  162. warn: (msg) => {
  163. transactionLogBox.log(`{yellow-fg}[⚠] ${msg}{/yellow-fg}`);
  164. screen.render();
  165. },
  166. error: (msg) => {
  167. transactionLogBox.log(`{red-fg}[✗] ${msg}{/red-fg}`);
  168. screen.render();
  169. },
  170. success: (msg) => {
  171. transactionLogBox.log(`{green-fg}[✓] ${msg}{/green-fg}`);
  172. screen.render();
  173. },
  174. loading: (msg) => {
  175. transactionLogBox.log(`{cyan-fg}[⟳] ${msg}{/cyan-fg}`);
  176. screen.render();
  177. },
  178. step: (msg) => {
  179. transactionLogBox.log(`{white-fg}[→] ${msg}{/white-fg}`);
  180. screen.render();
  181. },
  182. };
  183.  
  184. // Contract ABIs
  185. const UCS03_ABI = [
  186. {
  187. inputs: [
  188. { internalType: "uint32", name: "channelId", type: "uint32" },
  189. { internalType: "uint64", name: "timeoutHeight", type: "uint64" },
  190. { internalType: "uint64", name: "timeoutTimestamp", type: "uint64" },
  191. { internalType: "bytes32", name: "salt", type: "bytes32" },
  192. {
  193. components: [
  194. { internalType: "uint8", name: "version", type: "uint8" },
  195. { internalType: "uint8", name: "opcode", type: "uint8" },
  196. { internalType: "bytes", name: "operand", type: "bytes" },
  197. ],
  198. internalType: "struct Instruction",
  199. name: "instruction",
  200. type: "tuple",
  201. },
  202. ],
  203. name: "send",
  204. outputs: [],
  205. stateMutability: "nonpayable",
  206. type: "function",
  207. },
  208. ];
  209.  
  210. const USDC_ABI = [
  211. {
  212. constant: true,
  213. inputs: [{ name: "account", type: "address" }],
  214. name: "balanceOf",
  215. outputs: [{ name: "", type: "uint256" }],
  216. type: "function",
  217. stateMutability: "view",
  218. },
  219. {
  220. constant: true,
  221. inputs: [
  222. { name: "owner", type: "address" },
  223. { name: "spender", type: "address" },
  224. ],
  225. name: "allowance",
  226. outputs: [{ name: "", type: "uint256" }],
  227. type: "function",
  228. stateMutability: "view",
  229. },
  230. {
  231. constant: false,
  232. inputs: [
  233. { name: "spender", type: "address" },
  234. { name: "value", type: "uint256" },
  235. ],
  236. name: "approve",
  237. outputs: [{ name: "", type: "bool" }],
  238. type: "function",
  239. stateMutability: "nonpayable",
  240. },
  241. ];
  242.  
  243. const WETH_ABI = [
  244. {
  245. constant: false,
  246. inputs: [{ name: "wad", type: "uint256" }],
  247. name: "withdraw",
  248. outputs: [],
  249. payable: false,
  250. stateMutability: "nonpayable",
  251. type: "function",
  252. },
  253. {
  254. constant: false,
  255. inputs: [],
  256. name: "deposit",
  257. outputs: [],
  258. payable: true,
  259. stateMutability: "payable",
  260. type: "function",
  261. },
  262. {
  263. constant: true,
  264. inputs: [{ name: "account", type: "address" }],
  265. name: "balanceOf",
  266. outputs: [{ name: "", type: "uint256" }],
  267. type: "function",
  268. stateMutability: "view",
  269. },
  270. {
  271. constant: false,
  272. inputs: [
  273. { name: "guy", type: "address" },
  274. { name: "wad", type: "uint256" },
  275. ],
  276. name: "approve",
  277. outputs: [{ name: "", type: "bool" }],
  278. payable: false,
  279. stateMutability: "nonpayable",
  280. type: "function",
  281. },
  282. {
  283. constant: true,
  284. inputs: [
  285. { name: "owner", type: "address" },
  286. { name: "spender", type: "address" },
  287. ],
  288. name: "allowance",
  289. outputs: [{ name: "", type: "uint256" }],
  290. type: "function",
  291. stateMutability: "view",
  292. },
  293. ];
  294.  
  295. const contractAddress = "0x5FbE74A283f7954f10AA04C2eDf55578811aeb03";
  296. const USDC_ADDRESS = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238";
  297. const WETH_ADDRESS = "0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9";
  298. const graphqlEndpoint = "https://graphql.union.build/v1/graphql";
  299. const baseExplorerUrl = "https://sepolia.etherscan.io";
  300. const unionUrl = "https://app.union.build/explorer";
  301.  
  302. const rpcProviders = [
  303. new JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com"),
  304. ];
  305. let currentRpcProviderIndex = 0;
  306.  
  307. function provider() {
  308. return rpcProviders[currentRpcProviderIndex];
  309. }
  310.  
  311. // Create a blessed input element for user input
  312. const userInput = blessed.prompt({
  313. parent: screen,
  314. border: {
  315. type: "line",
  316. fg: "cyan",
  317. },
  318. height: "30%",
  319. width: "50%",
  320. top: "center",
  321. left: "center",
  322. label: " Input Required ",
  323. tags: true,
  324. keys: true,
  325. vi: true,
  326. hidden: true,
  327. });
  328.  
  329. function askQuestion(query) {
  330. return new Promise((resolve) => {
  331. userInput.hidden = false;
  332. userInput.input(query, "", (err, value) => {
  333. userInput.hidden = true;
  334. screen.render();
  335. resolve(value);
  336. });
  337. });
  338. }
  339.  
  340. const explorer = {
  341. tx: (txHash) => `${baseExplorerUrl}/tx/${txHash}`,
  342. address: (address) => `${baseExplorerUrl}/address/${address}`,
  343. };
  344.  
  345. const union = {
  346. tx: (txHash) => `${unionUrl}/transfers/${txHash}`,
  347. };
  348.  
  349. const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
  350.  
  351. function timelog() {
  352. return moment().tz("Asia/Jakarta").format("HH:mm:ss | DD-MM-YYYY");
  353. }
  354.  
  355. async function pollPacketHash(txHash, retries = 50, intervalMs = 5000) {
  356. const headers = {
  357. accept: "application/graphql-response+json, application/json",
  358. "accept-encoding": "gzip, deflate, br, zstd",
  359. "accept-language": "en-US,en;q=0.9,id;q=0.8",
  360. "content-type": "application/json",
  361. origin: "https://app-union.build",
  362. referer: "https://app.union.build/",
  363. "user-agent": "Mozilla/5.0",
  364. };
  365. const data = {
  366. query: `query ($submission_tx_hash: String!) {
  367. v2_transfers(args: {p_transaction_hash: $submission_tx_hash}) {
  368. packet_hash
  369. }
  370. }`,
  371. variables: {
  372. submission_tx_hash: txHash.startsWith("0x") ? txHash : `0x${txHash}`,
  373. },
  374. };
  375.  
  376. for (let i = 0; i < retries; i++) {
  377. try {
  378. const res = await axios.post(graphqlEndpoint, data, { headers });
  379. const result = res.data?.data?.v2_transfers;
  380. if (result && result.length > 0 && result[0].packet_hash) {
  381. return result[0].packet_hash;
  382. }
  383. } catch (e) {
  384. logger.error(`Packet error: ${e.message}`);
  385. }
  386. await delay(intervalMs);
  387. }
  388. }
  389.  
  390. async function checkBalanceAndApprove(
  391. wallet,
  392. tokenAddress,
  393. tokenAbi,
  394. spenderAddress,
  395. tokenName
  396. ) {
  397. const tokenContract = new ethers.Contract(tokenAddress, tokenAbi, wallet);
  398. const balance = await tokenContract.balanceOf(wallet.address);
  399.  
  400. if (balance === 0n) {
  401. logger.error(
  402. `${wallet.address} does not have enough ${tokenName}. Fund your wallet first!`
  403. );
  404. return false;
  405. }
  406.  
  407. const allowance = await tokenContract.allowance(
  408. wallet.address,
  409. spenderAddress
  410. );
  411. if (allowance === 0n) {
  412. logger.loading(
  413. `${tokenName} is not approved. Sending approve transaction...`
  414. );
  415. const approveAmount = ethers.MaxUint256;
  416. try {
  417. const tx = await tokenContract.approve(spenderAddress, approveAmount);
  418. txStats.pending++;
  419. updateCharts();
  420.  
  421. const receipt = await tx.wait();
  422. txStats.pending--;
  423. txStats.success++;
  424. updateCharts();
  425.  
  426. logger.success(`Approve confirmed: ${explorer.tx(receipt.hash)}`);
  427. await delay(3000);
  428. } catch (err) {
  429. txStats.pending--;
  430. txStats.failed++;
  431. updateCharts();
  432.  
  433. logger.error(`Approve failed: ${err.message}`);
  434. return false;
  435. }
  436. }
  437. return true;
  438. }
  439.  
  440. async function sendFromWallet(
  441. walletInfo,
  442. maxTransaction,
  443. transferType,
  444. destination
  445. ) {
  446. const wallet = new ethers.Wallet(walletInfo.privatekey, provider());
  447. logger.loading(
  448. `Sending from ${wallet.address} (${
  449. walletInfo.name || "Unnamed"
  450. }) to ${destination}`
  451. );
  452.  
  453. // Load Babylon wallets if destination is Babylon
  454. let babylonWallets = [];
  455. if (destination === "babylon") {
  456. if (!fs.existsSync(BABYLON_WALLET_FILE)) {
  457. logger.error(`baby_wallet.json not found at ${BABYLON_WALLET_FILE}`);
  458. await delay(5000);
  459. process.exit(1);
  460. }
  461. try {
  462. babylonWallets = require(BABYLON_WALLET_FILE).wallets;
  463. if (!babylonWallets || !Array.isArray(babylonWallets)) {
  464. throw new Error("Invalid baby_wallet.json format");
  465. }
  466. } catch (err) {
  467. logger.error(`Error loading baby_wallet.json: ${err.message}`);
  468. await delay(5000);
  469. process.exit(1);
  470. }
  471. }
  472.  
  473. let shouldProceed = false;
  474. let tokenName = "";
  475. let tokenAddress = "";
  476. let tokenAbi = [];
  477. let channelId = 0;
  478. const transferAmount = ethers.parseEther("0.0001"); // 0.0001 WETH
  479.  
  480. if (transferType === "usdc") {
  481. tokenName = "USDC";
  482. tokenAddress = USDC_ADDRESS;
  483. tokenAbi = USDC_ABI;
  484. // Set channel ID based on destination
  485. channelId = destination === "babylon" ? BABYLON_CHANNEL_ID : 8;
  486. shouldProceed = await checkBalanceAndApprove(
  487. wallet,
  488. tokenAddress,
  489. tokenAbi,
  490. contractAddress,
  491. tokenName
  492. );
  493. } else if (transferType === "weth") {
  494. tokenName = "WETH";
  495. tokenAddress = WETH_ADDRESS;
  496. tokenAbi = WETH_ABI;
  497. channelId = 9; // Channel ID for WETH transfers
  498.  
  499. // Check ETH balance first (needed for gas and potential WETH wrapping)
  500. const ethBalance = await provider().getBalance(wallet.address);
  501. const requiredEthForGas = ethers.parseEther("0.001");
  502. if (ethBalance < requiredEthForGas) {
  503. logger.error(
  504. `Insufficient ETH for gas. Need at least ${ethers.formatEther(
  505. requiredEthForGas
  506. )} ETH`
  507. );
  508. return;
  509. }
  510.  
  511. // Check WETH balance
  512. const wethContract = new ethers.Contract(WETH_ADDRESS, WETH_ABI, wallet);
  513. const wethBalance = await wethContract.balanceOf(wallet.address);
  514.  
  515. if (wethBalance < transferAmount) {
  516. // If not enough WETH, try to wrap ETH
  517. const ethToWrap = transferAmount - wethBalance;
  518. const totalEthNeeded = ethToWrap + requiredEthForGas;
  519.  
  520. if (ethBalance < totalEthNeeded) {
  521. logger.error(
  522. `Insufficient ETH to wrap. Need ${ethers.formatEther(
  523. totalEthNeeded
  524. )} ETH total (for wrapping and gas)`
  525. );
  526. return;
  527. }
  528.  
  529. logger.loading(
  530. `Wrapping ${ethers.formatEther(ethToWrap)} ETH to WETH...`
  531. );
  532. try {
  533. const wrapTx = await wethContract.deposit({ value: ethToWrap });
  534. await wrapTx.wait();
  535. logger.success(
  536. `Successfully wrapped ${ethers.formatEther(ethToWrap)} ETH to WETH`
  537. );
  538. } catch (err) {
  539. logger.error(`Failed to wrap ETH: ${err.message}`);
  540. return;
  541. }
  542. }
  543.  
  544. // Approve WETH spending
  545. shouldProceed = await checkBalanceAndApprove(
  546. wallet,
  547. tokenAddress,
  548. tokenAbi,
  549. contractAddress,
  550. tokenName
  551. );
  552. }
  553.  
  554. if (!shouldProceed) return;
  555.  
  556. const contract = new ethers.Contract(contractAddress, UCS03_ABI, wallet);
  557. const addressHex = wallet.address.slice(2).toLowerCase();
  558. const timeoutHeight = 0;
  559.  
  560. for (let i = 1; i <= maxTransaction; i++) {
  561. // Select a random Babylon wallet for each transfer
  562. const babylonWallet =
  563. destination === "babylon"
  564. ? babylonWallets[Math.floor(Math.random() * babylonWallets.length)]
  565. : null;
  566.  
  567. logger.step(
  568. `${
  569. walletInfo.name || "Unnamed"
  570. } | ${tokenName} Transaction ${i}/${maxTransaction} to ${destination}` +
  571. (babylonWallet ? ` (${babylonWallet.name || "Babylon wallet"})` : "")
  572. );
  573.  
  574. const now = BigInt(Date.now()) * 1_000_000n;
  575. const oneDayNs = 86_400_000_000_000n;
  576. const timeoutTimestamp = (now + oneDayNs).toString();
  577. const timestampNow = Math.floor(Date.now() / 1000);
  578. const salt = ethers.keccak256(
  579. ethers.solidityPacked(
  580. ["address", "uint256"],
  581. [wallet.address, timestampNow]
  582. )
  583. );
  584.  
  585. // Different operand based on transfer type
  586. let operand;
  587. if (transferType === "usdc") {
  588. // const randomAmount = getRandomUSDCAmount();
  589. const randomAmount = (0.001).toFixed(6);
  590. const amountInUnits = ethers.parseUnits(randomAmount, 6); // USDC has 6 decimals
  591.  
  592. logger.step(
  593. `Using random USDC amount: ${randomAmount} (${amountInUnits.toString()} units)`
  594. );
  595.  
  596. if (destination === "babylon") {
  597. // Fixed Babylon operand format
  598. const babylonAddress = babylonWallet.address;
  599. if (!babylonAddress.startsWith("bbn1")) {
  600. logger.error(`Invalid Babylon address format: ${babylonAddress}`);
  601. continue;
  602. }
  603.  
  604. const recipientHex = Buffer.from(babylonAddress, "utf8").toString(
  605. "hex"
  606. );
  607.  
  608. // Properly structured IBC packet data
  609. const packetData = {
  610. sourceAddress: wallet.address,
  611. destinationAddress: babylonAddress,
  612. tokenAddress: USDC_ADDRESS,
  613. amount: amountInUnits.toString(),
  614. denom: "USDC",
  615. };
  616.  
  617. // Encode the packet data
  618. // operand = ethers.AbiCoder.defaultAbiCoder().encode(
  619. // [
  620. // "tuple(bytes32,bytes32,bytes32,bytes32,bytes32)",
  621. // "tuple(bytes32,bytes32,bytes32,bytes32,bytes32)",
  622. // ],
  623. // [
  624. // [
  625. // ethers.zeroPadValue(ethers.toUtf8Bytes("sourceAddress"), 32),
  626. // ethers.zeroPadValue(
  627. // ethers.toUtf8Bytes(packetData.sourceAddress),
  628. // 32
  629. // ),
  630. // ethers.zeroPadValue(ethers.toUtf8Bytes("amount"), 32),
  631. // ethers.zeroPadValue(ethers.toUtf8Bytes(packetData.amount), 32),
  632. // ethers.zeroPadValue(ethers.toUtf8Bytes("tokenAddress"), 32),
  633. // ],
  634. // [
  635. // ethers.zeroPadValue(
  636. // ethers.toUtf8Bytes(packetData.tokenAddress),
  637. // 32
  638. // ),
  639. // ethers.zeroPadValue(ethers.toUtf8Bytes("destinationAddress"), 32),
  640. // ethers.zeroPadValue(
  641. // ethers.toUtf8Bytes(packetData.destinationAddress),
  642. // 32
  643. // ),
  644. // ethers.zeroPadValue(ethers.toUtf8Bytes("denom"), 32),
  645. // ethers.zeroPadValue(ethers.toUtf8Bytes(packetData.denom), 32),
  646. // ],
  647. // ]
  648. // );
  649. operand = `0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000027100000000000000000000000000000000000000000000000000000000000000014${addressHex}000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a${recipientHex}0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000141c7d4b196cb0c7b01d743fbc6116a902379c72380000000000000000000000000000000000000000000000000000000000000000000000000000000000000004555344430000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045553444300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e62626e317a7372763233616b6b6778646e77756c3732736674677632786a74356b68736e743377776a687030666668363833687a7035617135613068366e0000`;
  650. } else {
  651. // Original Holesky operand
  652. operand =
  653. "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002c00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000027100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000027100000000000000000000000000000000000000000000000000000000000000014" +
  654. addressHex +
  655. "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000014" +
  656. addressHex +
  657. "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000141c7d4b196cb0c7b01d743fbc6116a902379c72380000000000000000000000000000000000000000000000000000000000000000000000000000000000000004555344430000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045553444300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001457978bfe465ad9b1c0bf80f6c1539d300705ea50000000000000000000000000";
  658. }
  659. } else if (transferType === "weth") {
  660. // WETH transfer operand with 0.0001 WETH amount
  661. operand =
  662. "0xff0d7c2f00000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000183e3bcd270059c058ce191dc34ac59ab0ccef2037033d3b50e15022d39fd5fd451083f938fa829800000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002c00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000e8d4a5100000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000e8d4a510000000000000000000000000000000000000000000000000000000000000000014" +
  663. addressHex +
  664. "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000014" +
  665. addressHex +
  666. "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000147b79995e5f793a07bc00c21412e50ecae098e7f900000000000000000000000000000000000000000000000000000000000000000000000000000000000000045745544800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d57726170706564204574686572000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014b476983cc7853797fc5adc4bcad39b277bc79656000000000000000000000000";
  667. }
  668.  
  669. const instruction = {
  670. version: 0,
  671. opcode: 2,
  672. operand,
  673. };
  674.  
  675. try {
  676. const startTime = Date.now();
  677. txStats.pending++;
  678. updateCharts();
  679.  
  680. // Add gas limit to prevent out of gas errors
  681. const tx = await contract.send(
  682. channelId,
  683. timeoutHeight,
  684. timeoutTimestamp,
  685. salt,
  686. instruction,
  687. {
  688. gasLimit: 500000, // Increased gas limit for WETH transfers
  689. }
  690. );
  691.  
  692. const receipt = await tx.wait(1);
  693.  
  694. const endTime = Date.now();
  695. const txTime = endTime - startTime;
  696. txStats.times.push(txTime);
  697. txStats.pending--;
  698. txStats.success++;
  699. updateCharts();
  700.  
  701. // Enhanced logging showing Babylon wallet info
  702. const logMsg =
  703. `${timelog()} | ${walletInfo.name || "Unnamed"} | ${tokenName} to ` +
  704. (babylonWallet
  705. ? `${babylonWallet.name || "Babylon wallet"}`
  706. : destination) +
  707. ` Confirmed: ${explorer.tx(tx.hash)} (${txTime}ms)`;
  708. logger.success(logMsg);
  709.  
  710. const txHash = tx.hash.startsWith("0x") ? tx.hash : `0x${tx.hash}`;
  711. const packetHash = await pollPacketHash(txHash);
  712. if (packetHash) {
  713. logger.success(
  714. `${timelog()} | ${
  715. walletInfo.name || "Unnamed"
  716. } | Packet Submitted: ${union.tx(packetHash)}`
  717. );
  718. }
  719. } catch (err) {
  720. txStats.pending--;
  721. txStats.failed++;
  722. updateCharts();
  723.  
  724. // More detailed error logging
  725. if (err.reason) {
  726. logger.error(`Failed for ${wallet.address}: ${err.reason}`);
  727. } else if (err.message.includes("reverted")) {
  728. // Try to decode revert reason
  729. try {
  730. const revertReason = err.data
  731. ? await decodeRevertReason(err.data)
  732. : "unknown";
  733. logger.error(
  734. `Failed for ${wallet.address}: execution reverted (${revertReason})`
  735. );
  736. } catch {
  737. logger.error(
  738. `Failed for ${wallet.address}: execution reverted (unknown reason)`
  739. );
  740. }
  741. } else {
  742. logger.error(`Failed for ${wallet.address}: ${err.message}`);
  743. }
  744.  
  745. // Add delay after failure to prevent rapid retries
  746. await delay(2000);
  747. }
  748.  
  749. if (i < maxTransaction) {
  750. await delay(Math.floor(Math.random() * (1500 - 1000 + 1)) + 1000);
  751. }
  752.  
  753. updateStatusInfo(destination);
  754. }
  755. }
  756.  
  757. // Helper function to decode revert reasons
  758. async function decodeRevertReason(data) {
  759. try {
  760. const revertReason = ethers.toUtf8String("0x" + data.slice(138));
  761. return revertReason || "unknown";
  762. } catch {
  763. return "unknown";
  764. }
  765. }
  766.  
  767. async function main() {
  768. // Initialize UI
  769. screen.key(["escape", "q", "C-c"], function () {
  770. return process.exit(0);
  771. });
  772.  
  773. // Status bar at the bottom
  774. const statusBar = blessed.box({
  775. parent: screen,
  776. bottom: 0,
  777. width: "100%",
  778. height: 1,
  779. content:
  780. " {bold}{black-fg}Press Q/ESC to exit | Union Testnet Auto Bot - MeG787{/black-fg}{/bold}",
  781. tags: true,
  782. style: {
  783. fg: "black",
  784. bg: "blue",
  785. },
  786. });
  787.  
  788. screen.render();
  789. updateStatusInfo();
  790.  
  791. // Set initial data for UI components
  792. walletInfoTable.setData({
  793. headers: ["Name", "Address", "Balance"],
  794. data: [["Loading...", "Please wait", "0"]],
  795. });
  796.  
  797. const walletFilePath = path.join(__dirname, "wallet.json");
  798. if (!fs.existsSync(walletFilePath)) {
  799. logger.error(
  800. `wallet.json not found at ${walletFilePath}. Please create it with your wallet data.`
  801. );
  802. await delay(5000);
  803. process.exit(1);
  804. }
  805.  
  806. let walletData;
  807. try {
  808. walletData = require(walletFilePath);
  809. } catch (err) {
  810. logger.error(`Error loading wallet.json: ${err.message}`);
  811. await delay(5000);
  812. process.exit(1);
  813. }
  814.  
  815. if (!walletData.wallets || !Array.isArray(walletData.wallets)) {
  816. logger.error(`wallet.json does not contain a valid 'wallets' array.`);
  817. await delay(5000);
  818. process.exit(1);
  819. }
  820.  
  821. // Update wallet table with actual data
  822. const tableData = await Promise.all(
  823. walletData.wallets.map(async (wallet) => {
  824. try {
  825. const w = new ethers.Wallet(wallet.privatekey, provider());
  826. const usdcContract = new ethers.Contract(USDC_ADDRESS, USDC_ABI, w);
  827. const wethContract = new ethers.Contract(WETH_ADDRESS, WETH_ABI, w);
  828. const ethBalance = await provider().getBalance(w.address);
  829. const usdcBalance = await usdcContract.balanceOf(w.address);
  830. const wethBalance = await wethContract.balanceOf(w.address);
  831.  
  832. return [
  833. wallet.name || "Unnamed",
  834. w.address.slice(0, 12) + "..." + w.address.slice(-6),
  835. `USDC: ${ethers.formatUnits(
  836. usdcBalance,
  837. 6
  838. )}\nETH: ${ethers.formatEther(
  839. ethBalance
  840. )}\nWETH: ${ethers.formatEther(wethBalance)}`,
  841. ];
  842. } catch (e) {
  843. return [wallet.name || "Unnamed", "Error", "Error"];
  844. }
  845. })
  846. );
  847.  
  848. walletInfoTable.setData({
  849. headers: ["Name", "Address", "Balances"],
  850. data: tableData,
  851. });
  852.  
  853. screen.render();
  854.  
  855. // First prompt: Choose destination
  856. const destination = await askQuestion(
  857. "Choose destination:\n1. Babylon\n2. Holesky\nEnter choice (1 or 2): "
  858. );
  859. let destinationName, destinationKey;
  860.  
  861. if (destination === "1") {
  862. destinationName = "Babylon";
  863. destinationKey = "babylon";
  864. } else if (destination === "2") {
  865. destinationName = "Holesky";
  866. destinationKey = "holesky";
  867. } else {
  868. logger.error("Invalid choice. Please enter either 1 or 2.");
  869. await delay(5000);
  870. process.exit(1);
  871. }
  872.  
  873. // Then ask for transfer type
  874. const transferType = await askQuestion("Choose transfer type (usdc/weth): ");
  875. if (!["usdc", "weth"].includes(transferType.toLowerCase())) {
  876. logger.error(
  877. "Invalid transfer type. Please choose either 'usdc' or 'weth'."
  878. );
  879. await delay(5000);
  880. process.exit(1);
  881. }
  882.  
  883. // Validate WETH can only be sent to Holesky
  884. if (transferType === "weth" && destinationKey === "babylon") {
  885. logger.error("WETH transfers are only supported to Holesky");
  886. await delay(5000);
  887. process.exit(1);
  888. }
  889.  
  890. const maxTransactionInput = await askQuestion(
  891. "Enter the number of transactions per wallet: "
  892. );
  893. const maxTransaction = parseInt(maxTransactionInput.trim());
  894.  
  895. if (isNaN(maxTransaction) || maxTransaction <= 0) {
  896. logger.error("Invalid number. Please enter a positive number.");
  897. await delay(5000);
  898. process.exit(1);
  899. }
  900.  
  901. // Set up a timer to update the system info every 10 seconds
  902. setInterval(() => updateStatusInfo(destinationName), 10000);
  903.  
  904. for (const walletInfo of walletData.wallets) {
  905. if (!walletInfo.name) {
  906. logger.warn(`Wallet missing 'name' field. Using 'Unnamed' as default.`);
  907. }
  908. if (!walletInfo.privatekey) {
  909. logger.warn(
  910. `Skipping wallet '${walletInfo.name || "Unnamed"}': Missing privatekey.`
  911. );
  912. continue;
  913. }
  914. if (!walletInfo.privatekey.startsWith("0x")) {
  915. logger.warn(
  916. `Skipping wallet '${
  917. walletInfo.name || "Unnamed"
  918. }': Privatekey must start with '0x'.`
  919. );
  920. continue;
  921. }
  922. if (!/^(0x)[0-9a-fA-F]{64}$/.test(walletInfo.privatekey)) {
  923. logger.warn(
  924. `Skipping wallet '${
  925. walletInfo.name || "Unnamed"
  926. }': Privatekey is not a valid 64-character hexadecimal string.`
  927. );
  928. continue;
  929. }
  930.  
  931. logger.loading(
  932. `Sending ${maxTransaction} ${transferType.toUpperCase()} Transaction Sepolia to ${destinationName} from ${
  933. walletInfo.name || "Unnamed"
  934. }`
  935. );
  936. await sendFromWallet(
  937. walletInfo,
  938. maxTransaction,
  939. transferType.toLowerCase(),
  940. destinationKey
  941. );
  942. }
  943.  
  944. if (walletData.wallets.length === 0) {
  945. logger.warn("No wallets processed. Check wallet.json for valid entries.");
  946. }
  947.  
  948. // Keep the screen rendered until user exits
  949. logger.info("All transactions completed. Press Q or ESC to exit.");
  950. }
  951.  
  952. // Initialize the application
  953. main().catch((err) => {
  954. logger.error(`Main error: ${err.message}`);
  955. setTimeout(() => process.exit(1), 5000);
  956. });
  957.  
Add Comment
Please, Sign In to add comment