diff --git a/crypto/sol/app.py b/crypto/sol/app.py index a577157..04e96f5 100644 --- a/crypto/sol/app.py +++ b/crypto/sol/app.py @@ -7,12 +7,16 @@ from solana.transaction import Signature from solana.rpc.websocket_api import connect from solana.rpc.types import TokenAccountOpts, TxOpts from solana.rpc.commitment import Confirmed +from base58 import b58decode +from solders.signature import Signature from solders.pubkey import Pubkey from solders.keypair import Keypair +from solders.transaction import VersionedTransaction from solders.transaction import Transaction from solders.message import Message from solders.instruction import Instruction from solders.hash import Hash +from jupiter_python_sdk.jupiter import Jupiter, Jupiter_DCA from dexscreener import DexscreenerClient from telegram import Bot from telegram.constants import ParseMode @@ -25,7 +29,7 @@ import aiohttp from typing import List, Dict import requests import threading - +import re load_dotenv() app = Flask(__name__) @@ -245,11 +249,6 @@ async def get_token_balance(wallet_address, token_address): commitment=Confirmed ) - - # response = await solana_client.get_token_accounts_by_owner( - # Pubkey.from_string(wallet_address), - # {"mint": Pubkey.from_string(token_address)} - # ) if response['result']['value']: balance = await solana_client.get_token_account_balance( response['result']['value'][0]['pubkey'] @@ -367,56 +366,9 @@ async def get_wallet_balances(wallet_address): return balances, token_addresses -async def get_converted_balances(wallet_address): - balances, token_addresses = await get_wallet_balances(wallet_address) - token_prices = await get_token_prices(token_addresses) - sol_price = await get_sol_price() - converted_balances = await convert_balances_to_currency(balances, token_prices, sol_price) - return converted_balances -async def send_initial_wallet_states(followed_wallet, your_wallet): - followed_balances = await get_converted_balances(followed_wallet) - your_balances = await get_converted_balances(your_wallet) - - message = f"Initial Wallet States (Non-zero balances in {DISPLAY_CURRENCY}):\n\n" - message += f"Followed Wallet ({followed_wallet}):\n" - for token, amount in followed_balances.items(): - if amount and amount > 0: - message += f"{token}: {amount:.2f}\n" - - message += f"\nYour Wallet ({your_wallet}):\n" - for token, amount in your_balances.items(): - if amount and amount > 0: - message += f"{token}: {amount:.2f}\n" - - message += "\nMonitored Tokens:\n" - # Add monitored tokens logic here if needed - - await bot.send_message(chat_id=CHAT_ID, text=message) - - - -async def get_non_zero_token_balances(wallet_address): - - followed_wallet_balances, followed_token_addresses = await get_wallet_balances(FOLLOWED_WALLET) - # return non zero balances for followed wallet - non_zero_balances = {token: address for token, address in {**followed_wallet_balances}.items() if address is not None and address > 0} - logging.info(f"Getting non-zero balances for wallet: {wallet_address}") - return non_zero_balances - - - # non_zero_balances = {} - # logging.info(f"Getting non-zero balances for wallet: {wallet_address}") - # for token, address in TOKEN_ADDRESSES.items(): - # balance = await get_token_balance_rpc(wallet_address, address) - # if balance > 0: - # non_zero_balances[token] = address - # logging.debug(f"Non-zero balance for {token}: {balance}") - # return non_zero_balances - - async def list_initial_wallet_states(): global TOKEN_ADDRESSES, FOLLOWED_WALLET_VALUE, YOUR_WALLET_VALUE @@ -463,101 +415,9 @@ async def list_initial_wallet_states(): await send_telegram_message(message) -async def follow_move(move): - followed_balances = await get_wallet_balances(FOLLOWED_WALLET) - your_balances = await get_wallet_balances(YOUR_WALLET) - if move['token'] not in followed_balances or move['token'] not in your_balances: - logging.error(f"Invalid token: {move['token']}") - return - followed_balance = followed_balances[move['token']] - your_balance = your_balances[move['token']] - proportion = your_balance / followed_balance if followed_balance > 0 else 0 - amount_to_swap = move['amount'] * proportion - - if your_balance >= amount_to_swap: - # Perform the swap using Jupiter API - try: - swap_result = perform_swap(move['token'], move['to_token'], amount_to_swap) - - if swap_result['success']: - message = ( - f"Move Followed:\n" - f"Swapped {amount_to_swap:.6f} {move['token']} " - f"for {swap_result['outputAmount']:.6f} {move['to_token']}" - ) - logging.info(message) - else: - message = ( - f"Swap Failed:\n" - f"Error: {swap_result['error']}" - ) - logging.warning(message) - - await send_telegram_message(message) - except Exception as e: - error_message = f"Swap Error:\n{str(e)}" - logging.error(error_message) - await send_telegram_message(error_message) - else: - message = ( - f"Move Failed:\n" - f"Insufficient balance to swap {amount_to_swap:.6f} {move['token']}" - ) - logging.warning(message) - await send_telegram_message(message) - -def perform_swap(input_token, output_token, amount): - # Jupiter API endpoint - url = "https://quote-api.jup.ag/v4/quote" - - # Parameters for the API request - params = { - "inputMint": input_token, - "outputMint": output_token, - "amount": int(amount * 10**9), # Convert to lamports - "slippageBps": 50, # 0.5% slippage - } - - try: - response = requests.get(url, params=params) - response.raise_for_status() - quote = response.json() - - # Get the best route - route = quote['data'][0] - - # Perform the swap - swap_url = "https://quote-api.jup.ag/v4/swap" - swap_data = { - "quoteResponse": route, - "userPublicKey": YOUR_WALLET, - "wrapUnwrapSOL": True - } - - swap_response = requests.post(swap_url, json=swap_data) - swap_response.raise_for_status() - swap_result = swap_response.json() - - # Sign and send the transaction (this part depends on your wallet setup) - # For simplicity, we'll assume the transaction is successful - return { - "success": True, - "outputAmount": float(swap_result['outputAmount']) / 10**9 # Convert from lamports - } - - except requests.exceptions.RequestException as e: - return { - "success": False, - "error": str(e) - } - -from base58 import b58decode -from solders.pubkey import Pubkey -from solders.transaction import Transaction -from solders.signature import Signature async def get_transaction_details_rpc(tx_signature, readfromDump=False): url = SOLANA_HTTP_URL @@ -609,34 +469,6 @@ async def save_log(log): logging.error(f"Error saving RPC log: {e}") -def determine_token(pubkey, watched_tokens): - # Check if the pubkey matches any watched token addresses - for token, address in watched_tokens.items(): - if pubkey == address: - return token - return "Unknown" - -def parse_amount_from_logs(logs): - amount_in = 0 - amount_out = 0 - - for log in logs: - if 'SwapEvent' in log: - # Extract amounts from the log line - parts = log.split('amount_in: ')[1].split(', amount_out: ') - amount_in = int(parts[0]) - amount_out = int(parts[1].split(' ')[0]) - - return amount_in, amount_out - -def extract_swap_details(instruction, logs, watched_tokens): - # Extract source and target tokens along with amounts - from_pubkey = instruction['accounts'][0] - to_pubkey = instruction['accounts'][1] - amount_in, amount_out = parse_amount_from_logs(logs) - return from_pubkey, to_pubkey, amount_in, amount_out - - async def process_log(log_result): if log_result['value']['err']: @@ -651,69 +483,18 @@ async def process_log(log_result): for log_entry in logs: if any(op in log_entry for op in swap_operations): try: - - - watched_tokens = await get_non_zero_token_balances(FOLLOWED_WALLET) details = await parse_swap_logs(logs) - # transaction = await get_transaction_details_rpc(tx_signature_str, True) - - # tokens = [] - # source_token = None - # target_token = None - - # # Check inner instructions for transfers and mints - # for instruction_set in transaction.get('meta', {}).get('innerInstructions', []): - # for instruction in instruction_set.get('instructions', []): - # if 'parsed' in instruction and 'info' in instruction['parsed']: - # info = instruction['parsed']['info'] - # amount = None - # mint = 'Unknown' - - # # Check for amount in transfer and transferChecked instructions - # if 'amount' in info: - # amount = info['amount'] - # elif 'tokenAmount' in info and 'amount' in info['tokenAmount']: - # amount = info['tokenAmount']['amount'] - - # # Get mint if available - # if 'mint' in info: - # mint = info['mint'] - - # if amount is not None: - # tokens.append({'amount': amount, 'mint': mint}) - - # # Identify source and target tokens - # if 'source' in info: - # source_token = info['source'] - # if 'destination' in info: - # target_token = info['destination'] - - # # Check post token balances for final token states - # for balance in transaction.get('postTokenBalances', []): - # amount = balance['uiTokenAmount']['amount'] - # mint = balance['mint'] - # tokens.append({'amount': amount, 'mint': mint}) - - # Get amount_in, amount_out, tokens, and USD value - swap_details = { - 'amount_in': details['total_amount_in'], - 'amount_out': details['total_amount_out'], - 'tokens': tokens, - 'source_token': source_token, - 'target_token': target_token - } - + message_text = ( f"Swap detected:\n" - f"Amount In: {swap_details['amount_in']}\n" - f"Amount Out: {swap_details['amount_out']}\n" - f"Source Token: {swap_details['source_token']}\n" - f"Target Token: {swap_details['target_token']}\n" - f"Tokens: {tokens}" + f"Order ID: {details['order_id']}\n" + f"Token In: {details['token_in']}\n" + f"Token Out: {details['token_out']}\n" + f"Amount In USD: {details['amount_in_USD']}\n" ) await send_telegram_message(message_text) - await follow_move(swap_details) + await follow_move(details) except Exception as e: logging.error(f"Error fetching transaction details: {e}") @@ -724,59 +505,123 @@ async def process_log(log_result): - # "Program log: Instruction: Swap2", + # "Program log: Instruction: Swap2", # "Program log: order_id: 13985890735038016", # "Program log: AbrMJWfDVRZ2EWCQ1xSCpoVeVgZNpq1U2AoYG98oRXfn", source # "Program log: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", target - + # "Program log: before_source_balance: 58730110139, before_destination_balance: 202377778, amount_in: 58730110139, expect_amount_out: 270109505, min_return: 267408410", + # "Program log: after_source_balance: 0, after_destination_balance: 472509072", + # "Program log: source_token_change: 58730110139, destination_token_change: 270131294", async def parse_swap_logs(logs): - global TOKEN_ADDRESSES - token_in = None token_out = None amount_in = 0 amount_out_expected = 0 amount_out_actual = 0 + order_id = None for log in logs: + # Check for token identifiers if "Program log:" in log: - if "Swap2" in log: + if "order_id:" in log: + order_id = log.split("order_id: ")[-1].strip() + elif "Swap2" in log: token_in = None token_out = None - elif "order_id" in log: - order_id = log.split("order_id: ")[-1] - else: - if not token_in: - token_in = log.split("Program log: ")[-1].strip() - elif not token_out: - token_out = log.split("Program log: ")[-1].strip() + elif not token_in: + token_in = log.split("Program log: ")[-1].strip() + elif not token_out: + token_out = log.split("Program log: ")[-1].strip() - elif "source_token_change:" in log: + # Use regex to find amount_in and amount_out patterns + if "amount_in" in log or "amount_out" in log: + amount_matches = re.findall(r"(amount_in|amount_out): (\d+)", log) + for amount_type, value in amount_matches: + if amount_type == "amount_in": + amount_in = int(value)/1e6 + elif amount_type == "amount_out": + amount_out_expected = int(value)/1e6 + + # Check for source_token_change and destination_token_change for additional details + elif "source_token_change:" in log or "destination_token_change:" in log: changes = log.split(", ") for change in changes: if "source_token_change" in change: - amount_in = int(change.split(": ")[-1]) + amount_in = int(change.split(": ")[-1])/1e6 elif "destination_token_change" in change: - amount_out_expected = int(change.split(": ")[-1]) - - amount_out_actual = amount_out_expected # Modify if actual is derived separately - + amount_out_actual = int(change.split(": ")[-1])/1e6 token_prices = await get_token_prices([token_in, token_out]) amount_in_usd = amount_in * token_prices.get(token_in, 0) amount_out_usd = amount_out_actual * token_prices.get(token_out, 0) return { + "order_id": order_id, "token_in": token_in, "token_out": token_out, "amount_in": amount_in, "amount_out_expected": amount_out_expected, "amount_out_actual": amount_out_actual, - "amount_in_USD": amount_in_usd, + "amount_in_USD": amount_in_usd , "amount_out_USD": amount_out_usd, } +async def follow_move(move): + followed_balances = await get_wallet_balances(FOLLOWED_WALLET) + your_balances = await get_wallet_balances(YOUR_WALLET) + + followed_balance = followed_balances[move.token_in] + your_balance = your_balances[move.token_in] + + proportion = your_balance / followed_balance if followed_balance > 0 else 0 + amount_to_swap = move.amount_in * proportion + + if your_balance >= amount_to_swap: + try: + # Initialize Jupiter client + private_key = Keypair.from_bytes(base58.b58decode(os.getenv("PK"))) + async_client = AsyncClient(SOLANA_WS_URL) + jupiter = Jupiter(async_client, private_key) + + # Perform the swap + transaction_data = await jupiter.swap( + input_mint=move.token_in, + output_mint=move.token_out, + amount=int(amount_to_swap), + slippage_bps=1, + ) + + 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]) + opts = TxOpts(skip_preflight=False, preflight_commitment=Processed) + result = await async_client.send_raw_transaction(txn=bytes(signed_txn), opts=opts) + transaction_id = json.loads(result.to_json())['result'] + + message = ( + f"Move Followed:\n" + f"Swapped {amount_to_swap:.6f} {move['token']} " + f"for {transaction_data['outputAmount']:.6f} {move['to_token']}" + ) + logging.info(message) + await send_telegram_message(message) + except Exception as e: + error_message = f"Swap Error:\n{str(e)}" + logging.error(error_message) + await send_telegram_message(error_message) + else: + message = ( + f"Move Failed:\n" + f"Insufficient balance to swap {amount_to_swap:.6f} {move['token']}" + ) + logging.warning(message) + await send_telegram_message(message) + + + + + async def on_logs(log): logging.debug(f"Received log: {log}") await save_log(log)