├── .gitignore ├── LICENSE ├── README.md ├── config.json ├── constants.py ├── functions.py ├── images └── sniper_bot_watch.png ├── main.py ├── requirements.txt ├── tokens.json └── wallets.json /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | .env 3 | __pycache__/ 4 | ROADMAP.txt 5 | after_effects/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 TaoDev 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

📟 JUPITER PYTHON CLI 🪐

3 | 4 | https://github.com/0xTaoDev/jupiter-python-cli/assets/152310566/81f79ed5-8c47-469f-aeb8-c3be70c9541f 5 | 6 |
7 | 8 | --- 9 | 10 |

11 | 12 | 13 |
14 | 15 | 16 |
17 | 18 | 19 |
20 |

21 | 22 | # 📖 Introduction 23 | **Jupiter Python CLI** is a Command Line Interface (CLI) where you can use **[Jupiter](https://jup.ag/) features** including a **Sniper Bot**.
24 | 25 | # ⚠️ Disclaimer 26 | **Please note that I'm not responsible for any loss of funds, damages, or other libailities resulting from the use of this software or any associated services.
27 | This tool is provided for educational purposes only and should not be used as financial advice, it is still in expiremental phase so use it at your own risk.** 28 | 29 | # ✨ Quickstart 30 | 31 | This project has been made for Python 3.11 32 | 33 | ## 🛠️ Installation 34 | 35 | 💾 **Clone this repository** 36 | ```sh 37 | git clone https://github.com/0xtaodev/jupiter-python-cli 38 | ``` 39 | 💻 **Create a virtual environnment** 40 | ```sh 41 | python -m venv venv 42 | ``` 43 | 🌐 **Activate Virtual Environnement** 44 | ```sh 45 | .\venv\Scripts\Activate.ps1 46 | ``` 47 | ▶️ **Start CLI** 48 | ```sh 49 | python main.py 50 | ``` 51 | 52 | # 🗺️ CLI Overview 53 | ``` 54 | 📟 CLI 55 | │ 56 | ├── 🪐 Jupiter Exchange 57 | │ ├── Swap 58 | │ ├── Limit Order 59 | │ │ ├── Open Limit Order 60 | │ │ ├── Display Canceled Orders History 61 | │ │ └── Display Filled Orders History 62 | │ ├── DCA 63 | │ │ ├── Open DCA Account 64 | │ │ └── Manage DCA Accounts 65 | │ ├── Token Sniper 66 | │ │ ├── Add a token to snipe 67 | │ │ ├── Watch token 68 | │ │ └── Edit tokens 69 | │ └── Change wallet 70 | ├── 💳 Manage Wallets 71 | │ ├── Add wallet 72 | │ ├── Edit wallet name 73 | │ └── Delete wallet(s) 74 | ├── 🔧 CLI settings 75 | │ ├── Solana RPC URL Endpoint 76 | │ ├── Discord 77 | │ └── Telegram 78 | ├── ❓ About 79 | └── 🔚 Exit CLI 80 | ``` 81 | 82 | # 🤖 Sniper Bot 83 | **In top of most of the Jupiter features that you can use, you are also able to snipe token.**
84 | ❗**Please note that Sniper Bot is experimental and subject to change as there might be issues that I didn't see.** 85 | 86 | ### ⚙️ How it works 87 | Every second, the bot will send a GET request to [Jupiter API Quote](https://quote-api.jup.ag/v6/quote).
88 | If there is a route available for this token, it will then execute it.
89 | Please note that only tokens with sufficient liquidity and on-chain metadata are listed in Jupiter API: min. 250$ liquidty and buy/sell price impact are below 30%.
90 | When these criteria are met, it will take a few minutes to automatically add the token.
91 | 92 | ### 🆕 Add a token to snipe 93 | - Token/Project name 94 | - Token Address 95 | - Amount ($) to buy 96 | - Take Profit ($) 97 | - Stop Loss ($) 98 | - Slippage (%) 99 | 100 | If token has a launch date: 101 | - Month 102 | - Day 103 | - Hours 104 | - Minutes 105 | 106 | ### 🔭 Watch token 107 | You can watch your trading position by selecting the token.
108 | 109 | 110 | ### ✍🏻 Edit tokens 111 | You can modify token info as follow: 112 | - Name 113 | - Address 114 | - Selected Wallet 115 | - Buy Amount 116 | - Take Profit 117 | - Stop Loss 118 | - Slippage 119 | - Launch date 120 | - Delete 121 | 122 | # 🗨️ Q&A 123 | ### Where are my private keys? 124 | *Your private keys are stored in `wallets.json`.* 125 | ### Is there any fees when swapping using CLI? 126 | *There are no additional fees when performing swaps via the CLI; the costs should be the same as using the Jupiter UI.* 127 | ### Does sniper bot remains running if I close the CLI? 128 | *If you close the CLI, the sniper bot will stop running.* 129 | ### Is it possible to swap any tokens? 130 | *You can only swap tokens that are listed on Jupiter based on their criterias.* 131 | 132 | # 🚨 Known bugs 133 | ### ImportError: sync_native from spl.token.instructions 134 | 1. Go to https://github.com/michaelhly/solana-py/tree/master/src/spl/token and download ```instructions.py``` 135 | 2. In your packages folder, replace ```spl/token/instructions.py``` with the one you just downloaded. 136 | ### Sometimes 0.01 is added when typying numbers 137 | ### Invalid DCA Accounts listed (and cannot be deleted) 138 | ### ~~Discord Webhook or Telegram API not being added in `config.json`~~ 139 | ### Duplicating menus when resizing the window 140 | ### Sniper Bot: it's taking 10-20 seconds to get real trade position info after a swap route is found and executed 141 | 142 | # 📝 TO-DO 143 | - [ ] Clean up code ⚡ 144 | - [ ] Add docstrings 📑 145 | - [ ] Display tokens owned 🪙 146 | - [ ] Favorite tokens displayed in first tokens for swap/limit orders/dca... ⭐ 147 | - [ ] Wallet Duplication detection 148 | - [ ] Display message when swap failed (slippage error...) 149 | - [ ] Disable swap / limits orders / etc, if not enough $SOL to cover the tx fees 150 | - [ ] Give possibility to exit current choice (swap, limit order, dca, donation...) 🏃🚪 151 | - [ ] Adjust Wallets ID when one is deleted 152 | - [ ] Bridge 🌉 153 | - [ ] Perpetual 💸 154 | 155 | # 🤝 Contributions 156 | If you are interesting in contributing, fork the repository and submit a pull request in order to merge your improvements into the main repository.
157 | Contact me for any inquiry, I will reach you as soon as possible.
158 | [![Discord](https://img.shields.io/badge/Discord-%237289DA.svg?logo=discord&logoColor=white)](https://discord.gg/QxwPGcXDp7) 159 | [![Twitter](https://img.shields.io/badge/Twitter-%231DA1F2.svg?logo=Twitter&logoColor=white)](https://twitter.com/_TaoDev_) 160 | 161 | # 👑 Donations 162 | This project doesn't include platform fees. If you find value in it and would like to support its development, your donations are greatly appreciated.
163 | You can donate through CLI in About menu.
164 | **SOLANA ADDRESS** 165 | ```sh 166 | AyWu89SjZBW1MzkxiREmgtyMKxSkS1zVy8Uo23RyLphX 167 | ``` 168 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "FIRST_LOGIN": true, 3 | "LAST_WALLET_SELECTED": "1", 4 | "COLLECT_FEES": false, 5 | "RPC_URL": "", 6 | "DISCORD_WEBHOOK": "", 7 | "TELEGRAM_BOT_TOKEN": "", 8 | "TELEGRAM_CHAT_ID": 0 9 | } -------------------------------------------------------------------------------- /constants.py: -------------------------------------------------------------------------------- 1 | from colorama import Fore 2 | 3 | RESET = Fore.RESET 4 | GREEN = Fore.GREEN 5 | RED = Fore.RED 6 | YELLOW = Fore.YELLOW 7 | BLUE = Fore.BLUE 8 | PURPLE = Fore.MAGENTA 9 | CYAN = Fore.CYAN -------------------------------------------------------------------------------- /functions.py: -------------------------------------------------------------------------------- 1 | from pyfiglet import Figlet 2 | import json 3 | import httpx 4 | 5 | def send_discord_alert(message: str): 6 | DISCORD_WEBHOOK_URL = get_config_data()['DISCORD_WEBHOOK'] 7 | try: 8 | httpx.post(DISCORD_WEBHOOK_URL, json={'content': f'{message}'}) 9 | except: 10 | pass 11 | 12 | def send_telegram_alert(message: str): 13 | TELEGRAM_API_INFO = get_config_data() 14 | TELEGRAM_BOT_TOKEN = TELEGRAM_API_INFO['TELEGRAM_BOT_TOKEN'] 15 | TELEGRAM_CHAT_ID = TELEGRAM_API_INFO['TELEGRAM_CHAT_ID'] 16 | TELEGRAM_URL = "https://api.telegram.org/bot" + TELEGRAM_BOT_TOKEN + "/sendMessage" 17 | PAYLOAD = { 18 | 'chat_id': TELEGRAM_CHAT_ID, 19 | 'text': message, 20 | 'parse_mode': 'HTML' 21 | } 22 | try: 23 | httpx.post(url=TELEGRAM_URL, data=PAYLOAD) 24 | except: 25 | pass 26 | 27 | def display_logo() -> None: 28 | """Display Jupiter CLI logo.""" 29 | print("\033c\n", end="") 30 | print("-" * 51, "\n" + Figlet(font='small').renderText('JUPITER CLI\n') + "-" * 51 + "\n") 31 | 32 | def get_config_data() -> dict: 33 | """Fetch config file data. 34 | Returns: dict""" 35 | with open('config.json', 'r') as config_file: 36 | return json.load(config_file) 37 | 38 | def load_wallets() -> dict: 39 | """Returns all wallets stored in wallets.json.""" 40 | with open('wallets.json', 'r') as wallets_file: 41 | return json.load(wallets_file) 42 | 43 | def get_crypto_price(crypto: str) -> float: 44 | """Returns crypto price.""" 45 | API_BINANCE = f"https://www.binance.com/api/v3/ticker/price?symbol={crypto}USDT" 46 | crypto_price =float(httpx.get(API_BINANCE).json()['price']) 47 | return crypto_price 48 | 49 | def get_timestamp_formatted(unix_timestamp: int) -> str: 50 | """Returns timestamp formatted based on a unix timestamp.""" 51 | if unix_timestamp < 60: 52 | return f"{unix_timestamp} seconds" 53 | elif unix_timestamp < 3600: 54 | minutes = unix_timestamp // 60 55 | seconds = unix_timestamp % 60 56 | return f"{minutes} minute{'s' if minutes > 1 else ''} and {seconds} second{'s' if seconds > 1 else ''}" 57 | elif unix_timestamp < 86400: 58 | hours = unix_timestamp // 3600 59 | minutes = (unix_timestamp % 3600) // 60 60 | return f"{hours} hour{'s' if hours > 1 else ''}, {minutes} minute{'s' if minutes > 1 else ''}" 61 | elif unix_timestamp < 604800: 62 | days = unix_timestamp // 86400 63 | hours = (unix_timestamp % 86400) // 3600 64 | return f"{days} day{'s' if days > 1 else ''}, {hours} hour{'s' if hours > 1 else ''}" 65 | elif unix_timestamp < 2629746: # Approximately a month 66 | weeks = unix_timestamp // 604800 67 | days = (unix_timestamp % 604800) // 86400 68 | return f"{weeks} week{'s' if weeks > 1 else ''}, {days} day{'s' if days > 1 else ''}" 69 | else: 70 | months = unix_timestamp // 2629746 # Approximately a month 71 | days = (unix_timestamp % 2629746) // 86400 72 | return f"{months} month{'s' if months > 1 else ''}, {days} day{'s' if days > 1 else ''}" -------------------------------------------------------------------------------- /images/sniper_bot_watch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xTaoDev/jupiter-python-cli/66cf88d938d4fc804e9102e71d04a46100f658f1/images/sniper_bot_watch.png -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import json 2 | import base58 3 | import base64 4 | import time 5 | import re 6 | import httpx 7 | import asyncio 8 | from multiprocessing import Process 9 | import random 10 | 11 | from datetime import datetime 12 | 13 | from InquirerPy import inquirer 14 | 15 | from tabulate import tabulate 16 | import pandas as pd 17 | 18 | from yaspin import yaspin 19 | 20 | from solders import message 21 | from solders.keypair import Keypair 22 | from solders.pubkey import Pubkey 23 | from solders.transaction import VersionedTransaction 24 | from solders.signature import Signature 25 | from solders.system_program import transfer, TransferParams 26 | 27 | 28 | from solana.rpc.async_api import AsyncClient 29 | from solana.rpc.api import Client 30 | from solana.rpc.commitment import Processed 31 | from solana.rpc.types import TxOpts 32 | from solana.transaction import Transaction 33 | 34 | from spl.token.instructions import get_associated_token_address 35 | 36 | 37 | from jupiter_python_sdk.jupiter import Jupiter, Jupiter_DCA 38 | 39 | 40 | import functions as f 41 | import constants as c 42 | 43 | 44 | class Config_CLI(): 45 | 46 | @staticmethod 47 | async def get_config_data() -> dict: 48 | """Fetch config file data. 49 | Returns: dict""" 50 | with open('config.json', 'r') as config_file: 51 | return json.load(config_file) 52 | 53 | @staticmethod 54 | def get_config_data_no_async() -> dict: 55 | """Fetch config file data. 56 | Returns: dict""" 57 | with open('config.json', 'r') as config_file: 58 | return json.load(config_file) 59 | 60 | @staticmethod 61 | async def edit_config_file(config_data: dict): 62 | """Edit config file.""" 63 | with open('config.json', 'w') as config_file: 64 | json.dump(config_data, config_file, indent=4) 65 | return True 66 | 67 | @staticmethod 68 | async def edit_tokens_file(tokens_data: dict): 69 | """Edit tokens file.""" 70 | with open('tokens.json', 'w') as tokens_file: 71 | json.dump(tokens_data, tokens_file, indent=4) 72 | return True 73 | 74 | @staticmethod 75 | def edit_tokens_file_no_async(tokens_data: dict): 76 | """Edit tokens file.""" 77 | with open('tokens.json', 'w') as tokens_file: 78 | json.dump(tokens_data, tokens_file, indent=4) 79 | return True 80 | 81 | @staticmethod 82 | async def get_tokens_data() -> dict: 83 | """Fetch token file data. 84 | Returns: dict""" 85 | with open('tokens.json', 'r') as tokens_file: 86 | return json.load(tokens_file) 87 | 88 | @staticmethod 89 | def get_tokens_data_no_async() -> dict: 90 | """Fetch token file data. 91 | Returns: dict""" 92 | with open('tokens.json', 'r') as tokens_file: 93 | return json.load(tokens_file) 94 | 95 | @staticmethod 96 | async def prompt_collect_fees(): 97 | """Asks the user if they want the CLI to take a small percentage of fees during their swaps.""" 98 | collect_fees = inquirer.select(message="Would you like CLI to collect small fees from your swaps? (0.005%)", choices=["Yes", "No"]).execute_async() 99 | confirm = inquirer.select(message="Confirm?", choices=["Yes", "No"]).execute_async() 100 | if confirm == "Yes": 101 | config_data = Config_CLI.get_config_data() 102 | config_data['COLLECT_FEES'] = True if collect_fees == "Yes" else False 103 | Config_CLI.edit_config_file(config_data=config_data) 104 | return 105 | elif confirm == "No": 106 | Config_CLI.prompt_collect_fees() 107 | return 108 | 109 | @staticmethod 110 | async def prompt_rpc_url(): 111 | """Asks the user the RPC URL endpoint to be used.""" 112 | config_data = await Config_CLI.get_config_data() 113 | rpc_url = await inquirer.text(message="Enter your Solana RPC URL endpoint or press ENTER to skip:").execute_async() 114 | # rpc_url = os.getenv('RPC_URL') 115 | # confirm = "Yes" 116 | 117 | if rpc_url == "" and config_data['RPC_URL'] is None: 118 | print(f"{c.RED}! You need to have a RPC endpoint to user the CLI") 119 | await Config_CLI.prompt_rpc_url() 120 | return 121 | 122 | elif rpc_url != "": 123 | confirm = await inquirer.select(message="Confirm Solana RPC URL Endpoint?", choices=["Yes", "No"]).execute_async() 124 | if confirm == "Yes": 125 | if rpc_url.endswith("/"): 126 | rpc_url = rpc_url[:-1] 127 | 128 | test_client = AsyncClient(endpoint=rpc_url) 129 | 130 | if not await test_client.is_connected(): 131 | print(f"{c.RED}! Connection to RPC failed. Please enter a valid RPC.{c.RESET}") 132 | await Config_CLI.prompt_rpc_url() 133 | return 134 | else: 135 | config_data['RPC_URL'] = rpc_url 136 | await Config_CLI.edit_config_file(config_data=config_data) 137 | return 138 | 139 | elif confirm == "No": 140 | await Config_CLI.prompt_rpc_url() 141 | return 142 | 143 | return rpc_url 144 | 145 | @staticmethod 146 | async def prompt_discord_webhook(): 147 | """Asks user Discord Webhook URL to be notified for Sniper tool.""" 148 | config_data = await Config_CLI.get_config_data() 149 | discord_webhook = await inquirer.text(message="Enter your Discord Webhook or press ENTER to skip:").execute_async() 150 | 151 | if discord_webhook != "": 152 | confirm = await inquirer.select(message="Confirm Discord Webhook?", choices=["Yes", "No"]).execute_async() 153 | 154 | if confirm == "Yes": 155 | config_data['DISCORD_WEBHOOK'] = discord_webhook 156 | await Config_CLI.edit_config_file(config_data=config_data) 157 | f.send_discord_alert("Discord Alert added!") 158 | 159 | confirm = await inquirer.select(message="Is message sent in the Discord channel?", choices=["Yes", "No"]).execute_async() 160 | if confirm == "No": 161 | await Config_CLI.prompt_discord_webhook() 162 | return 163 | 164 | elif confirm == "No": 165 | await Config_CLI.prompt_discord_webhook() 166 | return 167 | 168 | return discord_webhook 169 | 170 | @staticmethod 171 | async def prompt_telegram_api(): 172 | """Asks user Telegram API to be notified for Sniper tool.""" 173 | config_data = await Config_CLI.get_config_data() 174 | telegram_bot_token = await inquirer.text(message="Enter Telegram Bot Token or press ENTER to skip:").execute_async() 175 | 176 | if telegram_bot_token != "": 177 | confirm = await inquirer.select(message="Confirm Telegram Bot Token?", choices=["Yes", "No"]).execute_async() 178 | 179 | if confirm == "Yes": 180 | config_data['TELEGRAM_BOT_TOKEN'] = telegram_bot_token 181 | 182 | while True: 183 | telegram_bot_token = await inquirer.text(message="Enter Telegram Chat ID").execute_async() 184 | confirm = await inquirer.select(message="Confirm Telegram Chat ID?", choices=["Yes", "No"]).execute_async() 185 | 186 | if confirm == "Yes": 187 | config_data['TELEGRAM_CHAT_ID'] = int(telegram_bot_token) 188 | await Config_CLI.edit_config_file(config_data=config_data) 189 | f.send_telegram_alert("Telegram Alert added!") 190 | 191 | confirm = await inquirer.select(message="Is message sent in the Telegram channel?", choices=["Yes", "No"]).execute_async() 192 | if confirm == "No": 193 | await Config_CLI.prompt_telegram_api() 194 | return 195 | 196 | break 197 | 198 | return 199 | 200 | elif confirm == "No": 201 | await Config_CLI.prompt_telegram_api() 202 | return 203 | 204 | return 205 | 206 | @staticmethod 207 | async def main_menu(): 208 | """Main menu for CLI settings.""" 209 | f.display_logo() 210 | print("[CLI SETTINGS]\n") 211 | config_data = await Config_CLI.get_config_data() 212 | 213 | # print(f"CLI collect fees (0.005%): {'Yes' if config_data['COLLECT_FEES'] else 'No'}") # TBD 214 | 215 | client = AsyncClient(endpoint=config_data['RPC_URL']) 216 | start_time = time.time() 217 | await client.is_connected() 218 | end_time = time.time() 219 | print(f"RPC URL Endpoint: {config_data['RPC_URL']} {c.GREEN}({round(end_time - start_time, 2)} ms){c.RESET}") 220 | print("Discord Webhook:", config_data['DISCORD_WEBHOOK']) 221 | print("Telegram Bot Token:", config_data['TELEGRAM_BOT_TOKEN'], "| Channel ID:", config_data['TELEGRAM_CHAT_ID']) 222 | 223 | print() 224 | 225 | config_cli_prompt_main_menu = await inquirer.select(message="Select CLI parameter to change:", choices=[ 226 | # "CLI collect fees", # TBD 227 | "Solana RPC URL Endpoint", 228 | "Discord", 229 | "Telegram", 230 | "Back to main menu", 231 | ]).execute_async() 232 | 233 | match config_cli_prompt_main_menu: 234 | case "CLI collect fees": 235 | await Config_CLI.prompt_collect_fees() 236 | await Config_CLI.main_menu() 237 | case "Solana RPC URL Endpoint": 238 | await Config_CLI.prompt_rpc_url() 239 | await Config_CLI.main_menu() 240 | case "Discord": 241 | await Config_CLI.prompt_discord_webhook() 242 | await Config_CLI.main_menu() 243 | case "Telegram": 244 | await Config_CLI.prompt_telegram_api() 245 | await Config_CLI.main_menu() 246 | case "Back to main menu": 247 | await Main_CLI.main_menu() 248 | return 249 | 250 | 251 | class Wallet(): 252 | 253 | def __init__(self, rpc_url: str, private_key: str, async_client: bool=True): 254 | self.wallet = Keypair.from_bytes(base58.b58decode(private_key)) 255 | if async_client: 256 | self.client = AsyncClient(endpoint=rpc_url) 257 | else: 258 | self.client = Client(endpoint=rpc_url) 259 | 260 | 261 | async def get_token_balance(self, token_mint_account: str) -> dict: 262 | 263 | if token_mint_account == self.wallet.pubkey().__str__(): 264 | get_token_balance = await self.client.get_balance(pubkey=self.wallet.pubkey()) 265 | token_balance = { 266 | 'decimals': 9, 267 | 'balance': { 268 | 'int': get_token_balance.value, 269 | 'float': float(get_token_balance.value / 10 ** 9) 270 | } 271 | } 272 | else: 273 | get_token_balance = await self.client.get_token_account_balance(pubkey=token_mint_account) 274 | try: 275 | token_balance = { 276 | 'decimals': int(get_token_balance.value.decimals), 277 | 'balance': { 278 | 'int': get_token_balance.value.amount, 279 | 'float': float(get_token_balance.value.amount) / 10 ** int(get_token_balance.value.decimals) 280 | } 281 | } 282 | except AttributeError: 283 | token_balance = { 284 | 'decimals': 0, 285 | 'balance': { 286 | 'int': 0, 287 | 'float':0 288 | } 289 | } 290 | 291 | return token_balance 292 | 293 | def get_token_balance_no_async(self, token_mint_account: str) -> dict: 294 | 295 | if token_mint_account == self.wallet.pubkey().__str__(): 296 | get_token_balance = self.client.get_balance(pubkey=self.wallet.pubkey()) 297 | token_balance = { 298 | 'decimals': 9, 299 | 'balance': { 300 | 'int': get_token_balance.value, 301 | 'float': float(get_token_balance.value / 10 ** 9) 302 | } 303 | } 304 | else: 305 | get_token_balance = self.client.get_token_account_balance(pubkey=token_mint_account) 306 | try: 307 | token_balance = { 308 | 'decimals': int(get_token_balance.value.decimals), 309 | 'balance': { 310 | 'int': get_token_balance.value.amount, 311 | 'float': float(get_token_balance.value.amount) / 10 ** int(get_token_balance.value.decimals) 312 | } 313 | } 314 | except AttributeError: 315 | token_balance = {'balance': {'int': 0, 'float':0}} 316 | 317 | return token_balance 318 | 319 | 320 | async def get_token_mint_account(self, token_mint: str) -> Pubkey: 321 | token_mint_account = get_associated_token_address(owner=self.wallet.pubkey(), mint=Pubkey.from_string(token_mint)) 322 | return token_mint_account 323 | 324 | def get_token_mint_account_no_async(self, token_mint: str) -> Pubkey: 325 | token_mint_account = get_associated_token_address(owner=self.wallet.pubkey(), mint=Pubkey.from_string(token_mint)) 326 | return token_mint_account 327 | 328 | 329 | async def sign_send_transaction(self, transaction_data: str, signatures_list: list=None, print_link: bool=True): 330 | signatures = [] 331 | 332 | raw_transaction = VersionedTransaction.from_bytes(base64.b64decode(transaction_data)) 333 | signature = self.wallet.sign_message(message.to_bytes_versioned(raw_transaction.message)) 334 | signatures.append(signature) 335 | if signatures_list: 336 | for signature in signatures_list: 337 | signatures.append(signature) 338 | signed_txn = VersionedTransaction.populate(raw_transaction.message, signatures) 339 | opts = TxOpts(skip_preflight=True, preflight_commitment=Processed) 340 | 341 | # print(signatures, transaction_data) 342 | # input() 343 | 344 | result = await self.client.send_raw_transaction(txn=bytes(signed_txn), opts=opts) 345 | transaction_hash = json.loads(result.to_json())['result'] 346 | if print_link is True: 347 | print(f"{c.GREEN}Transaction sent: https://explorer.solana.com/tx/{transaction_hash}{c.RESET}") 348 | await inquirer.text(message="\nPress ENTER to continue").execute_async() 349 | # await self.get_status_transaction(transaction_hash=transaction_hash) # TBD 350 | return 351 | 352 | def sign_send_transaction_no_async(self, transaction_data: str, signatures_list: list=None, print_link: bool=True): 353 | signatures = [] 354 | 355 | raw_transaction = VersionedTransaction.from_bytes(base64.b64decode(transaction_data)) 356 | signature = self.wallet.sign_message(message.to_bytes_versioned(raw_transaction.message)) 357 | signatures.append(signature) 358 | if signatures_list: 359 | for signature in signatures_list: 360 | signatures.append(signature) 361 | signed_txn = VersionedTransaction.populate(raw_transaction.message, signatures) 362 | opts = TxOpts(skip_preflight=True, preflight_commitment=Processed) 363 | 364 | # print(signatures, transaction_data) 365 | # input() 366 | 367 | result = self.client.send_raw_transaction(txn=bytes(signed_txn), opts=opts) 368 | transaction_hash = json.loads(result.to_json())['result'] 369 | if print_link is True: 370 | print(f"{c.GREEN}Transaction sent: https://explorer.solana.com/tx/{transaction_hash}{c.RESET}") 371 | # await self.get_status_transaction(transaction_hash=transaction_hash) # TBD 372 | return 373 | 374 | 375 | async def get_status_transaction(self, transaction_hash: str): 376 | print("Checking transaction status...") 377 | get_transaction_details = await self.client.confirm_transaction(tx_sig=Signature.from_string(transaction_hash), sleep_seconds=1) 378 | transaction_status = get_transaction_details.value[0].err 379 | 380 | if transaction_status is None: 381 | print("Transaction SUCCESS!") 382 | else: 383 | print(f"{c.RED}! Transaction FAILED!{c.RESET}") 384 | 385 | await inquirer.text(message="\nPress ENTER to continue").execute_async() 386 | return 387 | 388 | 389 | snipers_processes = [] 390 | class Token_Sniper(): 391 | 392 | def __init__(self, token_id, token_data): 393 | self.token_id = token_id 394 | self.token_data = token_data 395 | self.success = False 396 | 397 | def snipe_token(self): 398 | 399 | tokens_data = Config_CLI.get_tokens_data_no_async() 400 | config_data = Config_CLI.get_config_data_no_async() 401 | wallets = Wallets_CLI.get_wallets_no_async() 402 | wallet = Wallet(rpc_url=config_data['RPC_URL'], private_key=wallets[str(tokens_data[self.token_id]['WALLET'])]['private_key'], async_client=False) 403 | token_account = wallet.get_token_mint_account_no_async(self.token_data['ADDRESS']) 404 | token_balance = wallet.get_token_balance_no_async(token_mint_account=token_account) 405 | 406 | while True: 407 | if self.token_data['STATUS'] in ["NOT IN", "ERROR WHEN SWAPPING"]: 408 | 409 | while True: 410 | if self.token_data['TIMESTAMP'] is None: 411 | time.sleep(1) 412 | elif self.token_data['TIMESTAMP'] is not None: 413 | sleep_time = self.token_data['TIMESTAMP'] - int(time.time()) - 3 414 | try: 415 | time.sleep(sleep_time) 416 | except ValueError: 417 | pass 418 | 419 | sol_price = f.get_crypto_price('SOL') 420 | amount = int((self.token_data['BUY_AMOUNT']*10**9) / sol_price) 421 | quote_url = "https://quote-api.jup.ag/v6/quote?" + f"inputMint=So11111111111111111111111111111111111111112" + f"&outputMint={self.token_data['ADDRESS']}" + f"&amount={amount}" + f"&slippageBps={int(self.token_data['SLIPPAGE_BPS'])}" 422 | quote_response = httpx.get(url=quote_url).json() 423 | 424 | try: 425 | if quote_response['error']: 426 | time.sleep(1) 427 | except: 428 | break 429 | 430 | swap_data = { 431 | "quoteResponse": quote_response, 432 | "userPublicKey": wallet.wallet.pubkey().__str__(), 433 | "wrapUnwrapSOL": True 434 | } 435 | 436 | retries = 0 437 | while True: 438 | try: 439 | get_swap_data = httpx.post(url="https://quote-api.jup.ag/v6/swap", json=swap_data).json() 440 | swap_data = get_swap_data['swapTransaction'] 441 | wallet.sign_send_transaction_no_async(transaction_data=swap_data, print_link=False) 442 | self.success = True 443 | break 444 | except: 445 | if retries == 3: 446 | self.success = False 447 | break 448 | retries += 1 449 | time.sleep(0.5) 450 | 451 | if self.success is True: 452 | tokens_data[self.token_id]['STATUS'] = "IN" 453 | self.token_data['STATUS'] = "IN" 454 | alert_message = f"{self.token_data['NAME']} ({self.token_data['ADDRESS']}): IN" 455 | f.send_discord_alert(alert_message) 456 | f.send_telegram_alert(alert_message) 457 | else: 458 | tokens_data[self.token_id]['STATUS'] = "ERROR ON SWAPPING" 459 | self.token_data['STATUS'] = "ERROR WHEN SWAPPING" 460 | alert_message = f"{self.token_data['NAME']} ({self.token_data['ADDRESS']}): BUY FAILED" 461 | f.send_discord_alert(alert_message) 462 | f.send_telegram_alert(alert_message) 463 | Config_CLI.edit_tokens_file_no_async(tokens_data) 464 | 465 | elif self.token_data['STATUS'] not in ["NOT IN", "ERROR WHEN SWAPPING"] and not self.token_data['STATUS'].startswith('> '): 466 | time.sleep(1) 467 | sol_price = f.get_crypto_price('SOL') 468 | quote_url = "https://quote-api.jup.ag/v6/quote?" + f"inputMint={self.token_data['ADDRESS']}" + f"&outputMint=So11111111111111111111111111111111111111112" + f"&amount={token_balance['balance']['int']}" + f"&slippageBps={int(self.token_data['SLIPPAGE_BPS'])}" 469 | quote_response = httpx.get(quote_url).json() 470 | try: 471 | out_amount = (int(quote_response['outAmount']) / 10 ** 9) * sol_price 472 | 473 | amount_usd = out_amount 474 | 475 | if amount_usd < self.token_data['STOP_LOSS'] or amount_usd > self.token_data['TAKE_PROFIT']: 476 | swap_data = { 477 | "quoteResponse": quote_response, 478 | "userPublicKey": wallet.wallet.pubkey().__str__(), 479 | "wrapUnwrapSOL": True 480 | } 481 | get_swap_data = httpx.post(url="https://quote-api.jup.ag/v6/swap", json=swap_data).json() 482 | swap_data = get_swap_data['swapTransaction'] 483 | wallet.sign_send_transaction_no_async(transaction_data=swap_data, print_link=False) 484 | 485 | if amount_usd < self.token_data['STOP_LOSS']: 486 | tokens_data[self.token_id]['STATUS'] = f"> STOP LOSS" 487 | alert_message = f"{self.token_data['NAME']} ({self.token_data['ADDRESS']}): STOP LOSS @ ${amount_usd}" 488 | f.send_discord_alert(alert_message) 489 | f.send_telegram_alert(alert_message) 490 | elif amount_usd > self.token_data['TAKE_PROFIT']: 491 | tokens_data[self.token_id]['STATUS'] = f"> TAKE PROFIT" 492 | alert_message = f"{self.token_data['NAME']} ({self.token_data['ADDRESS']}): TAKE PROFIT @ ${amount_usd}" 493 | f.send_discord_alert(alert_message) 494 | f.send_telegram_alert(alert_message) 495 | 496 | Config_CLI.edit_tokens_file_no_async(tokens_data) 497 | break 498 | # If token balance not synchronized yet (on buy) 499 | except: 500 | pass 501 | 502 | else: 503 | break 504 | 505 | @staticmethod 506 | async def run(): 507 | """Starts all the sniper token instance""" 508 | tokens_snipe = await Config_CLI.get_tokens_data() 509 | for token_id, token_data in tokens_snipe.items(): 510 | token_sniper_instance = Token_Sniper(token_id, token_data) 511 | process = Process(target=token_sniper_instance.snipe_token, args=()) 512 | snipers_processes.append(process) 513 | 514 | for sniper_process in snipers_processes: 515 | sniper_process.start() 516 | 517 | 518 | class Jupiter_CLI(Wallet): 519 | 520 | def __init__(self, rpc_url: str, private_key: str) -> None: 521 | super().__init__(rpc_url=rpc_url, private_key=private_key) 522 | 523 | async def main_menu(self): 524 | """Main menu for Jupiter CLI.""" 525 | f.display_logo() 526 | print("[JUPITER CLI] [MAIN MENU]") 527 | await Wallets_CLI.display_selected_wallet() 528 | self.jupiter = Jupiter(async_client=self.client, keypair=self.wallet) 529 | 530 | jupiter_cli_prompt_main_menu = await inquirer.select(message="Select menu:", choices=[ 531 | "Swap", 532 | "Limit Order", 533 | "DCA", 534 | "Token Sniper", 535 | "Change wallet", 536 | "Back to main menu", 537 | ]).execute_async() 538 | 539 | match jupiter_cli_prompt_main_menu: 540 | case "Swap": 541 | await self.swap_menu() 542 | await self.main_menu() 543 | return 544 | case "Limit Order": 545 | await self.limit_order_menu() 546 | return 547 | case "DCA": 548 | await self.dca_menu() 549 | return 550 | case "Token Sniper": 551 | await self.token_sniper_menu() 552 | await self.main_menu() 553 | return 554 | case "Change wallet": 555 | wallet_id, wallet_private_key = await Wallets_CLI.prompt_select_wallet() 556 | if wallet_private_key: 557 | self.wallet = Keypair.from_bytes(base58.b58decode(wallet_private_key)) 558 | await self.main_menu() 559 | return 560 | case "Back to main menu": 561 | await Main_CLI.main_menu() 562 | return 563 | 564 | 565 | async def select_tokens(self, type_swap: str): 566 | """Prompts user to select tokens & amount to sell. 567 | 568 | type_swap (str): swap, limit_order, dca 569 | """ 570 | tokens_list = await Jupiter.get_tokens_list(list_type="all") 571 | tokens_list_dca = await Jupiter_DCA.get_available_dca_tokens() 572 | 573 | choices = [] 574 | for token in tokens_list: 575 | choices.append(f"{token['symbol']} ({token['address']})") 576 | 577 | # TOKEN TO SELL 578 | while True: 579 | select_sell_token = await inquirer.fuzzy(message="Enter token symbol or address you want to sell:", match_exact=True, choices=choices).execute_async() 580 | 581 | if select_sell_token is None: 582 | print(f"{c.RED}! Select a token to sell.{c.RESET}") 583 | 584 | elif select_sell_token is not None: 585 | confirm = await inquirer.select(message="Confirm token to sell?", choices=["Yes", "No"]).execute_async() 586 | if confirm == "Yes": 587 | if select_sell_token == "SOL (So11111111111111111111111111111111111111112)": 588 | sell_token_symbol = select_sell_token 589 | sell_token_address = "So11111111111111111111111111111111111111112" 590 | sell_token_account = self.wallet.pubkey().__str__() 591 | else: 592 | sell_token_symbol = re.search(r'^(.*?)\s*\(', select_sell_token).group(1) 593 | sell_token_address = re.search(r'\((.*?)\)', select_sell_token).group(1) 594 | sell_token_account = await self.get_token_mint_account(token_mint=sell_token_address) 595 | 596 | sell_token_account_info = await self.get_token_balance(token_mint_account=sell_token_account) 597 | if sell_token_account_info['balance']['float'] == 0: 598 | print(f"{c.RED}! You don't have any tokens to sell.{c.RESET}") 599 | elif type_swap == "dca" and sell_token_address not in tokens_list_dca: 600 | print(f"{c.RED}! Selected token to sell is not available for DCA{c.RESET}") 601 | else: 602 | choices.remove(select_sell_token) 603 | break 604 | 605 | # TOKEN TO BUY 606 | while True: 607 | select_buy_token = await inquirer.fuzzy(message="Enter symbol name or address you want to buy:", match_exact=True, choices=choices).execute_async() 608 | 609 | if select_sell_token is None: 610 | print(f"{c.RED}! Select a token to buy.{c.RESET}") 611 | 612 | elif select_sell_token is not None: 613 | 614 | confirm = await inquirer.select(message="Confirm token to buy?", choices=["Yes", "No"]).execute_async() 615 | if confirm == "Yes": 616 | if select_buy_token == "SOL": 617 | buy_token_symbol = select_buy_token 618 | buy_token_address = "So11111111111111111111111111111111111111112" 619 | buy_token_address = self.wallet.pubkey().__str__() 620 | else: 621 | buy_token_symbol = re.search(r'^(.*?)\s*\(', select_buy_token).group(1) 622 | buy_token_address = re.search(r'\((.*?)\)', select_buy_token).group(1) 623 | buy_token_account = await self.get_token_mint_account(token_mint=buy_token_address) 624 | 625 | buy_token_account_info = await self.get_token_balance(token_mint_account=buy_token_account) 626 | if type_swap == "dca" and sell_token_address not in tokens_list_dca: 627 | print(f"{c.RED}! Selected token to buy is not available for DCA{c.RESET}") 628 | else: 629 | choices.remove(select_buy_token) 630 | break 631 | 632 | # AMOUNT TO SELL 633 | while True: 634 | print(f"You own {sell_token_account_info['balance']['float']} ${sell_token_symbol}") 635 | prompt_amount_to_sell = await inquirer.number(message="Enter amount to sell:", float_allowed=True, max_allowed=sell_token_account_info['balance']['float']).execute_async() 636 | amount_to_sell = float(prompt_amount_to_sell) 637 | if float(amount_to_sell) == 0: 638 | print("! Amount to sell cannot be 0.") 639 | else: 640 | confirm_amount_to_sell = await inquirer.select(message="Confirm amount to sell?", choices=["Yes", "No"]).execute_async() 641 | if confirm_amount_to_sell == "Yes": 642 | break 643 | 644 | return sell_token_symbol, sell_token_address, buy_token_symbol, buy_token_address, amount_to_sell, sell_token_account_info, buy_token_account_info 645 | 646 | 647 | # SWAP 648 | async def swap_menu(self): 649 | """Jupiter CLI - SWAP MENU.""" 650 | f.display_logo() 651 | print("[JUPITER CLI] [SWAP MENU]") 652 | print() 653 | 654 | sell_token_symbol, sell_token_address, buy_token_symbol, buy_token_address, amount_to_sell, sell_token_account_info, buy_token_account_info = await self.select_tokens(type_swap="swap") 655 | 656 | # SLIPPAGE BPS 657 | while True: 658 | prompt_slippage_bps = await inquirer.number(message="Enter slippage percentage (%):", float_allowed=True, min_allowed=0.01, max_allowed=100.00).execute_async() 659 | slippage_bps = float(prompt_slippage_bps) 660 | 661 | confirm_slippage = await inquirer.select(message="Confirm slippage percentage?", choices=["Yes", "No"]).execute_async() 662 | if confirm_slippage == "Yes": 663 | break 664 | 665 | # DIRECT ROUTE 666 | # direct_route = await inquirer.select(message="Single hop routes only (usually for shitcoins)?", choices=["Yes", "No"]).execute_async() 667 | # if direct_route == "Yes": 668 | # direct_route = True 669 | # elif direct_route == "No": 670 | # direct_route = False 671 | 672 | print() 673 | print(f"[SELL {amount_to_sell} ${sell_token_symbol} -> ${buy_token_symbol} | SLIPPAGE: {slippage_bps}%]") 674 | confirm_swap = await inquirer.select(message="Execute swap?", choices=["Yes", "No"]).execute_async() 675 | if confirm_swap == "Yes": 676 | try: 677 | swap_data = await self.jupiter.swap( 678 | input_mint=sell_token_address, 679 | output_mint=buy_token_address, 680 | amount=int(amount_to_sell*10**sell_token_account_info['decimals']), 681 | slippage_bps=int(slippage_bps*100), 682 | # only_direct_routes=direct_route 683 | ) 684 | await self.sign_send_transaction(swap_data) 685 | except: 686 | print(f"{c.RED}! Swap execution failed.{c.RESET}") 687 | await inquirer.text(message="\nPress ENTER to continue").execute_async() 688 | return 689 | 690 | elif confirm_swap == "No": 691 | return 692 | 693 | 694 | # LIMIT ORDERS 695 | async def limit_order_menu(self): 696 | """Jupiter CLI - LIMIT ORDER MENU.""" 697 | loading_spinner = yaspin(text=f"{c.BLUE}Loading open limit orders{c.RESET}", color="blue") 698 | loading_spinner.start() 699 | f.display_logo() 700 | print("[JUPITER CLI] [LIMIT ORDER MENU]") 701 | print() 702 | 703 | choices = [ 704 | "Open Limit Order", 705 | "Display Canceled Orders History", 706 | "Display Filled Orders History", 707 | "Back to main menu", 708 | ] 709 | 710 | open_orders = await Jupiter_CLI.get_open_orders(wallet_address=self.wallet.pubkey().__str__()) 711 | if len(open_orders) > 0: 712 | choices.insert(1, "Cancel Limit Order(s)") 713 | await Jupiter_CLI.display_open_orders(wallet_address=self.wallet.pubkey().__str__()) 714 | 715 | loading_spinner.stop() 716 | limit_order_prompt_main_menu = await inquirer.select(message="Select menu:", choices=choices).execute_async() 717 | 718 | match limit_order_prompt_main_menu: 719 | case "Open Limit Order": 720 | sell_token_symbol, sell_token_address, buy_token_symbol, buy_token_address, amount_to_sell, sell_token_account_info, buy_token_account_info = await self.select_tokens(type_swap="limit_order") 721 | 722 | # AMOUNT TO BUY 723 | while True: 724 | amount_to_buy = await inquirer.number(message="Enter amount to buy:", float_allowed=True).execute_async() 725 | confirm = await inquirer.select(message="Confirm amount to buy?", choices=["Yes", "No"]).execute_async() 726 | if confirm == "Yes": 727 | amount_to_buy = float(amount_to_buy) 728 | break 729 | 730 | prompt_expired_at = await inquirer.select(message="Add expiration to the limit order?", choices=["Yes", "No"]).execute_async() 731 | if prompt_expired_at == "Yes": 732 | unit_time_expired_at = await inquirer.select(message="Select unit time:", choices=[ 733 | "Minute(s)", 734 | "Hour(s)", 735 | "Day(s)", 736 | "Week(s)", 737 | ]).execute_async() 738 | 739 | prompt_time_expired_at = await inquirer.number(message=f"Enter the number of {unit_time_expired_at.lower()} before your limit order expires:", float_allowed=False, min_allowed=1).execute_async() 740 | prompt_time_expired_at = int(prompt_time_expired_at) 741 | 742 | if unit_time_expired_at == "Minute(s)": 743 | expired_at = prompt_time_expired_at * 60 + int(time.time()) 744 | elif unit_time_expired_at == "Hour(s)": 745 | expired_at = prompt_time_expired_at * 3600 + int(time.time()) 746 | elif unit_time_expired_at == "Day(s)": 747 | expired_at = prompt_time_expired_at * 86400 + int(time.time()) 748 | elif unit_time_expired_at == "Week(s)": 749 | expired_at = prompt_time_expired_at * 604800 + int(time.time()) 750 | 751 | elif prompt_expired_at == "No": 752 | expired_at = None 753 | 754 | print("") 755 | expired_at_phrase = "Never Expires" if expired_at is None else f"Expires in {prompt_time_expired_at} {unit_time_expired_at.lower()}" 756 | 757 | print(f"[{amount_to_sell} ${sell_token_symbol} -> {amount_to_buy} ${buy_token_symbol} - {expired_at_phrase}]") 758 | confirm_open_order = await inquirer.select(message="Open order?", choices=["Yes", "No"]).execute_async() 759 | if confirm_open_order == "Yes": 760 | 761 | open_order_data = await self.jupiter.open_order( 762 | input_mint=sell_token_address, 763 | output_mint=buy_token_address, 764 | in_amount=int(amount_to_sell * 10 ** sell_token_account_info['decimals']), 765 | out_amount=int(amount_to_buy * 10 ** buy_token_account_info['decimals']), 766 | expired_at=expired_at, 767 | ) 768 | 769 | print() 770 | await self.sign_send_transaction( 771 | transaction_data=open_order_data['transaction_data'], 772 | signatures_list=[open_order_data['signature2']] 773 | ) 774 | 775 | await self.limit_order_menu() 776 | return 777 | case "Cancel Limit Order(s)": 778 | f.display_logo() 779 | 780 | loading_spinner = yaspin(text=f"{c.BLUE}Loading open limit orders{c.RESET}", color="blue") 781 | loading_spinner.start() 782 | open_orders = await Jupiter_CLI.display_open_orders(wallet_address=self.wallet.pubkey().__str__()) 783 | choices = [] 784 | 785 | for order_id, order_data in open_orders.items(): 786 | choices.append(f"ID {order_id} - {order_data['input_mint']['amount']} ${order_data['input_mint']['symbol']} -> {order_data['output_mint']['amount']} ${order_data['output_mint']['symbol']} (Account address: {order_data['open_order_pubkey']})") 787 | loading_spinner.stop() 788 | 789 | while True: 790 | prompt_select_cancel_orders = await inquirer.checkbox(message="Select orders to cancel (Max 10) or press ENTER to skip:", choices=choices).execute_async() 791 | 792 | if len(prompt_select_cancel_orders) > 10: 793 | print(f"{c.RED}! You can only cancel 10 orders at the time.{c.RESET}") 794 | elif len(prompt_select_cancel_orders) == 0: 795 | break 796 | 797 | confirm_cancel_orders = await inquirer.select(message="Cancel selected orders?", choices=["Yes", "No"]).execute_async() 798 | 799 | if confirm_cancel_orders == "Yes": 800 | orders_to_cancel = [] 801 | 802 | for order_to_cancel in prompt_select_cancel_orders: 803 | order_account_address = re.search(r"Account address: (\w+)", order_to_cancel).group(1) 804 | orders_to_cancel.append(order_account_address) 805 | 806 | cancel_orders_data = await self.jupiter.cancel_orders(orders=orders_to_cancel) 807 | await self.sign_send_transaction(cancel_orders_data) 808 | break 809 | 810 | elif confirm_cancel_orders == "No": 811 | break 812 | 813 | await self.limit_order_menu() 814 | return 815 | case "Display Canceled Orders History": 816 | loading_spinner = yaspin(text=f"{c.BLUE}Loading canceled limit orders{c.RESET}", color="blue") 817 | loading_spinner.start() 818 | tokens_list = await Jupiter.get_tokens_list(list_type="all") 819 | cancel_orders_history = await Jupiter.query_orders_history(wallet_address=self.wallet.pubkey().__str__()) 820 | data = { 821 | "ID": [], 822 | "CREATED AT": [], 823 | "TOKEN SOLD": [], 824 | "AMOUNT SOLD": [], 825 | "TOKEN BOUGHT": [], 826 | "AMOUNT BOUGHT": [], 827 | "STATE": [], 828 | } 829 | 830 | order_id = 1 831 | for order in cancel_orders_history: 832 | data['ID'].append(order_id) 833 | date = datetime.strptime(order['createdAt'], "%Y-%m-%dT%H:%M:%S.%fZ").strftime("%m-%d-%Y %H:%M:%S") 834 | data['CREATED AT'].append(date) 835 | 836 | token_sold_address = order['inputMint'] 837 | token_bought_address = order['outputMint'] 838 | 839 | token_sold_decimals = int(next((token.get("decimals", "") for token in tokens_list if token_sold_address == token.get("address", "")), None)) 840 | token_sold_symbol = next((token.get("symbol", "") for token in tokens_list if token_sold_address == token.get("address", "")), None) 841 | data['TOKEN SOLD'].append(token_sold_symbol) 842 | amount_sold = float(order['inAmount']) / 10 ** token_sold_decimals 843 | data['AMOUNT SOLD'].append(amount_sold) 844 | 845 | token_bought_decimals = int(next((token.get("decimals", "") for token in tokens_list if token_bought_address == token.get("address", "")), None)) 846 | token_bought_symbol = next((token.get("symbol", "") for token in tokens_list if token_bought_address == token.get("address", "")), None) 847 | data['TOKEN BOUGHT'].append(token_bought_symbol) 848 | amount_bought = float(order['outAmount']) / 10 ** token_bought_decimals 849 | data['AMOUNT BOUGHT'].append(amount_bought) 850 | 851 | state = order['state'] 852 | data['STATE'] = state 853 | 854 | order_id += 1 855 | 856 | dataframe = tabulate(pd.DataFrame(data), headers="keys", tablefmt="fancy_grid", showindex="never", numalign="center") 857 | loading_spinner.stop() 858 | print(dataframe) 859 | print() 860 | 861 | await inquirer.text(message="\nPress ENTER to continue").execute_async() 862 | await self.limit_order_menu() 863 | return 864 | case "Display Filled Orders History": 865 | loading_spinner = yaspin(text=f"{c.BLUE}Loading filled limit orders{c.RESET}", color="blue") 866 | loading_spinner.start() 867 | tokens_list = await Jupiter.get_tokens_list(list_type="all") 868 | filled_orders_history = await Jupiter.query_trades_history(wallet_address=self.wallet.pubkey().__str__()) 869 | data = { 870 | "ID": [], 871 | "CREATED AT": [], 872 | "TOKEN SOLD": [], 873 | "AMOUNT SOLD": [], 874 | "TOKEN BOUGHT": [], 875 | "AMOUNT BOUGHT": [], 876 | "STATE": [], 877 | } 878 | 879 | order_id = 1 880 | for order in filled_orders_history: 881 | data['ID'].append(order_id) 882 | date = datetime.strptime(order['createdAt'], "%Y-%m-%dT%H:%M:%S.%fZ").strftime("%m-%d-%Y %H:%M:%S") 883 | data['CREATED AT'].append(date) 884 | 885 | token_sold_address = order['order']['inputMint'] 886 | token_bought_address = order['order']['outputMint'] 887 | 888 | token_sold_decimals = int(next((token.get("decimals", "") for token in tokens_list if token_sold_address == token.get("address", "")), None)) 889 | token_sold_symbol = next((token.get("symbol", "") for token in tokens_list if token_sold_address == token.get("address", "")), None) 890 | data['TOKEN SOLD'].append(token_sold_symbol) 891 | amount_sold = float(order['inAmount']) / 10 ** token_sold_decimals 892 | data['AMOUNT SOLD'].append(amount_sold) 893 | 894 | token_bought_decimals = int(next((token.get("decimals", "") for token in tokens_list if token_bought_address == token.get("address", "")), None)) 895 | token_bought_symbol = next((token.get("symbol", "") for token in tokens_list if token_bought_address == token.get("address", "")), None) 896 | data['TOKEN BOUGHT'].append(token_bought_symbol) 897 | amount_bought = float(order['outAmount']) / 10 ** token_sold_decimals 898 | data['AMOUNT BOUGHT'].append(amount_bought) 899 | 900 | data['STATE'] = "FILLED" 901 | 902 | order_id += 1 903 | 904 | dataframe = tabulate(pd.DataFrame(data), headers="keys", tablefmt="fancy_grid", showindex="never", numalign="center") 905 | 906 | loading_spinner.stop() 907 | print(dataframe) 908 | print() 909 | 910 | await inquirer.text(message="\nPress ENTER to continue").execute_async() 911 | await self.limit_order_menu() 912 | return 913 | case "Back to main menu": 914 | await self.main_menu() 915 | return 916 | 917 | @staticmethod 918 | async def get_open_orders(wallet_address: str) -> dict: 919 | """Returns all open orders in a correct format.""" 920 | 921 | loading_spinner = yaspin(text=f"{c.BLUE}Loading open limit orders{c.RESET}", color="blue") 922 | loading_spinner.start() 923 | tokens_list = await Jupiter.get_tokens_list(list_type="all") 924 | open_orders_list = await Jupiter.query_open_orders(wallet_address=wallet_address) 925 | 926 | open_orders = {} 927 | 928 | order_id = 1 929 | for open_order in open_orders_list: 930 | open_order_pubkey = open_order['publicKey'] 931 | 932 | expired_at = open_order['account']['expiredAt'] 933 | if expired_at: 934 | expired_at = datetime.fromtimestamp(int(expired_at)).strftime('%m-%d-%Y %H:%M:%S') 935 | else: 936 | expired_at = "Never" 937 | 938 | input_mint_address = open_order['account']['inputMint'] 939 | input_mint_amount = int(open_order['account']['inAmount']) 940 | input_mint_symbol = next((token.get("symbol", "") for token in tokens_list if input_mint_address == token.get("address", "")), None) 941 | input_mint_decimals = int(next((token.get("decimals", "") for token in tokens_list if input_mint_address == token.get("address", "")), None)) 942 | 943 | output_mint_address = open_order['account']['outputMint'] 944 | output_mint_amount = int(open_order['account']['outAmount']) 945 | output_mint_symbol = next((token.get("symbol", "") for token in tokens_list if output_mint_address == token.get("address", "")), None) 946 | output_mint_decimals = int(next((token.get("decimals", "") for token in tokens_list if output_mint_address == token.get("address", "")), None)) 947 | 948 | open_orders[order_id] = { 949 | 'open_order_pubkey': open_order_pubkey, 950 | 'expired_at': expired_at, 951 | 'input_mint': { 952 | 'symbol': input_mint_symbol, 953 | 'amount': input_mint_amount / 10 ** input_mint_decimals 954 | }, 955 | 'output_mint': { 956 | 'symbol': output_mint_symbol, 957 | 'amount': output_mint_amount / 10 ** output_mint_decimals 958 | } 959 | } 960 | order_id += 1 961 | 962 | loading_spinner.stop() 963 | return open_orders 964 | 965 | @staticmethod 966 | async def display_open_orders(wallet_address: str) -> dict: 967 | """Displays current open orders and return open orders dict.""" 968 | loading_spinner = yaspin(text=f"{c.BLUE}Loading open limit orders{c.RESET}", color="blue") 969 | loading_spinner.start() 970 | open_orders = await Jupiter_CLI.get_open_orders(wallet_address=wallet_address) 971 | 972 | data = { 973 | 'ID': [], 974 | 'EXPIRED AT': [], 975 | 'SELL TOKEN': [], 976 | 'BUY TOKEN': [], 977 | 'ACCOUNT ADDRESS': [] 978 | } 979 | 980 | for open_order_id, open_order_data in open_orders.items(): 981 | data['ID'].append(open_order_id) 982 | data['EXPIRED AT'].append(open_order_data['expired_at']) 983 | data['SELL TOKEN'].append(f"{open_order_data['input_mint']['amount']} ${open_order_data['input_mint']['symbol']}") 984 | data['BUY TOKEN'].append(f"{open_order_data['output_mint']['amount']} ${open_order_data['output_mint']['symbol']}") 985 | data['ACCOUNT ADDRESS'].append(open_order_data['open_order_pubkey']) 986 | 987 | dataframe = tabulate(pd.DataFrame(data), headers="keys", tablefmt="fancy_grid", showindex="never", numalign="center") 988 | loading_spinner.stop() 989 | 990 | print(dataframe) 991 | print() 992 | return open_orders 993 | 994 | 995 | # DCA # 996 | async def dca_menu(self): 997 | """Jupiter CLI - DCA MENU.""" 998 | f.display_logo() 999 | print("[JUPITER CLI] [DCA MENU]") 1000 | print() 1001 | 1002 | choices = [ 1003 | "Open DCA Account", 1004 | "Manage DCA Accounts", 1005 | "Back to main menu" 1006 | ] 1007 | dca_menu_prompt_choice = await inquirer.select(message="Select menu:", choices=choices).execute_async() 1008 | 1009 | match dca_menu_prompt_choice: 1010 | case "Open DCA Account": 1011 | 1012 | sell_token_symbol, sell_token_address, buy_token_symbol, buy_token_address, amount_to_sell, sell_token_account_info, buy_token_account_info = await self.select_tokens(type_swap="dca") 1013 | 1014 | # IN AMOUNT PER CYCLE 1015 | while True: 1016 | in_amount_per_cycle = await inquirer.number(message="Enter amount per cycle to buy:", float_allowed=True, max_allowed=amount_to_sell).execute_async() 1017 | in_amount_per_cycle = float(in_amount_per_cycle) 1018 | confirm_in_amount_per_cycle = await inquirer.select(message="Confirm amount per cycle to buy?", choices=["Yes", "No"]).execute_async() 1019 | if confirm_in_amount_per_cycle == "Yes": 1020 | break 1021 | 1022 | # CYCLE FREQUENCY 1023 | while True: 1024 | unit_time_cycle_frequency = await inquirer.select(message="Select unit time for cycle frequency:", choices=[ 1025 | "Minute(s)", 1026 | "Hour(s)", 1027 | "Day(s)", 1028 | "Week(s)", 1029 | ]).execute_async() 1030 | 1031 | prompt_cycle_frequency = await inquirer.number(message=f"Enter the number of {unit_time_cycle_frequency.lower()} for every cycle:", float_allowed=False, min_allowed=1).execute_async() 1032 | prompt_cycle_frequency = int(prompt_cycle_frequency) 1033 | 1034 | if unit_time_cycle_frequency == "Minute(s)": 1035 | cycle_frequency = prompt_cycle_frequency * 60 1036 | elif unit_time_cycle_frequency == "Hour(s)": 1037 | cycle_frequency = prompt_cycle_frequency * 3600 1038 | elif unit_time_cycle_frequency == "Day(s)": 1039 | cycle_frequency = prompt_cycle_frequency * 86400 1040 | elif unit_time_cycle_frequency == "Week(s)": 1041 | cycle_frequency = prompt_cycle_frequency * 604800 1042 | 1043 | confirm_in_amount_per_cycle = await inquirer.select(message=f"Confirm number of {unit_time_cycle_frequency.lower()} for every cycle:", choices=["Yes", "No"]).execute_async() 1044 | if confirm_in_amount_per_cycle == "Yes": 1045 | break 1046 | 1047 | # START AT 1048 | unit_time_start_at = await inquirer.select(message="Select unit time to start DCA Account:", choices=[ 1049 | "Now", 1050 | "Minute(s)", 1051 | "Hour(s)", 1052 | "Day(s)", 1053 | "Week(s)", 1054 | ]).execute_async() 1055 | 1056 | if unit_time_start_at == "Now": 1057 | start_at = 0 1058 | else: 1059 | prompt_start_at = await inquirer.number(message=f"In how many {unit_time_start_at.lower()} does the DCA Account start:", float_allowed=False, min_allowed=1).execute_async() 1060 | prompt_start_at = int(prompt_start_at) 1061 | 1062 | if unit_time_start_at == "Minute(s)": 1063 | start_at = prompt_start_at * 60 + int(time.time()) 1064 | elif unit_time_start_at == "Hour(s)": 1065 | start_at = prompt_start_at * 3600 + int(time.time()) 1066 | elif unit_time_start_at == "Day(s)": 1067 | start_at = prompt_start_at * 86400 + int(time.time()) 1068 | elif unit_time_start_at == "Week(s)": 1069 | start_at = prompt_start_at * 604800 + int(time.time()) 1070 | 1071 | confirm_dca = await inquirer.select(message="Open DCA Account?", choices=["Yes", "No"]).execute_async() 1072 | if confirm_dca == "Yes": 1073 | try: 1074 | transaction_info = await self.jupiter.dca.create_dca( 1075 | input_mint=Pubkey.from_string(sell_token_address), 1076 | output_mint=Pubkey.from_string(buy_token_address), 1077 | total_in_amount=int(amount_to_sell*10**sell_token_account_info['decimals']), 1078 | in_amount_per_cycle=int(in_amount_per_cycle*10**sell_token_account_info['decimals']), 1079 | cycle_frequency=cycle_frequency, 1080 | start_at=start_at 1081 | ) 1082 | print(f"{c.GREEN}Transaction sent: https://explorer.solana.com/tx/{transaction_info['transaction_hash']}{c.RESET}") 1083 | 1084 | except: 1085 | print(f"{c.RED}! Creating DCA Account failed.{c.RESET}") 1086 | 1087 | await inquirer.text(message="\nPress ENTER to continue").execute_async() 1088 | 1089 | await self.dca_menu() 1090 | return 1091 | case "Manage DCA Accounts": 1092 | dca_accounts_data = await self.display_dca_accounts(wallet_address=self.wallet.pubkey().__str__()) 1093 | 1094 | choices = [] 1095 | dca_account_id = 1 1096 | for dca_account_data in dca_accounts_data: 1097 | choices.append(f"ID {dca_account_id} (DCA Account Address: {dca_account_data['dcaKey']})") 1098 | dca_account_id += 1 1099 | 1100 | dca_close_account_prompt_choice = await inquirer.checkbox(message="Select DCA Account to close with SPACEBAR or press ENTER to skip:", choices=choices).execute_async() 1101 | 1102 | if len(dca_close_account_prompt_choice) == 0: 1103 | await self.dca_menu() 1104 | return 1105 | 1106 | else: 1107 | for dca_account_to_close in dca_close_account_prompt_choice: 1108 | dca_account_id = re.search(r'ID (\d+)', dca_account_to_close).group(1) 1109 | dca_account_address = re.search(r'DCA Account Address: (\w+)', dca_account_to_close).group(1) 1110 | try: 1111 | await self.jupiter.dca.close_dca(dca_pubkey=Pubkey.from_string(dca_account_address)) 1112 | print(f"{c.GREEN}Deleted DCA Account #{dca_account_id}{c.RESET}") 1113 | except: 1114 | print(f"{c.RED}! Failed to delete DCA Account #{dca_account_id}{c.RESET}") 1115 | 1116 | await asyncio.sleep(1) 1117 | 1118 | await inquirer.text(message="\nPress ENTER to continue").execute_async() 1119 | await self.dca_menu() 1120 | return 1121 | case "Back to main menu": 1122 | await self.main_menu() 1123 | return 1124 | 1125 | async def display_dca_accounts(self, wallet_address: str): 1126 | loading_spinner = yaspin(text=f"{c.BLUE}Loading DCA Accounts{c.RESET}", color="blue") 1127 | loading_spinner.start() 1128 | tokens_list = await Jupiter.get_tokens_list(list_type="all") 1129 | get_dca_accounts = await self.jupiter.dca.fetch_user_dca_accounts(wallet_address=wallet_address, status=0) 1130 | loading_spinner.stop() 1131 | 1132 | dca_accounts = get_dca_accounts['data']['dcaAccounts'] 1133 | 1134 | data = { 1135 | 'ID': [], 1136 | 'CREATED AT': [], 1137 | 'END AT': [], 1138 | 'SELLING': [], 1139 | 'SELLING PER CYCLE': [], 1140 | "BUYING": [], 1141 | 'CYCLE FREQUENCY': [], 1142 | 'NEXT ORDER AT': [], 1143 | 'ORDERS LEFT': [] 1144 | } 1145 | 1146 | dca_account_id = 1 1147 | 1148 | for dca_account_data in dca_accounts: 1149 | data['ID'].append(dca_account_id) 1150 | 1151 | created_at = datetime.strptime(dca_account_data['createdAt'], "%Y-%m-%dT%H:%M:%S.%fZ").strftime("%m-%d-%y %H:%M") 1152 | data['CREATED AT'].append(created_at) 1153 | 1154 | end_at = int(dca_account_data['unfilledAmount']) / int(dca_account_data['inAmountPerCycle']) * int(dca_account_data['cycleFrequency']) 1155 | data['END AT'].append(datetime.fromtimestamp(end_at).strftime("%m-%d-%y %H:%M")) 1156 | 1157 | input_mint_address = dca_account_data['inputMint'] 1158 | input_mint_amount = int(dca_account_data['inDeposited']) 1159 | input_mint_symbol = next((token.get("symbol", "") for token in tokens_list if input_mint_address == token.get("address", "")), None) 1160 | input_mint_decimals = int(next((token.get("decimals", "") for token in tokens_list if input_mint_address == token.get("address", "")), None)) 1161 | data['SELLING'].append(f"{input_mint_amount/10**input_mint_decimals} ${input_mint_symbol}") 1162 | data['SELLING PER CYCLE'].append(f"{int(dca_account_data['inAmountPerCycle'])/10**input_mint_decimals} ${input_mint_symbol}") 1163 | 1164 | output_mint_address = dca_account_data['outputMint'] 1165 | output_mint_amount = int(dca_account_data['unfilledAmount']) 1166 | output_mint_symbol = next((token.get("symbol", "") for token in tokens_list if output_mint_address == token.get("address", "")), None) 1167 | output_mint_decimals = int(next((token.get("decimals", "") for token in tokens_list if output_mint_address == token.get("address", "")), None)) 1168 | data['BUYING'].append(f"{output_mint_amount/10**output_mint_decimals} ${output_mint_symbol}") 1169 | 1170 | data['CYCLE FREQUENCY'].append(f.get_timestamp_formatted(int(dca_account_data['cycleFrequency']))) 1171 | 1172 | # NEXT ORDER AT 1173 | creation_unix_timestamp = int(datetime.fromisoformat(dca_account_data['createdAt'].replace('Z', '+00:00')).timestamp()) 1174 | date_now_unix_timestamp = int(time.time()) 1175 | time_elapsed = date_now_unix_timestamp - creation_unix_timestamp 1176 | cycle_frequency = int(dca_account_data['cycleFrequency']) 1177 | total_orders = int(int(dca_account_data['inDeposited']) / int(dca_account_data['inAmountPerCycle'])) 1178 | total_orders_filled = int(len(dca_account_data['fills'])) 1179 | total_orders_unfilled = total_orders - total_orders_filled 1180 | 1181 | next_order_time_unix_timestamp = creation_unix_timestamp + (cycle_frequency * (total_orders_filled + 1)) 1182 | next_order_time_date = datetime.fromtimestamp(next_order_time_unix_timestamp).strftime("%m-%d-%y %H:%M") 1183 | data['NEXT ORDER AT'].append(next_order_time_date) 1184 | 1185 | data['ORDERS LEFT'].append(total_orders_unfilled) 1186 | 1187 | dca_account_id += 1 1188 | 1189 | 1190 | dataframe = tabulate(pd.DataFrame(data), headers="keys", tablefmt="fancy_grid", showindex="never", numalign="center") 1191 | loading_spinner.stop() 1192 | 1193 | print(dataframe) 1194 | print() 1195 | return dca_accounts 1196 | 1197 | 1198 | # TOKEN SNIPER # 1199 | async def token_sniper_menu(self): 1200 | """Jupiter CLI - TOKEN SNIPER MENU.""" 1201 | f.display_logo() 1202 | print("[JUPITER CLI] [TOKEN SNIPER MENU]") 1203 | print() 1204 | 1205 | await Jupiter_CLI.display_tokens_snipe() 1206 | 1207 | choices = [ 1208 | "Add a token to snipe", 1209 | "Watch token", 1210 | "Edit tokens", 1211 | "Back to main menu", 1212 | ] 1213 | token_sniper_menu_prompt_choices = await inquirer.select(message="Select menu:", choices=choices).execute_async() 1214 | 1215 | match token_sniper_menu_prompt_choices: 1216 | case "Add a token to snipe": 1217 | await self.add_token_snipe() 1218 | await self.token_sniper_menu() 1219 | return 1220 | case "Watch token": 1221 | tokens_snipe = await Config_CLI.get_tokens_data() 1222 | choices = [] 1223 | for token_id, token_data in tokens_snipe.items(): 1224 | if not token_data['STATUS'].startswith('> '): 1225 | choices.append(f"ID {token_id}") 1226 | choices.append("Back to main menu") 1227 | 1228 | prompt_select_token = await inquirer.select(message="Select token to watch:", choices=choices).execute_async() 1229 | 1230 | if prompt_select_token != "Back to main menu": 1231 | selected_token = re.search(r'\d+', prompt_select_token).group() 1232 | watch_process = Process(target=Jupiter_CLI.start_watch_async, args=(selected_token,)) 1233 | 1234 | watch_process.start() 1235 | 1236 | prompt_select_token = await inquirer.text(message="").execute_async() 1237 | watch_process.terminate() 1238 | watch_process.join() 1239 | 1240 | await self.token_sniper_menu() 1241 | return 1242 | case "Edit tokens": 1243 | await self.edit_tokens_snipe() 1244 | await self.token_sniper_menu() 1245 | return 1246 | case "Back to main menu": 1247 | await self.main_menu() 1248 | return 1249 | 1250 | @staticmethod 1251 | async def display_tokens_snipe(): 1252 | tokens_snipe = await Config_CLI.get_tokens_data() 1253 | 1254 | data = { 1255 | 'ID': [], 1256 | 'NAME': [], 1257 | 'ADDRESS': [], 1258 | 'WALLET': [], 1259 | 'STATUS':[], 1260 | 'BUY AMOUNT': [], 1261 | 'TAKE PROFIT': [], 1262 | 'STOP LOSS': [], 1263 | 'SLIPPAGE': [], 1264 | 'DATE LAUNCH': [] 1265 | } 1266 | 1267 | for token_id, token_data in tokens_snipe.items(): 1268 | data['ID'].append(token_id) 1269 | data['NAME'].append(token_data['NAME']) 1270 | data['ADDRESS'].append(token_data['ADDRESS']) 1271 | data['WALLET'].append(token_data['WALLET']) 1272 | data['STATUS'].append(token_data['STATUS']) 1273 | data['BUY AMOUNT'].append(f"${token_data['BUY_AMOUNT']}") 1274 | data['TAKE PROFIT'].append(f"${token_data['TAKE_PROFIT']}") 1275 | data['STOP LOSS'].append(f"${token_data['STOP_LOSS']}"), 1276 | data['SLIPPAGE'].append(f"{token_data['SLIPPAGE_BPS']/100}%") 1277 | if token_data['TIMESTAMP']: 1278 | data['DATE LAUNCH'].append(datetime.fromtimestamp(token_data['TIMESTAMP']).strftime('%Y-%m-%d %H:%M:%S')) 1279 | else: 1280 | data['DATE LAUNCH'].append("NO DATE LAUNCH") 1281 | 1282 | dataframe = tabulate(pd.DataFrame(data), headers="keys", tablefmt="fancy_grid", showindex="never", numalign="center") 1283 | 1284 | print(dataframe) 1285 | print() 1286 | 1287 | async def add_token_snipe(self): 1288 | """PROMPT ADD TOKEN TO SNIPE.""" 1289 | f.display_logo() 1290 | print("[JUPITER CLI] [ADD TOKEN TO SNIPE]") 1291 | print() 1292 | 1293 | token_name = await inquirer.text(message="Enter name for this project/token:").execute_async() 1294 | # token_name = "SYMPHONY 9" 1295 | 1296 | while True: 1297 | token_address = await inquirer.text(message="Enter token address:").execute_async() 1298 | # token_address = "AyWu89SjZBW1MzkxiREmgtyMKxSkS1zVy8Uo23RyLphX" 1299 | try: 1300 | Pubkey.from_string(token_address) 1301 | break 1302 | except: 1303 | print(f"{c.RED}! Please enter a valid token address") 1304 | 1305 | config_data = await Config_CLI.get_config_data() 1306 | client = AsyncClient(endpoint=config_data['RPC_URL']) 1307 | wallet_id, wallet_private_key = await Wallets_CLI.prompt_select_wallet() 1308 | # wallet_id = 1 1309 | wallet = Wallet(rpc_url=config_data['RPC_URL'], private_key=wallet_private_key) 1310 | get_wallet_sol_balance = await client.get_balance(pubkey=wallet.wallet.pubkey()) 1311 | sol_price = f.get_crypto_price("SOL") 1312 | sol_balance = round(get_wallet_sol_balance.value / 10 ** 9, 4) 1313 | sol_balance_usd = round(sol_balance * sol_price, 2) - 0.05 1314 | 1315 | amount_usd_to_buy = await inquirer.number(message="Enter amount $ to buy:", float_allowed=True, max_allowed=sol_balance_usd).execute_async() 1316 | # amount_usd_to_buy = 10 1317 | 1318 | take_profit_usd = await inquirer.number(message="Enter Take Profit ($) or press ENTER:", float_allowed=True, min_allowed=float(amount_usd_to_buy)).execute_async() 1319 | # take_profit_usd = 20 1320 | stop_loss_usd = await inquirer.number(message="Enter Stop Loss ($) or press ENTER:", float_allowed=True, max_allowed=float(amount_usd_to_buy)).execute_async() 1321 | # stop_loss_usd = 5 1322 | 1323 | slippage_bps = await inquirer.number(message="Enter Slippage (%) or press ENTER:", float_allowed=True, max_allowed=100, min_allowed=0.01, default=1).execute_async() 1324 | slippage_bps = float(slippage_bps) * 100 1325 | # slippage_bps = 1 1326 | 1327 | # alerts = await inquirer.select(message=f"Alerts (Discord/Telegram)?", choices=["Yes", "No"]).execute_async() 1328 | 1329 | while True: 1330 | confirm = await inquirer.select(message="Does token has a launch date?", choices=["Yes", "No"]).execute_async() 1331 | # confirm = "Yes" 1332 | if confirm == "Yes": 1333 | year = 2024 1334 | month = await inquirer.number(message="Month (1-12):", min_allowed=1, max_allowed=12, default=1).execute_async() 1335 | # month = 1 1336 | day = await inquirer.number(message="Day (1-31):", min_allowed=1, max_allowed=31, default=1).execute_async() 1337 | # day = 1 1338 | print("Enter time in 24-hour format (HH:MM)") 1339 | hours = await inquirer.number(message="Hours:", min_allowed=0, max_allowed=23, default=1).execute_async() 1340 | # hours = 17 1341 | minutes = await inquirer.number(message="Minutes:", min_allowed=0, max_allowed=59, default=1).execute_async() 1342 | # minutes = 30 1343 | timestamp = int((datetime(2024, int(month), int(day), int(hours), int(minutes)).timestamp())) 1344 | 1345 | confirm = await inquirer.select(message="Confirm launch date?", choices=["Yes", "No"]).execute_async() 1346 | # confirm = "Yes" 1347 | if timestamp < int(time.time()): 1348 | print(f"{c.RED}! Launch date is already passed{c.RESET}") 1349 | else: 1350 | if confirm == "Yes": 1351 | break 1352 | 1353 | elif confirm == "No": 1354 | timestamp = None 1355 | break 1356 | 1357 | if timestamp: 1358 | print(f"SNIPE {token_name} ({token_address}) | BUY: ${amount_usd_to_buy} - STOPLOSS: ${stop_loss_usd} - TAKEPROFIT: ${take_profit_usd} | LAUNCH DATE: {month}-{day}-{year} {hours}:{minutes}") 1359 | else: 1360 | print(f"SNIPE {token_name} ({token_address}) | BUY: ${amount_usd_to_buy} - STOPLOSS: ${stop_loss_usd} - TAKEPROFIT: ${take_profit_usd} | NO LAUNCH DATE") 1361 | 1362 | confirm = await inquirer.select(message="Confirm token?", choices=["Yes", "No"]).execute_async() 1363 | if confirm == "Yes": 1364 | tokens_data = await Config_CLI.get_tokens_data() 1365 | token_data = { 1366 | 'NAME': token_name, 1367 | 'ADDRESS': token_address, 1368 | 'WALLET': wallet_id, 1369 | 'BUY_AMOUNT': float(amount_usd_to_buy), 1370 | 'TAKE_PROFIT': float(take_profit_usd), 1371 | 'STOP_LOSS': float(stop_loss_usd), 1372 | 'SLIPPAGE_BPS': slippage_bps, 1373 | 'TIMESTAMP': timestamp, 1374 | 'STATUS': 'NOT IN', 1375 | } 1376 | tokens_data[len(tokens_data) + 1] = token_data 1377 | await Config_CLI.edit_tokens_file(tokens_data) 1378 | await inquirer.text(message="\nPress ENTER to continue").execute_async() 1379 | 1380 | # Restart Token Snipers processes to apply the changes 1381 | for p in snipers_processes: 1382 | p.terminate() 1383 | snipers_processes.clear() 1384 | await Token_Sniper.run() 1385 | return 1386 | 1387 | async def edit_tokens_snipe(self): 1388 | tokens_snipe = await Config_CLI.get_tokens_data() 1389 | choices = [] 1390 | for token_id, token_data in tokens_snipe.items(): 1391 | choices.append(f"ID {token_id}") 1392 | 1393 | prompt_select_token = await inquirer.select(message="Select token to edit:", choices=choices).execute_async() 1394 | selected_token = re.search(r'\d+', prompt_select_token).group() 1395 | 1396 | config_data = await Config_CLI.get_config_data() 1397 | wallets_data = await Wallets_CLI.get_wallets() 1398 | client = AsyncClient(endpoint=config_data['RPC_URL']) 1399 | wallet = Wallet(rpc_url=config_data['RPC_URL'], private_key=wallets_data[str(tokens_snipe[token_id]['WALLET'])]['private_key']) 1400 | get_wallet_sol_balance = await client.get_balance(pubkey=wallet.wallet.pubkey()) 1401 | sol_price = f.get_crypto_price("SOL") 1402 | sol_balance = round(get_wallet_sol_balance.value / 10 ** 9, 4) 1403 | sol_balance_usd = round(sol_balance * sol_price, 2) - 0.05 1404 | 1405 | choices = [ 1406 | "Name", 1407 | "Address", 1408 | "Selected Wallet", 1409 | "Buy Amount", 1410 | "Take Profit", 1411 | "Stop Loss", 1412 | "Slippage", 1413 | "Timestamp", 1414 | "Delete", 1415 | "Back to main menu" 1416 | ] 1417 | 1418 | while True: 1419 | 1420 | 1421 | prompt_select_options = await inquirer.select(message="Select info to edit:", choices=choices).execute_async() 1422 | 1423 | match prompt_select_options: 1424 | case "Name": 1425 | token_name = await inquirer.text(message="Enter name for this project/token:").execute_async() 1426 | tokens_snipe[selected_token]['NAME'] = token_name 1427 | await Config_CLI.edit_tokens_file(tokens_snipe) 1428 | print(f"{c.GREEN}Token ID {selected_token}: Name changed!{c.RESET}") 1429 | case "Address": 1430 | while True: 1431 | token_address = await inquirer.text(message="Enter token address:").execute_async() 1432 | try: 1433 | Pubkey.from_string(token_address) 1434 | break 1435 | except: 1436 | print(f"{c.RED}! Please enter a valid token address") 1437 | tokens_snipe[selected_token]['ADDRESS'] = token_address 1438 | await Config_CLI.edit_tokens_file(tokens_snipe) 1439 | print(f"{c.GREEN}Token ID {selected_token}: Address changed{c.RESET}") 1440 | case "Selected Wallet": 1441 | config_data = await Config_CLI.get_config_data() 1442 | client = AsyncClient(endpoint=config_data['RPC_URL']) 1443 | wallet_id, wallet_private_key = await Wallets_CLI.prompt_select_wallet() 1444 | tokens_snipe[selected_token]['WALLET'] = int(wallet_id) 1445 | await Config_CLI.edit_tokens_file(tokens_snipe) 1446 | print(f"{c.GREEN}Token ID {selected_token}: Selected Wallet {wallet_id}{c.RESET}") 1447 | case "Buy Amount": 1448 | amount_usd_to_buy = await inquirer.number(message="Enter amount $ to buy:", float_allowed=True, max_allowed=sol_balance_usd).execute_async() 1449 | tokens_snipe[selected_token]['BUY_AMOUNT'] = float(amount_usd_to_buy) 1450 | await Config_CLI.edit_tokens_file(tokens_snipe) 1451 | print(f"{c.GREEN}Token ID {selected_token}: Buy Amount ${amount_usd_to_buy}{c.RESET}") 1452 | case "Take Profit": 1453 | take_profit_usd = await inquirer.number(message="Enter Take Profit ($) or press ENTER:", float_allowed=True, min_allowed=float(tokens_snipe[selected_token]['BUY_AMOUNT'])).execute_async() 1454 | tokens_snipe[selected_token]['TAKE_PROFIT'] = float(take_profit_usd) 1455 | await Config_CLI.edit_tokens_file(tokens_snipe) 1456 | print(f"{c.GREEN}Token ID {selected_token}: Take Profit ${take_profit_usd}{c.RESET}") 1457 | case "Stop Loss": 1458 | stop_loss_usd = await inquirer.number(message="Enter Stop Loss ($) or press ENTER:", float_allowed=True, max_allowed=float(tokens_snipe[selected_token]['BUY_AMOUNT'])).execute_async() 1459 | tokens_snipe[selected_token]['STOP_LOSS'] = float(stop_loss_usd) 1460 | await Config_CLI.edit_tokens_file(tokens_snipe) 1461 | print(f"{c.GREEN}Token ID {selected_token}: Stop Loss ${stop_loss_usd}{c.RESET}") 1462 | case "Slippage": 1463 | slippage_bps = await inquirer.number(message="Enter Slippage (%) or press ENTER:", float_allowed=True, max_allowed=100.0, min_allowed=0.01, default=1).execute_async() 1464 | slippage_bps = float(slippage_bps) * 100 1465 | tokens_snipe[selected_token]['SLIPPAGE_BPS'] = int(slippage_bps) 1466 | await Config_CLI.edit_tokens_file(tokens_snipe) 1467 | print(f"{c.GREEN}Token ID {selected_token}: Slippage {slippage_bps}%{c.RESET}") 1468 | case "Timestamp": 1469 | while True: 1470 | confirm = await inquirer.select(message="Does token has a launch date?", choices=["Yes", "No"]).execute_async() 1471 | if confirm == "Yes": 1472 | year = 2024 1473 | month = await inquirer.number(message="Month (1-12):", min_allowed=1, max_allowed=12, default=1).execute_async() 1474 | day = await inquirer.number(message="Day (1-31):", min_allowed=1, max_allowed=31, default=1).execute_async() 1475 | print("Enter time in 24-hour format (HH:MM)") 1476 | hours = await inquirer.number(message="Hours:", min_allowed=0, max_allowed=23, default=1).execute_async() 1477 | minutes = await inquirer.number(message="Minutes:", min_allowed=0, max_allowed=59, default=1).execute_async() 1478 | timestamp = int((datetime(2024, int(month), int(day), int(hours), int(minutes)).timestamp())) 1479 | 1480 | if timestamp < int(time.time()): 1481 | print(f"{c.RED}! Launch date is already passed{c.RESET}") 1482 | else: 1483 | confirm = await inquirer.select(message="Confirm launch date?", choices=["Yes", "No"]).execute_async() 1484 | if confirm == "Yes": 1485 | break 1486 | 1487 | elif confirm == "No": 1488 | timestamp = None 1489 | break 1490 | 1491 | tokens_snipe[selected_token]['TIMESTAMP'] = timestamp 1492 | await Config_CLI.edit_tokens_file(tokens_snipe) 1493 | print(f"{c.GREEN}Token ID {selected_token}: Timestamp changed{c.RESET}") 1494 | case "Delete": 1495 | confirm = await inquirer.select(message=f"Confirm delete token ID {selected_token}?", choices=["Yes", "No"]).execute_async() 1496 | if confirm == "Yes": 1497 | del tokens_snipe[selected_token] 1498 | await Config_CLI.edit_tokens_file(tokens_snipe) 1499 | break 1500 | case "Back to main menu": 1501 | break 1502 | 1503 | # Restart Token Snipers processes to apply the changes 1504 | for p in snipers_processes: 1505 | p.terminate() 1506 | snipers_processes.clear() 1507 | await Token_Sniper.run() 1508 | 1509 | def start_watch_async(token_id): 1510 | asyncio.run(Jupiter_CLI.watch(token_id)) 1511 | 1512 | @staticmethod 1513 | async def watch(token_id): 1514 | tokens_snipe = await Config_CLI.get_tokens_data() 1515 | config_data = await Config_CLI.get_config_data() 1516 | wallets = await Wallets_CLI.get_wallets() 1517 | 1518 | token_name = tokens_snipe[token_id]['NAME'] 1519 | token_address = tokens_snipe[token_id]['ADDRESS'] 1520 | wallet = Wallet(rpc_url=config_data['RPC_URL'], private_key=wallets[token_id]['private_key']) 1521 | token_account = await wallet.get_token_mint_account(token_address) 1522 | buy_amount = tokens_snipe[token_id]['BUY_AMOUNT'] 1523 | take_profit = tokens_snipe[token_id]['TAKE_PROFIT'] 1524 | stop_loss = tokens_snipe[token_id]['STOP_LOSS'] 1525 | slippage_bps = tokens_snipe[token_id]['SLIPPAGE_BPS'] 1526 | timestamp = tokens_snipe[token_id]['TIMESTAMP'] 1527 | 1528 | async_client = AsyncClient(config_data['RPC_URL']) 1529 | jupiter = Jupiter(async_client, wallets[token_id]['private_key']) 1530 | 1531 | while True: 1532 | """Jupiter CLI - TOKEN SNIPER WATCH""" 1533 | f.display_logo() 1534 | print("[JUPITER CLI] [TOKEN SNIPER MENU]") 1535 | print() 1536 | 1537 | wallet_token_info = await wallet.get_token_balance(token_mint_account=token_account) 1538 | print(f"WATCHING {token_name} ({token_address})") 1539 | loading_spinner = yaspin(text=f"{c.BLUE}Loading token data{c.RESET}", color="blue") 1540 | loading_spinner.start() 1541 | 1542 | sol_price = f.get_crypto_price('SOL') 1543 | 1544 | if timestamp is None: 1545 | launch_date = "NO DATE LAUNCH" 1546 | else: 1547 | launch_date = datetime.fromtimestamp(int(timestamp)).strftime('%m-%d-%y %H:%M') 1548 | 1549 | if int(wallet_token_info['balance']['int']) == 0: 1550 | 1551 | data = { 1552 | f'{c.BLUE}BUY AMOUNT{c.RESET}': [f"{c.BLUE}${buy_amount}{c.RESET}"], 1553 | f'{c.GREEN}TAKE PROFIT{c.RESET}': [f"{c.GREEN}${take_profit}{c.RESET}"], 1554 | f'{c.RED}STOP LOSS{c.RESET}': [f"{c.RED}${stop_loss}{c.RESET}"], 1555 | 'SLIPPAGE': [f"{slippage_bps/100}%"], 1556 | 'DATE LAUNCH': [launch_date], 1557 | 'STATUS': ['NOT IN'] 1558 | } 1559 | dataframe = tabulate(pd.DataFrame(data), headers="keys", tablefmt="fancy_grid", showindex="never", numalign="center") 1560 | 1561 | elif int(wallet_token_info['balance']['int']) > 0: 1562 | 1563 | get_out_amount = await jupiter.quote( 1564 | input_mint=token_address, 1565 | output_mint='So11111111111111111111111111111111111111112', 1566 | amount=wallet_token_info['balance']['int'], 1567 | slippage_bps=int(slippage_bps) 1568 | ) 1569 | out_amount = int(get_out_amount['outAmount']) / 10 ** 9 * sol_price 1570 | 1571 | amount_token = round(wallet_token_info['balance']['float'], 5) 1572 | amount_usd = round(out_amount, 2) 1573 | pnl_usd = round(amount_usd - buy_amount, 2) 1574 | if pnl_usd >= 0: 1575 | pnl_usd_title = f"{c.GREEN}PnL ${c.RESET}" 1576 | pnl_usd = f"{c.GREEN}${pnl_usd}{c.RESET}" 1577 | else: 1578 | pnl_usd_title = f"{c.RED}PnL ${c.RESET}" 1579 | pnl_usd = f"{c.RED}${pnl_usd}{c.RESET}" 1580 | 1581 | pnl_percentage = round((amount_usd - buy_amount)/buy_amount*100, 2) 1582 | if pnl_percentage >= 0: 1583 | pnl_percentage_title = f"{c.GREEN}PnL %{c.RESET}" 1584 | pnl_percentage = f"{c.GREEN}{pnl_percentage}%{c.RESET}" 1585 | else: 1586 | pnl_percentage_title = f"{c.RED}PnL %{c.RESET}" 1587 | pnl_percentage = f"{c.RED}{pnl_percentage}%{c.RESET}" 1588 | 1589 | data = { 1590 | f'{c.BLUE}AMOUNT ${token_name}{c.RESET}': [f"{c.BLUE}{amount_token}{c.RESET}"], 1591 | f'{c.BLUE}VALUE USD{c.RESET}': [f"{c.BLUE}${amount_usd}{c.RESET}"], 1592 | f'{c.GREEN}TAKE PROFIT{c.RESET}': [f"{c.GREEN}${take_profit}{c.RESET}"], 1593 | f'{c.RED}STOP LOSS{c.RESET}': [f"{c.RED}${stop_loss}{c.RESET}"], 1594 | 'SLIPPAGE': [f"{slippage_bps/100}%"], 1595 | f'{pnl_usd_title}': [f'{pnl_usd}'], 1596 | f'{pnl_percentage_title}': [f'{pnl_percentage}'] 1597 | 1598 | } 1599 | dataframe = tabulate(pd.DataFrame(data), headers="keys", tablefmt="fancy_grid", showindex="never", numalign="center") 1600 | 1601 | 1602 | loading_spinner.stop() 1603 | sleep_time = random.uniform(5, 10) 1604 | print(dataframe) 1605 | print(f"\nRefresh in {round(sleep_time, 2)} secs\nPress ENTER to stop watching ") 1606 | time.sleep(sleep_time) 1607 | 1608 | 1609 | class Wallets_CLI(): 1610 | 1611 | @staticmethod 1612 | async def get_wallets() -> dict: 1613 | """Returns all wallets stored in wallets.json.""" 1614 | with open('wallets.json', 'r') as wallets_file: 1615 | return json.load(wallets_file) 1616 | 1617 | @staticmethod 1618 | def get_wallets_no_async() -> dict: 1619 | """Returns all wallets stored in wallets.json.""" 1620 | with open('wallets.json', 'r') as wallets_file: 1621 | return json.load(wallets_file) 1622 | 1623 | @staticmethod 1624 | async def prompt_select_wallet() -> str: 1625 | """Prompts user to select a wallet.""" 1626 | await Wallets_CLI.display_wallets() 1627 | wallets = await Wallets_CLI.get_wallets() 1628 | 1629 | choices = [] 1630 | for wallet_id, wallet_data in wallets.items(): 1631 | choices.append(f"ID {wallet_id} - {wallet_data['wallet_name']} - {wallet_data['pubkey']}") 1632 | 1633 | while True: 1634 | prompt_select_wallet = await inquirer.select(message="Select wallet:", choices=choices).execute_async() 1635 | confirm = await inquirer.select(message="Confirm wallet selected?", choices=["Yes", "No"]).execute_async() 1636 | if confirm == "Yes": 1637 | wallet_id = re.search(r'ID (\d+) -', prompt_select_wallet).group(1) 1638 | 1639 | config_data = await Config_CLI.get_config_data() 1640 | config_data['LAST_WALLET_SELECTED'] = wallet_id 1641 | await Config_CLI.edit_config_file(config_data=config_data) 1642 | 1643 | return wallet_id, wallets[wallet_id]['private_key'] 1644 | 1645 | @staticmethod 1646 | async def prompt_add_wallet(): 1647 | """Adds a wallet to wallets.json.""" 1648 | wallet_private_key = await inquirer.secret(message="Enter Wallet Private Key:").execute_async() 1649 | # wallet_private_key = os.getenv('PRIVATE_KEY') 1650 | 1651 | if wallet_private_key != "": 1652 | try: 1653 | keypair = Keypair.from_bytes(base58.b58decode(wallet_private_key)) 1654 | pubkey = keypair.pubkey() 1655 | except: 1656 | print(f"{c.RED}! Invalid private key.{c.RESET}") 1657 | await Wallets_CLI.prompt_add_wallet() 1658 | return 1659 | 1660 | confirm = await inquirer.select(message=f"Wallet Address: {pubkey.__str__()}\nConfirm?", choices=["Yes", "No"]).execute_async() 1661 | # confirm = "Yes" 1662 | if confirm == "Yes": 1663 | wallet_name = await inquirer.text(message="Enter wallet name:").execute_async() 1664 | # wallet_name = "DEGEN 1" 1665 | 1666 | with open('wallets.json', 'r') as wallets_file: 1667 | wallets_data = json.load(wallets_file) 1668 | 1669 | wallet_data = { 1670 | 'wallet_name': wallet_name, 1671 | 'pubkey': pubkey.__str__(), 1672 | 'private_key': wallet_private_key, 1673 | } 1674 | wallets_data[len(wallets_data) + 1] = wallet_data 1675 | 1676 | with open('wallets.json', 'w') as wallets_file: 1677 | json.dump(wallets_data, wallets_file, indent=4) 1678 | 1679 | elif confirm == "No": 1680 | await Wallets_CLI.prompt_add_wallet() 1681 | return 1682 | 1683 | @staticmethod 1684 | async def prompt_edit_wallet_name(): 1685 | """Prompts user to edit a wallet name.""" 1686 | wallets = await Wallets_CLI.get_wallets() 1687 | choices = [] 1688 | 1689 | for wallet_id, wallet_data in wallets.items(): 1690 | choices.append(f"ID {wallet_id} - {wallet_data['wallet_name']} - {wallet_data['pubkey']}") 1691 | 1692 | prompt_select_wallet_to_edit_name = await inquirer.select(message="Select wallet:", choices=choices).execute_async() 1693 | 1694 | confirm = await inquirer.select(message="Confirm wallet selected?", choices=["Yes", "No"]).execute_async() 1695 | if confirm == "Yes": 1696 | new_wallet_name = await inquirer.text(message="Enter Wallet new name:").execute_async() 1697 | 1698 | confirm = await inquirer.select(message="Confirm wallet new name?", choices=["Yes", "No"]).execute_async() 1699 | if confirm == "Yes": 1700 | wallet_id = re.search(r'ID (\d+) -', prompt_select_wallet_to_edit_name).group(1) 1701 | wallets[wallet_id]['wallet_name'] = new_wallet_name 1702 | with open('wallets.json', 'w') as wallets_file: 1703 | json.dump(wallets, wallets_file, indent=4) 1704 | return 1705 | elif confirm == "No": 1706 | await Wallets_CLI.prompt_edit_wallet_name() 1707 | return 1708 | 1709 | elif confirm == "No": 1710 | await Wallets_CLI.prompt_edit_wallet_name() 1711 | return 1712 | 1713 | @staticmethod 1714 | async def prompt_delete_wallet(): 1715 | """Prompts user to delete wallet(s)""" 1716 | wallets = await Wallets_CLI.get_wallets() 1717 | choices = [] 1718 | 1719 | for wallet_id, wallet_data in wallets.items(): 1720 | choices.append(f"ID {wallet_id} - {wallet_data['wallet_name']} - {wallet_data['pubkey']}") 1721 | 1722 | prompt_wallets_to_delete = await inquirer.checkbox(message="Select wallet(s) to delete with SPACEBAR or press ENTER to skip:", choices=choices).execute_async() 1723 | 1724 | if len(prompt_wallets_to_delete) == 0: 1725 | await Wallets_CLI.main_menu() 1726 | return 1727 | else: 1728 | confirm = await inquirer.select(message="Confirm delete wallet(s) selected?", choices=["Yes", "No"]).execute_async() 1729 | if confirm == "Yes": 1730 | for wallet_to_delete in prompt_wallets_to_delete: 1731 | wallet_id = re.search(r'ID (\d+) -', wallet_to_delete).group(1) 1732 | del wallets[wallet_id] 1733 | 1734 | with open('wallets.json', 'w') as wallets_file: 1735 | json.dump(wallets, wallets_file, indent=4) 1736 | return 1737 | 1738 | elif confirm == "No": 1739 | await Wallets_CLI.main_menu() 1740 | return 1741 | 1742 | @staticmethod 1743 | async def display_wallets(): 1744 | print() 1745 | loading_spinner = yaspin(text=f"{c.BLUE}Loading wallets{c.RESET}", color="blue") 1746 | loading_spinner.start() 1747 | data = { 1748 | 'ID': [], 1749 | 'NAME': [], 1750 | 'SOL BALANCE': [], 1751 | 'ADDRESS': [], 1752 | } 1753 | wallets = await Wallets_CLI.get_wallets() 1754 | get_rpc_url = await Config_CLI.get_config_data() 1755 | client = AsyncClient(endpoint=get_rpc_url['RPC_URL']) 1756 | sol_price = f.get_crypto_price(crypto='SOL') 1757 | 1758 | for wallet_id, wallet_data in wallets.items(): 1759 | data['ID'].append(wallet_id) 1760 | data['NAME'].append(wallet_data['wallet_name']) 1761 | data['ADDRESS'].append(wallet_data['pubkey']) 1762 | get_wallet_sol_balance = await client.get_balance(pubkey=Pubkey.from_string(wallet_data['pubkey'])) 1763 | sol_balance = round(get_wallet_sol_balance.value / 10 ** 9, 4) 1764 | sol_balance_usd = round(sol_balance * sol_price, 2) 1765 | data['SOL BALANCE'].append(f"{sol_balance} (${sol_balance_usd})") 1766 | 1767 | dataframe = tabulate(pd.DataFrame(data), headers="keys", tablefmt="fancy_grid", showindex="never", numalign="center") 1768 | loading_spinner.stop() 1769 | print(dataframe) 1770 | print() 1771 | return wallets 1772 | 1773 | @staticmethod 1774 | async def display_selected_wallet(): 1775 | print() 1776 | print("WALLET SELECTED") 1777 | loading_spinner = yaspin(text=f"{c.BLUE}Loading wallet selected{c.RESET}", color="blue") 1778 | loading_spinner.start() 1779 | 1780 | config_data = await Config_CLI.get_config_data() 1781 | wallets = await Wallets_CLI.get_wallets() 1782 | client = AsyncClient(endpoint=config_data['RPC_URL']) 1783 | 1784 | get_sol_balance = await client.get_balance(pubkey=Pubkey.from_string(wallets[config_data['LAST_WALLET_SELECTED']]['pubkey'])) 1785 | sol_balance = round(get_sol_balance.value / 10 ** 9, 4) 1786 | sol_price = f.get_crypto_price(crypto='SOL') 1787 | sol_balance_usd = round(sol_balance * sol_price, 2) 1788 | data = { 1789 | 'NAME': [wallets[config_data['LAST_WALLET_SELECTED']]['wallet_name']], 1790 | 'SOL BALANCE': [f"{sol_balance} (${sol_balance_usd})"], 1791 | 'ADDRESS': [wallets[config_data['LAST_WALLET_SELECTED']]['pubkey']], 1792 | } 1793 | 1794 | dataframe = tabulate(pd.DataFrame(data), headers="keys", tablefmt="fancy_grid", showindex="never", numalign="center") 1795 | loading_spinner.stop() 1796 | print(dataframe) 1797 | print() 1798 | return 1799 | 1800 | @staticmethod 1801 | async def main_menu(): 1802 | """Main menu for Wallets CLI.""" 1803 | f.display_logo() 1804 | print("[MANAGE WALLETS]") 1805 | await Wallets_CLI.display_wallets() 1806 | 1807 | wallets_cli_prompt_main_menu = await inquirer.select(message="Select choice:", choices=[ 1808 | "Add wallet", 1809 | "Edit wallet name", 1810 | "Delete wallet(s)", 1811 | "Back to main menu" 1812 | ]).execute_async() 1813 | 1814 | match wallets_cli_prompt_main_menu: 1815 | case "Add wallet": 1816 | await Wallets_CLI.prompt_add_wallet() 1817 | await Wallets_CLI.main_menu() 1818 | return 1819 | case "Edit wallet name": 1820 | await Wallets_CLI.prompt_edit_wallet_name() 1821 | await Wallets_CLI.main_menu() 1822 | return 1823 | case "Delete wallet(s)": 1824 | await Wallets_CLI.prompt_delete_wallet() 1825 | await Wallets_CLI.main_menu() 1826 | return 1827 | case "Back to main menu": 1828 | await Main_CLI.main_menu() 1829 | return 1830 | 1831 | 1832 | class Main_CLI(): 1833 | 1834 | async def start_CLI(): 1835 | config_data = await Config_CLI.get_config_data() 1836 | 1837 | if config_data['FIRST_LOGIN'] is True: 1838 | await Main_CLI.first_login() 1839 | await Wallets_CLI.prompt_add_wallet() 1840 | 1841 | config_data = await Config_CLI.get_config_data() 1842 | config_data['FIRST_LOGIN'] = False 1843 | config_data['LAST_WALLET_SELECTED'] = "1" 1844 | await Config_CLI.edit_config_file(config_data=config_data) 1845 | 1846 | await Main_CLI.main_menu() 1847 | 1848 | @staticmethod 1849 | async def first_login(): 1850 | """Setting up CLI configuration if it's the first login.""" 1851 | 1852 | f.display_logo() 1853 | print("Welcome to the Jupiter Python CLI v.0.0.1! Made by @_TaoDev_") 1854 | print("This is your first login, let's setup the CLI configuration.\n") 1855 | 1856 | # async Config_CLI.prompt_collect_fees() # TBD 1857 | while True: 1858 | rpc_url = await Config_CLI.prompt_rpc_url() 1859 | if rpc_url == "": 1860 | print(f"{c.RED}This is your first login, please enter a Solana RPC URL Endpoint.{c.RESET}") 1861 | else: 1862 | break 1863 | await Config_CLI.prompt_discord_webhook() 1864 | await Config_CLI.prompt_telegram_api() 1865 | 1866 | return 1867 | 1868 | @staticmethod 1869 | async def main_menu(): 1870 | """Main menu for CLI.""" 1871 | f.display_logo() 1872 | print("Welcome to the Jupiter Python CLI v.0.0.1! Made by @_TaoDev_\n") 1873 | cli_prompt_main_menu = await inquirer.select(message="Select menu:", choices=[ 1874 | "Jupiter Exchange", 1875 | "Manage Wallets", 1876 | "CLI settings", 1877 | "About", 1878 | "Exit CLI" 1879 | ]).execute_async() 1880 | 1881 | match cli_prompt_main_menu: 1882 | case "Jupiter Exchange": 1883 | config_data = await Config_CLI.get_config_data() 1884 | wallets = await Wallets_CLI.get_wallets() 1885 | last_wallet_selected = wallets[str(config_data['LAST_WALLET_SELECTED'])]['private_key'] 1886 | await Jupiter_CLI(rpc_url=config_data['RPC_URL'], private_key=last_wallet_selected).main_menu() 1887 | return 1888 | case "Manage Wallets": 1889 | await Wallets_CLI.main_menu() 1890 | return 1891 | case "CLI settings": 1892 | await Config_CLI.main_menu() 1893 | return 1894 | case "About": 1895 | print() 1896 | print("DESCRIPTION") 1897 | description = ( 1898 | "This tool is a commande-line interface to use Jupiter Exchange faster made by @_TaoDev_." + 1899 | "\nIt allows you to manage your wallets quickly, executes swaps, managing limit orders and DCA accounts, fetch wallet data (open orders, trades history...), tokens stats, and more!" 1900 | ) 1901 | await inquirer.text(message=f"{description}").execute_async() 1902 | print() 1903 | print("DISCLAIMER") 1904 | disclaimer = ( 1905 | "Please note that the creator of this tool is not responsible for any loss of funds, damages, or other libailities resulting from the use of this software or any associated services." + 1906 | "\nThis tool is provided for educational purposes only and should not be used as financial advice, it is still in expiremental phase so use it at your own risk." 1907 | ) 1908 | await inquirer.text(message=f"{disclaimer}").execute_async() 1909 | print() 1910 | print("CONTRIBUTIONS") 1911 | contributions = ( 1912 | "If you are interesting in contributing, fork the repository and submit a pull request in order to merge your improvements into the main repository." + 1913 | "\nContact me for any inquiry, I will reach you as soon as possible." + 1914 | "\nDiscord: _taodev_ | Twitter: @_TaoDev_ | Github: 0xTaoDev" 1915 | ) 1916 | await inquirer.text(message=f"{contributions}").execute_async() 1917 | print() 1918 | print("DONATIONS") 1919 | print("This project doesn't include platform fees.\nIf you find value in it and would like to support its development, your donations are greatly appreciated.") 1920 | confirm_make_donation = await inquirer.select(message="Would you make a donation?", choices=[ 1921 | "Yes", 1922 | "No", 1923 | ]).execute_async() 1924 | 1925 | if confirm_make_donation == "Yes": 1926 | config_data = await Config_CLI.get_config_data() 1927 | client = AsyncClient(endpoint=config_data['RPC_URL']) 1928 | 1929 | wallet_id, wallet_private_key = await Wallets_CLI.prompt_select_wallet() 1930 | wallet = Wallet(rpc_url=config_data['RPC_URL'], private_key=wallet_private_key) 1931 | 1932 | get_wallet_sol_balance = await client.get_balance(pubkey=wallet.wallet.pubkey()) 1933 | sol_price = f.get_crypto_price("SOL") 1934 | sol_balance = round(get_wallet_sol_balance.value / 10 ** 9, 4) 1935 | sol_balance_usd = round(sol_balance * sol_price, 2) - 0.05 1936 | 1937 | amount_usd_to_donate = await inquirer.number(message="Enter amount $ to donate:", float_allowed=True, max_allowed=sol_balance_usd).execute_async() 1938 | 1939 | prompt_donation_choice = await inquirer.select(message="Confirm donation?", choices=["Yes", "No"]).execute_async() 1940 | if prompt_donation_choice == "Yes": 1941 | transfer_IX = transfer(TransferParams( 1942 | from_pubkey=wallet.wallet.pubkey(), 1943 | to_pubkey=Pubkey.from_string("AyWu89SjZBW1MzkxiREmgtyMKxSkS1zVy8Uo23RyLphX"), 1944 | lamports=int(float(amount_usd_to_donate) / sol_price * 10 ** 9) 1945 | )) 1946 | transaction = Transaction().add(transfer_IX) 1947 | transaction.sign(wallet.wallet) 1948 | try: 1949 | await client.send_transaction(transaction, wallet.wallet, opts=TxOpts(skip_preflight=True, preflight_commitment=Processed)) 1950 | print(f"{c.GREEN}Thanks a lot for your donation{c.RESET}") 1951 | except: 1952 | print(f"{c.RED}Failed to send the donation.{c.RESET}") 1953 | 1954 | await inquirer.text(message="\nPress ENTER to continue").execute_async() 1955 | 1956 | await Main_CLI.main_menu() 1957 | return 1958 | case "Exit CLI": 1959 | print("\nBye!") 1960 | for p in snipers_processes: 1961 | p.terminate() 1962 | time.sleep(1) 1963 | exit() 1964 | 1965 | 1966 | if __name__ == "__main__": 1967 | print(f"{c.BLUE}STARTING CLI...{c.RESET}") 1968 | asyncio.run(Token_Sniper.run()) 1969 | asyncio.run(Main_CLI.start_CLI()) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | InquirerPy 2 | jupiter-python-sdk 3 | pyfiglet 4 | pandas 5 | tabulate 6 | yaspin 7 | -------------------------------------------------------------------------------- /tokens.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /wallets.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } --------------------------------------------------------------------------------