├── LICENSE ├── README.md ├── package.json ├── src ├── callbacks │ ├── tokens.ts │ └── wallets.ts ├── commands │ └── start.ts ├── config.ts ├── index.ts ├── server │ └── processSnipes.ts ├── types │ ├── options.d.ts │ ├── tokens.d.ts │ ├── tronweb.d.ts │ └── user.d.ts └── utils │ ├── format.ts │ ├── logs.ts │ └── tronWeb.ts ├── tsconfig.base.json └── tsconfig.json /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Predmet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TRON Sniper Bot 2 | 3 | This bot is designed to help you snipe and trade tokens on the TRON blockchain. It's highly configurable and easy to set up. 4 | 5 | ## Features 6 | 7 | - **Automated Token Sniping**: Monitor specific token contracts and automatically execute trades as soon as a liquidity pair is established on SunSwap. 8 | - **Effortless Trading**: Simplify buying and selling tokens on the TRON network with automated trade execution. 9 | - **Telegram Integration**: Control and monitor the bot directly from your Telegram account with an intuitive interface. 10 | - **Secure Data Storage with MongoDB**: Store user data securely using MongoDB, ensuring privacy and reliability. 11 | 12 | ## Getting Started 13 | 14 | ### Prerequisites 15 | 16 | Make sure you have the following installed on your machine: 17 | console 18 | - Node.js (v16 or higher) 19 | - npm or yarn 20 | - [MongoDB](https://www.mongodb.com/docs/manual/administration/install-community/) 21 | 22 | ### Installation 23 | 24 | 1. Clone the repository: 25 | 26 | ```bash 27 | git clone https://github.com/impredmet/tron-sniper.git 28 | cd tron-sniper 29 | ``` 30 | 31 | 2. Install the dependencies: 32 | 33 | ```bash 34 | npm install 35 | # or 36 | yarn install 37 | ``` 38 | 39 | 3. Ensure your MongoDB server is running. You can start it with the following command: 40 | 41 | ```bash 42 | mongod 43 | ``` 44 | 45 | 4. Open the `.env` file in the root directory of your project and modify it with your specific configuration. 46 | 47 | 5. Start the bot: 48 | 49 | ```bash 50 | npm start 51 | # or 52 | yarn start 53 | ``` 54 | 55 | The bot will now start and connect to Telegram, MongoDB, and the TRON network. 56 | 57 | ### Available Commands 58 | 59 | - `/start`: Initializes the bot and provides a welcome message. 60 | - `/wallets`: Displays all wallets associated with your account. 61 | - `/positions`: Shows your current token positions. 62 | - `/pendingsnipes`: Lists all your pending sniping operations. 63 | 64 | ### License 65 | 66 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 67 | 68 | ### Contributions 69 | 70 | Contributions are welcome! If you have ideas for improvements or new features, feel free to fork the repository and submit a pull request. 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tron-sniper", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "start": "tsx src/index.ts" 6 | }, 7 | "author": "impredmet", 8 | "license": "MIT", 9 | "description": "The ultimate bot for sniping and trading on TRON.", 10 | "dependencies": { 11 | "@noble/secp256k1": "^2.1.0", 12 | "bignumber.js": "^9.1.2", 13 | "colors": "^1.4.0", 14 | "dotenv": "^16.4.5", 15 | "ethers": "^6.13.2", 16 | "mongodb": "^6.8.0", 17 | "node-telegram-bot-api": "^0.66.0", 18 | "tronweb": "^5.3.2" 19 | }, 20 | "devDependencies": { 21 | "@types/node-telegram-bot-api": "^0.64.7", 22 | "tsx": "^4.17.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/callbacks/tokens.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from "bignumber.js"; 2 | import TelegramBot from "node-telegram-bot-api"; 3 | import { TRC20_ABI, WTRX_DECIMALS } from "../config"; 4 | import { formatElapsedTime, formatNumber } from "../utils/format"; 5 | import { errorLOG } from "../utils/logs"; 6 | import SniperUtils from "../utils/tronWeb"; 7 | 8 | /* ------------------------------ */ 9 | /* BUY PART */ 10 | /* ------------------------------ */ 11 | 12 | export async function tokenSentCallback( 13 | bot: TelegramBot, 14 | chatId: number, 15 | tokenAddress: string 16 | ) { 17 | try { 18 | const tokenContract = await SniperUtils.getContractInstance( 19 | TRC20_ABI, 20 | tokenAddress 21 | ); 22 | 23 | if (!tokenContract) { 24 | bot.sendMessage(chatId, "Invalid token address.", { 25 | reply_markup: { 26 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 27 | }, 28 | }); 29 | return; 30 | } 31 | 32 | const testContract = await tokenContract.name().call(); 33 | 34 | if (!testContract) { 35 | bot.sendMessage(chatId, "Invalid token address.", { 36 | reply_markup: { 37 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 38 | }, 39 | }); 40 | return; 41 | } 42 | 43 | const pairAddress = await SniperUtils.getPairAddress(tokenAddress); 44 | 45 | if (!pairAddress) { 46 | bot.sendMessage(chatId, `Would you like to snipe ${testContract}?`, { 47 | reply_markup: { 48 | inline_keyboard: [ 49 | [ 50 | { 51 | text: "🔥 Snipe", 52 | callback_data: `snipe_${tokenAddress}`, 53 | }, 54 | ], 55 | [ 56 | { 57 | text: "❌ Close", 58 | callback_data: "close", 59 | }, 60 | ], 61 | ], 62 | }, 63 | }); 64 | return; 65 | } 66 | 67 | const tokenInformations = await SniperUtils.getTokenInformations( 68 | pairAddress 69 | ); 70 | 71 | if (!tokenInformations) { 72 | bot.sendMessage(chatId, "Invalid token address.", { 73 | reply_markup: { 74 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 75 | }, 76 | }); 77 | return; 78 | } 79 | 80 | const slippagePercentage = 10; 81 | 82 | bot.sendMessage( 83 | chatId, 84 | `💎 *${tokenInformations.name}* (${ 85 | tokenInformations.symbol 86 | }) | ⏰ ${formatElapsedTime(new Date(tokenInformations.pairCreatedAt))} 87 | 88 | 💰 *Market Cap*: $${formatNumber(tokenInformations.marketCapInUSD)} 89 | 💧 *Liquidity*: $${formatNumber(tokenInformations.liquidityInUSD)} 90 | 📊 *24h Volume*: $${formatNumber(tokenInformations.volumeInUSD)} 91 | 92 | 💵 *Price*: $${tokenInformations.tokenPriceInUSD.toLocaleString()} 93 | 94 | ⚖️ *Slippage*: ${slippagePercentage}%`, 95 | { 96 | reply_markup: { 97 | inline_keyboard: [ 98 | [ 99 | { 100 | text: "🔹 Buy 100 TRX", 101 | callback_data: `enter_10_${tokenAddress}_${slippagePercentage}`, 102 | }, 103 | { 104 | text: "🔹 Buy 1,000 TRX", 105 | callback_data: `enter_100_${tokenAddress}_${slippagePercentage}`, 106 | }, 107 | ], 108 | [ 109 | { 110 | text: "🔹 Buy 10,000 TRX", 111 | callback_data: `enter_1000_${tokenAddress}_${slippagePercentage}`, 112 | }, 113 | { 114 | text: "🔸 Buy custom amount", 115 | callback_data: `enter_custom_${tokenAddress}_${slippagePercentage}`, 116 | }, 117 | ], 118 | [ 119 | { 120 | text: "🔄 Refresh", 121 | callback_data: `refreshbuy_${tokenAddress}_${slippagePercentage}`, 122 | }, 123 | ], 124 | [ 125 | { 126 | text: "⚙️ Change slippage", 127 | callback_data: `change_slippage_${tokenAddress}`, 128 | }, 129 | ], 130 | ], 131 | }, 132 | parse_mode: "Markdown", 133 | } 134 | ); 135 | } catch (error) { 136 | console.error(`${errorLOG} ${error}`); 137 | bot.sendMessage(chatId, "Invalid token address.", { 138 | reply_markup: { 139 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 140 | }, 141 | }); 142 | } 143 | } 144 | 145 | export async function enterAmountCallback( 146 | bot: TelegramBot, 147 | chatId: number, 148 | tokenAddress: string, 149 | slippage: number 150 | ) { 151 | try { 152 | const text = `Enter the amount of TRX you want to spend on this token.`; 153 | 154 | bot 155 | .sendMessage(chatId, text, { 156 | reply_markup: { 157 | force_reply: true, 158 | }, 159 | }) 160 | .then((msg) => { 161 | bot.onReplyToMessage(chatId, msg.message_id, (reply) => { 162 | const amount = reply.text; 163 | 164 | if (!amount) { 165 | bot.sendMessage(chatId, "Invalid amount.", { 166 | reply_markup: { 167 | inline_keyboard: [ 168 | [{ text: "❌ Close", callback_data: "close" }], 169 | ], 170 | }, 171 | }); 172 | return; 173 | } 174 | 175 | const parsedNumber = parseInt(amount); 176 | 177 | if (isNaN(parsedNumber)) { 178 | bot.sendMessage(chatId, "Invalid amount.", { 179 | reply_markup: { 180 | inline_keyboard: [ 181 | [{ text: "❌ Close", callback_data: "close" }], 182 | ], 183 | }, 184 | }); 185 | return; 186 | } 187 | 188 | if (parsedNumber <= 0) { 189 | bot.sendMessage(chatId, "Amount must be greater than 0.", { 190 | reply_markup: { 191 | inline_keyboard: [ 192 | [{ text: "❌ Close", callback_data: "close" }], 193 | ], 194 | }, 195 | }); 196 | return; 197 | } 198 | 199 | bot.sendMessage( 200 | chatId, 201 | `You will spend ${parsedNumber} TRX on this token. Confirm?`, 202 | { 203 | reply_markup: { 204 | inline_keyboard: [ 205 | [ 206 | { 207 | text: "✅ Confirm", 208 | callback_data: `enter_${parsedNumber}_${tokenAddress}_${slippage}`, 209 | }, 210 | { 211 | text: "❌ Cancel", 212 | callback_data: `close`, 213 | }, 214 | ], 215 | ], 216 | }, 217 | } 218 | ); 219 | }); 220 | }); 221 | } catch (error) { 222 | console.error(`${errorLOG} ${error}`); 223 | bot.sendMessage(chatId, "An error occurred while entering the amount.", { 224 | reply_markup: { 225 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 226 | }, 227 | }); 228 | } 229 | } 230 | 231 | export async function changeSlippageCallback( 232 | bot: TelegramBot, 233 | chatId: number, 234 | tokenAddress: string, 235 | message: TelegramBot.Message 236 | ) { 237 | try { 238 | const text = `Enter the slippage percentage you want to use.`; 239 | 240 | bot 241 | .sendMessage(chatId, text, { 242 | reply_markup: { 243 | force_reply: true, 244 | }, 245 | }) 246 | .then((msg) => { 247 | bot.onReplyToMessage(chatId, msg.message_id, async (reply) => { 248 | const slippageText = reply.text; 249 | 250 | if (!slippageText) { 251 | bot.sendMessage(chatId, "Invalid slippage.", { 252 | reply_markup: { 253 | inline_keyboard: [ 254 | [{ text: "❌ Close", callback_data: "close" }], 255 | ], 256 | }, 257 | }); 258 | return; 259 | } 260 | 261 | const slippage = parseInt(slippageText); 262 | 263 | if (isNaN(slippage)) { 264 | bot.sendMessage(chatId, "Invalid slippage.", { 265 | reply_markup: { 266 | inline_keyboard: [ 267 | [{ text: "❌ Close", callback_data: "close" }], 268 | ], 269 | }, 270 | }); 271 | return; 272 | } 273 | 274 | if (slippage < 0 || slippage > 100) { 275 | bot.sendMessage(chatId, "Slippage must be between 0 and 100.", { 276 | reply_markup: { 277 | inline_keyboard: [ 278 | [{ text: "❌ Close", callback_data: "close" }], 279 | ], 280 | }, 281 | }); 282 | return; 283 | } 284 | 285 | const pairAddress = await SniperUtils.getPairAddress(tokenAddress); 286 | 287 | if (!pairAddress) { 288 | bot.sendMessage(chatId, "No pair found for this token.", { 289 | reply_markup: { 290 | inline_keyboard: [ 291 | [{ text: "❌ Close", callback_data: "close" }], 292 | ], 293 | }, 294 | }); 295 | return; 296 | } 297 | 298 | const tokenInformations = await SniperUtils.getTokenInformations( 299 | pairAddress 300 | ); 301 | 302 | if (!tokenInformations) { 303 | bot.sendMessage(chatId, "Token informations not found.", { 304 | reply_markup: { 305 | inline_keyboard: [ 306 | [{ text: "❌ Close", callback_data: "close" }], 307 | ], 308 | }, 309 | }); 310 | return; 311 | } 312 | 313 | bot.editMessageText( 314 | `💎 *${tokenInformations.name}* (${ 315 | tokenInformations.symbol 316 | }) | ⏰ ${formatElapsedTime( 317 | new Date(tokenInformations.pairCreatedAt) 318 | )} 319 | 320 | 💰 *Market Cap*: $${formatNumber(tokenInformations.marketCapInUSD)} 321 | 💧 *Liquidity*: $${formatNumber(tokenInformations.liquidityInUSD)} 322 | 📊 *24h Volume*: $${formatNumber(tokenInformations.volumeInUSD)} 323 | 324 | 💵 *Price*: $${tokenInformations.tokenPriceInUSD.toLocaleString()} 325 | 326 | ⚖️ *Slippage*: ${slippage}%`, 327 | { 328 | chat_id: chatId, 329 | message_id: message.message_id, 330 | reply_markup: { 331 | inline_keyboard: [ 332 | [ 333 | { 334 | text: "🔹 Buy 100 TRX", 335 | callback_data: `enter_10_${tokenAddress}_${slippage}`, 336 | }, 337 | { 338 | text: "🔹 Buy 1,000 TRX", 339 | callback_data: `enter_100_${tokenAddress}_${slippage}`, 340 | }, 341 | ], 342 | [ 343 | { 344 | text: "🔹 Buy 10,000 TRX", 345 | callback_data: `enter_1000_${tokenAddress}_${slippage}`, 346 | }, 347 | { 348 | text: "🔸 Buy custom amount", 349 | callback_data: `enter_custom_${tokenAddress}_${slippage}`, 350 | }, 351 | ], 352 | [ 353 | { 354 | text: "🔄 Refresh", 355 | callback_data: `refreshbuy_${tokenAddress}_${slippage}`, 356 | }, 357 | ], 358 | [ 359 | { 360 | text: "⚙️ Change slippage", 361 | callback_data: `change_slippage_${tokenAddress}`, 362 | }, 363 | ], 364 | ], 365 | }, 366 | parse_mode: "Markdown", 367 | } 368 | ); 369 | 370 | bot.sendMessage( 371 | chatId, 372 | "Slippage changed successfully to " + slippage + "%", 373 | { 374 | reply_markup: { 375 | inline_keyboard: [ 376 | [{ text: "❌ Close", callback_data: "close" }], 377 | ], 378 | }, 379 | } 380 | ); 381 | }); 382 | }); 383 | } catch (error) { 384 | console.error(`${errorLOG} ${error}`); 385 | bot.sendMessage(chatId, "An error occurred while changing the slippage.", { 386 | reply_markup: { 387 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 388 | }, 389 | }); 390 | } 391 | } 392 | 393 | export async function refreshCallback( 394 | bot: TelegramBot, 395 | chatId: number, 396 | tokenAddress: string, 397 | slippage: number, 398 | message: TelegramBot.Message 399 | ) { 400 | try { 401 | const pairAddress = await SniperUtils.getPairAddress(tokenAddress); 402 | 403 | if (!pairAddress) { 404 | bot.sendMessage(chatId, "No pair found for this token.", { 405 | reply_markup: { 406 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 407 | }, 408 | }); 409 | return; 410 | } 411 | 412 | const tokenInformations = await SniperUtils.getTokenInformations( 413 | pairAddress 414 | ); 415 | 416 | if (!tokenInformations) { 417 | bot.sendMessage(chatId, "Token informations not found.", { 418 | reply_markup: { 419 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 420 | }, 421 | }); 422 | return; 423 | } 424 | 425 | bot.editMessageText( 426 | `💎 *${tokenInformations.name}* (${ 427 | tokenInformations.symbol 428 | }) | ⏰ ${formatElapsedTime(new Date(tokenInformations.pairCreatedAt))} 429 | 430 | 💰 *Market Cap*: $${formatNumber(tokenInformations.marketCapInUSD)} 431 | 💧 *Liquidity*: $${formatNumber(tokenInformations.liquidityInUSD)} 432 | 📊 *24h Volume*: $${formatNumber(tokenInformations.volumeInUSD)} 433 | 434 | 💵 *Price*: $${tokenInformations.tokenPriceInUSD.toLocaleString()} 435 | 436 | ⚖️ *Slippage*: ${slippage}%`, 437 | { 438 | chat_id: chatId, 439 | message_id: message.message_id, 440 | reply_markup: { 441 | inline_keyboard: [ 442 | [ 443 | { 444 | text: "🔹 Buy 100 TRX", 445 | callback_data: `enter_10_${tokenAddress}_${slippage}`, 446 | }, 447 | { 448 | text: "🔹 Buy 1,000 TRX", 449 | callback_data: `enter_100_${tokenAddress}_${slippage}`, 450 | }, 451 | ], 452 | [ 453 | { 454 | text: "🔹 Buy 10,000 TRX", 455 | callback_data: `enter_1000_${tokenAddress}_${slippage}`, 456 | }, 457 | { 458 | text: "🔸 Buy custom amount", 459 | callback_data: `enter_custom_${tokenAddress}_${slippage}`, 460 | }, 461 | ], 462 | [ 463 | { 464 | text: "🔄 Refresh", 465 | callback_data: `refreshbuy_${tokenAddress}_${slippage}`, 466 | }, 467 | ], 468 | [ 469 | { 470 | text: "⚙️ Change slippage", 471 | callback_data: `change_slippage_${tokenAddress}`, 472 | }, 473 | ], 474 | ], 475 | }, 476 | parse_mode: "Markdown", 477 | } 478 | ); 479 | } catch (error) { 480 | console.error(`${errorLOG} ${error}`); 481 | bot.sendMessage(chatId, "An error occurred while refreshing the token.", { 482 | reply_markup: { 483 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 484 | }, 485 | }); 486 | } 487 | } 488 | 489 | export async function buyTokenCallback( 490 | user: User, 491 | bot: TelegramBot, 492 | chatId: number, 493 | amount: number, 494 | tokenAddress: string, 495 | slippage: number 496 | ) { 497 | try { 498 | const wallets = user.wallets; 499 | 500 | if (wallets.length === 0) { 501 | bot.sendMessage(chatId, "You have no wallets added.", { 502 | reply_markup: { 503 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 504 | }, 505 | }); 506 | return; 507 | } 508 | 509 | const balances = await Promise.all( 510 | wallets.map(async (wallet) => { 511 | const balance = await SniperUtils.getBalance(wallet.address); 512 | 513 | const balanceTRX = new BigNumber(balance).div( 514 | new BigNumber(10).pow(WTRX_DECIMALS) 515 | ); 516 | 517 | return `💰 *${balanceTRX.toFixed(2)} TRX* in \`${wallet.address}\`\n`; 518 | }) 519 | ); 520 | 521 | const text = `Select a wallet to use: 522 | 523 | ${balances.join("\n")}`; 524 | 525 | bot 526 | .sendMessage(chatId, text, { 527 | reply_markup: { 528 | inline_keyboard: wallets.map((wallet, index) => [ 529 | { 530 | text: wallet.address, 531 | callback_data: `buy_${index}_${amount}_${tokenAddress}_${slippage}`, 532 | }, 533 | ]), 534 | }, 535 | parse_mode: "Markdown", 536 | }) 537 | .then((msg) => { 538 | setTimeout(() => { 539 | bot.deleteMessage(chatId, msg.message_id); 540 | }, 60000); 541 | }); 542 | } catch (error) { 543 | console.error(`${errorLOG} ${error}`); 544 | bot.sendMessage(chatId, "An error occurred while buying the token.", { 545 | reply_markup: { 546 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 547 | }, 548 | }); 549 | } 550 | } 551 | 552 | export async function confirmBuyCallback( 553 | user: User, 554 | bot: TelegramBot, 555 | chatId: number, 556 | walletIndex: number, 557 | amount: number, 558 | tokenAddress: string, 559 | slippage: number 560 | ) { 561 | try { 562 | const wallet = user.wallets[walletIndex]; 563 | 564 | if (!wallet) { 565 | bot.sendMessage(chatId, "Invalid wallet.", { 566 | reply_markup: { 567 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 568 | }, 569 | }); 570 | return; 571 | } 572 | 573 | const balance = await SniperUtils.getBalance(wallet.address); 574 | 575 | const balanceTRX = new BigNumber(balance) 576 | .div(new BigNumber(10).pow(WTRX_DECIMALS)) 577 | .toNumber(); 578 | 579 | if (balanceTRX < amount) { 580 | bot.sendMessage(chatId, "Insufficient balance.", { 581 | reply_markup: { 582 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 583 | }, 584 | }); 585 | return; 586 | } else if (balanceTRX < amount + 75) { 587 | bot.sendMessage( 588 | chatId, 589 | "Insufficient balance to pay for fees. (Balance < Amount + ~75 TRX)", 590 | { 591 | reply_markup: { 592 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 593 | }, 594 | } 595 | ); 596 | return; 597 | } 598 | 599 | const pairAddress = await SniperUtils.getPairAddress(tokenAddress); 600 | 601 | if (!pairAddress) { 602 | bot.sendMessage(chatId, "No pair found for this token.", { 603 | reply_markup: { 604 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 605 | }, 606 | }); 607 | return; 608 | } 609 | 610 | const txID = await SniperUtils.buyToken( 611 | tokenAddress, 612 | pairAddress, 613 | amount, 614 | slippage, 615 | wallet.address, 616 | wallet.privateKey 617 | ); 618 | 619 | if (!txID) { 620 | bot.sendMessage(chatId, "No TXID found.", { 621 | reply_markup: { 622 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 623 | }, 624 | }); 625 | return; 626 | } 627 | 628 | bot.sendMessage( 629 | chatId, 630 | `Transaction sent: [View on Tronscan](https://tronscan.org/#/transaction/${txID})`, 631 | { 632 | parse_mode: "Markdown", 633 | reply_markup: { 634 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 635 | }, 636 | } 637 | ); 638 | } catch (error) { 639 | console.error(`${errorLOG} ${error}`); 640 | bot.sendMessage(chatId, "An error occurred while confirming the buy.", { 641 | reply_markup: { 642 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 643 | }, 644 | }); 645 | } 646 | } 647 | 648 | export async function snipeTokenCallback( 649 | user: User, 650 | bot: TelegramBot, 651 | chatId: number, 652 | tokenAddress: string 653 | ) { 654 | try { 655 | const wallets = user.wallets; 656 | 657 | if (wallets.length === 0) { 658 | bot.sendMessage(chatId, "You have no wallets added.", { 659 | reply_markup: { 660 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 661 | }, 662 | }); 663 | return; 664 | } 665 | 666 | const balances = await Promise.all( 667 | wallets.map(async (wallet) => { 668 | const balance = await SniperUtils.getBalance(wallet.address); 669 | 670 | const balanceTRX = new BigNumber(balance).div( 671 | new BigNumber(10).pow(WTRX_DECIMALS) 672 | ); 673 | 674 | return `💰 *${balanceTRX.toFixed(2)} TRX* in \`${wallet.address}\`\n`; 675 | }) 676 | ); 677 | 678 | const text = `Select a wallet to use: 679 | 680 | ${balances.join("\n")}`; 681 | 682 | bot 683 | .sendMessage(chatId, text, { 684 | reply_markup: { 685 | inline_keyboard: wallets.map((wallet, index) => [ 686 | { 687 | text: wallet.address, 688 | callback_data: `snipenow_${index}_${tokenAddress}`, 689 | }, 690 | ]), 691 | }, 692 | parse_mode: "Markdown", 693 | }) 694 | .then((msg) => { 695 | setTimeout(() => { 696 | bot.deleteMessage(chatId, msg.message_id); 697 | }, 60000); 698 | }); 699 | } catch (error) { 700 | console.error(`${errorLOG} ${error}`); 701 | bot.sendMessage(chatId, "An error occurred while sniping the token.", { 702 | reply_markup: { 703 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 704 | }, 705 | }); 706 | } 707 | } 708 | 709 | export async function snipeNowCallback( 710 | usersCollection: any, 711 | user: User, 712 | bot: TelegramBot, 713 | chatId: number, 714 | walletIndex: number, 715 | tokenAddress: string 716 | ) { 717 | try { 718 | const wallet = user.wallets[walletIndex]; 719 | 720 | if (!wallet) { 721 | bot.sendMessage(chatId, "Invalid wallet.", { 722 | reply_markup: { 723 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 724 | }, 725 | }); 726 | return; 727 | } 728 | 729 | const text = `Enter the slippage percentage you want to use.`; 730 | 731 | bot 732 | .sendMessage(chatId, text, { 733 | reply_markup: { 734 | force_reply: true, 735 | }, 736 | }) 737 | .then((msg) => { 738 | bot.onReplyToMessage(chatId, msg.message_id, async (reply) => { 739 | const slippageText = reply.text; 740 | 741 | if (!slippageText) { 742 | bot.sendMessage(chatId, "Invalid slippage.", { 743 | reply_markup: { 744 | inline_keyboard: [ 745 | [{ text: "❌ Close", callback_data: "close" }], 746 | ], 747 | }, 748 | }); 749 | return; 750 | } 751 | 752 | const slippage = parseInt(slippageText); 753 | 754 | if (isNaN(slippage)) { 755 | bot.sendMessage(chatId, "Invalid slippage.", { 756 | reply_markup: { 757 | inline_keyboard: [ 758 | [{ text: "❌ Close", callback_data: "close" }], 759 | ], 760 | }, 761 | }); 762 | return; 763 | } 764 | 765 | if (slippage < 0 || slippage > 100) { 766 | bot.sendMessage(chatId, "Slippage must be between 0 and 100.", { 767 | reply_markup: { 768 | inline_keyboard: [ 769 | [{ text: "❌ Close", callback_data: "close" }], 770 | ], 771 | }, 772 | }); 773 | return; 774 | } 775 | 776 | const text = `Enter the amount of TRX you want to spend on this token.`; 777 | 778 | bot 779 | .sendMessage(chatId, text, { 780 | reply_markup: { 781 | force_reply: true, 782 | }, 783 | }) 784 | .then((msg) => { 785 | bot.onReplyToMessage(chatId, msg.message_id, async (reply) => { 786 | const amount = reply.text; 787 | 788 | if (!amount) { 789 | bot.sendMessage(chatId, "Invalid amount.", { 790 | reply_markup: { 791 | inline_keyboard: [ 792 | [{ text: "❌ Close", callback_data: "close" }], 793 | ], 794 | }, 795 | }); 796 | return; 797 | } 798 | 799 | const parsedNumber = parseInt(amount); 800 | 801 | if (isNaN(parsedNumber)) { 802 | bot.sendMessage(chatId, "Invalid amount.", { 803 | reply_markup: { 804 | inline_keyboard: [ 805 | [{ text: "❌ Close", callback_data: "close" }], 806 | ], 807 | }, 808 | }); 809 | return; 810 | } 811 | 812 | if (parsedNumber <= 0) { 813 | bot.sendMessage(chatId, "Amount must be greater than 0.", { 814 | reply_markup: { 815 | inline_keyboard: [ 816 | [{ text: "❌ Close", callback_data: "close" }], 817 | ], 818 | }, 819 | }); 820 | return; 821 | } 822 | 823 | const balance = await SniperUtils.getBalance(wallet.address); 824 | 825 | const balanceTRX = new BigNumber(balance) 826 | .div(new BigNumber(10).pow(WTRX_DECIMALS)) 827 | .toNumber(); 828 | 829 | if (balanceTRX < parsedNumber) { 830 | bot.sendMessage(chatId, "Insufficient balance.", { 831 | reply_markup: { 832 | inline_keyboard: [ 833 | [{ text: "❌ Close", callback_data: "close" }], 834 | ], 835 | }, 836 | }); 837 | return; 838 | } else if (balanceTRX < parsedNumber + 75) { 839 | bot.sendMessage( 840 | chatId, 841 | "Insufficient balance to pay for fees. (Balance < Amount + ~75 TRX)", 842 | { 843 | reply_markup: { 844 | inline_keyboard: [ 845 | [{ text: "❌ Close", callback_data: "close" }], 846 | ], 847 | }, 848 | } 849 | ); 850 | return; 851 | } 852 | 853 | const dataToInsert = { 854 | address: tokenAddress, 855 | amountToInvestInTRX: parsedNumber, 856 | privateKey: wallet.privateKey, 857 | slippage: slippage, 858 | } as Snipe; 859 | 860 | await usersCollection.updateOne( 861 | { id: chatId }, 862 | { 863 | $push: { 864 | snipes: dataToInsert, 865 | }, 866 | } 867 | ); 868 | 869 | bot.sendMessage( 870 | chatId, 871 | `Snipe added successfully. You will snipe ${parsedNumber} TRX on this token with ${slippage}% slippage.`, 872 | { 873 | parse_mode: "Markdown", 874 | reply_markup: { 875 | inline_keyboard: [ 876 | [ 877 | { 878 | text: "❌ Cancel Snipe", 879 | callback_data: `cancel_snipe_${tokenAddress}`, 880 | }, 881 | ], 882 | [{ text: "❌ Close", callback_data: "close" }], 883 | ], 884 | }, 885 | } 886 | ); 887 | }); 888 | }); 889 | }); 890 | }); 891 | } catch (error) { 892 | console.error(`${errorLOG} ${error}`); 893 | bot.sendMessage(chatId, "An error occurred while sniping the token.", { 894 | reply_markup: { 895 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 896 | }, 897 | }); 898 | } 899 | } 900 | 901 | export async function cancelSnipeCallback( 902 | usersCollection: any, 903 | bot: TelegramBot, 904 | chatId: number, 905 | tokenAddress: string 906 | ) { 907 | try { 908 | const dataToDelete = { 909 | address: tokenAddress, 910 | } as Snipe; 911 | 912 | await usersCollection.updateOne( 913 | { id: chatId }, 914 | { 915 | $pull: { 916 | snipes: dataToDelete, 917 | }, 918 | } 919 | ); 920 | 921 | bot.sendMessage(chatId, "Snipe canceled successfully.", { 922 | reply_markup: { 923 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 924 | }, 925 | }); 926 | } catch (error) { 927 | console.error(`${errorLOG} ${error}`); 928 | bot.sendMessage(chatId, "An error occurred while canceling the snipe.", { 929 | reply_markup: { 930 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 931 | }, 932 | }); 933 | } 934 | } 935 | 936 | export async function mySnipesCallback( 937 | user: User, 938 | bot: TelegramBot, 939 | chatId: number 940 | ) { 941 | try { 942 | const snipes = user.snipes; 943 | 944 | if (snipes.length === 0) { 945 | bot.sendMessage(chatId, "You have no pending snipes.", { 946 | reply_markup: { 947 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 948 | }, 949 | }); 950 | return; 951 | } 952 | 953 | for (const snipe of snipes) { 954 | const tokenContract = await SniperUtils.getContractInstance( 955 | TRC20_ABI, 956 | snipe.address 957 | ); 958 | 959 | if (!tokenContract) continue; 960 | 961 | const name = await tokenContract.name().call(); 962 | const symbol = await tokenContract.symbol().call(); 963 | 964 | bot.sendMessage( 965 | chatId, 966 | `🎯 *${name}* (${symbol}) | 💰 ${snipe.amountToInvestInTRX} TRX | ⚖️ ${snipe.slippage}%`, 967 | { 968 | reply_markup: { 969 | inline_keyboard: [ 970 | [ 971 | { 972 | text: "❌ Cancel", 973 | callback_data: `cancel_snipe_${snipe.address}`, 974 | }, 975 | ], 976 | ], 977 | }, 978 | parse_mode: "Markdown", 979 | } 980 | ); 981 | } 982 | } catch (error) { 983 | console.error(`${errorLOG} ${error}`); 984 | bot.sendMessage(chatId, "An error occurred while fetching your snipes.", { 985 | reply_markup: { 986 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 987 | }, 988 | }); 989 | } 990 | } 991 | 992 | /* ------------------------------ */ 993 | /* SELL PART */ 994 | /* ------------------------------ */ 995 | 996 | export async function myPositionsCallback( 997 | user: User, 998 | bot: TelegramBot, 999 | chatId: number 1000 | ) { 1001 | try { 1002 | const wallets = user.wallets; 1003 | 1004 | if (wallets.length === 0) { 1005 | bot.sendMessage(chatId, "You have no wallets added.", { 1006 | reply_markup: { 1007 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1008 | }, 1009 | }); 1010 | return; 1011 | } 1012 | 1013 | let positionsFound = false; 1014 | for (const walletIndex in wallets) { 1015 | const wallet = wallets[walletIndex]; 1016 | const positions = (await SniperUtils.getTokensBalance( 1017 | wallet.address 1018 | )) as Token[]; 1019 | 1020 | if (positions.length === 0) continue; 1021 | 1022 | for (const position of positions) { 1023 | const positionContract = await SniperUtils.getContractInstance( 1024 | TRC20_ABI, 1025 | position.address 1026 | ); 1027 | 1028 | if (!positionContract) continue; 1029 | 1030 | const decimals = await positionContract.decimals().call(); 1031 | 1032 | if (!decimals) continue; 1033 | 1034 | const balance = new BigNumber(position.balance) 1035 | .div(new BigNumber(10).pow(decimals)) 1036 | .toNumber() 1037 | .toFixed(3); 1038 | 1039 | if (Number(balance) === 0) continue; 1040 | 1041 | const pairAddress = await SniperUtils.getPairAddress(position.address); 1042 | 1043 | if (!pairAddress) { 1044 | bot.sendMessage( 1045 | chatId, 1046 | `Pair not found for ${position.name} (${position.symbol}).`, 1047 | { 1048 | reply_markup: { 1049 | inline_keyboard: [ 1050 | [{ text: "❌ Close", callback_data: "close" }], 1051 | ], 1052 | }, 1053 | } 1054 | ); 1055 | continue; 1056 | } 1057 | 1058 | const tokenInformations = await SniperUtils.getTokenInformations( 1059 | pairAddress 1060 | ); 1061 | 1062 | if (!tokenInformations) { 1063 | bot.sendMessage( 1064 | chatId, 1065 | `Unable to retrieve information for ${position.name} (${position.symbol}).`, 1066 | { 1067 | reply_markup: { 1068 | inline_keyboard: [ 1069 | [{ text: "❌ Close", callback_data: "close" }], 1070 | ], 1071 | }, 1072 | } 1073 | ); 1074 | continue; 1075 | } 1076 | 1077 | const slippagePercentage = 10; 1078 | 1079 | bot.sendMessage( 1080 | chatId, 1081 | `💎 *${tokenInformations.name}* (${ 1082 | tokenInformations.symbol 1083 | }) | ⏰ ${formatElapsedTime( 1084 | new Date(tokenInformations.pairCreatedAt) 1085 | )} 1086 | 1087 | 💰 *Market Cap*: $${formatNumber(tokenInformations.marketCapInUSD)} 1088 | 💧 *Liquidity*: $${formatNumber(tokenInformations.liquidityInUSD)} 1089 | 📊 *24h Volume*: $${formatNumber(tokenInformations.volumeInUSD)} 1090 | 1091 | 💵 *Price*: $${tokenInformations.tokenPriceInUSD.toLocaleString()} 1092 | 🎒 *Your Balance*: ${balance} ${position.symbol} 1093 | 1094 | ⚖️ *Slippage*: ${slippagePercentage}%`, 1095 | { 1096 | reply_markup: { 1097 | inline_keyboard: [ 1098 | [ 1099 | { 1100 | text: "🔻 Sell 100%", 1101 | callback_data: `sell_100_${walletIndex}_${position.address}_${slippagePercentage}`, 1102 | }, 1103 | { 1104 | text: "🔻 Sell 50%", 1105 | callback_data: `sell_50_${walletIndex}_${position.address}_${slippagePercentage}`, 1106 | }, 1107 | ], 1108 | [ 1109 | { 1110 | text: "🔻 Sell Custom %", 1111 | callback_data: `sellcustom_${walletIndex}_${position.address}_${slippagePercentage}`, 1112 | }, 1113 | ], 1114 | [ 1115 | { 1116 | text: "🔄 Refresh", 1117 | callback_data: `refreshsell_${walletIndex}_${position.address}_${slippagePercentage}`, 1118 | }, 1119 | ], 1120 | [ 1121 | { 1122 | text: "⚙️ Change slippage", 1123 | callback_data: `change_slippagesell_${walletIndex}_${position.address}`, 1124 | }, 1125 | ], 1126 | ], 1127 | }, 1128 | parse_mode: "Markdown", 1129 | } 1130 | ); 1131 | 1132 | positionsFound = true; 1133 | } 1134 | } 1135 | 1136 | if (!positionsFound) { 1137 | bot.sendMessage(chatId, "No positions found.", { 1138 | reply_markup: { 1139 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1140 | }, 1141 | }); 1142 | } 1143 | } catch (error) { 1144 | console.error(`${errorLOG} ${error}`); 1145 | bot.sendMessage( 1146 | chatId, 1147 | "An error occurred while fetching your positions.", 1148 | { 1149 | reply_markup: { 1150 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1151 | }, 1152 | } 1153 | ); 1154 | } 1155 | } 1156 | 1157 | export async function refreshSellCallback( 1158 | user: User, 1159 | bot: TelegramBot, 1160 | chatId: number, 1161 | walletIndex: number, 1162 | tokenAddress: string, 1163 | slippage: number, 1164 | message: TelegramBot.Message 1165 | ) { 1166 | try { 1167 | const wallets = user.wallets; 1168 | const wallet = wallets[walletIndex]; 1169 | 1170 | const positions = (await SniperUtils.getTokensBalance( 1171 | wallet.address 1172 | )) as Token[]; 1173 | 1174 | if (positions.length === 0) { 1175 | bot.sendMessage(chatId, "No positions found.", { 1176 | reply_markup: { 1177 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1178 | }, 1179 | }); 1180 | return; 1181 | } 1182 | 1183 | const position = positions.find( 1184 | (position) => position.address === tokenAddress 1185 | ); 1186 | 1187 | if (!position) { 1188 | bot.sendMessage(chatId, "Position not found.", { 1189 | reply_markup: { 1190 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1191 | }, 1192 | }); 1193 | return; 1194 | } 1195 | 1196 | const positionContract = await SniperUtils.getContractInstance( 1197 | TRC20_ABI, 1198 | position.address 1199 | ); 1200 | 1201 | if (!positionContract) { 1202 | bot.sendMessage(chatId, "No contract found.", { 1203 | reply_markup: { 1204 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1205 | }, 1206 | }); 1207 | return; 1208 | } 1209 | 1210 | const decimals = await positionContract.decimals().call(); 1211 | 1212 | if (!decimals) { 1213 | bot.sendMessage(chatId, "No decimals found.", { 1214 | reply_markup: { 1215 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1216 | }, 1217 | }); 1218 | return; 1219 | } 1220 | 1221 | const balance = new BigNumber(position.balance) 1222 | .div(new BigNumber(10).pow(decimals)) 1223 | .toNumber() 1224 | .toFixed(3); 1225 | 1226 | const pairAddress = await SniperUtils.getPairAddress(position.address); 1227 | 1228 | if (!pairAddress) { 1229 | bot.sendMessage(chatId, "No pair found for this token.", { 1230 | reply_markup: { 1231 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1232 | }, 1233 | }); 1234 | return; 1235 | } 1236 | 1237 | const tokenInformations = await SniperUtils.getTokenInformations( 1238 | pairAddress 1239 | ); 1240 | 1241 | if (!tokenInformations) { 1242 | bot.sendMessage(chatId, "No token informations found.", { 1243 | reply_markup: { 1244 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1245 | }, 1246 | }); 1247 | return; 1248 | } 1249 | 1250 | bot.editMessageText( 1251 | `💎 *${tokenInformations.name}* (${ 1252 | tokenInformations.symbol 1253 | }) | ⏰ ${formatElapsedTime(new Date(tokenInformations.pairCreatedAt))} 1254 | 1255 | 💰 *Market Cap*: $${formatNumber(tokenInformations.marketCapInUSD)} 1256 | 💧 *Liquidity*: $${formatNumber(tokenInformations.liquidityInUSD)} 1257 | 📊 *24h Volume*: $${formatNumber(tokenInformations.volumeInUSD)} 1258 | 1259 | 💵 *Price*: $${tokenInformations.tokenPriceInUSD.toLocaleString()} 1260 | 🎒 *Your Balance*: ${balance} ${position.symbol} 1261 | 1262 | ⚖️ *Slippage*: ${slippage}%`, 1263 | { 1264 | chat_id: chatId, 1265 | message_id: message.message_id, 1266 | reply_markup: { 1267 | inline_keyboard: [ 1268 | [ 1269 | { 1270 | text: "🔻 Sell 100%", 1271 | callback_data: `sell_100_${walletIndex}_${tokenAddress}_${slippage}`, 1272 | }, 1273 | { 1274 | text: "🔻 Sell 50%", 1275 | callback_data: `sell_50_${walletIndex}_${tokenAddress}_${slippage}`, 1276 | }, 1277 | ], 1278 | [ 1279 | { 1280 | text: "🔻 Sell Custom %", 1281 | callback_data: `sellcustom_${walletIndex}_${tokenAddress}_${slippage}`, 1282 | }, 1283 | ], 1284 | [ 1285 | { 1286 | text: "🔄 Refresh", 1287 | callback_data: `refreshsell_${walletIndex}_${tokenAddress}_${slippage}`, 1288 | }, 1289 | ], 1290 | [ 1291 | { 1292 | text: "⚙️ Change slippage", 1293 | callback_data: `change_slippagesell_${walletIndex}_${tokenAddress}`, 1294 | }, 1295 | ], 1296 | ], 1297 | }, 1298 | parse_mode: "Markdown", 1299 | } 1300 | ); 1301 | } catch (error) { 1302 | console.error(`${errorLOG} ${error}`); 1303 | bot.sendMessage(chatId, "An error occurred while refreshing the token.", { 1304 | reply_markup: { 1305 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1306 | }, 1307 | }); 1308 | } 1309 | } 1310 | 1311 | export async function changeSlippageSellCallback( 1312 | user: User, 1313 | bot: TelegramBot, 1314 | chatId: number, 1315 | walletIndex: number, 1316 | tokenAddress: string, 1317 | message: TelegramBot.Message 1318 | ) { 1319 | try { 1320 | const wallets = user.wallets; 1321 | const wallet = wallets[walletIndex]; 1322 | 1323 | const positions = (await SniperUtils.getTokensBalance( 1324 | wallet.address 1325 | )) as Token[]; 1326 | 1327 | if (positions.length === 0) { 1328 | bot.sendMessage(chatId, "No positions found.", { 1329 | reply_markup: { 1330 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1331 | }, 1332 | }); 1333 | return; 1334 | } 1335 | 1336 | const position = positions.find( 1337 | (position) => position.address === tokenAddress 1338 | ); 1339 | 1340 | if (!position) { 1341 | bot.sendMessage(chatId, "Position not found.", { 1342 | reply_markup: { 1343 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1344 | }, 1345 | }); 1346 | return; 1347 | } 1348 | 1349 | const positionContract = await SniperUtils.getContractInstance( 1350 | TRC20_ABI, 1351 | position.address 1352 | ); 1353 | 1354 | if (!positionContract) { 1355 | bot.sendMessage(chatId, "No contract found.", { 1356 | reply_markup: { 1357 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1358 | }, 1359 | }); 1360 | return; 1361 | } 1362 | 1363 | const decimals = await positionContract.decimals().call(); 1364 | 1365 | if (!decimals) { 1366 | bot.sendMessage(chatId, "No decimals found.", { 1367 | reply_markup: { 1368 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1369 | }, 1370 | }); 1371 | return; 1372 | } 1373 | 1374 | const balance = new BigNumber(position.balance) 1375 | .div(new BigNumber(10).pow(decimals)) 1376 | .toNumber() 1377 | .toFixed(3); 1378 | 1379 | const pairAddress = await SniperUtils.getPairAddress(position.address); 1380 | 1381 | if (!pairAddress) { 1382 | bot.sendMessage(chatId, "No pair found for this token.", { 1383 | reply_markup: { 1384 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1385 | }, 1386 | }); 1387 | return; 1388 | } 1389 | 1390 | const tokenInformations = await SniperUtils.getTokenInformations( 1391 | pairAddress 1392 | ); 1393 | 1394 | if (!tokenInformations) { 1395 | bot.sendMessage(chatId, "No token informations found.", { 1396 | reply_markup: { 1397 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1398 | }, 1399 | }); 1400 | return; 1401 | } 1402 | 1403 | const text = `Enter the slippage percentage you want to use.`; 1404 | 1405 | bot 1406 | .sendMessage(chatId, text, { 1407 | reply_markup: { 1408 | force_reply: true, 1409 | }, 1410 | }) 1411 | .then((msg) => { 1412 | bot.onReplyToMessage(chatId, msg.message_id, async (reply) => { 1413 | const slippageText = reply.text; 1414 | 1415 | if (!slippageText) { 1416 | bot.sendMessage(chatId, "Invalid slippage.", { 1417 | reply_markup: { 1418 | inline_keyboard: [ 1419 | [{ text: "❌ Close", callback_data: "close" }], 1420 | ], 1421 | }, 1422 | }); 1423 | return; 1424 | } 1425 | 1426 | const slippage = parseInt(slippageText); 1427 | 1428 | if (isNaN(slippage)) { 1429 | bot.sendMessage(chatId, "Invalid slippage.", { 1430 | reply_markup: { 1431 | inline_keyboard: [ 1432 | [{ text: "❌ Close", callback_data: "close" }], 1433 | ], 1434 | }, 1435 | }); 1436 | return; 1437 | } 1438 | 1439 | if (slippage < 0 || slippage > 100) { 1440 | bot.sendMessage(chatId, "Slippage must be between 0 and 100.", { 1441 | reply_markup: { 1442 | inline_keyboard: [ 1443 | [{ text: "❌ Close", callback_data: "close" }], 1444 | ], 1445 | }, 1446 | }); 1447 | return; 1448 | } 1449 | 1450 | bot.editMessageText( 1451 | `💎 *${tokenInformations.name}* (${ 1452 | tokenInformations.symbol 1453 | }) | ⏰ ${formatElapsedTime( 1454 | new Date(tokenInformations.pairCreatedAt) 1455 | )} 1456 | 1457 | 💰 *Market Cap*: $${formatNumber(tokenInformations.marketCapInUSD)} 1458 | 💧 *Liquidity*: $${formatNumber(tokenInformations.liquidityInUSD)} 1459 | 📊 *24h Volume*: $${formatNumber(tokenInformations.volumeInUSD)} 1460 | 1461 | 💵 *Price*: $${tokenInformations.tokenPriceInUSD.toLocaleString()} 1462 | 🎒 *Your Balance*: ${balance} ${position.symbol} 1463 | 1464 | ⚖️ *Slippage*: ${slippage}%`, 1465 | { 1466 | chat_id: chatId, 1467 | message_id: message.message_id, 1468 | reply_markup: { 1469 | inline_keyboard: [ 1470 | [ 1471 | { 1472 | text: "🔻 Sell 100%", 1473 | callback_data: `sell_100_${walletIndex}_${tokenAddress}_${slippage}`, 1474 | }, 1475 | { 1476 | text: "🔻 Sell 50%", 1477 | callback_data: `sell_50_${walletIndex}_${tokenAddress}_${slippage}`, 1478 | }, 1479 | ], 1480 | [ 1481 | { 1482 | text: "🔻 Sell Custom %", 1483 | callback_data: `sellcustom_${walletIndex}_${tokenAddress}_${slippage}`, 1484 | }, 1485 | ], 1486 | [ 1487 | { 1488 | text: "🔄 Refresh", 1489 | callback_data: `refreshsell_${walletIndex}_${tokenAddress}_${slippage}`, 1490 | }, 1491 | ], 1492 | [ 1493 | { 1494 | text: "⚙️ Change slippage", 1495 | callback_data: `change_slippagesell_${walletIndex}_${tokenAddress}`, 1496 | }, 1497 | ], 1498 | ], 1499 | }, 1500 | parse_mode: "Markdown", 1501 | } 1502 | ); 1503 | 1504 | bot.sendMessage( 1505 | chatId, 1506 | "Slippage changed successfully to " + slippage + "%", 1507 | { 1508 | reply_markup: { 1509 | inline_keyboard: [ 1510 | [{ text: "❌ Close", callback_data: "close" }], 1511 | ], 1512 | }, 1513 | } 1514 | ); 1515 | }); 1516 | }); 1517 | } catch (error) { 1518 | console.error(`${errorLOG} ${error}`); 1519 | bot.sendMessage(chatId, "An error occurred while changing the slippage.", { 1520 | reply_markup: { 1521 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1522 | }, 1523 | }); 1524 | } 1525 | } 1526 | 1527 | export async function sellTokenCallback( 1528 | user: User, 1529 | bot: TelegramBot, 1530 | chatId: number, 1531 | walletIndex: number, 1532 | percentageToSell: number, 1533 | tokenAddress: string, 1534 | slippage: number 1535 | ) { 1536 | try { 1537 | const wallet = user.wallets[walletIndex]; 1538 | 1539 | if (!wallet) { 1540 | bot.sendMessage(chatId, "Invalid wallet.", { 1541 | reply_markup: { 1542 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1543 | }, 1544 | }); 1545 | return; 1546 | } 1547 | 1548 | const positions = (await SniperUtils.getTokensBalance( 1549 | wallet.address 1550 | )) as Token[]; 1551 | 1552 | if (positions.length === 0) { 1553 | bot.sendMessage(chatId, "No positions found.", { 1554 | reply_markup: { 1555 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1556 | }, 1557 | }); 1558 | return; 1559 | } 1560 | 1561 | const position = positions.find( 1562 | (position) => position.address === tokenAddress 1563 | ); 1564 | 1565 | if (!position) { 1566 | bot.sendMessage(chatId, "Position not found.", { 1567 | reply_markup: { 1568 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1569 | }, 1570 | }); 1571 | return; 1572 | } 1573 | 1574 | const positionContract = await SniperUtils.getContractInstance( 1575 | TRC20_ABI, 1576 | position.address 1577 | ); 1578 | 1579 | if (!positionContract) { 1580 | bot.sendMessage(chatId, "No contract found.", { 1581 | reply_markup: { 1582 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1583 | }, 1584 | }); 1585 | return; 1586 | } 1587 | 1588 | const decimals = await positionContract.decimals().call(); 1589 | 1590 | if (!decimals) { 1591 | bot.sendMessage(chatId, "No decimals found.", { 1592 | reply_markup: { 1593 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1594 | }, 1595 | }); 1596 | return; 1597 | } 1598 | 1599 | const balance = new BigNumber(position.balance) 1600 | .div(new BigNumber(10).pow(decimals)) 1601 | .toNumber(); 1602 | 1603 | const amount = (balance * percentageToSell) / 100; 1604 | 1605 | if (balance < amount) { 1606 | bot.sendMessage(chatId, "Insufficient balance.", { 1607 | reply_markup: { 1608 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1609 | }, 1610 | }); 1611 | return; 1612 | } 1613 | 1614 | const balanceTRX = await SniperUtils.getBalance(wallet.address); 1615 | const balanceTRXNumber = new BigNumber(balanceTRX) 1616 | .div(new BigNumber(10).pow(WTRX_DECIMALS)) 1617 | .toNumber(); 1618 | 1619 | if (balanceTRXNumber < 75) { 1620 | bot.sendMessage( 1621 | chatId, 1622 | "Insufficient balance to pay for fees. (Balance < ~75 TRX)", 1623 | { 1624 | reply_markup: { 1625 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1626 | }, 1627 | } 1628 | ); 1629 | return; 1630 | } 1631 | 1632 | const pairAddress = await SniperUtils.getPairAddress(tokenAddress); 1633 | 1634 | if (!pairAddress) { 1635 | bot.sendMessage(chatId, "No pair found for this token.", { 1636 | reply_markup: { 1637 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1638 | }, 1639 | }); 1640 | return; 1641 | } 1642 | 1643 | const sellingMessage = await bot.sendMessage(chatId, `Selling...`); 1644 | 1645 | const txID = await SniperUtils.sellToken( 1646 | tokenAddress, 1647 | pairAddress, 1648 | amount, 1649 | slippage, 1650 | wallet.address, 1651 | wallet.privateKey 1652 | ); 1653 | 1654 | if (!txID) { 1655 | bot.sendMessage(chatId, "No TXID found.", { 1656 | reply_markup: { 1657 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1658 | }, 1659 | }); 1660 | return; 1661 | } 1662 | 1663 | bot.sendMessage( 1664 | chatId, 1665 | `Transaction sent: [View on Tronscan](https://tronscan.org/#/transaction/${txID})`, 1666 | { 1667 | parse_mode: "Markdown", 1668 | reply_markup: { 1669 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1670 | }, 1671 | } 1672 | ); 1673 | bot.deleteMessage(chatId, sellingMessage.message_id); 1674 | } catch (error) { 1675 | console.error(`${errorLOG} ${error}`); 1676 | bot.sendMessage(chatId, "An error occurred while selling the token.", { 1677 | reply_markup: { 1678 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1679 | }, 1680 | }); 1681 | } 1682 | } 1683 | 1684 | export async function sellCustomTokenCallback( 1685 | user: User, 1686 | bot: TelegramBot, 1687 | chatId: number, 1688 | walletIndex: number, 1689 | tokenAddress: string, 1690 | slippage: number 1691 | ) { 1692 | try { 1693 | const wallet = user.wallets[walletIndex]; 1694 | 1695 | if (!wallet) { 1696 | bot.sendMessage(chatId, "Invalid wallet.", { 1697 | reply_markup: { 1698 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1699 | }, 1700 | }); 1701 | return; 1702 | } 1703 | 1704 | const positions = (await SniperUtils.getTokensBalance( 1705 | wallet.address 1706 | )) as Token[]; 1707 | 1708 | if (positions.length === 0) { 1709 | bot.sendMessage(chatId, "No positions found.", { 1710 | reply_markup: { 1711 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1712 | }, 1713 | }); 1714 | return; 1715 | } 1716 | 1717 | const position = positions.find( 1718 | (position) => position.address === tokenAddress 1719 | ); 1720 | 1721 | if (!position) { 1722 | bot.sendMessage(chatId, "Position not found.", { 1723 | reply_markup: { 1724 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1725 | }, 1726 | }); 1727 | return; 1728 | } 1729 | 1730 | const positionContract = await SniperUtils.getContractInstance( 1731 | TRC20_ABI, 1732 | position.address 1733 | ); 1734 | 1735 | if (!positionContract) { 1736 | bot.sendMessage(chatId, "No contract found.", { 1737 | reply_markup: { 1738 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1739 | }, 1740 | }); 1741 | return; 1742 | } 1743 | 1744 | const decimals = await positionContract.decimals().call(); 1745 | 1746 | if (!decimals) { 1747 | bot.sendMessage(chatId, "No decimals found.", { 1748 | reply_markup: { 1749 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1750 | }, 1751 | }); 1752 | return; 1753 | } 1754 | 1755 | const balance = new BigNumber(position.balance) 1756 | .div(new BigNumber(10).pow(decimals)) 1757 | .toNumber(); 1758 | 1759 | const text = `Enter the percentage of ${position.symbol} you want to sell.`; 1760 | 1761 | bot 1762 | .sendMessage(chatId, text, { 1763 | reply_markup: { 1764 | force_reply: true, 1765 | }, 1766 | }) 1767 | .then((msg) => { 1768 | bot.onReplyToMessage(chatId, msg.message_id, (reply) => { 1769 | const amount = reply.text; 1770 | 1771 | if (!amount) { 1772 | bot.sendMessage(chatId, "Invalid amount.", { 1773 | reply_markup: { 1774 | inline_keyboard: [ 1775 | [{ text: "❌ Close", callback_data: "close" }], 1776 | ], 1777 | }, 1778 | }); 1779 | return; 1780 | } 1781 | 1782 | const percentageNumber = parseFloat(amount); 1783 | 1784 | if (isNaN(percentageNumber)) { 1785 | bot.sendMessage(chatId, "Invalid amount.", { 1786 | reply_markup: { 1787 | inline_keyboard: [ 1788 | [{ text: "❌ Close", callback_data: "close" }], 1789 | ], 1790 | }, 1791 | }); 1792 | return; 1793 | } 1794 | 1795 | if (percentageNumber < 0 || percentageNumber > 100) { 1796 | bot.sendMessage( 1797 | chatId, 1798 | "The percentage must be between 0 and 100.", 1799 | { 1800 | reply_markup: { 1801 | inline_keyboard: [ 1802 | [{ text: "❌ Close", callback_data: "close" }], 1803 | ], 1804 | }, 1805 | } 1806 | ); 1807 | return; 1808 | } 1809 | 1810 | const parsedNumber = balance * (percentageNumber / 100); 1811 | 1812 | if (balance < parsedNumber) { 1813 | bot.sendMessage(chatId, "Insufficient balance.", { 1814 | reply_markup: { 1815 | inline_keyboard: [ 1816 | [{ text: "❌ Close", callback_data: "close" }], 1817 | ], 1818 | }, 1819 | }); 1820 | return; 1821 | } 1822 | 1823 | const pairAddress = SniperUtils.getPairAddress(tokenAddress); 1824 | 1825 | if (!pairAddress) { 1826 | bot.sendMessage(chatId, "No pair found for this token.", { 1827 | reply_markup: { 1828 | inline_keyboard: [ 1829 | [{ text: "❌ Close", callback_data: "close" }], 1830 | ], 1831 | }, 1832 | }); 1833 | return; 1834 | } 1835 | 1836 | bot.sendMessage( 1837 | chatId, 1838 | `You will sell ${parsedNumber} ${position.symbol}. Confirm?`, 1839 | { 1840 | reply_markup: { 1841 | inline_keyboard: [ 1842 | [ 1843 | { 1844 | text: "✅ Confirm", 1845 | callback_data: `sell_${percentageNumber}_${walletIndex}_${tokenAddress}_${slippage}`, 1846 | }, 1847 | { 1848 | text: "❌ Cancel", 1849 | callback_data: `close`, 1850 | }, 1851 | ], 1852 | ], 1853 | }, 1854 | } 1855 | ); 1856 | }); 1857 | }); 1858 | } catch (error) { 1859 | console.error(`${errorLOG} ${error}`); 1860 | bot.sendMessage(chatId, "An error occurred while selling the token.", { 1861 | reply_markup: { 1862 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 1863 | }, 1864 | }); 1865 | } 1866 | } 1867 | -------------------------------------------------------------------------------- /src/callbacks/wallets.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from "bignumber.js"; 2 | import TelegramBot from "node-telegram-bot-api"; 3 | import { WTRX_DECIMALS } from "../config"; 4 | import { errorLOG } from "../utils/logs"; 5 | import SniperUtils from "../utils/tronWeb"; 6 | 7 | export async function walletCallback( 8 | user: User, 9 | bot: TelegramBot, 10 | chatId: number 11 | ) { 12 | try { 13 | const wallets = user.wallets as Wallet[]; 14 | 15 | if (wallets.length === 0) { 16 | bot.sendMessage(chatId, "You don't have any wallets yet.", { 17 | reply_markup: { 18 | inline_keyboard: [ 19 | [ 20 | { 21 | text: "➕ Add Wallet", 22 | callback_data: "add_wallet", 23 | }, 24 | ], 25 | [ 26 | { 27 | text: "🔄 Refresh", 28 | callback_data: "refresh_wallet", 29 | }, 30 | { 31 | text: "🔙 Back", 32 | callback_data: "close", 33 | }, 34 | ], 35 | ], 36 | }, 37 | }); 38 | 39 | return; 40 | } 41 | 42 | const balances = await Promise.all( 43 | wallets.map(async (wallet) => { 44 | const balance = await SniperUtils.getBalance(wallet.address); 45 | 46 | const balanceTRX = new BigNumber(balance).div( 47 | new BigNumber(10).pow(WTRX_DECIMALS) 48 | ); 49 | 50 | return `💰 *${balanceTRX.toFixed(2)} TRX* in \`${wallet.address}\`\n`; 51 | }) 52 | ); 53 | 54 | const balancesText = balances.map((balance) => balance); 55 | 56 | const text = `🔑 *Wallets* 57 | 58 | ${balancesText.join("")} 59 | 💣 Number of wallets: ${wallets.length}/10`; 60 | 61 | bot.sendMessage(chatId, text, { 62 | parse_mode: "Markdown", 63 | disable_web_page_preview: true, 64 | reply_markup: { 65 | inline_keyboard: [ 66 | [ 67 | { 68 | text: "➕ Add Wallet", 69 | callback_data: "add_wallet", 70 | }, 71 | ], 72 | [ 73 | { 74 | text: "🔍 Wallet Info", 75 | callback_data: "wallet_info", 76 | }, 77 | { 78 | text: "🗑 Remove Wallet", 79 | callback_data: "remove_wallet", 80 | }, 81 | ], 82 | [ 83 | { 84 | text: "🔄 Refresh", 85 | callback_data: "refresh_wallet", 86 | }, 87 | { 88 | text: "🔙 Back", 89 | callback_data: "close", 90 | }, 91 | ], 92 | ], 93 | }, 94 | }); 95 | } catch (error) { 96 | console.error(`${errorLOG} ${error}`); 97 | bot.sendMessage(chatId, "An error occurred while fetching the wallets.", { 98 | reply_markup: { 99 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 100 | }, 101 | }); 102 | } 103 | } 104 | 105 | export async function addWalletCallback(bot: TelegramBot, chatId: number) { 106 | try { 107 | const text = `🔑 *Add Wallet* 108 | 109 | How would you like to add your wallet?`; 110 | 111 | bot.sendMessage(chatId, text, { 112 | parse_mode: "Markdown", 113 | reply_markup: { 114 | inline_keyboard: [ 115 | [ 116 | { 117 | text: "🔑 Import Wallet", 118 | callback_data: "import_wallet", 119 | }, 120 | ], 121 | [ 122 | { 123 | text: "🔑 Generate Wallet", 124 | callback_data: "generate_wallet", 125 | }, 126 | ], 127 | [ 128 | { 129 | text: "🔙 Back", 130 | callback_data: "close", 131 | }, 132 | ], 133 | ], 134 | }, 135 | }); 136 | } catch (error) { 137 | console.error(`${errorLOG} ${error}`); 138 | bot.sendMessage(chatId, "An error occurred while adding the wallet.", { 139 | reply_markup: { 140 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 141 | }, 142 | }); 143 | } 144 | } 145 | 146 | export async function genWalletCallback( 147 | usersCollection: any, 148 | user: User, 149 | bot: TelegramBot, 150 | chatId: number, 151 | message: TelegramBot.Message 152 | ) { 153 | try { 154 | if (user.wallets.length >= 10) { 155 | bot.sendMessage(chatId, "You can't have more than 10 wallets.", { 156 | reply_markup: { 157 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 158 | }, 159 | }); 160 | 161 | return; 162 | } 163 | 164 | const wallet = (await SniperUtils.createAccount()) as { 165 | privateKey: string; 166 | publicKey: string; 167 | address: { 168 | base58: string; 169 | hex: string; 170 | }; 171 | }; 172 | 173 | if (!wallet) throw new Error("Error while creating the wallet."); 174 | 175 | await usersCollection.updateOne( 176 | { id: chatId }, 177 | { 178 | $push: { 179 | wallets: { 180 | privateKey: wallet.privateKey, 181 | address: wallet.address.base58, 182 | }, 183 | }, 184 | } 185 | ); 186 | 187 | bot.deleteMessage(chatId, message.message_id); 188 | 189 | const textNewWallet = `💣 *New Taproot (P2TR) Wallet Generated* 190 | 191 | Your wallet address is: \`${wallet.address.base58}\` 192 | 193 | Your wallet private key is: \`${wallet.privateKey}\``; 194 | 195 | bot.sendMessage(chatId, textNewWallet, { 196 | reply_markup: { 197 | inline_keyboard: [[{ text: "🔙 Back", callback_data: "close" }]], 198 | }, 199 | parse_mode: "Markdown", 200 | }); 201 | } catch (error) { 202 | console.error(`${errorLOG} ${error}`); 203 | bot.sendMessage(chatId, "An error occurred while generating the wallet.", { 204 | reply_markup: { 205 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 206 | }, 207 | }); 208 | } 209 | } 210 | 211 | export async function importWalletCallback( 212 | usersCollection: any, 213 | user: User, 214 | bot: TelegramBot, 215 | chatId: number, 216 | message: TelegramBot.Message 217 | ) { 218 | try { 219 | if (user.wallets.length >= 10) { 220 | bot.sendMessage(chatId, "You can't have more than 10 wallets.", { 221 | reply_markup: { 222 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 223 | }, 224 | }); 225 | 226 | return; 227 | } 228 | 229 | const text = `🔑 *Import Wallet* 230 | 231 | Please enter your private key.`; 232 | 233 | bot.deleteMessage(chatId, message.message_id); 234 | 235 | bot 236 | .sendMessage(chatId, text, { 237 | parse_mode: "Markdown", 238 | reply_markup: { 239 | force_reply: true, 240 | selective: true, 241 | }, 242 | }) 243 | .then((msg) => { 244 | bot.onReplyToMessage(chatId, msg.message_id, async (reply) => { 245 | try { 246 | const privateKey = reply.text; 247 | 248 | if (!privateKey) { 249 | bot.sendMessage(chatId, "Invalid private key.", { 250 | reply_markup: { 251 | inline_keyboard: [ 252 | [{ text: "❌ Close", callback_data: "close" }], 253 | ], 254 | }, 255 | }); 256 | 257 | return; 258 | } 259 | 260 | const address = SniperUtils.importAccount(privateKey); 261 | 262 | if (!address) throw new Error("Error while importing the wallet."); 263 | 264 | const wallets = user.wallets as Wallet[]; 265 | 266 | if (wallets.some((wallet) => wallet.address === address)) { 267 | bot.sendMessage(chatId, "This wallet already exists.", { 268 | reply_markup: { 269 | inline_keyboard: [ 270 | [{ text: "❌ Close", callback_data: "close" }], 271 | ], 272 | }, 273 | }); 274 | 275 | return; 276 | } 277 | 278 | await usersCollection.updateOne( 279 | { id: chatId }, 280 | { 281 | $push: { 282 | wallets: { 283 | privateKey, 284 | address, 285 | }, 286 | }, 287 | } 288 | ); 289 | 290 | const textNewWallet = `💣 *New Taproot (P2TR) Wallet Imported* 291 | 292 | Your wallet address is: \`${address}\` 293 | 294 | Your wallet private key is: \`${privateKey}\``; 295 | 296 | bot.sendMessage(chatId, textNewWallet, { 297 | reply_markup: { 298 | inline_keyboard: [ 299 | [{ text: "🔙 Back", callback_data: "close" }], 300 | ], 301 | }, 302 | parse_mode: "Markdown", 303 | }); 304 | } catch (error) { 305 | console.error(`${errorLOG} ${error}`); 306 | bot.sendMessage( 307 | chatId, 308 | "An error occurred while importing the wallet.", 309 | { 310 | reply_markup: { 311 | inline_keyboard: [ 312 | [{ text: "❌ Close", callback_data: "close" }], 313 | ], 314 | }, 315 | } 316 | ); 317 | } 318 | }); 319 | }); 320 | } catch (error) { 321 | console.error(`${errorLOG} ${error}`); 322 | bot.sendMessage(chatId, "An error occurred while importing the wallet.", { 323 | reply_markup: { 324 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 325 | }, 326 | }); 327 | } 328 | } 329 | 330 | export async function walletInfoCallback( 331 | user: User, 332 | bot: TelegramBot, 333 | chatId: number, 334 | message: TelegramBot.Message 335 | ) { 336 | try { 337 | const wallets = user.wallets; 338 | 339 | if (wallets.length === 0) { 340 | bot.editMessageText("You don't have any wallets yet.", { 341 | chat_id: chatId, 342 | message_id: message.message_id, 343 | reply_markup: { 344 | inline_keyboard: [ 345 | [ 346 | { 347 | text: "➕ Add Wallet", 348 | callback_data: "add_wallet", 349 | }, 350 | ], 351 | [ 352 | { 353 | text: "🔄 Refresh", 354 | callback_data: "refresh_wallet", 355 | }, 356 | { 357 | text: "🔙 Back", 358 | callback_data: "close", 359 | }, 360 | ], 361 | ], 362 | }, 363 | }); 364 | 365 | return; 366 | } 367 | 368 | const walletsText = wallets.map((wallet: Wallet, index: number) => { 369 | const address = wallet.address; 370 | 371 | return `💰 *${index + 1}* · \`${address}\`\n\n`; 372 | }); 373 | 374 | const text = `🔑 *Wallets* 375 | 376 | Which wallet would you like to view? 377 | 378 | ${walletsText.join("")}`; 379 | 380 | bot.editMessageText(text, { 381 | chat_id: chatId, 382 | message_id: message.message_id, 383 | parse_mode: "Markdown", 384 | disable_web_page_preview: true, 385 | reply_markup: { 386 | inline_keyboard: wallets.map((_: any, index: number) => [ 387 | { 388 | text: `🔍 Wallet ${index + 1}`, 389 | callback_data: `wallet_info_${index}`, 390 | }, 391 | ]), 392 | }, 393 | }); 394 | } catch (error) { 395 | console.error(`${errorLOG} ${error}`); 396 | bot.sendMessage(chatId, "An error occurred while fetching the wallets.", { 397 | reply_markup: { 398 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 399 | }, 400 | }); 401 | } 402 | } 403 | 404 | export async function walletInfoIndexCallback( 405 | user: User, 406 | bot: TelegramBot, 407 | chatId: number, 408 | message: TelegramBot.Message, 409 | index: number 410 | ) { 411 | try { 412 | const wallets = user.wallets; 413 | 414 | if (wallets.length === 0) { 415 | bot.editMessageText("You don't have any wallets yet.", { 416 | chat_id: chatId, 417 | message_id: message.message_id, 418 | reply_markup: { 419 | inline_keyboard: [ 420 | [ 421 | { 422 | text: "➕ Add Wallet", 423 | callback_data: "add_wallet", 424 | }, 425 | ], 426 | [ 427 | { 428 | text: "🔄 Refresh", 429 | callback_data: "refresh_wallet", 430 | }, 431 | { 432 | text: "🔙 Back", 433 | callback_data: "close", 434 | }, 435 | ], 436 | ], 437 | }, 438 | }); 439 | 440 | return; 441 | } 442 | 443 | const wallet = wallets[index]; 444 | 445 | const address = wallet.address; 446 | 447 | bot.editMessageText( 448 | `💰 *Wallet Info* 449 | 450 | Your wallet address is: \`${address}\` 451 | 452 | Your wallet private key is: \`${wallet.privateKey}\``, 453 | { 454 | chat_id: chatId, 455 | message_id: message.message_id, 456 | parse_mode: "Markdown", 457 | reply_markup: { 458 | inline_keyboard: [ 459 | [ 460 | { 461 | text: "🔙 Back", 462 | callback_data: "close", 463 | }, 464 | ], 465 | ], 466 | }, 467 | } 468 | ); 469 | } catch (error) { 470 | console.error(`${errorLOG} ${error}`); 471 | bot.sendMessage(chatId, "An error occurred while fetching the wallets.", { 472 | reply_markup: { 473 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 474 | }, 475 | }); 476 | } 477 | } 478 | 479 | export async function removeWalletCallback( 480 | user: User, 481 | bot: TelegramBot, 482 | chatId: number, 483 | message: TelegramBot.Message 484 | ) { 485 | try { 486 | const wallets = user.wallets; 487 | 488 | if (wallets.length === 0) { 489 | bot.editMessageText("You don't have any wallets yet.", { 490 | chat_id: chatId, 491 | message_id: message.message_id, 492 | reply_markup: { 493 | inline_keyboard: [ 494 | [ 495 | { 496 | text: "➕ Add Wallet", 497 | callback_data: "add_wallet", 498 | }, 499 | ], 500 | [ 501 | { 502 | text: "🔄 Refresh", 503 | callback_data: "refresh_wallet", 504 | }, 505 | { 506 | text: "🔙 Back", 507 | callback_data: "close", 508 | }, 509 | ], 510 | ], 511 | }, 512 | }); 513 | 514 | return; 515 | } 516 | 517 | const walletsText = wallets.map((wallet: Wallet, index: number) => { 518 | const address = wallet.address; 519 | 520 | return `💰 *${index + 1}* · \`${address}\`\n\n`; 521 | }); 522 | 523 | const text = `🔑 *Remove Wallet* 524 | 525 | Which wallet would you like to remove? 526 | 527 | ${walletsText.join("")}`; 528 | 529 | bot.editMessageText(text, { 530 | chat_id: chatId, 531 | message_id: message.message_id, 532 | parse_mode: "Markdown", 533 | disable_web_page_preview: true, 534 | reply_markup: { 535 | inline_keyboard: wallets.map((_: any, index: number) => [ 536 | { 537 | text: `❌ Remove Wallet ${index + 1}`, 538 | callback_data: `remove_wallet_${index}`, 539 | }, 540 | ]), 541 | }, 542 | }); 543 | } catch (error) { 544 | console.error(`${errorLOG} ${error}`); 545 | bot.sendMessage(chatId, "An error occurred while removing the wallet.", { 546 | reply_markup: { 547 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 548 | }, 549 | }); 550 | } 551 | } 552 | 553 | export async function removeWalletIndexCallback( 554 | usersCollection: any, 555 | user: User, 556 | bot: TelegramBot, 557 | chatId: number, 558 | message: TelegramBot.Message, 559 | index: number 560 | ) { 561 | try { 562 | const wallets = user.wallets; 563 | 564 | if (wallets.length === 0) { 565 | bot.editMessageText("You don't have any wallets yet.", { 566 | chat_id: chatId, 567 | message_id: message.message_id, 568 | reply_markup: { 569 | inline_keyboard: [ 570 | [ 571 | { 572 | text: "➕ Add Wallet", 573 | callback_data: "add_wallet", 574 | }, 575 | ], 576 | [ 577 | { 578 | text: "🔄 Refresh", 579 | callback_data: "refresh_wallet", 580 | }, 581 | { 582 | text: "🔙 Back", 583 | callback_data: "close", 584 | }, 585 | ], 586 | ], 587 | }, 588 | }); 589 | 590 | return; 591 | } 592 | 593 | const wallet = wallets[index]; 594 | 595 | await usersCollection.updateOne( 596 | { id: chatId }, 597 | { 598 | $pull: { 599 | wallets: { 600 | address: wallet.address, 601 | }, 602 | }, 603 | } 604 | ); 605 | 606 | bot.editMessageText( 607 | `💣 *Wallet Removed* 608 | 609 | Your wallet address was: \`${wallet.address}\` 610 | 611 | Your wallet private key (WIF format) was: \`${wallet.privateKey}\``, 612 | { 613 | chat_id: chatId, 614 | message_id: message.message_id, 615 | parse_mode: "Markdown", 616 | reply_markup: { 617 | inline_keyboard: [ 618 | [ 619 | { 620 | text: "🔙 Back", 621 | callback_data: "close", 622 | }, 623 | ], 624 | ], 625 | }, 626 | } 627 | ); 628 | } catch (error) { 629 | console.error(`${errorLOG} ${error}`); 630 | bot.sendMessage(chatId, "An error occurred while removing the wallet.", { 631 | reply_markup: { 632 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 633 | }, 634 | }); 635 | } 636 | } 637 | 638 | export async function refreshWalletCallback( 639 | user: User, 640 | bot: TelegramBot, 641 | chatId: number, 642 | message: TelegramBot.Message 643 | ) { 644 | try { 645 | const wallets = user.wallets; 646 | 647 | if (wallets.length === 0) { 648 | bot.editMessageText("You don't have any wallets yet.", { 649 | chat_id: chatId, 650 | message_id: message.message_id, 651 | reply_markup: { 652 | inline_keyboard: [ 653 | [ 654 | { 655 | text: "➕ Add Wallet", 656 | callback_data: "add_wallet", 657 | }, 658 | ], 659 | [ 660 | { 661 | text: "🔄 Refresh", 662 | callback_data: "refresh_wallet", 663 | }, 664 | { 665 | text: "🔙 Back", 666 | callback_data: "close", 667 | }, 668 | ], 669 | ], 670 | }, 671 | }); 672 | 673 | return; 674 | } 675 | 676 | const balances = await Promise.all( 677 | wallets.map(async (wallet) => { 678 | const balance = await SniperUtils.getBalance(wallet.address); 679 | 680 | const balanceTRX = new BigNumber(balance).div( 681 | new BigNumber(10).pow(WTRX_DECIMALS) 682 | ); 683 | 684 | return `💰 *${balanceTRX.toFixed(2)} TRX* in \`${wallet.address}\`\n`; 685 | }) 686 | ); 687 | 688 | const balancesText = balances.map((balance) => balance); 689 | 690 | const text = `🔑 *Wallets* 691 | 692 | ${balancesText.join("")} 693 | 💣 Number of wallets: ${wallets.length}/10`; 694 | 695 | bot.editMessageText(text, { 696 | chat_id: chatId, 697 | message_id: message.message_id, 698 | parse_mode: "Markdown", 699 | disable_web_page_preview: true, 700 | reply_markup: { 701 | inline_keyboard: [ 702 | [ 703 | { 704 | text: "➕ Add Wallet", 705 | callback_data: "add_wallet", 706 | }, 707 | ], 708 | [ 709 | { 710 | text: "🔍 Wallet Info", 711 | callback_data: "wallet_info", 712 | }, 713 | { 714 | text: "🗑 Remove Wallet", 715 | callback_data: "remove_wallet", 716 | }, 717 | ], 718 | [ 719 | { 720 | text: "🔄 Refresh", 721 | callback_data: "refresh_wallet", 722 | }, 723 | { 724 | text: "🔙 Back", 725 | callback_data: "close", 726 | }, 727 | ], 728 | ], 729 | }, 730 | }); 731 | } catch (error) { 732 | console.error(`${errorLOG} ${error}`); 733 | bot.sendMessage(chatId, "An error occurred while refreshing the wallets.", { 734 | reply_markup: { 735 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 736 | }, 737 | }); 738 | } 739 | } 740 | -------------------------------------------------------------------------------- /src/commands/start.ts: -------------------------------------------------------------------------------- 1 | import TelegramBot from "node-telegram-bot-api"; 2 | import { GENERIC_ERROR_MESSAGE } from "../config.js"; 3 | import { errorLOG } from "../utils/logs.js"; 4 | 5 | /* ------------------------------ */ 6 | /* START COMMAND */ 7 | /* ------------------------------ */ 8 | 9 | export function startCommand(msg: TelegramBot.Message, bot: TelegramBot) { 10 | try { 11 | const chatId = msg.chat.id; 12 | 13 | const text = `Welcome to TRON Sniper Bot 14 | 15 | 🔹 The ultimate bot for sniping and trading on TRON. 16 | 17 | 💡 _Enter the token address below to get started_ 18 | 19 | [X](https://x.com/impredmet)`; 20 | 21 | bot.sendPhoto(chatId, `https://i.ibb.co/CBFZwn2/TRANCHESBOT-BANNER.png`, { 22 | caption: text, 23 | parse_mode: "Markdown", 24 | }); 25 | } catch (error) { 26 | console.error(`${errorLOG} ${error}`); 27 | const chatId = msg.chat.id; 28 | bot.sendMessage(chatId, GENERIC_ERROR_MESSAGE, { 29 | reply_markup: { 30 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 31 | }, 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | /* ------------------------------ */ 2 | /* CONFIG PART */ 3 | /* ------------------------------ */ 4 | 5 | export const SUNSWAP_FACTORY_ADDRESS = "TKWJdrQkqHisa1X8HUdHEfREvTzw4pMAaY"; // https://www.sunswap.com/docs/sunswapV2-interfaces_en.pdf 6 | export const SUNSWAP_ROUTER_ADDRESS = "TKzxdSv2FZKQrEqkKVgp5DcwEXBEKMg2Ax"; // https://www.sunswap.com/docs/sunswapV2-interfaces_en.pdf 7 | export const WTRX_ADDRESS = "TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR"; // https://tronscan.org/#/contract/TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR 8 | export const WTRX_DECIMALS = 6; 9 | export const GENERIC_ERROR_MESSAGE = 10 | "An error occurred. Please try again later."; 11 | 12 | export const SUNSWAP_FACTORY_ABI = { 13 | entrys: [ 14 | { 15 | outputs: [ 16 | { 17 | type: "address", 18 | }, 19 | ], 20 | constant: true, 21 | name: "feeTo", 22 | stateMutability: "View", 23 | type: "Function", 24 | }, 25 | { 26 | outputs: [ 27 | { 28 | type: "address", 29 | }, 30 | ], 31 | constant: true, 32 | name: "feeToSetter", 33 | stateMutability: "View", 34 | type: "Function", 35 | }, 36 | { 37 | outputs: [ 38 | { 39 | type: "address", 40 | }, 41 | ], 42 | constant: true, 43 | inputs: [ 44 | { 45 | type: "uint256", 46 | }, 47 | ], 48 | name: "allPairs", 49 | stateMutability: "View", 50 | type: "Function", 51 | }, 52 | { 53 | outputs: [ 54 | { 55 | type: "uint256", 56 | }, 57 | ], 58 | constant: true, 59 | name: "allPairsLength", 60 | stateMutability: "View", 61 | type: "Function", 62 | }, 63 | { 64 | outputs: [ 65 | { 66 | type: "bytes32", 67 | }, 68 | ], 69 | constant: true, 70 | name: "getPairHash", 71 | stateMutability: "View", 72 | type: "Function", 73 | }, 74 | { 75 | inputs: [ 76 | { 77 | name: "_feeToSetter", 78 | type: "address", 79 | }, 80 | ], 81 | name: "setFeeToSetter", 82 | stateMutability: "Nonpayable", 83 | type: "Function", 84 | }, 85 | { 86 | outputs: [ 87 | { 88 | name: "pair", 89 | type: "address", 90 | }, 91 | ], 92 | inputs: [ 93 | { 94 | name: "tokenA", 95 | type: "address", 96 | }, 97 | { 98 | name: "tokenB", 99 | type: "address", 100 | }, 101 | ], 102 | name: "createPair", 103 | stateMutability: "Nonpayable", 104 | type: "Function", 105 | }, 106 | { 107 | outputs: [ 108 | { 109 | type: "address", 110 | }, 111 | ], 112 | constant: true, 113 | inputs: [ 114 | { 115 | type: "address", 116 | }, 117 | { 118 | type: "address", 119 | }, 120 | ], 121 | name: "getPair", 122 | stateMutability: "View", 123 | type: "Function", 124 | }, 125 | { 126 | inputs: [ 127 | { 128 | name: "_feeTo", 129 | type: "address", 130 | }, 131 | ], 132 | name: "setFeeTo", 133 | stateMutability: "Nonpayable", 134 | type: "Function", 135 | }, 136 | { 137 | inputs: [ 138 | { 139 | name: "_feeToSetter", 140 | type: "address", 141 | }, 142 | ], 143 | stateMutability: "Nonpayable", 144 | type: "Constructor", 145 | }, 146 | { 147 | inputs: [ 148 | { 149 | indexed: true, 150 | name: "token0", 151 | type: "address", 152 | }, 153 | { 154 | indexed: true, 155 | name: "token1", 156 | type: "address", 157 | }, 158 | { 159 | name: "pair", 160 | type: "address", 161 | }, 162 | { 163 | type: "uint256", 164 | }, 165 | ], 166 | name: "PairCreated", 167 | type: "Event", 168 | }, 169 | ], 170 | }; 171 | export const SUNSWAP_PAIR_ABI = { 172 | entrys: [ 173 | { inputs: [], stateMutability: "nonpayable", type: "constructor" }, 174 | { 175 | inputs: [ 176 | { 177 | indexed: true, 178 | name: "owner", 179 | internalType: "address", 180 | type: "address", 181 | }, 182 | { 183 | indexed: true, 184 | name: "spender", 185 | internalType: "address", 186 | type: "address", 187 | }, 188 | { 189 | indexed: false, 190 | name: "value", 191 | internalType: "uint256", 192 | type: "uint256", 193 | }, 194 | ], 195 | name: "Approval", 196 | anonymous: false, 197 | type: "event", 198 | }, 199 | { 200 | inputs: [ 201 | { 202 | indexed: true, 203 | name: "sender", 204 | internalType: "address", 205 | type: "address", 206 | }, 207 | { 208 | indexed: false, 209 | name: "amount0", 210 | internalType: "uint256", 211 | type: "uint256", 212 | }, 213 | { 214 | indexed: false, 215 | name: "amount1", 216 | internalType: "uint256", 217 | type: "uint256", 218 | }, 219 | { indexed: true, name: "to", internalType: "address", type: "address" }, 220 | ], 221 | name: "Burn", 222 | anonymous: false, 223 | type: "event", 224 | }, 225 | { 226 | inputs: [ 227 | { 228 | indexed: true, 229 | name: "sender", 230 | internalType: "address", 231 | type: "address", 232 | }, 233 | { 234 | indexed: false, 235 | name: "amount0", 236 | internalType: "uint256", 237 | type: "uint256", 238 | }, 239 | { 240 | indexed: false, 241 | name: "amount1", 242 | internalType: "uint256", 243 | type: "uint256", 244 | }, 245 | ], 246 | name: "Mint", 247 | anonymous: false, 248 | type: "event", 249 | }, 250 | { 251 | inputs: [ 252 | { 253 | indexed: true, 254 | name: "sender", 255 | internalType: "address", 256 | type: "address", 257 | }, 258 | { 259 | indexed: false, 260 | name: "amount0In", 261 | internalType: "uint256", 262 | type: "uint256", 263 | }, 264 | { 265 | indexed: false, 266 | name: "amount1In", 267 | internalType: "uint256", 268 | type: "uint256", 269 | }, 270 | { 271 | indexed: false, 272 | name: "amount0Out", 273 | internalType: "uint256", 274 | type: "uint256", 275 | }, 276 | { 277 | indexed: false, 278 | name: "amount1Out", 279 | internalType: "uint256", 280 | type: "uint256", 281 | }, 282 | { indexed: true, name: "to", internalType: "address", type: "address" }, 283 | ], 284 | name: "Swap", 285 | anonymous: false, 286 | type: "event", 287 | }, 288 | { 289 | inputs: [ 290 | { 291 | indexed: false, 292 | name: "reserve0", 293 | internalType: "uint112", 294 | type: "uint112", 295 | }, 296 | { 297 | indexed: false, 298 | name: "reserve1", 299 | internalType: "uint112", 300 | type: "uint112", 301 | }, 302 | ], 303 | name: "Sync", 304 | anonymous: false, 305 | type: "event", 306 | }, 307 | { 308 | inputs: [ 309 | { 310 | indexed: true, 311 | name: "from", 312 | internalType: "address", 313 | type: "address", 314 | }, 315 | { indexed: true, name: "to", internalType: "address", type: "address" }, 316 | { 317 | indexed: false, 318 | name: "value", 319 | internalType: "uint256", 320 | type: "uint256", 321 | }, 322 | ], 323 | name: "Transfer", 324 | anonymous: false, 325 | type: "event", 326 | }, 327 | { 328 | outputs: [{ name: "", internalType: "bytes32", type: "bytes32" }], 329 | inputs: [], 330 | name: "DOMAIN_SEPARATOR", 331 | stateMutability: "view", 332 | type: "function", 333 | }, 334 | { 335 | outputs: [{ name: "", internalType: "uint256", type: "uint256" }], 336 | inputs: [], 337 | name: "MINIMUM_LIQUIDITY", 338 | stateMutability: "view", 339 | type: "function", 340 | }, 341 | { 342 | outputs: [{ name: "", internalType: "bytes32", type: "bytes32" }], 343 | inputs: [], 344 | name: "PERMIT_TYPEHASH", 345 | stateMutability: "view", 346 | type: "function", 347 | }, 348 | { 349 | outputs: [{ name: "", internalType: "uint256", type: "uint256" }], 350 | inputs: [ 351 | { name: "", internalType: "address", type: "address" }, 352 | { name: "", internalType: "address", type: "address" }, 353 | ], 354 | name: "allowance", 355 | stateMutability: "view", 356 | type: "function", 357 | }, 358 | { 359 | outputs: [{ name: "", internalType: "bool", type: "bool" }], 360 | inputs: [ 361 | { name: "spender", internalType: "address", type: "address" }, 362 | { name: "value", internalType: "uint256", type: "uint256" }, 363 | ], 364 | name: "approve", 365 | stateMutability: "nonpayable", 366 | type: "function", 367 | }, 368 | { 369 | outputs: [{ name: "", internalType: "uint256", type: "uint256" }], 370 | inputs: [{ name: "", internalType: "address", type: "address" }], 371 | name: "balanceOf", 372 | stateMutability: "view", 373 | type: "function", 374 | }, 375 | { 376 | outputs: [{ name: "", internalType: "uint8", type: "uint8" }], 377 | inputs: [], 378 | name: "decimals", 379 | stateMutability: "view", 380 | type: "function", 381 | }, 382 | { 383 | outputs: [{ name: "", internalType: "address", type: "address" }], 384 | inputs: [], 385 | name: "factory", 386 | stateMutability: "view", 387 | type: "function", 388 | }, 389 | { 390 | outputs: [{ name: "", internalType: "uint256", type: "uint256" }], 391 | inputs: [], 392 | name: "kLast", 393 | stateMutability: "view", 394 | type: "function", 395 | }, 396 | { 397 | outputs: [{ name: "", internalType: "string", type: "string" }], 398 | inputs: [], 399 | name: "name", 400 | stateMutability: "view", 401 | type: "function", 402 | }, 403 | { 404 | outputs: [{ name: "", internalType: "uint256", type: "uint256" }], 405 | inputs: [{ name: "", internalType: "address", type: "address" }], 406 | name: "nonces", 407 | stateMutability: "view", 408 | type: "function", 409 | }, 410 | { 411 | outputs: [], 412 | inputs: [ 413 | { name: "owner", internalType: "address", type: "address" }, 414 | { name: "spender", internalType: "address", type: "address" }, 415 | { name: "value", internalType: "uint256", type: "uint256" }, 416 | { name: "deadline", internalType: "uint256", type: "uint256" }, 417 | { name: "v", internalType: "uint8", type: "uint8" }, 418 | { name: "r", internalType: "bytes32", type: "bytes32" }, 419 | { name: "s", internalType: "bytes32", type: "bytes32" }, 420 | ], 421 | name: "permit", 422 | stateMutability: "nonpayable", 423 | type: "function", 424 | }, 425 | { 426 | outputs: [{ name: "", internalType: "uint256", type: "uint256" }], 427 | inputs: [], 428 | name: "price0CumulativeLast", 429 | stateMutability: "view", 430 | type: "function", 431 | }, 432 | { 433 | outputs: [{ name: "", internalType: "uint256", type: "uint256" }], 434 | inputs: [], 435 | name: "price1CumulativeLast", 436 | stateMutability: "view", 437 | type: "function", 438 | }, 439 | { 440 | outputs: [{ name: "", internalType: "string", type: "string" }], 441 | inputs: [], 442 | name: "symbol", 443 | stateMutability: "view", 444 | type: "function", 445 | }, 446 | { 447 | outputs: [{ name: "", internalType: "address", type: "address" }], 448 | inputs: [], 449 | name: "token0", 450 | stateMutability: "view", 451 | type: "function", 452 | }, 453 | { 454 | outputs: [{ name: "", internalType: "address", type: "address" }], 455 | inputs: [], 456 | name: "token1", 457 | stateMutability: "view", 458 | type: "function", 459 | }, 460 | { 461 | outputs: [{ name: "", internalType: "uint256", type: "uint256" }], 462 | inputs: [], 463 | name: "totalSupply", 464 | stateMutability: "view", 465 | type: "function", 466 | }, 467 | { 468 | outputs: [{ name: "", internalType: "bool", type: "bool" }], 469 | inputs: [ 470 | { name: "to", internalType: "address", type: "address" }, 471 | { name: "value", internalType: "uint256", type: "uint256" }, 472 | ], 473 | name: "transfer", 474 | stateMutability: "nonpayable", 475 | type: "function", 476 | }, 477 | { 478 | outputs: [{ name: "", internalType: "bool", type: "bool" }], 479 | inputs: [ 480 | { name: "from", internalType: "address", type: "address" }, 481 | { name: "to", internalType: "address", type: "address" }, 482 | { name: "value", internalType: "uint256", type: "uint256" }, 483 | ], 484 | name: "transferFrom", 485 | stateMutability: "nonpayable", 486 | type: "function", 487 | }, 488 | { 489 | outputs: [ 490 | { name: "_reserve0", internalType: "uint112", type: "uint112" }, 491 | { name: "_reserve1", internalType: "uint112", type: "uint112" }, 492 | { name: "_blockTimestampLast", internalType: "uint32", type: "uint32" }, 493 | ], 494 | inputs: [], 495 | name: "getReserves", 496 | stateMutability: "view", 497 | type: "function", 498 | }, 499 | { 500 | outputs: [], 501 | inputs: [ 502 | { name: "_token0", internalType: "address", type: "address" }, 503 | { name: "_token1", internalType: "address", type: "address" }, 504 | ], 505 | name: "initialize", 506 | stateMutability: "nonpayable", 507 | type: "function", 508 | }, 509 | { 510 | outputs: [ 511 | { name: "liquidity", internalType: "uint256", type: "uint256" }, 512 | ], 513 | inputs: [{ name: "to", internalType: "address", type: "address" }], 514 | name: "mint", 515 | stateMutability: "nonpayable", 516 | type: "function", 517 | }, 518 | { 519 | outputs: [ 520 | { name: "amount0", internalType: "uint256", type: "uint256" }, 521 | { name: "amount1", internalType: "uint256", type: "uint256" }, 522 | ], 523 | inputs: [{ name: "to", internalType: "address", type: "address" }], 524 | name: "burn", 525 | stateMutability: "nonpayable", 526 | type: "function", 527 | }, 528 | { 529 | outputs: [], 530 | inputs: [ 531 | { name: "amount0Out", internalType: "uint256", type: "uint256" }, 532 | { name: "amount1Out", internalType: "uint256", type: "uint256" }, 533 | { name: "to", internalType: "address", type: "address" }, 534 | { name: "data", internalType: "bytes", type: "bytes" }, 535 | ], 536 | name: "swap", 537 | stateMutability: "nonpayable", 538 | type: "function", 539 | }, 540 | { 541 | outputs: [], 542 | inputs: [{ name: "to", internalType: "address", type: "address" }], 543 | name: "skim", 544 | stateMutability: "nonpayable", 545 | type: "function", 546 | }, 547 | { 548 | outputs: [], 549 | inputs: [], 550 | name: "sync", 551 | stateMutability: "nonpayable", 552 | type: "function", 553 | }, 554 | ], 555 | }; 556 | export const SUNSWAP_ROUTER_ABI = { 557 | entrys: [ 558 | { 559 | inputs: [ 560 | { name: "_factory", type: "address" }, 561 | { name: "_WETH", type: "address" }, 562 | ], 563 | stateMutability: "Nonpayable", 564 | type: "Constructor", 565 | }, 566 | { 567 | outputs: [{ type: "address" }], 568 | name: "WETH", 569 | stateMutability: "view", 570 | type: "function", 571 | }, 572 | { 573 | outputs: [ 574 | { name: "amountA", type: "uint256" }, 575 | { name: "amountB", type: "uint256" }, 576 | { name: "liquidity", type: "uint256" }, 577 | ], 578 | inputs: [ 579 | { name: "tokenA", type: "address" }, 580 | { name: "tokenB", type: "address" }, 581 | { name: "amountADesired", type: "uint256" }, 582 | { name: "amountBDesired", type: "uint256" }, 583 | { name: "amountAMin", type: "uint256" }, 584 | { name: "amountBMin", type: "uint256" }, 585 | { name: "to", type: "address" }, 586 | { name: "deadline", type: "uint256" }, 587 | ], 588 | name: "addLiquidity", 589 | stateMutability: "nonpayable", 590 | type: "function", 591 | }, 592 | { 593 | outputs: [ 594 | { name: "amountToken", type: "uint256" }, 595 | { name: "amountETH", type: "uint256" }, 596 | { name: "liquidity", type: "uint256" }, 597 | ], 598 | inputs: [ 599 | { name: "token", type: "address" }, 600 | { name: "amountTokenDesired", type: "uint256" }, 601 | { name: "amountTokenMin", type: "uint256" }, 602 | { name: "amountETHMin", type: "uint256" }, 603 | { name: "to", type: "address" }, 604 | { name: "deadline", type: "uint256" }, 605 | ], 606 | name: "addLiquidityETH", 607 | stateMutability: "payable", 608 | type: "function", 609 | }, 610 | { 611 | outputs: [{ type: "address" }], 612 | name: "factory", 613 | stateMutability: "view", 614 | type: "function", 615 | }, 616 | { 617 | outputs: [{ name: "amountIn", type: "uint256" }], 618 | inputs: [ 619 | { name: "amountOut", type: "uint256" }, 620 | { name: "reserveIn", type: "uint256" }, 621 | { name: "reserveOut", type: "uint256" }, 622 | ], 623 | name: "getAmountIn", 624 | stateMutability: "pure", 625 | type: "function", 626 | }, 627 | { 628 | outputs: [{ name: "amountOut", type: "uint256" }], 629 | inputs: [ 630 | { name: "amountIn", type: "uint256" }, 631 | { name: "reserveIn", type: "uint256" }, 632 | { name: "reserveOut", type: "uint256" }, 633 | ], 634 | name: "getAmountOut", 635 | stateMutability: "pure", 636 | type: "function", 637 | }, 638 | { 639 | outputs: [{ name: "amounts", type: "uint256[]" }], 640 | inputs: [ 641 | { name: "amountOut", type: "uint256" }, 642 | { name: "path", type: "address[]" }, 643 | ], 644 | name: "getAmountsIn", 645 | stateMutability: "view", 646 | type: "function", 647 | }, 648 | { 649 | outputs: [{ name: "amounts", type: "uint256[]" }], 650 | inputs: [ 651 | { name: "amountIn", type: "uint256" }, 652 | { name: "path", type: "address[]" }, 653 | ], 654 | name: "getAmountsOut", 655 | stateMutability: "view", 656 | type: "function", 657 | }, 658 | { 659 | outputs: [{ name: "pair", type: "address" }], 660 | inputs: [ 661 | { name: "tokenA", type: "address" }, 662 | { name: "tokenB", type: "address" }, 663 | ], 664 | name: "getPairOffChain", 665 | stateMutability: "view", 666 | type: "function", 667 | }, 668 | { 669 | outputs: [{ name: "amountB", type: "uint256" }], 670 | inputs: [ 671 | { name: "amountA", type: "uint256" }, 672 | { name: "reserveA", type: "uint256" }, 673 | { name: "reserveB", type: "uint256" }, 674 | ], 675 | name: "quote", 676 | stateMutability: "pure", 677 | type: "function", 678 | }, 679 | { 680 | outputs: [ 681 | { name: "amountA", type: "uint256" }, 682 | { name: "amountB", type: "uint256" }, 683 | ], 684 | inputs: [ 685 | { name: "tokenA", type: "address" }, 686 | { name: "tokenB", type: "address" }, 687 | { name: "liquidity", type: "uint256" }, 688 | { name: "amountAMin", type: "uint256" }, 689 | { name: "amountBMin", type: "uint256" }, 690 | { name: "to", type: "address" }, 691 | { name: "deadline", type: "uint256" }, 692 | ], 693 | name: "removeLiquidity", 694 | stateMutability: "nonpayable", 695 | type: "function", 696 | }, 697 | { 698 | outputs: [ 699 | { name: "amountToken", type: "uint256" }, 700 | { name: "amountETH", type: "uint256" }, 701 | ], 702 | inputs: [ 703 | { name: "token", type: "address" }, 704 | { name: "liquidity", type: "uint256" }, 705 | { name: "amountTokenMin", type: "uint256" }, 706 | { name: "amountETHMin", type: "uint256" }, 707 | { name: "to", type: "address" }, 708 | { name: "deadline", type: "uint256" }, 709 | ], 710 | name: "removeLiquidityETH", 711 | stateMutability: "nonpayable", 712 | type: "function", 713 | }, 714 | { 715 | outputs: [{ name: "amountETH", type: "uint256" }], 716 | inputs: [ 717 | { name: "token", type: "address" }, 718 | { name: "liquidity", type: "uint256" }, 719 | { name: "amountTokenMin", type: "uint256" }, 720 | { name: "amountETHMin", type: "uint256" }, 721 | { name: "to", type: "address" }, 722 | { name: "deadline", type: "uint256" }, 723 | ], 724 | name: "removeLiquidityETHSupportingFeeOnTransferTokens", 725 | stateMutability: "nonpayable", 726 | type: "function", 727 | }, 728 | { 729 | outputs: [ 730 | { name: "amountToken", type: "uint256" }, 731 | { name: "amountETH", type: "uint256" }, 732 | ], 733 | inputs: [ 734 | { name: "token", type: "address" }, 735 | { name: "liquidity", type: "uint256" }, 736 | { name: "amountTokenMin", type: "uint256" }, 737 | { name: "amountETHMin", type: "uint256" }, 738 | { name: "to", type: "address" }, 739 | { name: "deadline", type: "uint256" }, 740 | { name: "approveMax", type: "bool" }, 741 | { name: "v", type: "uint8" }, 742 | { name: "r", type: "bytes32" }, 743 | { name: "s", type: "bytes32" }, 744 | ], 745 | name: "removeLiquidityETHWithPermit", 746 | stateMutability: "nonpayable", 747 | type: "function", 748 | }, 749 | { 750 | outputs: [{ name: "amountETH", type: "uint256" }], 751 | inputs: [ 752 | { name: "token", type: "address" }, 753 | { name: "liquidity", type: "uint256" }, 754 | { name: "amountTokenMin", type: "uint256" }, 755 | { name: "amountETHMin", type: "uint256" }, 756 | { name: "to", type: "address" }, 757 | { name: "deadline", type: "uint256" }, 758 | { name: "approveMax", type: "bool" }, 759 | { name: "v", type: "uint8" }, 760 | { name: "r", type: "bytes32" }, 761 | { name: "s", type: "bytes32" }, 762 | ], 763 | name: "removeLiquidityETHWithPermitSupportingFeeOnTransferTokens", 764 | stateMutability: "nonpayable", 765 | type: "function", 766 | }, 767 | { 768 | outputs: [ 769 | { name: "amountA", type: "uint256" }, 770 | { name: "amountB", type: "uint256" }, 771 | ], 772 | inputs: [ 773 | { name: "tokenA", type: "address" }, 774 | { name: "tokenB", type: "address" }, 775 | { name: "liquidity", type: "uint256" }, 776 | { name: "amountAMin", type: "uint256" }, 777 | { name: "amountBMin", type: "uint256" }, 778 | { name: "to", type: "address" }, 779 | { name: "deadline", type: "uint256" }, 780 | { name: "approveMax", type: "bool" }, 781 | { name: "v", type: "uint8" }, 782 | { name: "r", type: "bytes32" }, 783 | { name: "s", type: "bytes32" }, 784 | ], 785 | name: "removeLiquidityWithPermit", 786 | stateMutability: "nonpayable", 787 | type: "function", 788 | }, 789 | { 790 | outputs: [{ name: "amounts", type: "uint256[]" }], 791 | inputs: [ 792 | { name: "amountOut", type: "uint256" }, 793 | { name: "path", type: "address[]" }, 794 | { name: "to", type: "address" }, 795 | { name: "deadline", type: "uint256" }, 796 | ], 797 | name: "swapETHForExactTokens", 798 | stateMutability: "payable", 799 | type: "function", 800 | }, 801 | { 802 | outputs: [{ name: "amounts", type: "uint256[]" }], 803 | inputs: [ 804 | { name: "amountOutMin", type: "uint256" }, 805 | { name: "path", type: "address[]" }, 806 | { name: "to", type: "address" }, 807 | { name: "deadline", type: "uint256" }, 808 | ], 809 | name: "swapExactETHForTokens", 810 | stateMutability: "payable", 811 | type: "function", 812 | }, 813 | { 814 | inputs: [ 815 | { name: "amountOutMin", type: "uint256" }, 816 | { name: "path", type: "address[]" }, 817 | { name: "to", type: "address" }, 818 | { name: "deadline", type: "uint256" }, 819 | ], 820 | name: "swapExactETHForTokensSupportingFeeOnTransferTokens", 821 | stateMutability: "payable", 822 | type: "function", 823 | }, 824 | { 825 | outputs: [{ name: "amounts", type: "uint256[]" }], 826 | inputs: [ 827 | { name: "amountIn", type: "uint256" }, 828 | { name: "amountOutMin", type: "uint256" }, 829 | { name: "path", type: "address[]" }, 830 | { name: "to", type: "address" }, 831 | { name: "deadline", type: "uint256" }, 832 | ], 833 | name: "swapExactTokensForETH", 834 | stateMutability: "nonpayable", 835 | type: "function", 836 | }, 837 | { 838 | inputs: [ 839 | { name: "amountIn", type: "uint256" }, 840 | { name: "amountOutMin", type: "uint256" }, 841 | { name: "path", type: "address[]" }, 842 | { name: "to", type: "address" }, 843 | { name: "deadline", type: "uint256" }, 844 | ], 845 | name: "swapExactTokensForETHSupportingFeeOnTransferTokens", 846 | stateMutability: "nonpayable", 847 | type: "function", 848 | }, 849 | { 850 | outputs: [{ name: "amounts", type: "uint256[]" }], 851 | inputs: [ 852 | { name: "amountIn", type: "uint256" }, 853 | { name: "amountOutMin", type: "uint256" }, 854 | { name: "path", type: "address[]" }, 855 | { name: "to", type: "address" }, 856 | { name: "deadline", type: "uint256" }, 857 | ], 858 | name: "swapExactTokensForTokens", 859 | stateMutability: "nonpayable", 860 | type: "function", 861 | }, 862 | { 863 | inputs: [ 864 | { name: "amountIn", type: "uint256" }, 865 | { name: "amountOutMin", type: "uint256" }, 866 | { name: "path", type: "address[]" }, 867 | { name: "to", type: "address" }, 868 | { name: "deadline", type: "uint256" }, 869 | ], 870 | name: "swapExactTokensForTokensSupportingFeeOnTransferTokens", 871 | stateMutability: "nonpayable", 872 | type: "function", 873 | }, 874 | { 875 | outputs: [{ name: "amounts", type: "uint256[]" }], 876 | inputs: [ 877 | { name: "amountOut", type: "uint256" }, 878 | { name: "amountInMax", type: "uint256" }, 879 | { name: "path", type: "address[]" }, 880 | { name: "to", type: "address" }, 881 | { name: "deadline", type: "uint256" }, 882 | ], 883 | name: "swapTokensForExactETH", 884 | stateMutability: "nonpayable", 885 | type: "function", 886 | }, 887 | { 888 | outputs: [{ name: "amounts", type: "uint256[]" }], 889 | inputs: [ 890 | { name: "amountOut", type: "uint256" }, 891 | { name: "amountInMax", type: "uint256" }, 892 | { name: "path", type: "address[]" }, 893 | { name: "to", type: "address" }, 894 | { name: "deadline", type: "uint256" }, 895 | ], 896 | name: "swapTokensForExactTokens", 897 | stateMutability: "nonpayable", 898 | type: "function", 899 | }, 900 | { stateMutability: "Payable", type: "Receive" }, 901 | ], 902 | }; 903 | export const TRC20_ABI = [ 904 | { 905 | inputs: [ 906 | { internalType: "string", name: "name", type: "string" }, 907 | { internalType: "string", name: "symbol", type: "string" }, 908 | { internalType: "uint256", name: "totalSupply", type: "uint256" }, 909 | ], 910 | stateMutability: "nonpayable", 911 | type: "constructor", 912 | }, 913 | { 914 | anonymous: false, 915 | inputs: [ 916 | { 917 | indexed: true, 918 | internalType: "address", 919 | name: "owner", 920 | type: "address", 921 | }, 922 | { 923 | indexed: true, 924 | internalType: "address", 925 | name: "spender", 926 | type: "address", 927 | }, 928 | { 929 | indexed: false, 930 | internalType: "uint256", 931 | name: "value", 932 | type: "uint256", 933 | }, 934 | ], 935 | name: "Approval", 936 | type: "event", 937 | }, 938 | { 939 | anonymous: false, 940 | inputs: [ 941 | { 942 | indexed: true, 943 | internalType: "address", 944 | name: "previousOwner", 945 | type: "address", 946 | }, 947 | { 948 | indexed: true, 949 | internalType: "address", 950 | name: "newOwner", 951 | type: "address", 952 | }, 953 | ], 954 | name: "OwnershipTransferred", 955 | type: "event", 956 | }, 957 | { 958 | anonymous: false, 959 | inputs: [ 960 | { indexed: true, internalType: "address", name: "from", type: "address" }, 961 | { indexed: true, internalType: "address", name: "to", type: "address" }, 962 | { 963 | indexed: false, 964 | internalType: "uint256", 965 | name: "value", 966 | type: "uint256", 967 | }, 968 | ], 969 | name: "Transfer", 970 | type: "event", 971 | }, 972 | { 973 | inputs: [], 974 | name: "MODE_NORMAL", 975 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 976 | stateMutability: "view", 977 | type: "function", 978 | }, 979 | { 980 | inputs: [], 981 | name: "MODE_TRANSFER_CONTROLLED", 982 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 983 | stateMutability: "view", 984 | type: "function", 985 | }, 986 | { 987 | inputs: [], 988 | name: "MODE_TRANSFER_RESTRICTED", 989 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 990 | stateMutability: "view", 991 | type: "function", 992 | }, 993 | { 994 | inputs: [], 995 | name: "_mode", 996 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 997 | stateMutability: "view", 998 | type: "function", 999 | }, 1000 | { 1001 | inputs: [ 1002 | { internalType: "address", name: "owner", type: "address" }, 1003 | { internalType: "address", name: "spender", type: "address" }, 1004 | ], 1005 | name: "allowance", 1006 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 1007 | stateMutability: "view", 1008 | type: "function", 1009 | }, 1010 | { 1011 | inputs: [ 1012 | { internalType: "address", name: "spender", type: "address" }, 1013 | { internalType: "uint256", name: "amount", type: "uint256" }, 1014 | ], 1015 | name: "approve", 1016 | outputs: [{ internalType: "bool", name: "", type: "bool" }], 1017 | stateMutability: "nonpayable", 1018 | type: "function", 1019 | }, 1020 | { 1021 | inputs: [{ internalType: "address", name: "account", type: "address" }], 1022 | name: "balanceOf", 1023 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 1024 | stateMutability: "view", 1025 | type: "function", 1026 | }, 1027 | { 1028 | inputs: [], 1029 | name: "decimals", 1030 | outputs: [{ internalType: "uint8", name: "", type: "uint8" }], 1031 | stateMutability: "view", 1032 | type: "function", 1033 | }, 1034 | { 1035 | inputs: [ 1036 | { internalType: "address", name: "spender", type: "address" }, 1037 | { internalType: "uint256", name: "subtractedValue", type: "uint256" }, 1038 | ], 1039 | name: "decreaseAllowance", 1040 | outputs: [{ internalType: "bool", name: "", type: "bool" }], 1041 | stateMutability: "nonpayable", 1042 | type: "function", 1043 | }, 1044 | { 1045 | inputs: [ 1046 | { internalType: "address", name: "spender", type: "address" }, 1047 | { internalType: "uint256", name: "addedValue", type: "uint256" }, 1048 | ], 1049 | name: "increaseAllowance", 1050 | outputs: [{ internalType: "bool", name: "", type: "bool" }], 1051 | stateMutability: "nonpayable", 1052 | type: "function", 1053 | }, 1054 | { 1055 | inputs: [], 1056 | name: "name", 1057 | outputs: [{ internalType: "string", name: "", type: "string" }], 1058 | stateMutability: "view", 1059 | type: "function", 1060 | }, 1061 | { 1062 | inputs: [], 1063 | name: "owner", 1064 | outputs: [{ internalType: "address", name: "", type: "address" }], 1065 | stateMutability: "view", 1066 | type: "function", 1067 | }, 1068 | { 1069 | inputs: [], 1070 | name: "renounceOwnership", 1071 | outputs: [], 1072 | stateMutability: "nonpayable", 1073 | type: "function", 1074 | }, 1075 | { 1076 | inputs: [{ internalType: "uint256", name: "v", type: "uint256" }], 1077 | name: "setMode", 1078 | outputs: [], 1079 | stateMutability: "nonpayable", 1080 | type: "function", 1081 | }, 1082 | { 1083 | inputs: [], 1084 | name: "symbol", 1085 | outputs: [{ internalType: "string", name: "", type: "string" }], 1086 | stateMutability: "view", 1087 | type: "function", 1088 | }, 1089 | { 1090 | inputs: [], 1091 | name: "totalSupply", 1092 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 1093 | stateMutability: "view", 1094 | type: "function", 1095 | }, 1096 | { 1097 | inputs: [ 1098 | { internalType: "address", name: "to", type: "address" }, 1099 | { internalType: "uint256", name: "amount", type: "uint256" }, 1100 | ], 1101 | name: "transfer", 1102 | outputs: [{ internalType: "bool", name: "", type: "bool" }], 1103 | stateMutability: "nonpayable", 1104 | type: "function", 1105 | }, 1106 | { 1107 | inputs: [ 1108 | { internalType: "address", name: "from", type: "address" }, 1109 | { internalType: "address", name: "to", type: "address" }, 1110 | { internalType: "uint256", name: "amount", type: "uint256" }, 1111 | ], 1112 | name: "transferFrom", 1113 | outputs: [{ internalType: "bool", name: "", type: "bool" }], 1114 | stateMutability: "nonpayable", 1115 | type: "function", 1116 | }, 1117 | { 1118 | inputs: [{ internalType: "address", name: "newOwner", type: "address" }], 1119 | name: "transferOwnership", 1120 | outputs: [], 1121 | stateMutability: "nonpayable", 1122 | type: "function", 1123 | }, 1124 | ]; 1125 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { Collection, MongoClient } from "mongodb"; 3 | import TelegramBot from "node-telegram-bot-api"; 4 | import { 5 | buyTokenCallback, 6 | cancelSnipeCallback, 7 | changeSlippageCallback, 8 | changeSlippageSellCallback, 9 | confirmBuyCallback, 10 | enterAmountCallback, 11 | myPositionsCallback, 12 | mySnipesCallback, 13 | refreshCallback, 14 | refreshSellCallback, 15 | sellCustomTokenCallback, 16 | sellTokenCallback, 17 | snipeNowCallback, 18 | snipeTokenCallback, 19 | tokenSentCallback, 20 | } from "./callbacks/tokens"; 21 | import { 22 | addWalletCallback, 23 | genWalletCallback, 24 | importWalletCallback, 25 | refreshWalletCallback, 26 | removeWalletCallback, 27 | removeWalletIndexCallback, 28 | walletCallback, 29 | walletInfoCallback, 30 | walletInfoIndexCallback, 31 | } from "./callbacks/wallets"; 32 | import { startCommand } from "./commands/start"; 33 | import { GENERIC_ERROR_MESSAGE } from "./config"; 34 | import { processSnipes } from "./server/processSnipes"; 35 | import { errorLOG, informationsLOG, successLOG } from "./utils/logs"; 36 | import SniperUtils from "./utils/tronWeb"; 37 | 38 | const token = process.env.TELEGRAM_BOT_TOKEN as string; 39 | const bot = new TelegramBot(token, { polling: true }); 40 | 41 | const mongoUri = process.env.MONGODB_URI as string; 42 | const client = new MongoClient(mongoUri); 43 | 44 | const dbName = process.env.MONGODB_DB_NAME as string; 45 | const usersCollectionName = process.env.MONGODB_COLLECTION_NAME as string; 46 | 47 | async function getOrCreateUser( 48 | chatId: number, 49 | usersCollection: Collection 50 | ): Promise { 51 | let user = (await usersCollection.findOne({ id: chatId })) as User | null; 52 | if (!user) { 53 | await usersCollection.insertOne({ 54 | id: chatId, 55 | wallets: [], 56 | snipes: [], 57 | } as User); 58 | user = (await usersCollection.findOne({ id: chatId })) as User | null; 59 | } 60 | return user; 61 | } 62 | 63 | async function main() { 64 | try { 65 | console.log(`${informationsLOG} Initializing SniperUtils...`); 66 | await SniperUtils.initialize(); 67 | console.log(`${successLOG} Initialized SniperUtils...`); 68 | 69 | console.log(`${informationsLOG} Connecting to MongoDB...`); 70 | await client.connect(); 71 | console.log(`${successLOG} Connected to MongoDB...`); 72 | 73 | console.log(`${informationsLOG} Starting processSnipes interval...`); 74 | setInterval(() => { 75 | processSnipes(bot, usersCollection); 76 | }, 5000); 77 | console.log(`${successLOG} Started processSnipes interval...`); 78 | 79 | const db = client.db(dbName); 80 | const usersCollection = db.collection(usersCollectionName); 81 | await usersCollection.createIndex({ id: 1 }, { unique: true }); 82 | 83 | console.log(`${informationsLOG} Setting up bot...`); 84 | 85 | bot.onText(/\/start/, async (msg: TelegramBot.Message) => { 86 | startCommand(msg, bot); 87 | }); 88 | 89 | bot.onText(/\/wallets/, async (msg: TelegramBot.Message) => { 90 | const chatId = msg.chat.id; 91 | 92 | const user = await getOrCreateUser(chatId, usersCollection); 93 | 94 | if (!user) { 95 | console.error(`${errorLOG} User not found.`); 96 | return; 97 | } 98 | 99 | await walletCallback(user, bot, chatId); 100 | }); 101 | 102 | bot.onText(/\/positions/, async (msg: TelegramBot.Message) => { 103 | const chatId = msg.chat.id; 104 | 105 | const user = await getOrCreateUser(chatId, usersCollection); 106 | 107 | if (!user) { 108 | console.error(`${errorLOG} User not found.`); 109 | return; 110 | } 111 | 112 | await myPositionsCallback(user, bot, chatId); 113 | }); 114 | 115 | bot.onText(/\/pendingsnipes/, async (msg: TelegramBot.Message) => { 116 | const chatId = msg.chat.id; 117 | 118 | const user = await getOrCreateUser(chatId, usersCollection); 119 | 120 | if (!user) { 121 | console.error(`${errorLOG} User not found.`); 122 | return; 123 | } 124 | 125 | await mySnipesCallback(user, bot, chatId); 126 | }); 127 | 128 | bot.on("message", async (msg) => { 129 | try { 130 | if (!msg.text) return; 131 | 132 | const chatId = msg.chat.id; 133 | const text = msg.text; 134 | 135 | if (text.startsWith("/")) return; 136 | 137 | if (text.length !== 34) return; 138 | 139 | const user = await getOrCreateUser(chatId, usersCollection); 140 | 141 | if (!user) { 142 | console.error(`${errorLOG} User not found.`); 143 | return; 144 | } 145 | 146 | await tokenSentCallback(bot, chatId, text); 147 | } catch (error) { 148 | const chatId = msg.chat.id; 149 | console.error(`${errorLOG} ${error}`); 150 | bot.sendMessage(chatId, GENERIC_ERROR_MESSAGE, { 151 | reply_markup: { 152 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 153 | }, 154 | }); 155 | } 156 | }); 157 | 158 | bot.on("callback_query", async (callbackQuery) => { 159 | try { 160 | const message = callbackQuery.message; 161 | 162 | if (!message) return; 163 | 164 | const chatId = message.chat.id; 165 | const data = callbackQuery.data; 166 | 167 | if (!data) return; 168 | 169 | const user = await getOrCreateUser(chatId, usersCollection); 170 | 171 | if (!user) { 172 | console.error(`${errorLOG} User not found.`); 173 | return; 174 | } 175 | 176 | if (data.startsWith("remove_wallet_")) { 177 | const index = parseInt(data.split("_")[2]); 178 | await removeWalletIndexCallback( 179 | usersCollection, 180 | user, 181 | bot, 182 | chatId, 183 | message, 184 | index 185 | ); 186 | bot.answerCallbackQuery(callbackQuery.id); 187 | return; 188 | } else if (data.startsWith("wallet_info_")) { 189 | const index = parseInt(data.split("_")[2]); 190 | await walletInfoIndexCallback(user, bot, chatId, message, index); 191 | bot.answerCallbackQuery(callbackQuery.id); 192 | return; 193 | } else if (data.startsWith("enter_custom_")) { 194 | const tokenAddress = data.split("_")[2]; 195 | const slippage = parseFloat(data.split("_")[3]); 196 | await enterAmountCallback(bot, chatId, tokenAddress, slippage); 197 | bot.answerCallbackQuery(callbackQuery.id); 198 | return; 199 | } else if (data.startsWith("change_slippage_")) { 200 | const tokenAddress = data.split("_")[2]; 201 | await changeSlippageCallback(bot, chatId, tokenAddress, message); 202 | bot.answerCallbackQuery(callbackQuery.id); 203 | return; 204 | } else if (data.startsWith("refreshbuy_")) { 205 | const tokenAddress = data.split("_")[1]; 206 | const slippage = parseFloat(data.split("_")[2]); 207 | await refreshCallback(bot, chatId, tokenAddress, slippage, message); 208 | bot.answerCallbackQuery(callbackQuery.id); 209 | return; 210 | } else if (data.startsWith("enter_")) { 211 | const amount = parseFloat(data.split("_")[1]); 212 | const tokenAddress = data.split("_")[2]; 213 | const slippage = parseFloat(data.split("_")[3]); 214 | await buyTokenCallback( 215 | user, 216 | bot, 217 | chatId, 218 | amount, 219 | tokenAddress, 220 | slippage 221 | ); 222 | bot.answerCallbackQuery(callbackQuery.id); 223 | return; 224 | } else if (data.startsWith("buy_")) { 225 | const index = parseInt(data.split("_")[1]); 226 | const amount = parseFloat(data.split("_")[2]); 227 | const tokenAddress = data.split("_")[3]; 228 | const slippage = parseFloat(data.split("_")[4]); 229 | await confirmBuyCallback( 230 | user, 231 | bot, 232 | chatId, 233 | index, 234 | amount, 235 | tokenAddress, 236 | slippage 237 | ); 238 | bot.answerCallbackQuery(callbackQuery.id); 239 | return; 240 | } else if (data.startsWith("refreshsell_")) { 241 | const walletIndex = Number(data.split("_")[1]); 242 | const tokenAddress = data.split("_")[2]; 243 | const slippage = parseFloat(data.split("_")[3]); 244 | await refreshSellCallback( 245 | user, 246 | bot, 247 | chatId, 248 | walletIndex, 249 | tokenAddress, 250 | slippage, 251 | message 252 | ); 253 | bot.answerCallbackQuery(callbackQuery.id); 254 | return; 255 | } else if (data.startsWith("change_slippagesell_")) { 256 | const walletIndex = Number(data.split("_")[2]); 257 | const tokenAddress = data.split("_")[3]; 258 | await changeSlippageSellCallback( 259 | user, 260 | bot, 261 | chatId, 262 | walletIndex, 263 | tokenAddress, 264 | message 265 | ); 266 | bot.answerCallbackQuery(callbackQuery.id); 267 | return; 268 | } else if (data.startsWith("sell_")) { 269 | const percentageToSell = parseFloat(data.split("_")[1]); 270 | const walletIndex = Number(data.split("_")[2]); 271 | const tokenAddress = data.split("_")[3]; 272 | const slippage = parseFloat(data.split("_")[4]); 273 | await sellTokenCallback( 274 | user, 275 | bot, 276 | chatId, 277 | walletIndex, 278 | percentageToSell, 279 | tokenAddress, 280 | slippage 281 | ); 282 | bot.answerCallbackQuery(callbackQuery.id); 283 | return; 284 | } else if (data.startsWith("sellcustom_")) { 285 | const walletIndex = Number(data.split("_")[1]); 286 | const tokenAddress = data.split("_")[2]; 287 | const slippage = parseFloat(data.split("_")[3]); 288 | await sellCustomTokenCallback( 289 | user, 290 | bot, 291 | chatId, 292 | walletIndex, 293 | tokenAddress, 294 | slippage 295 | ); 296 | bot.answerCallbackQuery(callbackQuery.id); 297 | return; 298 | } else if (data.startsWith("snipe_")) { 299 | const tokenAddress = data.split("_")[1]; 300 | await snipeTokenCallback(user, bot, chatId, tokenAddress); 301 | bot.answerCallbackQuery(callbackQuery.id); 302 | return; 303 | } else if (data.startsWith("snipenow_")) { 304 | const walletIndex = Number(data.split("_")[1]); 305 | const tokenAddress = data.split("_")[2]; 306 | await snipeNowCallback( 307 | usersCollection, 308 | user, 309 | bot, 310 | chatId, 311 | walletIndex, 312 | tokenAddress 313 | ); 314 | bot.answerCallbackQuery(callbackQuery.id); 315 | return; 316 | } else if (data.startsWith("cancel_snipe_")) { 317 | const tokenAddress = data.split("_")[2]; 318 | await cancelSnipeCallback(usersCollection, bot, chatId, tokenAddress); 319 | bot.answerCallbackQuery(callbackQuery.id); 320 | return; 321 | } 322 | 323 | switch (data) { 324 | case "wallets": 325 | await walletCallback(user, bot, chatId); 326 | break; 327 | 328 | case "add_wallet": 329 | await addWalletCallback(bot, chatId); 330 | break; 331 | 332 | case "import_wallet": 333 | await importWalletCallback( 334 | usersCollection, 335 | user, 336 | bot, 337 | chatId, 338 | message 339 | ); 340 | break; 341 | 342 | case "generate_wallet": 343 | await genWalletCallback( 344 | usersCollection, 345 | user, 346 | bot, 347 | chatId, 348 | message 349 | ); 350 | break; 351 | 352 | case "mypositions": 353 | await myPositionsCallback(user, bot, chatId); 354 | break; 355 | 356 | case "wallet_info": 357 | await walletInfoCallback(user, bot, chatId, message); 358 | break; 359 | 360 | case "remove_wallet": 361 | await removeWalletCallback(user, bot, chatId, message); 362 | break; 363 | 364 | case "refresh_wallet": 365 | await refreshWalletCallback(user, bot, chatId, message); 366 | break; 367 | 368 | case "mypendingsnipes": 369 | await mySnipesCallback(user, bot, chatId); 370 | break; 371 | 372 | case "close": 373 | bot.deleteMessage(chatId, message.message_id); 374 | break; 375 | 376 | default: 377 | console.error(`${errorLOG} Unknown command.`); 378 | bot.sendMessage(chatId, "Unknown command.", { 379 | parse_mode: "Markdown", 380 | reply_markup: { 381 | inline_keyboard: [ 382 | [{ text: "❌ Close", callback_data: "close" }], 383 | ], 384 | }, 385 | }); 386 | } 387 | 388 | bot.answerCallbackQuery(callbackQuery.id); 389 | } catch (error) { 390 | if (!callbackQuery.message) return; 391 | 392 | const chatId = callbackQuery.message.chat.id; 393 | console.error(`${errorLOG} ${error}`); 394 | bot.sendMessage(chatId, GENERIC_ERROR_MESSAGE, { 395 | reply_markup: { 396 | inline_keyboard: [[{ text: "❌ Close", callback_data: "close" }]], 397 | }, 398 | }); 399 | } 400 | }); 401 | 402 | console.log(`${successLOG} TRON Sniper/Trading Bot is running...`); 403 | } catch (error) { 404 | console.error(`${errorLOG} ${error}`); 405 | } 406 | } 407 | 408 | main().catch(console.error); 409 | -------------------------------------------------------------------------------- /src/server/processSnipes.ts: -------------------------------------------------------------------------------- 1 | import TelegramBot from "node-telegram-bot-api"; 2 | import { confirmBuyCallback } from "../callbacks/tokens"; 3 | import { errorLOG } from "../utils/logs"; 4 | import SniperUtils from "../utils/tronWeb"; 5 | 6 | /* ------------------------------ */ 7 | /* SNIPE PART */ 8 | /* ------------------------------ */ 9 | 10 | export async function processSnipes(bot: TelegramBot, usersCollection: any) { 11 | try { 12 | const users = await usersCollection.find().toArray(); 13 | 14 | for (const user of users) { 15 | const { id: chatId, snipes } = user; 16 | 17 | if (!snipes || snipes.length === 0) continue; 18 | 19 | for (const snipe of snipes) { 20 | const { 21 | address: tokenAddress, 22 | amountToInvestInTRX, 23 | privateKey, 24 | slippage, 25 | } = snipe; 26 | 27 | const walletIndex = user.wallets.findIndex( 28 | (wallet: Wallet) => wallet.privateKey === privateKey 29 | ); 30 | 31 | if (walletIndex === -1) { 32 | console.log( 33 | `Wallet with private key ${privateKey} not found for user: ${chatId}` 34 | ); 35 | continue; 36 | } 37 | 38 | const pairAddress = await SniperUtils.getPairAddress(tokenAddress); 39 | 40 | if (!pairAddress) { 41 | console.log( 42 | `${chatId}: Still waiting for pair address for ${tokenAddress}` 43 | ); 44 | continue; 45 | } 46 | 47 | bot.sendMessage( 48 | chatId, 49 | `Buying ${amountToInvestInTRX} TRX of ${tokenAddress} with slippage ${slippage}%...` 50 | ); 51 | 52 | await confirmBuyCallback( 53 | user, 54 | bot, 55 | chatId, 56 | walletIndex, 57 | amountToInvestInTRX, 58 | tokenAddress, 59 | slippage 60 | ); 61 | } 62 | } 63 | } catch (error) { 64 | console.error(`${errorLOG} ${error}`); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/types/options.d.ts: -------------------------------------------------------------------------------- 1 | interface Options { 2 | feeLimit?: number; 3 | callValue?: number; 4 | tokenValue?: number; 5 | tokenId?: number; 6 | Permission_id?: number; 7 | } 8 | -------------------------------------------------------------------------------- /src/types/tokens.d.ts: -------------------------------------------------------------------------------- 1 | interface Token { 2 | address: string; 3 | name: string; 4 | symbol: string; 5 | balance: string; 6 | decimals: number; 7 | type: string; 8 | logo: string; 9 | holders: number; 10 | } 11 | -------------------------------------------------------------------------------- /src/types/tronweb.d.ts: -------------------------------------------------------------------------------- 1 | declare module "tronweb" { 2 | import { BigNumber } from "bignumber.js"; 3 | import { 4 | Account, 5 | AccountMnemonic, 6 | AssetTRC10, 7 | AssetUpdate, 8 | BlockInfo, 9 | BlockInput, 10 | BlockTransaction, 11 | BytesLike, 12 | ChainParameter, 13 | ContractExecutionParams, 14 | CreateRandomOptions, 15 | DelegatedResourceAccount, 16 | DelegatedResourceList, 17 | EnergyEstimate, 18 | EventResult, 19 | Exchange, 20 | Header, 21 | HexString, 22 | JsonFragment, 23 | KeyValue, 24 | Miner, 25 | NodeInfo, 26 | Proposal, 27 | Resource, 28 | SideOptions, 29 | TokenInfo, 30 | Transaction, 31 | TransactionResult, 32 | TriggerConstantContractResult, 33 | TronAccountResource, 34 | TronContract, 35 | TronContractResult, 36 | TronWebConstructor, 37 | TrxAccount, 38 | } from "tronweb/interfaces"; 39 | export class TronWeb { 40 | address: typeof TronWeb.address; 41 | transactionBuilder: typeof TronWeb.transactionBuilder; 42 | trx: typeof TronWeb.trx; 43 | utils: typeof TronWeb.utils & { 44 | transaction: typeof TronWeb.utils.transaction; 45 | }; 46 | constructor( 47 | fullNode: string, 48 | solidityNode: string, 49 | eventServer: string | boolean, 50 | privateKey?: string | boolean 51 | ); 52 | constructor( 53 | fullNode: string, 54 | solidityNode: string, 55 | eventServer: string | boolean, 56 | sideOptions: SideOptions, 57 | privateKey?: string | boolean 58 | ); 59 | constructor(obj: TronWebConstructor); 60 | contract(data: JsonFragment[], address: string): TronContract; 61 | setHeader(header: Header): void | Error; 62 | currentProvider(): any; 63 | currentProviders(): any; 64 | getEventByTransactionID(transactionID: string): Promise; 65 | getEventResult( 66 | contractAddress: string, 67 | options?: Object 68 | ): Promise; 69 | isConnected(): Object; 70 | isValidProvider(provider: any): any; 71 | setAddress(address: string): void | Error; 72 | setDefaultBlock(blockID?: BlockInput): void | string | boolean; 73 | setEventServer(eventServer: any): void | Error; 74 | setFullNode(fullNode: any): void | Error; 75 | setPrivateKey(privateKey: string): void | Error; 76 | setSolidityNode(solidityNode: any): void | Error; 77 | createAccount(): Promise; 78 | createRandom(options?: CreateRandomOptions): Promise; 79 | fromAscii(string: any, padding: any): any; 80 | fromDecimal(value: number | string): string; 81 | fromSun(sun: string | number): string; 82 | fromUtf8(string: string): string; 83 | fromMnemonic( 84 | mnemonic: string, 85 | path?: string, 86 | wordlist?: string 87 | ): AccountMnemonic | Error; 88 | isAddress(address: string): boolean; 89 | sha3(string: string, prefix?: boolean): HexString; 90 | toAscii(hex: HexString): string; 91 | toBigNumber(amount: number | string | HexString): BigNumber | Object; 92 | toDecimal(value: string | HexString): number | string; 93 | toHex(val: string | number | object | [] | BigNumber): HexString; 94 | toSun(trx: number): string; 95 | toUtf8(hex: string): string; 96 | BigNumber(val: number | string | HexString): BigNumber; 97 | } 98 | export namespace TronWeb { 99 | namespace transactionBuilder { 100 | function addUpdateData( 101 | unsignedTransaction: JSON | Object, 102 | memo: string 103 | ): Promise; 104 | function applyForSR( 105 | address: string, 106 | url: string, 107 | options?: number 108 | ): Promise; 109 | function createAccount( 110 | address: string, 111 | options?: JSON | Object 112 | ): Promise; 113 | function createAsset( 114 | options: AssetTRC10, 115 | issuerAddress: string 116 | ): Promise; 117 | function createProposal( 118 | parameters: KeyValue[], 119 | issuerAddress: string, 120 | options?: number 121 | ): Promise; 122 | function createSmartContract( 123 | options: ContractExecutionParams, 124 | issuerAddress: string 125 | ): Promise; 126 | function createToken( 127 | options: AssetTRC10, 128 | issuerAddress: string 129 | ): Promise; 130 | function delegateResource( 131 | amount: number, 132 | receiverAddress: string, 133 | resource: string, 134 | address: string, 135 | lock: boolean, 136 | options?: Object 137 | ): Promise; 138 | function deleteProposal( 139 | proposalID: number, 140 | issuerAddress: string, 141 | options?: number 142 | ): Promise; 143 | function estimateEnergy( 144 | contractAddress: string | HexString, 145 | functionSelector: string, 146 | options: Object, 147 | parameter: any[], 148 | issuerAddress: string | HexString 149 | ): Promise; 150 | function extendExpiration( 151 | transaction: Transaction | JSON | Object, 152 | extension: number 153 | ): Promise; 154 | function freezeBalance( 155 | amount: number, 156 | duration: number, 157 | resource: Resource, 158 | ownerAddress: string, 159 | receiverAddress: string, 160 | options?: number 161 | ): Promise; 162 | function freezeBalanceV2( 163 | amount: number, 164 | resource: Resource, 165 | ownerAddress: string, 166 | options?: Object 167 | ): Promise; 168 | function injectExchangeTokens( 169 | exchangeID: number, 170 | tokenID: string, 171 | tokenAmount: number, 172 | ownerAddress: string, 173 | options?: number 174 | ): Promise; 175 | function purchaseAsset( 176 | issuerAddress: string, 177 | tokenID: string, 178 | amount: number, 179 | buyer?: string, 180 | options?: number 181 | ): Promise; 182 | function purchaseToken( 183 | issuerAddress: string, 184 | tokenID: string, 185 | amount: number, 186 | buyer?: string, 187 | options?: number 188 | ): Promise; 189 | function sendAsset( 190 | to: string, 191 | amount: number, 192 | tokenID: string, 193 | from: string, 194 | options: number 195 | ): Promise; 196 | function sendToken( 197 | to: string, 198 | amount: number | string, 199 | tokenID: string, 200 | pk?: string 201 | ): Promise; 202 | function sendTrx( 203 | to: string, 204 | amount: number, 205 | from: string, 206 | options: number 207 | ): Promise; 208 | function tradeExchangeTokens( 209 | exchangeID: number, 210 | tokenID: string, 211 | tokenAmountSold: number, 212 | tokenAmountExpected: number, 213 | ownerAddress: string, 214 | options: number 215 | ): Promise; 216 | function triggerConfirmedConstantContract( 217 | contractAddress: string, 218 | functions: string, 219 | options: Object, 220 | parameter: any[], 221 | issuerAddress: string 222 | ): Promise; 223 | function triggerConstantContract( 224 | contractAddress: string, 225 | functions: string, 226 | options: Object, 227 | parameter: any[], 228 | issuerAddress: string 229 | ): Promise; 230 | function triggerSmartContract( 231 | contractAddress: string, 232 | functions: string, 233 | options: Object, 234 | parameter: any, 235 | issuerAddress: string 236 | ): Promise; 237 | function undelegateResource( 238 | amount: number, 239 | receiverAddress: string, 240 | resource: string, 241 | address: string, 242 | options?: Object 243 | ): Promise; 244 | function unfreezeBalance( 245 | resource: Resource, 246 | address: string, 247 | receiver: string, 248 | options: number 249 | ): Promise; 250 | function unfreezeBalanceV2( 251 | amount: number, 252 | resource: Resource, 253 | address: string, 254 | options: Object 255 | ): Promise; 256 | function updateSetting( 257 | contract_address: string, 258 | consume_user_resource_percent: number, 259 | owner_address: string, 260 | options: number 261 | ): Promise; 262 | function updateAccountPermissions( 263 | owner_address: string, 264 | ownerPermissions: Object, 265 | witnessPermissions: Object | null, 266 | activesPermissions: Object[] 267 | ): Promise; 268 | function updateAsset( 269 | options: AssetUpdate, 270 | issuerAddress: string 271 | ): Promise; 272 | function updateBrokerage( 273 | brokerage: number, 274 | ownerAddress: string 275 | ): Promise; 276 | function updateEnergyLimit( 277 | contract_address: string, 278 | origin_energy_limit: number, 279 | owner_address: string, 280 | options: number 281 | ): Promise; 282 | function updateToken( 283 | options: AssetUpdate, 284 | issuerAddress: string 285 | ): Promise; 286 | function vote( 287 | votes: Object, 288 | voterAddress: string, 289 | option: number 290 | ): Promise; 291 | function voteProposal( 292 | proposalID: number, 293 | hasApproval: string, 294 | voterAddress: string, 295 | options: number 296 | ): Promise; 297 | function withdrawBlockRewards( 298 | address: string, 299 | options: number 300 | ): Promise; 301 | function withdrawExchangeTokens( 302 | exchangeID: number, 303 | tokenID: string, 304 | tokenAmount: number, 305 | ownerAddress: string, 306 | options: number 307 | ): Promise; 308 | function withdrawExpireUnfreeze(address: string): Promise; 309 | } 310 | namespace trx { 311 | function getAccount(address: HexString | string): Promise; 312 | function getAccountResources( 313 | address: HexString | string 314 | ): Promise; 315 | function getApprovedList(r: Transaction): Promise; 316 | function getAvailableUnfreezeCount( 317 | address: string | HexString, 318 | options?: Object 319 | ): Promise; 320 | function getBalance(address: string | HexString): Promise; 321 | function getBandwidth(address: string | HexString): Promise; 322 | function getBlock(block?: number | string): Promise; 323 | function getBlockByHash(blockHash: string): Promise; 324 | function getBlockByNumber(blockID: number): Promise; 325 | function getBlockRange(start: number, end: number): Promise; 326 | function getBlockTransactionCount( 327 | block: number | string 328 | ): Promise; 329 | function getBrokerage(address: string | HexString): Promise; 330 | function getCanDelegatedMaxSize( 331 | address: string | HexString, 332 | resource?: Resource, 333 | options?: Object 334 | ): Promise; 335 | function getCanWithdrawUnfreezeAmount( 336 | address: string | HexString, 337 | timestamp?: number, 338 | options?: Object 339 | ): Promise; 340 | function getChainParameters(): Promise; 341 | function getConfirmedTransaction(transactionID: string): Promise; 342 | function getContract( 343 | contractAddress: string | HexString 344 | ): Promise; 345 | function getCurrentBlock(): Promise; 346 | function getDelegatedResourceV2( 347 | fromAddress: string | HexString, 348 | toAddress: string | HexString, 349 | options?: Object 350 | ): Promise; 351 | function getDelegatedResourceAccountIndexV2( 352 | address: string | HexString, 353 | options?: Object 354 | ): Promise; 355 | function getExchangeByID(exchangeID: number): Promise; 356 | function getNodeInfo(): Promise; 357 | function getReward(address: string | HexString): Promise; 358 | function getSignWeight( 359 | tx: Transaction 360 | ): Promise; 361 | function getTokenByID( 362 | tknID: string | number 363 | ): Promise; 364 | function getTokenFromID(tokenID: string | number): Promise; 365 | function getTokenListByName( 366 | name: string 367 | ): Promise; 368 | function getTokensIssuedByAddress( 369 | address: string | HexString 370 | ): Promise; 371 | function getTransaction( 372 | transactionID: string 373 | ): Promise; 374 | function getTransactionFromBlock( 375 | block: number | string, 376 | index: number 377 | ): Promise; 378 | function getTransactionInfo( 379 | transactionID: string 380 | ): Promise; 381 | function getUnconfirmedBalance(address: string): Promise; 382 | function getUnconfirmedBrokerage(address: string): Promise; 383 | function getUnconfirmedReward(address: string): Promise; 384 | function getUnconfirmedTransactionInfo( 385 | txid: string 386 | ): Promise; 387 | function listExchanges(): Promise; 388 | function listExchangesPaginated( 389 | limit: number, 390 | offset: number 391 | ): Promise; 392 | function listNodes(): Promise; 393 | function listProposals(): Promise; 394 | function listSuperRepresentatives(): Promise; 395 | function listTokens( 396 | limit?: number, 397 | offset?: number 398 | ): Promise; 399 | function sendRawTransaction( 400 | signedTransaction: JSON | Object, 401 | options?: any 402 | ): Promise; 403 | function sendHexTransaction( 404 | signedHexTransaction: string | HexString 405 | ): Promise; 406 | function sendToken( 407 | to: string, 408 | amount: number, 409 | tokenID: string, 410 | from: string, 411 | options: number 412 | ): Promise; 413 | function sendTransaction( 414 | to: string, 415 | amount: number, 416 | pk?: string 417 | ): Promise; 418 | function sign( 419 | transaction: Object, 420 | privateKey: string 421 | ): Promise; 422 | function sign(str: string, privateKey: string): Promise; 423 | function signMessageV2( 424 | msg: string | BytesLike, 425 | privateKey: string 426 | ): Promise; 427 | function timeUntilNextVoteCycle(): Promise; 428 | function multiSign( 429 | tx: JSON | Object, 430 | pk: string, 431 | permissionId: number 432 | ): Promise; 433 | function verifyMessage( 434 | message: string | HexString, 435 | signature: string, 436 | address: string 437 | ): Promise; 438 | function verifyMessageV2( 439 | message: string | HexString, 440 | signature: string 441 | ): Promise; 442 | function _signTypedData( 443 | domain: JSON | Object, 444 | types: JSON | Object, 445 | value: JSON | Object, 446 | privateKey: string 447 | ): Promise; 448 | function verifyTypedData( 449 | domain: JSON | Object, 450 | types: JSON | Object, 451 | value: JSON | Object, 452 | signature: string, 453 | address: string 454 | ): Promise; 455 | } 456 | namespace address { 457 | function fromHex(hex: string): string; 458 | function fromPrivateKey(pk: string): string; 459 | function toHex(base58: string): string; 460 | } 461 | namespace utils { 462 | namespace transaction { 463 | function txJsonToPb(tx: JSON | Object): Object; 464 | function txPbToTxID(tx: JSON | Object): string; 465 | } 466 | } 467 | } 468 | export default TronWeb; 469 | } 470 | -------------------------------------------------------------------------------- /src/types/user.d.ts: -------------------------------------------------------------------------------- 1 | interface User { 2 | id: number; 3 | wallets: Wallet[]; 4 | snipes: Snipe[]; 5 | } 6 | 7 | interface Wallet { 8 | privateKey: string; 9 | address: string; 10 | } 11 | 12 | interface Snipe { 13 | address: string; 14 | amountToInvestInTRX: number; 15 | privateKey: string; 16 | slippage: number; 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/format.ts: -------------------------------------------------------------------------------- 1 | export function formatNumber(number: number) { 2 | if (number >= 1e9) { 3 | return (number / 1e9).toFixed(2) + "B"; 4 | } 5 | if (number >= 1e6) { 6 | return (number / 1e6).toFixed(2) + "M"; 7 | } 8 | if (number >= 1e3) { 9 | return (number / 1e3).toFixed(2) + "K"; 10 | } 11 | return number.toFixed(2); 12 | } 13 | 14 | export function formatElapsedTime(createdDate: Date) { 15 | const now = Date.now(); 16 | const elapsedTime = now - createdDate.getTime(); 17 | 18 | const days = Math.floor(elapsedTime / 86400000); 19 | const hours = Math.floor((elapsedTime % 86400000) / 3600000); 20 | const minutes = Math.floor((elapsedTime % 3600000) / 60000); 21 | const seconds = Math.floor((elapsedTime % 60000) / 1000); 22 | 23 | if (elapsedTime >= 86400000) { 24 | return `${days}d ${hours}h ${minutes}m`; 25 | } 26 | if (elapsedTime >= 3600000) { 27 | return `${hours}h ${minutes}m`; 28 | } 29 | if (elapsedTime >= 60000) { 30 | return `${minutes}m ${seconds}s`; 31 | } 32 | return `${seconds}s`; 33 | } 34 | -------------------------------------------------------------------------------- /src/utils/logs.ts: -------------------------------------------------------------------------------- 1 | import colors from "colors"; 2 | 3 | export const successLOG = colors.green("[SUCCESS]"); 4 | export const informationsLOG = colors.blue("[INFO]"); 5 | export const warningLOG = colors.yellow("[WARNING]"); 6 | export const errorLOG = colors.red("[ERROR]"); 7 | -------------------------------------------------------------------------------- /src/utils/tronWeb.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from "bignumber.js"; 2 | import dotenv from "dotenv"; 3 | import TronWeb from "tronweb"; 4 | import { 5 | SUNSWAP_FACTORY_ABI, 6 | SUNSWAP_FACTORY_ADDRESS, 7 | SUNSWAP_PAIR_ABI, 8 | SUNSWAP_ROUTER_ADDRESS, 9 | TRC20_ABI, 10 | WTRX_ADDRESS, 11 | WTRX_DECIMALS, 12 | } from "../config"; 13 | import { errorLOG } from "./logs"; 14 | 15 | class SniperUtils { 16 | private static instance: TronWeb; 17 | private static cachedTRXPrice: number | null = null; 18 | private static cacheTimestamp: number = 0; 19 | 20 | private constructor() {} 21 | 22 | /* ------------------------------ */ 23 | /* TRONWEB PART */ 24 | /* ------------------------------ */ 25 | 26 | static getInstance() { 27 | if (!this.instance) { 28 | throw new Error("TronWeb instance not initialized"); 29 | } 30 | 31 | return this.instance; 32 | } 33 | 34 | static async initialize() { 35 | try { 36 | const { parsed: env } = dotenv.config(); 37 | 38 | if (!env) { 39 | throw new Error("No .env file found"); 40 | } 41 | 42 | const requiredEnvVariables = ["TRON_FULL_HOST", "API_KEY", "PRIVATE_KEY"]; 43 | for (const variable of requiredEnvVariables) { 44 | if (!env[variable]) { 45 | throw new Error(`${variable} is missing in .env file`); 46 | } 47 | } 48 | 49 | this.instance = new TronWeb({ 50 | fullHost: env.TRON_FULL_HOST, 51 | headers: { "TRON-PRO-API-KEY": env.API_KEY }, 52 | privateKey: env.PRIVATE_KEY, 53 | }); 54 | } catch (error) { 55 | console.error(`${errorLOG} ${error}`); 56 | process.exit(1); 57 | } 58 | } 59 | 60 | static async setPrivateKey(privateKey: string) { 61 | this.instance.setPrivateKey(privateKey); 62 | } 63 | 64 | /* ------------------------------ */ 65 | /* ACCOUNT PART */ 66 | /* ------------------------------ */ 67 | 68 | static importAccount(privateKey: string) { 69 | return this.instance.address.fromPrivateKey(privateKey); 70 | } 71 | 72 | static async createAccount() { 73 | return await this.instance.createAccount(); 74 | } 75 | 76 | static async getAccount(accountAddress: string) { 77 | return await this.instance.trx.getAccount(accountAddress); 78 | } 79 | 80 | static async getBalance(accountAddress: string) { 81 | return await this.instance.trx.getBalance(accountAddress); 82 | } 83 | 84 | static async getTokensBalance(accountAddress: string) { 85 | try { 86 | const res = await fetch( 87 | `https://apilist.tronscanapi.com/api/account/tokens?address=${accountAddress}&start=0&limit=150&token=&hidden=0&show=0&sortType=0&sortBy=0` 88 | ); 89 | 90 | const data = await res.json(); 91 | 92 | if (!data.data) { 93 | throw new Error("No data found"); 94 | } 95 | 96 | const tokens = data.data.map((token: any) => { 97 | const type = token.tokenType; 98 | 99 | if (type === "trc10") return; 100 | 101 | return { 102 | address: token.tokenId, 103 | name: token.tokenName, 104 | symbol: token.tokenAbbr, 105 | balance: token.balance, 106 | decimals: token.tokenDecimal, 107 | type: token.tokenType, 108 | logo: token.tokenLogo, 109 | holders: token.nrOfTokenHolders, 110 | } as Token; 111 | }); 112 | 113 | if (!tokens) throw new Error("No tokens found"); 114 | 115 | const finalTokens = tokens.filter((token: Token) => token); 116 | 117 | if (finalTokens.length === 0) return []; 118 | 119 | return finalTokens; 120 | } catch (error) { 121 | console.error(`${errorLOG} ${error}`); 122 | return []; 123 | } 124 | } 125 | 126 | /* ------------------------------ */ 127 | /* CONTRACT PART */ 128 | /* ------------------------------ */ 129 | 130 | static async getContractABI(contractAddress: string) { 131 | return await this.instance.trx.getContract(contractAddress); 132 | } 133 | 134 | static async getContractInstance(abi: any, contractAddress: string) { 135 | return await this.instance.contract(abi, contractAddress); 136 | } 137 | 138 | /* ------------------------------ */ 139 | /* SUNSWAP PART */ 140 | /* ------------------------------ */ 141 | 142 | static async getPairAddress(tokenAddress: string) { 143 | try { 144 | const contract = await this.getContractInstance( 145 | SUNSWAP_FACTORY_ABI.entrys, 146 | SUNSWAP_FACTORY_ADDRESS 147 | ); 148 | 149 | if (!contract) throw new Error("SunSwap factory contract not found"); 150 | 151 | const encodedPairAddress = await contract 152 | .getPair(tokenAddress, WTRX_ADDRESS) 153 | .call(); 154 | 155 | if ( 156 | !encodedPairAddress || 157 | encodedPairAddress === "410000000000000000000000000000000000000000" 158 | ) 159 | return null; 160 | 161 | return this.getAddressFromHex(encodedPairAddress); 162 | } catch (error) { 163 | console.error(`${errorLOG} ${error}`); 164 | return null; 165 | } 166 | } 167 | 168 | static async getPairInformations(pairAddress: string) { 169 | try { 170 | const contract = await this.getContractInstance( 171 | SUNSWAP_PAIR_ABI.entrys, 172 | pairAddress 173 | ); 174 | 175 | if (!contract) throw new Error("No contract found"); 176 | 177 | const reserves = (await contract.getReserves().call()) as [ 178 | bigint, 179 | bigint, 180 | number 181 | ]; 182 | 183 | if (!reserves) throw new Error("No reserves found"); 184 | 185 | const token0 = await contract.token0().call(); 186 | const token1 = await contract.token1().call(); 187 | 188 | if (!token0 || !token1) throw new Error("No tokens found"); 189 | 190 | if (this.getAddressFromHex(token0) === WTRX_ADDRESS) { 191 | const tokenAddress = this.getAddressFromHex(token1); 192 | 193 | if (!tokenAddress) throw new Error("No token addresses found"); 194 | 195 | const reserveWTRX = new BigNumber(reserves[0].toString()); 196 | const reserveToken = new BigNumber(reserves[1].toString()); 197 | const timestamp = reserves[2]; 198 | 199 | return { 200 | tokenAddress, 201 | reserveToken, 202 | reserveWTRX, 203 | timestamp, 204 | }; 205 | } 206 | 207 | const tokenAddress = this.getAddressFromHex(token0); 208 | 209 | if (!tokenAddress) throw new Error("No token addresses found"); 210 | 211 | const reserveToken = new BigNumber(reserves[0].toString()); 212 | const reserveWTRX = new BigNumber(reserves[1].toString()); 213 | const timestamp = reserves[2]; 214 | 215 | return { 216 | tokenAddress, 217 | reserveToken, 218 | reserveWTRX, 219 | timestamp, 220 | }; 221 | } catch (error) { 222 | console.error(`${errorLOG} ${error}`); 223 | return null; 224 | } 225 | } 226 | 227 | static async getAmountOutMinUsingWTRX( 228 | pairAddress: string, 229 | amountIn: number, 230 | slippagePercentage: number 231 | ) { 232 | const reserves = await this.getPairInformations(pairAddress); 233 | 234 | if (!reserves) { 235 | throw new Error("No reserves found"); 236 | } 237 | 238 | const tokenContract = await this.getContractInstance( 239 | TRC20_ABI, 240 | reserves.tokenAddress 241 | ); 242 | 243 | if (!tokenContract) { 244 | throw new Error("No token contract found"); 245 | } 246 | 247 | const tokenDecimals = await tokenContract.decimals().call(); 248 | 249 | if (!tokenDecimals) { 250 | throw new Error("No token decimals found"); 251 | } 252 | 253 | const reserveToken = reserves.reserveToken.div( 254 | new BigNumber(10).pow(tokenDecimals) 255 | ); 256 | 257 | const reserveWTRX = reserves.reserveWTRX.div( 258 | new BigNumber(10).pow(WTRX_DECIMALS) 259 | ); 260 | 261 | const amountOutMin = reserveToken 262 | .multipliedBy(new BigNumber(amountIn)) 263 | .div(reserveWTRX); 264 | 265 | const slippageAmount = amountOutMin 266 | .multipliedBy(new BigNumber(slippagePercentage)) 267 | .div(new BigNumber(100)); 268 | 269 | return amountOutMin.minus(slippageAmount).toFixed(0); 270 | } 271 | 272 | static async getAmountOutMinUsingToken( 273 | pairAddress: string, 274 | amountIn: number, 275 | slippagePercentage: number 276 | ) { 277 | const reserves = await this.getPairInformations(pairAddress); 278 | 279 | if (!reserves) { 280 | throw new Error("No reserves found"); 281 | } 282 | 283 | const tokenContract = await this.getContractInstance( 284 | TRC20_ABI, 285 | reserves.tokenAddress 286 | ); 287 | 288 | if (!tokenContract) { 289 | throw new Error("No token contract found"); 290 | } 291 | 292 | const tokenDecimals = await tokenContract.decimals().call(); 293 | 294 | if (!tokenDecimals) { 295 | throw new Error("No token decimals found"); 296 | } 297 | 298 | const reserveToken = reserves.reserveToken.div( 299 | new BigNumber(10).pow(tokenDecimals) 300 | ); 301 | 302 | const reserveWTRX = reserves.reserveWTRX.div( 303 | new BigNumber(10).pow(WTRX_DECIMALS) 304 | ); 305 | 306 | const amountOutMin = reserveWTRX 307 | .multipliedBy(new BigNumber(amountIn)) 308 | .div(reserveToken); 309 | 310 | const slippageAmount = amountOutMin 311 | .multipliedBy(new BigNumber(slippagePercentage)) 312 | .div(new BigNumber(100)); 313 | 314 | return amountOutMin.minus(slippageAmount).toFixed(0); 315 | } 316 | 317 | static async buyToken( 318 | tokenAddress: string, 319 | pairAddress: string, 320 | amountInWTRX: number, 321 | slippagePercentage: number, 322 | walletAddress: string, 323 | privateKey: string 324 | ) { 325 | try { 326 | const amountOutMin = await this.getAmountOutMinUsingWTRX( 327 | pairAddress, 328 | amountInWTRX, 329 | slippagePercentage 330 | ); 331 | 332 | if (!amountOutMin) throw new Error("No amount found"); 333 | 334 | const deadline = this.getDeadline(); 335 | 336 | if (!deadline) throw new Error("No deadline found"); 337 | 338 | const wrtxAddressInHEX = this.getAddressInHex(WTRX_ADDRESS, true); 339 | const tokenAddressInHEX = this.getAddressInHex(tokenAddress, true); 340 | const routerAddressInHEX = this.getAddressInHex( 341 | SUNSWAP_ROUTER_ADDRESS, 342 | false 343 | ); 344 | const walletAddressInHEX = this.getAddressInHex(walletAddress, false); 345 | 346 | if (!tokenAddressInHEX || !wrtxAddressInHEX) 347 | throw new Error("No address found"); 348 | 349 | const options = { 350 | callValue: new BigNumber(amountInWTRX) 351 | .multipliedBy(new BigNumber(10).pow(WTRX_DECIMALS)) 352 | .toNumber(), 353 | } as unknown as Options; 354 | 355 | const parameter = [ 356 | { type: "uint256", value: amountOutMin.toString() }, 357 | { type: "address[]", value: [wrtxAddressInHEX, tokenAddressInHEX] }, 358 | { type: "address", value: walletAddress }, 359 | { type: "uint256", value: deadline }, 360 | ]; 361 | 362 | const { transaction } = 363 | await this.getInstance().transactionBuilder.triggerSmartContract( 364 | routerAddressInHEX, 365 | "swapExactETHForTokens(uint256,address[],address,uint256)", 366 | options, 367 | parameter, 368 | walletAddressInHEX 369 | ); 370 | 371 | if (!transaction) throw new Error("No transaction found"); 372 | 373 | const signedTransaction = await this.getInstance().trx.sign( 374 | transaction, 375 | privateKey 376 | ); 377 | 378 | if (!signedTransaction) throw new Error("No signed transaction found"); 379 | 380 | const broadcast = await this.getInstance().trx.sendRawTransaction( 381 | signedTransaction 382 | ); 383 | 384 | if (!broadcast) throw new Error("No broadcast found"); 385 | 386 | console.log( 387 | `The user bought ${amountInWTRX} WTRX of ${tokenAddress}`, 388 | broadcast 389 | ); 390 | 391 | const result = broadcast.result; 392 | const tx = broadcast.transaction; 393 | 394 | if (!result || !tx) throw new Error("No result or transaction found"); 395 | 396 | const tokenContract = await this.getContractInstance( 397 | TRC20_ABI, 398 | tokenAddress 399 | ); 400 | 401 | if (!tokenContract) throw new Error("No token contract found"); 402 | 403 | const decimals = await tokenContract.decimals().call(); 404 | 405 | if (!decimals) throw new Error("No decimals found"); 406 | 407 | const approve = await this.approveToken( 408 | tokenAddress, 409 | walletAddress, 410 | SUNSWAP_ROUTER_ADDRESS, 411 | privateKey 412 | ); 413 | 414 | if (!approve) throw new Error("No approve found"); 415 | 416 | return tx.txID; 417 | } catch (error) { 418 | console.error(`${errorLOG} ${error}`); 419 | return null; 420 | } 421 | } 422 | 423 | static async sellToken( 424 | tokenAddress: string, 425 | pairAddress: string, 426 | amountInTokens: number, 427 | slippagePercentage: number, 428 | walletAddress: string, 429 | privateKey: string 430 | ) { 431 | try { 432 | const tokenContract = await this.getContractInstance( 433 | TRC20_ABI, 434 | tokenAddress 435 | ); 436 | 437 | if (!tokenContract) throw new Error("No token contract found"); 438 | 439 | const decimals = await tokenContract.decimals().call(); 440 | 441 | if (!decimals) throw new Error("No decimals found"); 442 | 443 | const roundedAmountInTokens = Math.floor(amountInTokens * 10) / 10; 444 | 445 | const amountToSend = new BigNumber(roundedAmountInTokens) 446 | .multipliedBy(new BigNumber(10).pow(decimals)) 447 | .toFixed(0); 448 | 449 | const tokenBalance = await tokenContract.balanceOf(walletAddress).call(); 450 | 451 | if (new BigNumber(tokenBalance).lt(amountToSend)) 452 | throw new Error("Insufficient balance"); 453 | 454 | const amountOutMin = await this.getAmountOutMinUsingToken( 455 | pairAddress, 456 | roundedAmountInTokens, 457 | slippagePercentage 458 | ); 459 | 460 | if (!amountOutMin) throw new Error("No amount found"); 461 | 462 | const deadline = this.getDeadline(); 463 | 464 | if (!deadline) throw new Error("No deadline found"); 465 | 466 | const allowance = await this.getAllowance( 467 | tokenAddress, 468 | walletAddress, 469 | SUNSWAP_ROUTER_ADDRESS 470 | ); 471 | 472 | if (!allowance || new BigNumber(allowance).lt(amountToSend)) { 473 | const approve = await this.approveToken( 474 | tokenAddress, 475 | walletAddress, 476 | SUNSWAP_ROUTER_ADDRESS, 477 | privateKey 478 | ); 479 | 480 | if (!approve) throw new Error("No approve found"); 481 | 482 | await new Promise((resolve) => setTimeout(resolve, 10000)); 483 | } 484 | 485 | const wrtxAddressInHEX = this.getAddressInHex(WTRX_ADDRESS, true); 486 | const tokenAddressInHEX = this.getAddressInHex(tokenAddress, true); 487 | const routerAddressInHEX = this.getAddressInHex( 488 | SUNSWAP_ROUTER_ADDRESS, 489 | false 490 | ); 491 | const walletAddressInHEX = this.getAddressInHex(walletAddress, false); 492 | 493 | if (!tokenAddressInHEX || !wrtxAddressInHEX) 494 | throw new Error("No address found"); 495 | 496 | const parameter = [ 497 | { type: "uint256", value: amountToSend.toString() }, 498 | { type: "uint256", value: amountOutMin.toString() }, 499 | { type: "address[]", value: [tokenAddressInHEX, wrtxAddressInHEX] }, 500 | { type: "address", value: walletAddress }, 501 | { type: "uint256", value: deadline }, 502 | ]; 503 | 504 | const { transaction } = 505 | await this.getInstance().transactionBuilder.triggerSmartContract( 506 | routerAddressInHEX, 507 | "swapExactTokensForETH(uint256,uint256,address[],address,uint256)", 508 | {}, 509 | parameter, 510 | walletAddressInHEX 511 | ); 512 | 513 | if (!transaction) throw new Error("No transaction found"); 514 | 515 | const signedTransaction = await this.getInstance().trx.sign( 516 | transaction, 517 | privateKey 518 | ); 519 | 520 | if (!signedTransaction) throw new Error("No signed transaction found"); 521 | 522 | const broadcast = await this.getInstance().trx.sendRawTransaction( 523 | signedTransaction 524 | ); 525 | 526 | if (!broadcast) throw new Error("No broadcast found"); 527 | 528 | console.log( 529 | `The user sold ${amountInTokens} of ${tokenAddress}`, 530 | broadcast 531 | ); 532 | 533 | const result = broadcast.result; 534 | const tx = broadcast.transaction; 535 | 536 | if (!result || !tx) throw new Error("No result or transaction found"); 537 | 538 | return tx.txID; 539 | } catch (error) { 540 | console.error(`${errorLOG} ${error}`); 541 | return null; 542 | } 543 | } 544 | 545 | /* ------------------------------ */ 546 | /* TOKEN PART */ 547 | /* ------------------------------ */ 548 | 549 | static async getAllowance( 550 | tokenAddress: string, 551 | ownerAddress: string, 552 | spenderAddress: string 553 | ) { 554 | try { 555 | const tokenContract = await this.getContractInstance( 556 | TRC20_ABI, 557 | tokenAddress 558 | ); 559 | 560 | if (!tokenContract) throw new Error("No token contract found"); 561 | 562 | const allowance = await tokenContract 563 | .allowance(ownerAddress, spenderAddress) 564 | .call(); 565 | 566 | if (!allowance) throw new Error("No allowance found"); 567 | 568 | return new BigNumber(allowance.toString()); 569 | } catch (error) { 570 | console.error(`${errorLOG} ${error}`); 571 | return null; 572 | } 573 | } 574 | 575 | static async approveToken( 576 | tokenAddress: string, 577 | walletAddress: string, 578 | ownerAddress: string, 579 | privateKey: string 580 | ) { 581 | try { 582 | const tokenContract = await this.getContractInstance( 583 | TRC20_ABI, 584 | tokenAddress 585 | ); 586 | 587 | if (!tokenContract) throw new Error("No token contract found"); 588 | 589 | const parameter = [ 590 | { type: "address", value: ownerAddress }, 591 | { 592 | type: "uint256", 593 | value: 594 | "115792089237316195423570985008687907853269984665640564039457584007913129639935", 595 | }, 596 | ]; 597 | 598 | const { transaction } = 599 | await this.getInstance().transactionBuilder.triggerSmartContract( 600 | tokenAddress, 601 | "approve(address,uint256)", 602 | {}, 603 | parameter, 604 | walletAddress 605 | ); 606 | 607 | if (!transaction) throw new Error("No transaction found"); 608 | 609 | const signedTransaction = await this.getInstance().trx.sign( 610 | transaction, 611 | privateKey 612 | ); 613 | 614 | if (!signedTransaction) throw new Error("No signed transaction found"); 615 | 616 | const broadcast = await this.getInstance().trx.sendRawTransaction( 617 | signedTransaction 618 | ); 619 | 620 | if (!broadcast) throw new Error("No broadcast found"); 621 | 622 | console.log(`The user approved ${tokenAddress}`, broadcast); 623 | 624 | const result = broadcast.result; 625 | const tx = broadcast.transaction; 626 | 627 | if (!result || !tx) throw new Error("No result or transaction found"); 628 | 629 | return tx.txID; 630 | } catch (error) { 631 | console.error(`${errorLOG} ${error}`); 632 | return null; 633 | } 634 | } 635 | 636 | static async getPriceOfTRX() { 637 | const CACHE_DURATION = 60 * 1000; 638 | if ( 639 | this.cachedTRXPrice && 640 | Date.now() - this.cacheTimestamp < CACHE_DURATION 641 | ) 642 | return this.cachedTRXPrice; 643 | 644 | try { 645 | const res = await fetch( 646 | "https://apilist.tronscanapi.com/api/token/price?token=trx" 647 | ); 648 | const data = await res.json(); 649 | 650 | if (!data || !data.price_in_usd) throw new Error("No data found"); 651 | 652 | this.cachedTRXPrice = data.price_in_usd; 653 | this.cacheTimestamp = Date.now(); 654 | 655 | return this.cachedTRXPrice; 656 | } catch (error) { 657 | console.error(`${errorLOG} ${error}`); 658 | return 0; 659 | } 660 | } 661 | 662 | static async getTokenInformations(pairAddress: string) { 663 | try { 664 | const res = await fetch( 665 | `https://api.dexscreener.com/latest/dex/pairs/tron/${pairAddress}` 666 | ); 667 | 668 | const data = (await res.json()) as { 669 | pair: { 670 | baseToken: { 671 | name: string; 672 | symbol: string; 673 | }; 674 | priceNative: string; 675 | priceUsd: string; 676 | volume: { 677 | h24: number; 678 | }; 679 | liquidity: { 680 | usd: number; 681 | base: number; 682 | quote: number; 683 | }; 684 | fdv: number; 685 | pairCreatedAt: number; 686 | }; 687 | }; 688 | 689 | if (!data || !data.pair) throw new Error("No data found"); 690 | 691 | const pair = data.pair; 692 | 693 | const price = await this.getPriceOfTRX(); 694 | 695 | if (!price) throw new Error("No price found"); 696 | 697 | const marketCapInUSD = pair.fdv * price; 698 | 699 | return { 700 | name: pair.baseToken.name, 701 | symbol: pair.baseToken.symbol, 702 | tokenPriceInUSD: Number(pair.priceUsd), 703 | volumeInUSD: pair.volume.h24, 704 | liquidityInUSD: pair.liquidity.usd, 705 | marketCapInUSD, 706 | pairCreatedAt: pair.pairCreatedAt, 707 | }; 708 | } catch (error) { 709 | console.error(`${errorLOG} ${error}`); 710 | return null; 711 | } 712 | } 713 | 714 | /* ------------------------------ */ 715 | /* UTILS PART */ 716 | /* ------------------------------ */ 717 | 718 | static getAddressFromHex(hex: string) { 719 | return this.instance.address.fromHex(hex); 720 | } 721 | 722 | static getAddressInHex(address: string, withPrefix: boolean) { 723 | const addressInHex = this.instance.address.toHex(address); 724 | return withPrefix ? "0x" + addressInHex.slice(2) : addressInHex; 725 | } 726 | 727 | static getDeadline() { 728 | return Math.floor(Date.now() / 1000) + 60 * 2; 729 | } 730 | } 731 | 732 | export default SniperUtils; 733 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "noImplicitAny": true, 5 | "removeComments": true, 6 | "suppressImplicitAnyIndexErrors": false, 7 | "preserveConstEnums": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": false, 11 | "moduleDetection": "force", 12 | "experimentalDecorators": true, 13 | "lib": [ 14 | "es6", 15 | "es2020", 16 | "es2021", 17 | "es2022", 18 | "esnext", 19 | "webworker", 20 | "dom", 21 | "scripthost" 22 | ], 23 | "strict": true, 24 | "strictNullChecks": true, 25 | "strictFunctionTypes": true, 26 | "strictBindCallApply": true, 27 | "strictPropertyInitialization": true, 28 | "alwaysStrict": true, 29 | "moduleResolution": "node", 30 | "allowJs": true, 31 | "incremental": true, 32 | "allowSyntheticDefaultImports": true, 33 | "esModuleInterop": true 34 | }, 35 | "include": [ 36 | "src/**/*.ts", 37 | "src/*", 38 | "src/**/*.js", 39 | "src/*.ts", 40 | "src/*.js", 41 | "src/*.cjs" 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "esnext", 5 | "outDir": "build", 6 | "target": "esnext" 7 | } 8 | } 9 | --------------------------------------------------------------------------------