From 7bb73ae3b05d2db0a9b0542236b380998da7318d Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Tue, 8 Oct 2024 17:40:33 +0300 Subject: [PATCH] token info name and cache --- crypto/sol/app.py | 176 ++++++++++++++++++++++++++----------------- crypto/sol/readme.md | 7 ++ 2 files changed, 114 insertions(+), 69 deletions(-) diff --git a/crypto/sol/app.py b/crypto/sol/app.py index 645c76e..416876b 100644 --- a/crypto/sol/app.py +++ b/crypto/sol/app.py @@ -153,7 +153,11 @@ TOKEN_ADDRESSES = { } TOKENS_INFO = {} - +try: + with open('./logs/token_info.json', 'r') as f: + TOKENS_INFO = json.load(f) +except Exception as e: + logging.error(f"Error loading token info: {str(e)}") # # # # # # # # # # TELEGRAM # # # # # # # # # # async def send_telegram_message(message): @@ -329,11 +333,11 @@ async def get_token_name(mint_address): TOKENS_INFO[mint_address]['decimals'] = account_data_info['decimals'] else: TOKENS_INFO[mint_address] = {'decimals': account_data_info['decimals']} - token_address = Pubkey.from_string(mint_address) - # token = Token(solana_client, token_address, TOKEN_PROGRAM_ID, Keypair()) - token = AsyncToken(solana_client, token_address, TOKEN_PROGRAM_ID, Keypair()) - tokenInfo = await token.get_mint_info() - token_info = await solana_client.get_account_info_json_parsed(Pubkey.from_string(mint_address)) + # token_address = Pubkey.from_string(mint_address) + # # token = Token(solana_client, token_address, TOKEN_PROGRAM_ID, Keypair()) + # token = AsyncToken(solana_client, token_address, TOKEN_PROGRAM_ID, Keypair()) + # tokenInfo = await token.get_mint_info() + # token_info = await solana_client.get_account_info_json_parsed(Pubkey.from_string(mint_address)) # if token_info.value and 'symbol' in token_info.value: # return token_info.value['symbol'] metadata = await get_token_metadata(mint_address) @@ -452,7 +456,7 @@ async def get_token_metadata(mint_address): async def get_wallet_balances(wallet_address): balances = {} logging.info(f"Getting balances for wallet: {wallet_address}") - + global TOKENS_INFO try: response = await solana_client.get_token_accounts_by_owner_json_parsed( Pubkey.from_string(wallet_address), @@ -464,25 +468,37 @@ async def get_wallet_balances(wallet_address): if response.value: for account in response.value: - parsed_data = account.account.data.parsed - if isinstance(parsed_data, dict) and 'info' in parsed_data: - info = parsed_data['info'] - if isinstance(info, dict) and 'mint' in info and 'tokenAmount' in info: - mint = info['mint'] - #amount = float(info['tokenAmount']['amount']) / (10 ** info['tokenAmount']['decimals']) - amount = float(info['tokenAmount']['amount'])/10**info['tokenAmount']['decimals'] - decimals = info['tokenAmount']['decimals'] - if amount > 0: - token_name = await get_token_name(mint) or 'Unknown' - balances[mint] = { - 'name': token_name, - 'address': mint, - 'amount': amount, - 'decimals': decimals - } + try: + parsed_data = account.account.data.parsed + if isinstance(parsed_data, dict) and 'info' in parsed_data: + info = parsed_data['info'] + if isinstance(info, dict) and 'mint' in info and 'tokenAmount' in info: + mint = info['mint'] + decimals = info['tokenAmount']['decimals'] + amount = float(info['tokenAmount']['amount'])/10**decimals + if amount > 0: + if mint in TOKENS_INFO: + token_name = TOKENS_INFO[mint].get('symbol') + else: + token_name = await get_token_name(mint) or 'N/A' + # sleep for 1 second to avoid rate limiting + await asyncio.sleep(2) + + + TOKENS_INFO[mint]['holdedAmount'] = amount + TOKENS_INFO[mint]['decimals'] = decimals + balances[mint] = { + 'name': token_name, + 'address': mint, + 'amount': amount, + 'decimals': decimals + } + # sleep for 1 second to avoid rate limiting logging.debug(f"Balance for {token_name} ({mint}): {amount}") - else: - logging.warning(f"Unexpected data format for account: {account}") + else: + logging.warning(f"Unexpected data format for account: {account}") + except Exception as e: + logging.error(f"Error parsing account data: {str(e)}") sol_balance = await solana_client.get_balance(Pubkey.from_string(wallet_address)) if sol_balance.value is not None: @@ -496,7 +512,7 @@ async def get_wallet_balances(wallet_address): except Exception as e: logging.error(f"Error getting wallet balances: {str(e)}") - + logging.info(f"Found {len(response.value)} ({len(balances)} non zero) token accounts for wallet: {wallet_address}") return balances async def convert_balances_to_currency(balances , sol_price): @@ -735,6 +751,7 @@ async def solana_jsonrpc(method, params = None, jsonParsed = True): async def list_initial_wallet_states(): global TOKEN_ADDRESSES, FOLLOWED_WALLET_VALUE, YOUR_WALLET_VALUE, TOKEN_PRICES + global TOKENS_INFO # new followed_wallet_balances = await get_wallet_balances(FOLLOWED_WALLET) your_wallet_balances = await get_wallet_balances(YOUR_WALLET) @@ -745,7 +762,8 @@ async def list_initial_wallet_states(): followed_converted_balances = await convert_balances_to_currency(followed_wallet_balances, sol_price) your_converted_balances = await convert_balances_to_currency(your_wallet_balances, sol_price) - + + TOKEN_ADDRESSES = { address: info for address, info in {**followed_converted_balances, **your_converted_balances}.items() if info['value'] is not None and info['value'] > 0 @@ -756,14 +774,14 @@ async def list_initial_wallet_states(): FOLLOWED_WALLET_VALUE = 0 for address, info in followed_converted_balances.items(): if info['value'] is not None and info['value'] > 0: - followed_wallet_state.append(f"{info['name']} ({address}): {info['value']:.2f} {DISPLAY_CURRENCY}") + followed_wallet_state.append(f"{info['name']}: {info['value']:.2f} {DISPLAY_CURRENCY}") FOLLOWED_WALLET_VALUE += info['value'] your_wallet_state = [] YOUR_WALLET_VALUE = 0 for address, info in your_converted_balances.items(): if info['value'] is not None and info['value'] > 0: - your_wallet_state.append(f"{info['name']} ({address}): {info['value']:.2f} {DISPLAY_CURRENCY}") + your_wallet_state.append(f"{info['name']}: {info['value']:.2f} {DISPLAY_CURRENCY}") YOUR_WALLET_VALUE += info['value'] message = ( @@ -780,8 +798,13 @@ async def list_initial_wallet_states(): logging.info(message) await send_telegram_message(message) + # save token info to file + with open('./logs/token_info.json', 'w') as f: + json.dump(TOKENS_INFO, f, indent=2) -async def get_transaction_details_with_retry(transaction_id, retry_delay = 10, max_retries = 10): + + +async def get_transaction_details_with_retry(transaction_id, retry_delay = 5, max_retries = 20): # wait for the transaction to be confirmed # await async_client.wait_for_confirmation(Signature.from_string(transaction_id)) # qwery every 5 seconds for the transaction details untill not None or 30 seconds @@ -877,27 +900,37 @@ async def process_log(log_result): # GET DETAILS FROM TRANSACTION IF NOT FOUND IN LOGS - if tr_details["token_in"] is None or tr_details["token_out"] is None or tr_details["amount_in"] == 0 or tr_details["amount_out"] == 0: - logging.warning("Incomplete swap details found in logs. Getting details from transaction") - tr_details = await get_transaction_details_info(tx_signature_str, logs) - # onlt needed if no details got - if before_source_balance > 0 and source_token_change > 0: - tr_details["percentage_swapped"] = (source_token_change / before_source_balance) * 100 - #dirty fix for percentage > 100 (decimals 9 but expecting 6) - if tr_details["percentage_swapped"] > 100: - tr_details["percentage_swapped"] = tr_details["percentage_swapped"] / 1000 + try: + if tr_details["token_in"] is None or tr_details["token_out"] is None or tr_details["amount_in"] == 0 or tr_details["amount_out"] == 0: + logging.warning("Incomplete swap details found in logs. Getting details from transaction") + tr_details = await get_transaction_details_info(tx_signature_str, logs) + # onlt needed if no details got + if before_source_balance > 0 and source_token_change > 0: + tr_details["percentage_swapped"] = (source_token_change / before_source_balance) * 100 + #dirty fix for percentage > 100 (decimals 9 but expecting 6) + if tr_details["percentage_swapped"] > 100: + tr_details["percentage_swapped"] = tr_details["percentage_swapped"] / 1000 - - message_text = ( - f"Swap detected:\n" - f"Token In: {tr_details['token_in']}\n" - f"Token Out: {tr_details['token_out']}\n" - f"Amount In USD: {tr_details['amount_in_USD']}\n" - f"Percentage Swapped: {tr_details['percentage_swapped']:.2f}%" - ) - - await send_telegram_message(message_text) - await follow_move(tr_details) + try: + tr_details["symbol_in"] = TOKENS_INFO[tr_details["token_in"]].get('symbol') or await get_token_name(tr_details["token_in"]) + tr_details["symbol_out"] = TOKENS_INFO[tr_details["token_out"]].get('symbol') or await get_token_name(tr_details["token_out"]) + tr_details['amount_in_USD'] = tr_details['amount_in'] * TOKEN_PRICES.get(tr_details['token_in'], 0) + tr_details['amount_out_USD'] = tr_details['amount_out'] * TOKEN_PRICES.get(tr_details['token_out'], 0) + except Exception as e: + logging.error(f"Error fetching token prices: {e}") + + message_text = ( + f"Swap detected:\n" + f"Token In: {tr_details['symbol_in']}\n" + f"Token Out: {tr_details['symbol_out']}\n" + f"Amount In USD: {tr_details['amount_out_USD']:.2f}\n" + f"Percentage Swapped: {tr_details['percentage_swapped']:.2f}%" + ) + await send_telegram_message(message_text) + await follow_move(tr_details) + except Exception as e: + logging.error(f"Error aquiring log details and following: {e}") + return except Exception as e: logging.error(f"Error processing log: {e}") @@ -923,6 +956,12 @@ async def get_transaction_details_info(tx_signature_str: str, logs: List[str]) - # Fetch token prices token_prices = await get_token_prices([tr_info['token_in'], tr_info['token_out']]) + for token, price in token_prices.items(): + if price is None: + if token in TOKENS_INFO: + TOKENS_INFO[token]['price'] = price + else: + TOKENS_INFO[token] = {'price': price} # Calculate USD values tr_info['amount_in_usd'] = tr_info['amount_in'] * token_prices.get(tr_info['token_in'], 0) @@ -950,8 +989,9 @@ async def follow_move(move): await send_telegram_message(msg) return - your_balance = your_balance_info['amount'] - token_name = your_balance_info['name'] + your_balance = TOKENS_INFO[move['token_in']].get('holdedAmount') or your_balance_info['amount'] + token_name_in = TOKENS_INFO[move['token_in']].get('symbol') or await get_token_name(move['token_in']) + token_name_out = TOKENS_INFO[move['token_out']].get('symbol') or await get_token_name(move['token_out']) # move["percentage_swapped"] = (move["amount_out"] / move["amount_in"]) * 100 # Calculate the amount to swap based on the same percentage as the followed move @@ -968,12 +1008,22 @@ async def follow_move(move): # Convert to lamports # if decimals is 6, then amount = amount * 1e6; if 9, then amount = amount * 1e9 amount = int(amount_to_swap * 10**your_balance_info['decimals']) + transaction_data = await jupiter.swap( input_mint=move['token_in'], output_mint=move['token_out'], amount=amount, slippage_bps=100, # Increased to 1% ) + + notification = ( + f"Initiating move:\n (decimals: {your_balance_info['decimals']})\n" + f"Swapping {move['percentage_swapped']:.2f}% ({amount_to_swap:.2f}) {token_name_in} for {token_name_out}" + ) + logging.info(notification) + error_logger.info(notification) + await send_telegram_message(notification) + raw_transaction = VersionedTransaction.from_bytes(base64.b64decode(transaction_data)) signature = private_key.sign_message(message.to_bytes_versioned(raw_transaction.message)) signed_txn = VersionedTransaction.populate(raw_transaction.message, [signature]) @@ -983,35 +1033,23 @@ async def follow_move(move): transaction_id = json.loads(result.to_json())['result'] print(f"Follow Transaction Sent: https://solscan.io/tx/{transaction_id}") - notification = ( - f"Move Initiated:\n (decimals: {your_balance_info['decimals']})\n" - f"Swapping {move['percentage_swapped']:.2f}% ({amount_to_swap}) {token_name} ({move['token_in']}) " - f"for {move['token_out']}" - f"\n\nTransaction: {transaction_id}" - ) - logging.info(notification) - await send_telegram_message(notification) - tx_details = await get_transaction_details_with_retry(transaction_id) if tx_details is None: logging.info(f"Failed to get transaction details for {transaction_id}") notification = ( f"Move Followed:\n" - f"Swapped {amount_to_swap:.6f} {token_name} ({move['token_in']}) " + f"Swapped {amount_to_swap:.6f} {token_name_in} ({move['token_in']}) " f"(same {move['percentage_swapped']:.2f}% as followed wallet)\n" f"\n\nTransaction: {transaction_id}" ) else: - output_token_info = your_balances.get(move['token_out'], {'name': 'Unknown'}) - output_token_name = output_token_info['name'] - notification = ( f"Move Followed:\n" - f"Swapped {amount_to_swap:.6f} {token_name} ({move['token_in']}) " + f"Swapped {amount_to_swap:.6f} {token_name_in} ({move['symbol_in']}) " f"(same {move['percentage_swapped']:.2f}% as followed wallet)\n" - f"for {tx_details['amount_out']:.6f} {output_token_name} ({move['token_out']})" + f"for {tx_details['amount_out']:.6f} {token_name_out} ({move['symbol_out']})" # f"Amount In USD: {tr_details['amount_in_USD']}\n" f"\n\nTransaction: {transaction_id}" ) @@ -1028,7 +1066,7 @@ async def follow_move(move): else: msg = ( f"Move Not Followed:\n" - f"Insufficient balance to swap {amount_to_swap:.6f} {token_name} ({move['token_in']})" + f"Insufficient balance to swap {amount_to_swap:.6f} {token_name_in} ({move['token_in']})" ) logging.warning(msg) await send_telegram_message(msg) @@ -1121,7 +1159,7 @@ async def subscribe_to_wallet(): async def main(): # Initialize logging await send_telegram_message("Solana Agent Started. Connecting to mainnet...") - await list_initial_wallet_states() + asyncio.create_task( list_initial_wallet_states()) await subscribe_to_wallet() def run_flask(): diff --git a/crypto/sol/readme.md b/crypto/sol/readme.md index 6afaabf..0da5bee 100644 --- a/crypto/sol/readme.md +++ b/crypto/sol/readme.md @@ -33,3 +33,10 @@ pip-compile requirements.in # pip-sync requirements.txt echo "Requirements have been updated in requirements.txt" + + + +max ammount on FATGF gives error: + ERROR:error_logger:Swap Follow Error: +Program log: Instruction: Transfer", "Program log: Error: insufficient funds", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4300 of 1363889 compute units" +