refactor wip
This commit is contained in:
@ -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()
|
@ -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
|
@ -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
|
||||
|
Reference in New Issue
Block a user