diff --git a/crypto/sol/.env b/crypto/sol/.env index 1e5dfd2..ce15948 100644 --- a/crypto/sol/.env +++ b/crypto/sol/.env @@ -24,7 +24,7 @@ TELEGRAM_BOT_TOKEN="6749075936:AAHUHiPTDEIu6JH7S2fQdibwsu6JVG3FNG0" DISPLAY_CURRENCY=USD #FOLLOW_AMOUNT=3 # proportional, xx%, -FOLLOW_AMOUNT=5% +FOLLOW_AMOUNT=2% LIQUIDITY_TOKENS=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v,So11111111111111111111111111111111111111112 diff --git a/crypto/sol/modules/SolanaAPI.py b/crypto/sol/modules/SolanaAPI.py index 8b4a95d..df29b45 100644 --- a/crypto/sol/modules/SolanaAPI.py +++ b/crypto/sol/modules/SolanaAPI.py @@ -14,17 +14,18 @@ from jupiter_python_sdk.jupiter import Jupiter, Jupiter_DCA from dexscreener import DexscreenerClient from solana.rpc.types import TokenAccountOpts, TxOpts from solana.rpc.async_api import AsyncClient -from solana.transaction import Signature from solana.rpc.websocket_api import connect from solana.rpc.commitment import Confirmed, Processed -from solana.transaction import Transaction +from solana.rpc.async_api import AsyncClient +from solana.rpc.types import TxOpts +from solana.rpc.commitment import Confirmed, Finalized, Processed +from solana.rpc.core import RPCException + +from solana.transaction import Signature, Transaction from spl.token.client import Token from base64 import b64decode import base58 from threading import Thread -from solana.rpc.async_api import AsyncClient -from solana.rpc.types import TxOpts -from solana.rpc.commitment import Confirmed, Finalized, Processed from solders.rpc.requests import GetTransaction @@ -304,7 +305,9 @@ class SolanaAPI: finally: receive_task.cancel() process_task.cancel() - + except asyncio.CancelledError: + logging.info("Websocket connection cancelled, attempting to reconnect...") + await asyncio.sleep(5) # Wait before reconnecting except Exception as e: logger.error(f"An unexpected error occurred: {e}") logger.error("".join(traceback.format_exception(None, e, e.__traceback__))) @@ -361,7 +364,7 @@ class SolanaAPI: await asyncio.sleep(1) async def get_token_metadata_symbol(self, mint_address): - if mint_address in DEX.TOKENS_INFO and 'symbol' in DEX.TOKENS_INFO[mint_address]: + if mint_address in DEX.TOKENS_INFO and DEX.TOKENS_INFO[mint_address] is not None: return DEX.TOKENS_INFO[mint_address].get('symbol') try: @@ -370,6 +373,8 @@ class SolanaAPI: account_data_data = account_data_result['value']['data'] if 'parsed' in account_data_data and 'info' in account_data_data['parsed']: account_data_info = account_data_data['parsed']['info'] + if DEX.TOKENS_INFO[mint_address] is None: + DEX.TOKENS_INFO[mint_address] = {} if 'decimals' in account_data_info: if mint_address in DEX.TOKENS_INFO: DEX.TOKENS_INFO[mint_address]['decimals'] = account_data_info['decimals'] @@ -780,10 +785,10 @@ class SolanaAPI: # ) # response_data = response.json() - result = response_data['swapTransaction'] + try: - response_data['swapTransaction'] - return response_data['swapTransaction'] + result = response_data['swapTransaction'] + return (response_data['swapTransaction'], response_data['lastValidBlockHeight']) except: raise Exception(response_data['error']) # Return the serialized transaction @@ -793,15 +798,16 @@ class SolanaAPI: async def follow_move(self,move): try: try: + your_balance = 0 your_balances = await DEX.get_wallet_balances(YOUR_WALLET, doGetTokenName=False) your_balance_info = next((balance for balance in your_balances.values() if balance['address'] == move['token_in']), None) - if your_balance_info is not None: + if your_balance_info is not None and 'amount' in your_balance_info and float(your_balance_info['amount']) > 0: # Use the balance print(f"Your balance: {your_balance_info['amount']} {move['symbol_in']}") - # else: - # print(f"No ballance found for {move['symbol_in']}. Skipping move.") - # await telegram_utils.send_telegram_message(f"No ballance found for {move['symbol_in']}. Skipping move.") - # return + else: + print(f"No ballance found for {move['symbol_in']}. Skipping move.") + await telegram_utils.send_telegram_message(f"No ballance found for {move['symbol_in']}. Skipping move.") + return your_balance = your_balance_info['amount'] @@ -812,7 +818,7 @@ class SolanaAPI: return except Exception as e: - logging.error(f"Error fetching your balance: {e}") + logging.error(f"Error fetching your balance: {str(e)}", exc_info=True) if FOLLOW_AMOUNT == 'proportional': return @@ -898,7 +904,7 @@ class SolanaAPI: # https://station.jup.ag/api-v6/post-swap #transaction_data = await jupiter.swap( - transaction_data = await self.swap_on_jupiter( + (transaction_data, block) = await self.swap_on_jupiter( input_mint=move['token_in'], output_mint=move['token_out'], amount=amount_lamports, @@ -933,6 +939,32 @@ class SolanaAPI: else: logging.warning(f"Failed to get transaction details for {transaction_id}.\n Probably transaction failed. Retrying again...") await asyncio.sleep(3) + except RPCException as rpce: + # Convert to string and parse for key information + error_str = str(rpce) + + # Get the main error message + if "Error processing Instruction" in error_str: + # Check for common errors + if "insufficient funds" in error_str: + logging.error("Transaction failed: Insufficient funds") + elif "custom program error: 0x28" in error_str: + logging.error("Transaction failed: Custom program error 40 (0x28) - Likely insufficient funds") + + # Parse logs if available + try: + logs = rpce.data.result.logs + if logs: + # Get the last error message from logs + error_logs = [log for log in logs if "Error:" in log] + if error_logs: + logging.error(f"Program error: {error_logs[-1]}") + except AttributeError: + pass + + # Full debug logging + logging.debug(f"Full RPC error: {error_str}") + except Exception as e: # decode transacion data (try base58/64) # decoded_data = base58.b58decode(transaction_data).decode('utf-8') @@ -1070,7 +1102,8 @@ class SolanaDEX: if 'symbol' not in token_info: token_info['symbol'] = await SAPI.get_token_metadata_symbol(token) token_info['price'] = price - token_info['lastUpdated'] = datetime.now().isoformat() + token_info['lastUpdated'] = datetime.now().isoformat() + token_info['address'] = token return prices @@ -1225,6 +1258,9 @@ class SolanaDEX: ) if response.value: + + if self.TOKENS_INFO is None: + self.TOKENS_INFO = {} for account in response.value: try: parsed_data = account.account.data.parsed @@ -1237,7 +1273,7 @@ class SolanaDEX: # amount = float(info['tokenAmount']['amount']) # amount = float(amount / 10**decimals) token_name = None - if mint in self.TOKENS_INFO: + if mint in self.TOKENS_INFO and isinstance(self.TOKENS_INFO[mint], dict): token_name = self.TOKENS_INFO[mint].get('symbol') elif doGetTokenName: token_name = await SAPI.get_token_metadata_symbol(mint) @@ -1252,27 +1288,31 @@ class SolanaDEX: self.TOKENS_INFO[mint] = self.TOKENS_INFO[mint].update(balances[mint]) try: - logging.debug(f"Account balance for {token_name or "N/A"} ({mint}): {amount}") + logging.debug(f"Account balance for {token_name or 'N/A'} ({mint}): {amount}") except Exception as e: - logging.error(f"Error logging account balance: {str(e)}") + logging.error(f"Error logging account balance: {str(e)}", exc_info=True) else: logging.warning(f"Unexpected data format for account: {account}") except Exception as e: - logging.error(f"Error parsing account data: {str(e)}") + logging.error(f"Error parsing account data: {str(e)}", exc_info=True) self.TOKENS_INFO["lastUpdated"] = datetime.now().isoformat() - # sol_balance = await self.solana_client.get_balance(Pubkey.from_string(wallet_address)) - # if sol_balance.value is not None: - # balances['SOL'] = { - # 'name': 'SOL', - # 'address': 'SOL', - # 'amount': sol_balance.value / 1e9 - # } - # else: - # logging.warning(f"SOL balance response missing for wallet: {wallet_address}") + sol_balance = await self.solana_client.get_balance(Pubkey.from_string(wallet_address)) + if sol_balance.value is not None: + mint = 'So11111111111111111111111111111111111111112' + balances[mint] = { + 'name': 'SOL', + 'address': mint, + 'amount': sol_balance.value / 1e9, + 'decimals': 9 + } + self.TOKENS_INFO[mint] = {'symbol': token_name} + self.TOKENS_INFO[mint] = self.TOKENS_INFO[mint].update(balances[mint]) + else: + logging.warning(f"SOL balance response missing for wallet: {wallet_address}") except Exception as e: - logging.error(f"Error getting wallet balances: {str(e)} {e.error_msg}") + logging.error(f"Error getting wallet balances: {str(e)}", exc_info=True) if response and response.value: logging.info(f"Found {len(response.value)} ({len(balances)} non zero) token accounts for wallet: {wallet_address}") else: diff --git a/crypto/sol/modules/webui.py b/crypto/sol/modules/webui.py index 20aca8c..e58ca3e 100644 --- a/crypto/sol/modules/webui.py +++ b/crypto/sol/modules/webui.py @@ -335,13 +335,15 @@ def init_app(tr_handler=None): tr["token_out"] ) # ToDo - optimize - # prices = await SolanaAPI.DEX.get_token_prices( - # [tr["token_in"], tr["token_out"]] - # ) - # tr["token_in_price"] = prices.get(tr["token_in"], 0) - # tr["token_out_price"] = prices.get(tr["token_out"], 0) - # tr["value_in_USD"] = prices.get(tr["token_in"], 0) * tr["amount_in"] - # tr["value_out_USD"] = prices.get(tr["token_out"], 0) * tr["amount_out"] + prices = await SolanaAPI.DEX.get_token_prices( + [tr["token_in"], tr["token_out"]] + ) + await SolanaAPI.DEX.save_token_info() + + tr["token_in_price"] = prices.get(tr["token_in"], 0) + tr["token_out_price"] = prices.get(tr["token_out"], 0) + tr["value_in_USD"] = prices.get(tr["token_in"], 0) * tr["amount_in"] + tr["value_out_USD"] = prices.get(tr["token_out"], 0) * tr["amount_out"] # notification = f"Got WH notification:: {tr['amount_in']} {tr['symbol_in'] or tr["token_in"]} swapped for {tr['amount_out']} {tr['symbol_out']} ${tr['value_out_USD']}\n" # logging.info(notification)