diff --git a/crypto/sol/.env b/crypto/sol/.env
index e14028e..872d843 100644
--- a/crypto/sol/.env
+++ b/crypto/sol/.env
@@ -22,8 +22,8 @@ DEVELOPER_CHAT_ID="777826553"
TELEGRAM_BOT_TOKEN="6749075936:AAHUHiPTDEIu6JH7S2fQdibwsu6JVG3FNG0"
DISPLAY_CURRENCY=USD
-FOLLOW_AMOUNT=3
-#FOLLOW_AMOUNT=percentage
+#FOLLOW_AMOUNT=3
+FOLLOW_AMOUNT=percentage
LIQUIDITY_TOKENS=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v,So11111111111111111111111111111111111111112
diff --git a/crypto/sol/modules/SolanaAPI.py b/crypto/sol/modules/SolanaAPI.py
index 97e373a..59e57f0 100644
--- a/crypto/sol/modules/SolanaAPI.py
+++ b/crypto/sol/modules/SolanaAPI.py
@@ -1,3 +1,4 @@
+import base64
import struct
import sys
import os
@@ -50,7 +51,7 @@ from datetime import datetime
from solana.rpc.types import TokenAccountOpts, TxOpts
from typing import List, Dict, Any, Tuple
import traceback
-import base64
+import httpx
# # # solders/solana libs (solana_client) # # #
from spl.token._layouts import MINT_LAYOUT
@@ -234,6 +235,17 @@ class SolanaWS:
class SolanaAPI:
pk = None
+
+ ENDPOINT_APIS_URL = {
+ "QUOTE": "https://quote-api.jup.ag/v6/quote?",
+ "SWAP": "https://quote-api.jup.ag/v6/swap",
+ "OPEN_ORDER": "https://jup.ag/api/limit/v1/createOrder",
+ "CANCEL_ORDERS": "https://jup.ag/api/limit/v1/cancelOrders",
+ "QUERY_OPEN_ORDERS": "https://jup.ag/api/limit/v1/openOrders?wallet=",
+ "QUERY_ORDER_HISTORY": "https://jup.ag/api/limit/v1/orderHistory",
+ "QUERY_TRADE_HISTORY": "https://jup.ag/api/limit/v1/tradeHistory"
+ }
+
def __init__(self, process_transaction_callback = None, on_initial_subscription_callback = None, on_bot_message=None):
self.process_transaction = process_transaction_callback
self.on_initial_subscription = on_initial_subscription_callback
@@ -247,6 +259,7 @@ class SolanaAPI:
self.dex = DEX
self.solana_ws = SolanaWS(on_message=self.process_transaction)
+
async def process_messages(self, solana_ws):
while True:
message = await solana_ws.message_queue.get()
@@ -587,7 +600,7 @@ class SolanaAPI:
except Exception as e:
logging.error(f"Error fetching transaction details: {e}")
- logging.info(f"({_} of {max_retries}) Waiting for transaction details for {transaction_id}. retry in {retry_delay} s.")
+ logging.info(f"({_} of {max_retries}) Waiting for transaction details for {transaction_id} [ retry in {retry_delay} s.]")
await asyncio.sleep(retry_delay)
if backoff:
retry_delay = retry_delay * 1.2
@@ -672,6 +685,100 @@ class SolanaAPI:
logging.error(f"Error getting balance for {token_address} in {wallet_address}: {str(e)} \r\n {e}")
return 0
+
+
+ async def swap_on_jupiter(
+ self,
+ input_mint: str,
+ output_mint: str,
+ amount: int,
+ slippage_bps: int = 1,
+ swap_mode: str = "ExactIn",
+ priority_fee: int = 0,
+ only_direct_routes: bool = False,
+ as_legacy_transaction: bool = False,
+ exclude_dexes: list = None,
+ max_accounts: int = None
+ ) -> str:
+ """Perform a swap on Jupiter with an option to set priority.
+
+ Args:
+ input_mint (str): Input token mint.
+ output_mint (str): Output token mint.
+ amount (int): Amount to swap, considering token decimals.
+ slippage_bps (int): Slippage in basis points.
+ swap_mode (str): Swap mode, either 'ExactIn' or 'ExactOut'.
+ priority_level (int): Priority level for the transaction fee.
+ only_direct_routes (bool): Limit to direct routes only.
+ as_legacy_transaction (bool): Use legacy transaction format.
+ exclude_dexes (list): List of DEXes to exclude.
+ max_accounts (int): Max number of accounts involved.
+
+ Returns:
+ str: Serialized transaction data for the swap.
+ """
+ # Get a quote from Jupiter
+ quote_url = self.ENDPOINT_APIS_URL['QUOTE'] + "inputMint=" + input_mint + "&outputMint=" + output_mint + "&amount=" + str(amount) + "&swapMode=" + swap_mode + "&onlyDirectRoutes=" + str(only_direct_routes).lower() + "&asLegacyTransaction=" + str(as_legacy_transaction).lower()
+ if slippage_bps:
+ quote_url += "&slippageBps=" + str(slippage_bps)
+ if exclude_dexes:
+ quote_url += "&excludeDexes=" + ','.join(exclude_dexes).lower()
+ if max_accounts:
+ quote_url += "&maxAccounts=" + str(max_accounts)
+
+ quote_response = httpx.get(url=quote_url).json()
+
+ try:
+ quote_response['routePlan']
+ except:
+ raise Exception(quote_response['error'])
+
+
+ # Prepare transaction parameters
+ fees = "auto" if priority_fee == 0 else priority_fee
+
+ pair = Keypair.from_bytes(base58.b58decode(self.pk))
+ pubK = pair.pubkey().__str__()
+ transaction_parameters = {
+ "quoteResponse":quote_response,
+ "userPublicKey": pubK,
+ "wrapAndUnwrapSol": True,
+ "computeUnitPriceMicroLamports":fees
+ }
+ # This will raise an error if data isn't JSON serializable
+ json_string = json.dumps(transaction_parameters)
+ validated_data = json.loads(json_string)
+
+ response = httpx.post(url=self.ENDPOINT_APIS_URL['SWAP'], json=validated_data)
+ response_data = response.json()
+
+ # headers = {
+ # 'Content-Type': 'application/json',
+ # 'Accept': 'application/json'
+ # }
+ # response = requests.request("POST", self.ENDPOINT_APIS_URL['SWAP'], headers=headers, data=validated_data)
+ # response_data = response.json()
+
+ # result = response_data['swapTransaction']
+
+ # # # Send the swap request to Jupiter
+ # async with httpx.AsyncClient() as client:
+ # response = await client.post(
+ # self.ENDPOINT_APIS_URL['SWAP'],
+ # json=validated_data
+ # )
+ # response_data = response.json()
+
+ result = response_data['swapTransaction']
+ try:
+ response_data['swapTransaction']
+ return response_data['swapTransaction']
+ except:
+ raise Exception(response_data['error'])
+ # Return the serialized transaction
+ return result
+
+
async def follow_move(self,move):
try:
your_balances = await DEX.get_wallet_balances(YOUR_WALLET, doGetTokenName=False)
@@ -756,101 +863,27 @@ class SolanaAPI:
if self.pk is None:
self.pk = await get_pk()
- for retry in range(1):
+ for retry in range(2):
try:
private_key = Keypair.from_bytes(base58.b58decode(self.pk))
async_client = AsyncClient(SOLANA_WS_URL)
- jupiter = Jupiter(async_client, private_key)
- transaction_data = await jupiter.swap(
+ jupiter = Jupiter(async_client, private_key)
+
+ # https://station.jup.ag/api-v6/post-swap
+ #transaction_data = await jupiter.swap(
+ transaction_data = await self.swap_on_jupiter(
input_mint=move['token_in'],
output_mint=move['token_out'],
amount=amount_lamports,
slippage_bps=300, # Increased to 3%
+ #priority_fee= 100_000 commented for auto
)
logging.info(f"Initiating move. Transaction data:\n {transaction_data}")
raw_transaction = VersionedTransaction.from_bytes(base64.b64decode(transaction_data))
- fee = await async_client.get_fee_for_message(raw_transaction.message)
- priority_fee = fee.value or 100_000
-
-
- # fee = await async_client.get_fee_for_message(transaction_data)
- # priority_fee = 0
- # if PRIORITY:
- # priority_fee = 100 * PRIORITY # defalt if we can't get current rate
- # try:
- # priority_fee = await calculate_priority_fee(async_client, PRIORITY)
- # except:
- # logging.warning(f"Failed to get priority fee. Using default value: {priority_fee}")
-
- # error_logger.info(f"Initiating move. Transaction data:\n {transaction_data}")
- # message = raw_transaction.message
-
- # Add compute budget instruction to set priority fee
- # from solders.compute_budget import set_compute_unit_price
- # compute_budget_instruction = set_compute_unit_price(priority_fee)
-
- # Add compute budget instruction to the transaction
- # raw_transaction.message.add_instruction(compute_budget_instruction)
-
- # Create new instructions list with compute budget instruction first
- # new_instructions = [compute_budget_instruction] + list(raw_transaction.message.instructions)
-
- # # Create a new message with the updated instructions
- # from solders.message import MessageV0
- # new_message = MessageV0(
- # instructions=new_instructions,
- # address_table_lookups=raw_transaction.message.address_table_lookups,
- # recent_blockhash=raw_transaction.message.recent_blockhash,
- # payer=raw_transaction.message.payer
- # )
-
# working - no priority fee
signature = private_key.sign_message(message.to_bytes_versioned(raw_transaction.message))
signed_txn = VersionedTransaction.populate(raw_transaction.message, [signature])
- # # # # # # # # # # # # # # # # # #
- # new - not working
- # signature = private_key.sign_message(new_message.to_bytes_versioned())
- # signed_txn = VersionedTransaction.populate(new_message, [signature])
- # from solders.compute_budget import set_compute_unit_price, ID as COMPUTE_BUDGET_ID
-
- # priority_fee_ix = set_compute_unit_price(priority_fee)
-
- # # Get the current message
- # msg = raw_transaction.message
-
- # new_account_keys = msg.account_keys
- # program_id_index = 0
- # if COMPUTE_BUDGET_ID not in msg.account_keys:
- # new_account_keys = msg.account_keys + [COMPUTE_BUDGET_ID]
- # program_id_index = len(msg.account_keys) # Index of the newly added program ID
- # else:
- # new_account_keys = msg.account_keys
- # program_id_index = msg.account_keys.index(COMPUTE_BUDGET_ID)
-
- # # Compile the priority fee instruction
- # compiled_priority_fee_ix = CompiledInstruction(
- # program_id_index=program_id_index,
- # accounts=bytes([]),
- # data=priority_fee_ix.data
- # )
-
- # # Add priority fee instruction at the beginning
- # new_instructions = [compiled_priority_fee_ix] + msg.instructions
-
- # # Create new message with updated instructions
- # new_message = Message.new_with_compiled_instructions(
- # num_required_signatures=msg.header.num_required_signatures,
- # num_readonly_signed_accounts=msg.header.num_readonly_signed_accounts,
- # num_readonly_unsigned_accounts=msg.header.num_readonly_unsigned_accounts,
- # account_keys=new_account_keys,
- # recent_blockhash=msg.recent_blockhash,
- # instructions=new_instructions
- # )
-
- # signature = private_key.sign_message(message.to_bytes_versioned(new_message))
- # signed_txn = VersionedTransaction.populate(new_message, [signature])
- # # # # # # # # # # # # # # # # # #
opts = TxOpts(
skip_preflight=False,
@@ -861,7 +894,7 @@ class SolanaAPI:
result = await async_client.send_raw_transaction(txn=bytes(signed_txn), opts=opts)
transaction_id = json.loads(result.to_json())['result']
- notification = f"Follow Transaction Sent:\nTransaction: swapping {amount_to_swap:.2f} {token_name_in}"
+ notification = f"Follow {move.get('type', 'SWAP').upper()} Success:\nTransaction: swapping {amount_to_swap:.2f} {token_name_in}"
logging.info(notification)
await telegram_utils.send_telegram_message(notification)
tx_details = await SAPI.get_transaction_details_with_retry(transaction_id, retry_delay=5, max_retries=10, backoff=False)
@@ -879,7 +912,7 @@ class SolanaAPI:
# error_logger.error(error_message)
# error_logger.exception(e)
await telegram_utils.send_telegram_message(error_message)
- amount = int(amount * 0.9)
+ amount_to_swap = int(amount_to_swap * 0.9)
await DEX.get_wallet_balances(YOUR_WALLET, doGetTokenName=False)
@@ -922,19 +955,20 @@ class SolanaAPI:
except Exception as e:
logging.error(f"Error following move: {e}")
- async def calculate_priority_fee(async_client, priority_level=5):
+async def calculate_priority_fee(self, async_client, priority_level=5):
+ try:
recent_fees = await async_client.get_recent_prioritization_fees()
- if not recent_fees:
- return 1000 # fallback value in microlamports
+ if not recent_fees or len(recent_fees) == 0:
+ return 100_000 # fallback value in microlamports
# Calculate average and max fees
- fees = [fee.prioritization_fee for fee in recent_fees]
+ fees = [slot_fee.prioritization_fee for slot_fee in recent_fees]
avg_fee = sum(fees) / len(fees)
max_fee = max(fees)
# Calculate base fee (weighted average between mean and max)
- base_fee = (2 * avg_fee + max_fee) / 3 # You can adjust this weighting
+ base_fee = (2 * avg_fee + max_fee) / 3
# Calculate scaling factor (priority_level / 5)
# priority 5 = 1x base_fee
@@ -945,8 +979,11 @@ class SolanaAPI:
final_fee = int(base_fee * scaling_factor)
# Set minimum fee to avoid too low values
- return max(final_fee, 100) # minimum 100 microlamports
-
+ return max(final_fee, 100_001) # minimum 100,000 microlamports
+
+ except Exception as e:
+ logging.warning(f"Error calculating priority fee: {str(e)}")
+ return 100_000 # fallback value in microlamports
class SolanaDEX:
def __init__(self, DISPLAY_CURRENCY: str):
@@ -1269,4 +1306,4 @@ class SolanaDEX:
DEX = SolanaDEX(DISPLAY_CURRENCY)
-SAPI = SolanaAPI( on_initial_subscription_callback=DEX.list_initial_wallet_states(FOLLOWED_WALLET,YOUR_WALLET))
\ No newline at end of file
+SAPI = SolanaAPI( on_initial_subscription_callback=DEX.list_initial_wallet_states(FOLLOWED_WALLET,YOUR_WALLET))
diff --git a/crypto/sol/modules/webui.py b/crypto/sol/modules/webui.py
index 6dce7e2..01b1c05 100644
--- a/crypto/sol/modules/webui.py
+++ b/crypto/sol/modules/webui.py
@@ -10,7 +10,7 @@ from flask_login import LoginManager, UserMixin, login_user, login_required, log
import secrets
import json
# from crypto.sol.config import LIQUIDITY_TOKENS
-from config import LIQUIDITY_TOKENS
+from config import LIQUIDITY_TOKENS, YOUR_WALLET
from modules import storage, utils, SolanaAPI
from modules.utils import async_safe_call, decode_instruction_data
@@ -24,6 +24,8 @@ def init_app(tr_handler=None):
global on_transaction
on_transaction = tr_handler
app = Flask(__name__, template_folder='../templates', static_folder='../static')
+
+ app.secret_key = secrets.token_hex(16)
executor = ThreadPoolExecutor(max_workers=10) # Adjust the number of workers as needed
login_manager = LoginManager(app)
login_manager.login_view = 'login'
@@ -423,6 +425,11 @@ def init_app(tr_handler=None):
def index():
return render_template('index.html')
+ @login_manager.unauthorized_handler
+ def unauthorized():
+ return redirect('/login?next=' + request.path)
+ # return jsonify({'error': 'Unauthorized'}), 401
+
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
@@ -460,12 +467,14 @@ def init_app(tr_handler=None):
@app.route('/wallet//transactions', methods=['GET'])
@login_required
+ @login_required
def get_transactions(wallet_id):
transactions = storage.get_transactions(wallet_id)
return jsonify(transactions)
@app.route('/wallet//holdings', methods=['GET'])
@login_required
+ @login_required
def get_holdings(wallet_id):
holdings = storage.get_holdings(wallet_id)
return jsonify(holdings)