refactor wip

This commit is contained in:
Dobromir Popov
2024-10-22 01:27:46 +03:00
parent 98364fc1da
commit 7a1ef2fb7a
5 changed files with 234 additions and 597 deletions

View File

@ -4,6 +4,28 @@ import os
import aiohttp
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from solders import message
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 spl.token.client import Token
from base64 import b64decode
import base58
from solders.rpc.requests import GetTransaction
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 solders.instruction import CompiledInstruction
import asyncio
import json
import logging
@ -14,20 +36,37 @@ import requests
from datetime import datetime
from solana.rpc.types import TokenAccountOpts, TxOpts
# # # solders/solana libs (solana_client) # # #
from spl.token._layouts import MINT_LAYOUT
from solana.rpc.api import Client, Pubkey
from spl.token.async_client import AsyncToken
from spl.token.constants import TOKEN_PROGRAM_ID
from borsh_construct import String, CStruct
# ------------------
logger = logging.getLogger(__name__)
SOLANA_ENDPOINTS = [
"wss://api.mainnet-beta.solana.com",
]
PING_INTERVAL = 30
SUBSCRIBE_INTERVAL = 1*60 # Resubscribe every 1 minute
SUBSCRIBE_INTERVAL = 10*60 # Resubscribe every 1 minute
from config import (
FOLLOWED_WALLET, SOLANA_HTTP_URL, DISPLAY_CURRENCY
FOLLOWED_WALLET, SOLANA_HTTP_URL, DISPLAY_CURRENCY, SOLANA_ENDPOINTS
)
from modules.utils import telegram_utils
# Use the production Solana RPC endpoint
solana_client = AsyncClient(SOLANA_HTTP_URL)
dexscreener_client = DexscreenerClient()
class SolanaWS:
def __init__(self, on_message: Optional[callable] = None):
self.websocket = None
@ -149,7 +188,7 @@ class SolanaWS:
return None
class SolanaAPI:
def __init__(self, process_transaction_callback, on_initial_subscription_callback = None, on_bot_message=None):
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
self.on_bot_message = on_bot_message,
@ -162,6 +201,7 @@ class SolanaAPI:
message = await solana_ws.message_queue.get()
await self.process_transaction(message)
_first_subscription = True
async def wallet_watch_loop(self):
solana_ws = SolanaWS(on_message=self.process_transaction)
@ -399,8 +439,107 @@ class SolanaAPI:
except requests.exceptions.RequestException as e:
print("Error fetching transaction details:", e)
async def get_transaction_details_with_retry(transaction_id, retry_delay = 5, max_retries = 16):
# wait for the transaction to be confirmed
# await async_client.wait_for_confirmation(Signature.from_string(transaction_id))
# query every 5 seconds for the transaction details until not None or 30 seconds
for _ in range(max_retries):
try:
tx_details = await self.get_transaction_details_rpc(transaction_id)
if tx_details is not None:
break
except Exception as e:
logging.error(f"Error fetching transaction details: {e}")
retry_delay = retry_delay * 1.2
logging.info(f"({_} of {max_retries}) Waiting for transaction details for {transaction_id}. retry in {retry_delay} s.")
await asyncio.sleep(retry_delay)
retry_delay *= 1.2
return tx_details
async def get_swap_transaction_details(tx_signature_str):
t = await self.get_transaction(Signature.from_string(tx_signature_str), max_supported_transaction_version=0)
try:
parsed_result = {
"order_id": None,
"token_in": None,
"token_out": None,
"amount_in": 0,
"amount_out": 0,
"amount_in_USD": 0,
"amount_out_USD": 0,
"percentage_swapped": 0
}
instructions = t.value.transaction.transaction.message.instructions
# Parse the swap instruction to extract token addresses, amounts, and types
for instruction in instructions:
if isinstance(instruction, CompiledInstruction):
if instruction.program_id == Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"):
parsed_info = instruction.parsed.info
mint = parsed_info["mint"]
amount = float(parsed_info["tokenAmount"]["amount"]) / (10 ** parsed_info["tokenAmount"]["decimals"])
# Determine token in and token out based on balances
if parsed_result["token_in"] is None and amount > 0:
parsed_result["token_in"] = mint
parsed_result["amount_in"] = amount
elif parsed_result["token_out"] is None:
parsed_result["token_out"] = mint
parsed_result["amount_out"] = amount
# Calculate percentage swapped
if parsed_result["amount_in"] > 0 and parsed_result["amount_out"] > 0:
parsed_result["percentage_swapped"] = (parsed_result["amount_out"] / parsed_result["amount_in"]) * 100
return parsed_result
except Exception as e:
logging.error(f"Error fetching transaction details: {e}")
return None
async def get_token_balance_rpc(wallet_address, token_address):
try:
accounts = await self.solana_ws.solana_jsonrpc("getTokenAccountsByOwner", [
wallet_address,
{
"mint": token_address
}])
if accounts['value']:
first_account = accounts['value'][0]['pubkey']
balance_data = {
"jsonrpc": "2.0",
"id": 1,
"method": "getTokenAccountBalance",
"params": [
first_account
]
}
balance = self.solana_ws.solana_jsonrpc("getTokenAccountBalance", first_account)
if 'value' in balance:
amount = float(balance['value']['uiAmount'])
logging.debug(f"Balance for {token_address} in {wallet_address}: {amount}")
return amount
else:
logging.debug(f"No balance found for {token_address} in {wallet_address}")
return 0
else:
logging.debug(f"No account found for {token_address} in {wallet_address}")
return 0
except requests.exceptions.RequestException as e:
logging.error(f"Error getting balance for {token_address} in {wallet_address}: {str(e)} \r\n {e}")
return 0
@ -585,6 +724,7 @@ class SolanaDEX:
sol_address = "So11111111111111111111111111111111111111112" # Solana's wrapped SOL address
return await get_token_prices([sol_address]).get(sol_address, 0.0)
async def get_wallet_balances(wallet_address, doGetTokenName=True):
balances = {}
logging.info(f"Getting balances for wallet: {wallet_address}")
@ -716,4 +856,10 @@ class SolanaDEX:
# save token info to file
await save_token_info()
async def save_token_info():
with open('./logs/token_info.json', 'w') as f:
json.dump(TOKENS_INFO, f, indent=2)
SAPI = SolanaAPI()

