├── .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 | [](https://discord.gg/QxwPGcXDp7)
159 | [](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 | }
--------------------------------------------------------------------------------