View File

@ -33,7 +33,7 @@ class TelegramUtils:
await self.initialize()
try:
# await self.bot.send_message(chat_id=DEVELOPER_CHAT_ID, text=f"[{BOT_NAME}] {message}", parse_mode=ParseMode.HTML)
await self.bot.send_message(chat_id=DEVELOPER_CHAT_ID, text=f"[{BOT_NAME}] {message}", parse_mode=ParseMode.HTML)
logging.info(f"Telegram message sent: {message}")
except Exception as e:
logging.error(f"Error sending Telegram message: {str(e)}")
@ -42,10 +42,7 @@ class TelegramUtils:
if self.conn_pool:
await self.conn_pool.close()
class Log:
# Set up success logger for accounting CSV
class CSVFormatter(logging.Formatter):
class CSVFormatter(logging.Formatter):
def __init__(self):
super().__init__()
self.output = None
@ -66,7 +63,9 @@ class Log:
record.wallet_address
])
return ''
class Log:
# Set up success logger for accounting CSV
def __init__(self):
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG)
@ -85,7 +84,7 @@ class Log:
error_logger.addHandler(error_file_handler)
success_log_file = os.path.join(log_dir, 'successful_swaps.csv')
success_file_handler = RotatingFileHandler(success_log_file, maxBytes=10*1024*1024, backupCount=5)
success_file_handler.setFormatter(self.CSVFormatter())
success_file_handler.setFormatter(CSVFormatter())
success_logger_accounting_csv = logging.getLogger('success_logger_accounting_csv')
success_logger_accounting_csv.setLevel(logging.INFO)
success_logger_accounting_csv.addHandler(success_file_handler)
@ -104,9 +103,39 @@ class Log:
})
def safe_get_property(info, property_name, default='Unknown'):
if not isinstance(info, dict):
return str(default)
value = info.get(property_name, default)
return str(value) if value is not None else str(default)
# Create a global instance of TelegramUtils
telegram_utils = TelegramUtils()
log = Log()
# You can add more Telegram-related methods to the TelegramUtils class if needed
# You can add more Telegram-related methods to the TelegramUtils class if needed
pk = os.getenv("PK")
async def get_pk():
global pk
if not pk:
try:
script_dir = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(script_dir, 'secret.pk'), 'r') as f:
pk = f.read().strip()
if pk:
logging.info("Private key loaded successfully from file.")
else:
logging.warning("Private key file is empty.")
except FileNotFoundError:
logging.warning("Private key file not found.")
except Exception as e:
logging.error(f"Error reading private key file: {str(e)}")
if not pk:
logging.error("Private key not found in environment variables. Will not be able to sign transactions.")
# send TG warning message
await telegram_utils.send_telegram_message("<b>Warning:</b> Private key not found in environment variables. Will not be able to sign transactions.")
return pk

View File

@ -2,9 +2,10 @@ from flask import Flask, jsonify, request, render_template, redirect, url_for
# from flask_oauthlib.client import OAuth
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
import secrets
from modules import storage
from modules import storage, utils, SolanaAPI
import os
def init_app():
app = Flask(__name__, template_folder='../templates', static_folder='../static')
app.config['SECRET_KEY'] = 'your-secret-key'
@ -117,11 +118,11 @@ def init_app():
return jsonify({"error": "No log files found"}), 404
try:
logger.info(f"Processing latest log file: {latest_log_file}")
utils.log.info(f"Processing latest log file: {latest_log_file}")
with open(latest_log_file, 'r') as f:
log = json.load(f)
result = await process_log(log)
result = await SolanaAPI.process_log(log)
return jsonify({
"file": latest_log_file,
@ -130,7 +131,7 @@ def init_app():
}), 200
except Exception as e:
logging.error(f"Error processing log dump: {e}")
utils.log.error(f"Error processing log dump: {e}")
return jsonify({"error": "Failed to process log"}), 500
@ -141,7 +142,7 @@ def init_app():
@app.route('/tr/<wallet>/<tx_signature>', methods=['GET', 'POST'])
async def transaction_notified(wallet, tx_signature):
try:
logger.info(f"Processing transaction notification for wallet: {wallet}, tx: {tx_signature}")
utils.log.info(f"Processing transaction notification for wallet: {wallet}, tx: {tx_signature}")
# Process the transaction
# tr = await get_swap_transaction_details(tx_signature)
tr = await get_transaction_details_info(tx_signature, [])
@ -152,7 +153,7 @@ def init_app():
await save_token_info()
return jsonify(tr), 200
except Exception as e:
logging.error(f"Error processing transaction: {e}")
utils.log.error(f"Error processing transaction: {e}")
return jsonify({"error": "Failed to process transaction"}), 500
@ -174,5 +175,5 @@ def get_latest_log_file():
latest_file = max(files, key=lambda x: os.path.getmtime(os.path.join(log_dir, x)))
return os.path.join(log_dir, latest_file)
except Exception as e:
logging.error(f"Error fetching latest log file: {e}")
utils.log.error(f"Error fetching latest log file: {e}")
return None