fixes and starbility ()tetrt

This commit is contained in:
Dobromir Popov 2024-11-05 16:38:37 +02:00
parent 8f6a788717
commit 0780878c50
4 changed files with 227 additions and 165 deletions

View File

@ -15,6 +15,9 @@ TELEGRAM_BOT_TOKEN="6749075936:AAHUHiPTDEIu6JH7S2fQdibwsu6JVG3FNG0"
DISPLAY_CURRENCY=USD
FOLLOW_AMOUNT=2
FOLLOW_AMOUNT=percentage
LIQUIDITY_TOKENS=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v,So11111111111111111111111111111111111111112
# Niki's to Sync: [PROD]
FOLLOWED_WALLET="7keSmTZozjmuX66gd9GBSJYEHnMqsyutWpvuuKtXZKDH"

View File

@ -21,6 +21,7 @@ SOLANA_HTTP_URL = os.getenv("SOLANA_HTTP_URL")
DISPLAY_CURRENCY = os.getenv('DISPLAY_CURRENCY', 'USD')
BOT_NAME = os.getenv("BOT_NAME")
FOLLOW_AMOUNT = os.getenv('FOLLOW_AMOUNT', 'percentage')
LIQUIDITY_TOKENS = os.getenv('LIQUIDITY_TOKENS', 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v').split(',')
SOLANA_ENDPOINTS = [
"wss://api.mainnet-beta.solana.com",

View File

@ -25,11 +25,18 @@ from solana.rpc.async_api import AsyncClient
from solana.rpc.types import TxOpts
from solana.rpc.commitment import Confirmed, Finalized, Processed
from solders.transaction import VersionedTransaction
from solders.rpc.requests import GetTransaction
from solders.signature import Signature
from solders.pubkey import Pubkey
from solders.keypair import Keypair
from solana.rpc.async_api import AsyncClient
from solana.rpc.commitment import Processed
from solana.rpc.types import TxOpts
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
from solders import message
from jupiter_python_sdk.jupiter import Jupiter
import asyncio
@ -251,8 +258,8 @@ class SolanaAPI:
await solana_ws.subscribe()
if first_subscription:
await async_safe_call( self.on_initial_subscription, solana_ws.subscription_id)
first_subscription = False
await async_safe_call(self.on_initial_subscription, solana_ws.subscription_id)
await async_safe_call(self.on_bot_message,f"Solana mainnet connected ({solana_ws.subscription_id})...")
@ -503,7 +510,7 @@ class SolanaAPI:
if transfer['mint'] in DEX.TOKENS_INFO or 'decimals' not in DEX.TOKENS_INFO[transfer['mint']]:
await self.get_token_metadata_symbol(transfer['mint'])
# get actual prices
current_price = await self.get_token_prices([transfer['mint']])
current_price = await DEX.get_token_prices([transfer['mint']])
if parsed_result["token_in"] is None:
parsed_result["token_in"] = transfer['mint']
@ -529,7 +536,9 @@ class SolanaAPI:
parsed_result["percentage_swapped"] = (parsed_result["amount_in"] / parsed_result["before_source_balance"]) * 100
else:
# calculate based on total wallet value: FOLLOWED_WALLET_VALUE
parsed_result["percentage_swapped"] = (parsed_result["amount_in_USD"] / DEX.FOLLOWED_WALLET_VALUE) * 100
# division by 0
# parsed_result["percentage_swapped"] = (parsed_result["amount_in_USD"] / DEX.FOLLOWED_WALLET_VALUE) * 100
pass
except Exception as e:
logging.error(f"Error calculating percentage swapped: {e}")
@ -563,7 +572,7 @@ class SolanaAPI:
# return float(balance['uiTokenAmount']['amount'])
# return 0.0
async def get_transaction_details_with_retry(self, transaction_id, retry_delay = 5, max_retries = 16):
async def get_transaction_details_with_retry(self, transaction_id, retry_delay = 5, max_retries = 16, backoff= True):
# 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
@ -574,10 +583,11 @@ class SolanaAPI:
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
if backoff:
retry_delay = retry_delay * 1.2
return tx_details
@ -662,162 +672,174 @@ class SolanaAPI:
return 0
async def follow_move(self,move):
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:
# 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
try:
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:
# 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
your_balance = your_balance_info['amount']
your_balance = your_balance_info['amount']
token_info = DEX.TOKENS_INFO.get(move['token_in'])
token_name_in = token_info.get('symbol') or await SAPI.get_token_metadata_symbol(move['token_in'])
token_name_out = DEX.TOKENS_INFO[move['token_out']].get('symbol') or await SAPI.get_token_metadata_symbol(move['token_out'])
token_info = DEX.TOKENS_INFO.get(move['token_in'])
token_name_in = token_info.get('symbol') or await SAPI.get_token_metadata_symbol(move['token_in'])
token_name_out = DEX.TOKENS_INFO[move['token_out']].get('symbol') or await SAPI.get_token_metadata_symbol(move['token_out'])
if not your_balance:
msg = f"<b>Move not followed:</b>\nNo balance found for token {move['symbol_in']}. Cannot follow move."
logging.warning(msg)
await telegram_utils.send_telegram_message(msg)
return
if FOLLOW_AMOUNT == 'percentage':
# Calculate the amount to swap based on the same percentage as the followed move
amount_to_swap = your_balance * (move['percentage_swapped'] / 100)
elif FOLLOW_AMOUNT == 'exact':
amount_to_swap = move['amount_in']
else:
try:
fixed_amount = float(FOLLOW_AMOUNT) # un USD
fixed_amount_in_token = fixed_amount / move["token_in_price"]
amount_to_swap = min(fixed_amount_in_token, your_balance)
except ValueError:
msg = f"<b>Move not followed:</b>\nInvalid FOLLOW_AMOUNT '{FOLLOW_AMOUNT}'. Must be 'percentage' or a number."
if not your_balance:
msg = f"<b>Move not followed:</b>\nNo balance found for token {move['symbol_in']}. Cannot follow move."
logging.warning(msg)
await telegram_utils.send_telegram_message(msg)
return
amount_to_swap = min(amount_to_swap, your_balance) # Ensure we're not trying to swap more than we have
decimals = token_info.get('decimals')
# Convert to lamports
# if decimals is 6, then amount = amount * 1e6; if 9, then amount = amount * 1e9
amount = int(amount_to_swap * 10**decimals)
amount = int(amount)
logging.debug(f"Calculated amount in lamports: {amount}")
if your_balance < amount_to_swap: # should not happen
msg = (
f"<b>Warning:</b>\n"
f"Insufficient balance: {your_balance:.6f} {token_name_in}. We want to swap {amount_to_swap:.6f}\n({move['symbol_in']}, decimals {token_info.get('decimals')} amount {amount}).\n This will probably fail. But we will try anyway."
)
logging.warning(msg)
await telegram_utils.send_telegram_message(msg)
try:
try:
notification = (
f"<b>Initiating move:</b>\n"
f"Swapping {amount_to_swap:.2f} {token_name_in} for {token_name_out}"
+ (f" ({move['percentage_swapped']:.2f}%)" if 'percentage_swapped' in move else "")
)
# logging.info(notification)
# error_logger.info(notification)
# await telegram_utils.send_telegram_message(notification)
except Exception as e:
logging.error(f"Error sending notification: {e}")
if self.pk is None:
self.pk = await get_pk()
for retry in range(3):
try:
private_key = Keypair.from_bytes(base58.b58decode(self.pk))
async_client = AsyncClient(SOLANA_WS_URL)
jupiter = Jupiter(async_client, private_key)
transaction_data = await jupiter.swap(
input_mint=move['token_in'],
output_mint=move['token_out'],
amount=int(amount),
slippage_bps=300, # Increased to 3%
)
logging.info(f"Initiating move. Transaction data:\n {transaction_data}")
# error_logger.info(f"Initiating move. Transaction data:\n {transaction_data}")
raw_transaction = VersionedTransaction.from_bytes(base64.b64decode(transaction_data))
message = raw_transaction.message
signature = private_key.sign_message( bytes(message) )
# signature = private_key.sign_message(message.to_bytes_versioned())
signed_txn = VersionedTransaction.populate(raw_transaction.message, [signature])
opts = TxOpts(skip_preflight=False, preflight_commitment=Processed)
# send the transaction
result = await async_client.send_raw_transaction(txn=bytes(signed_txn), opts=opts)
transaction_id = json.loads(result.to_json())['result']
print(f"Follow Transaction Sent: https://solscan.io/tx/{transaction_id}")
# append to notification
notification += f"\n\n<b>Transaction:</b> <a href='https://solscan.io/tx/{transaction_id}'>{transaction_id}</a>"
await telegram_utils.send_telegram_message(f"Follow Transaction Sent: {transaction_id}")
tx_details = await SAPI.get_transaction_details_with_retry(transaction_id)
if tx_details is not None:
break
if FOLLOW_AMOUNT == 'percentage':
# Calculate the amount to swap based on the same percentage as the followed move
if move.get('percentage_swapped') is None:
followed_ballances = await DEX.get_wallet_balances(FOLLOWED_WALLET, doGetTokenName=False)
followed_ballance = next((balance for balance in followed_ballances.values() if balance['address'] == move['token_in']), None)
if followed_ballance is not None:
# Use the balance
print(f"Followed balance: {followed_ballance['amount']} {move['symbol_in']}")
move['percentage_swapped'] = (move['amount_in']/followed_ballance['amount']) * 100
amount_to_swap = your_balance * (move['percentage_swapped'] / 100)
else:
logging.warning(f"Failed to get transaction details for {transaction_id}. Probably transaction failed. Retrying again...")
await asyncio.sleep(3)
except Exception as e:
error_message = f"<b>Move Failed:</b>\n{str(e)}</b>\n{transaction_data}</b>\n{move}"
logging.error(error_message)
# log the errors to /logs/errors.log
# error_logger.error(error_message)
# error_logger.exception(e)
await telegram_utils.send_telegram_message(error_message)
amount = int(amount * 0.75)
await DEX.get_wallet_balances(YOUR_WALLET, doGetTokenName=False)
try:
if tx_details is None:
logging.info(f"Failed to get transaction details for {transaction_id}")
notification = (
f"<b>Move Followed, failed to get transaction details.</b>\n"
f"Swapped {amount_to_swap:.6f} {token_name_in} ({move['token_in']}) "
f"(same {move['percentage_swapped']:.2f}% as followed wallet)\n"
f"\n\n<b>Transaction:</b> <a href='https://solscan.io/tx/{transaction_id}'>{transaction_id}</a>"
# log_successful_swap ()
)
#fallback to 100 USD
amount_to_swap = 100
else:
notification = (
f"<b>Move Followed:</b>\n"
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']:.2f} {token_name_out}"
# f"Amount In USD: {tr_details['amount_in_USD']}\n"
f"\n\n<b>Transaction:</b> <a href='https://solscan.io/tx/{transaction_id}'>{transaction_id}</a>"
)
logging.info(notification)
await telegram_utils.send_telegram_message(notification)
except Exception as e:
logging.error(f"Error sending notification: {e}")
except Exception as e:
error_message = f"<b>Swap Follow Error:</b>\n{str(e)}"
logging.error(error_message)
# log the errors to /logs/errors.log
# error_logger.error(error_message)
# error_logger.exception(e)
# if error_message contains 'Program log: Error: insufficient funds'
if 'insufficient funds' in error_message:
await telegram_utils.send_telegram_message("Insufficient funds. Cannot follow move. Please check your balance.")
amount_to_swap = your_balance * (move['percentage_swapped'] / 100)
elif FOLLOW_AMOUNT == 'exact':
amount_to_swap = move['amount_in']
else:
await telegram_utils.send_telegram_message(error_message)
try:
fixed_amount = float(FOLLOW_AMOUNT) # un USD
fixed_amount_in_token = fixed_amount / move["token_in_price"]
amount_to_swap = min(fixed_amount_in_token, your_balance)
except ValueError:
msg = f"<b>Move not followed:</b>\nInvalid FOLLOW_AMOUNT '{FOLLOW_AMOUNT}'. Must be 'percentage' or a number."
logging.warning(msg)
await telegram_utils.send_telegram_message(msg)
return
amount_to_swap = min(amount_to_swap, your_balance) # Ensure we're not trying to swap more than we have
decimals = token_info.get('decimals')
# Convert to lamports
# if decimals is 6, then amount = amount * 1e6; if 9, then amount = amount * 1e9
amount_lamports = int(amount_to_swap * 10**decimals)
logging.debug(f"Calculated amount in lamports: {amount_lamports}")
if your_balance < amount_to_swap: # should not happen
msg = (
f"<b>Warning:</b>\n"
f"Insufficient balance: {your_balance:.6f} {token_name_in}. We want to swap {amount_to_swap:.6f}\n({move['symbol_in']}, decimals {token_info.get('decimals')} amount {amount_lamports}).\n This will probably fail. But we will try anyway."
)
logging.warning(msg)
await telegram_utils.send_telegram_message(msg)
try:
try:
notification = (
f"<b>Initiating move:</b>\n"
f"Swapping {amount_to_swap:.2f} {token_name_in} for {token_name_out}"
+ (f" ({move['percentage_swapped']:.2f}%)" if 'percentage_swapped' in move else "")
)
# logging.info(notification)
# error_logger.info(notification)
# await telegram_utils.send_telegram_message(notification)
except Exception as e:
logging.error(f"Error sending notification: {e}")
if self.pk is None:
self.pk = await get_pk()
for retry in range(3):
try:
private_key = Keypair.from_bytes(base58.b58decode(self.pk))
async_client = AsyncClient(SOLANA_WS_URL)
jupiter = Jupiter(async_client, private_key)
transaction_data = await jupiter.swap(
input_mint=move['token_in'],
output_mint=move['token_out'],
amount=amount_lamports,
slippage_bps=300, # Increased to 3%
)
logging.info(f"Initiating move. Transaction data:\n {transaction_data}")
# error_logger.info(f"Initiating move. Transaction data:\n {transaction_data}")
raw_transaction = VersionedTransaction.from_bytes(base64.b64decode(transaction_data))
# message = raw_transaction.message
# signature = private_key.sign_message( bytes(message) )
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)
# send the transaction
result = await async_client.send_raw_transaction(txn=bytes(signed_txn), opts=opts)
transaction_id = json.loads(result.to_json())['result']
notification = f"Follow Transaction Sent:\n<b>Transaction:</b> <a href='https://solscan.io/tx/{transaction_id}'>swapping {amount_to_swap:.2f} {token_name_in}</a>"
logging.info(notification)
await telegram_utils.send_telegram_message(notification)
tx_details = await SAPI.get_transaction_details_with_retry(transaction_id, retry_delay=5, max_retries=6, backoff=False)
if tx_details is not None:
break
else:
logging.warning(f"Failed to get transaction details for {transaction_id}.\n Probably transaction failed. Retrying again...")
await asyncio.sleep(3)
except Exception as e:
decoded_data = ''# base64.b64decode(transaction_data)
error_message = f"<b>Move Failed:</b>\n{str(e)}</b>\n{decoded_data}</b>\n{move}"
logging.error(error_message)
# log the errors to /logs/errors.log
# error_logger.error(error_message)
# error_logger.exception(e)
await telegram_utils.send_telegram_message(error_message)
amount = int(amount * 0.9)
await DEX.get_wallet_balances(YOUR_WALLET, doGetTokenName=False)
try:
if tx_details is None:
logging.info(f"Failed to get transaction details for {transaction_id}")
notification = (
f"<b>Move Followed, failed to get transaction details.</b>\n"
f"Swapped {amount_to_swap:.6f} {token_name_in} ({move['token_in']}) "
f"(same {move['percentage_swapped']:.2f}% as followed wallet)\n"
f"\n\n<b>Transaction:</b> <a href='https://solscan.io/tx/{transaction_id}'>solscan.io</a>"
# log_successful_swap ()
)
else:
notification = (
f"<b>Move Followed:</b>\n"
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']:.2f} {token_name_out}"
# f"Amount In USD: {tr_details['amount_in_USD']}\n"
f"\n\n<b>Transaction:</b> <a href='https://solscan.io/tx/{transaction_id}'>solscan.io</a>"
)
logging.info(notification)
await telegram_utils.send_telegram_message(notification)
except Exception as e:
logging.error(f"Error sending notification: {e}")
except Exception as e:
error_message = f"<b>Swap Follow Error:</b>\n{str(e)}"
logging.error(error_message)
# log the errors to /logs/errors.log
# error_logger.error(error_message)
# error_logger.exception(e)
# if error_message contains 'Program log: Error: insufficient funds'
if 'insufficient funds' in error_message:
await telegram_utils.send_telegram_message("Insufficient funds. Cannot follow move. Please check your balance.")
else:
await telegram_utils.send_telegram_message(error_message)
except Exception as e:
logging.error(f"Error following move: {e}")

View File

@ -1,8 +1,16 @@
import asyncio
import os
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
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
import json
# from crypto.sol.config import LIQUIDITY_TOKENS
from config import LIQUIDITY_TOKENS
from modules import storage, utils, SolanaAPI
from modules.utils import async_safe_call
import os
@ -98,8 +106,10 @@ def init_app(tr_handler=None):
with open( os.path.join(SolanaAPI.root_path, 'logs', f'wh_{current_time}.json') , 'w') as f:
json.dump(request_data, f)
process_wh(request_data)
return jsonify({"status": "Webhook processed"}), 200
# await process_wh(request_data)
# don't wait for the process to finish
asyncio.create_task(process_wh(request_data ))
return jsonify({"status": "OK"}), 200
except Exception as e:
logging.error(f"Error processing webhook: {e}")
return jsonify({"error": "Failed to process webhook"}), 500
@ -124,12 +134,38 @@ def init_app(tr_handler=None):
logging.warning("Missing token inputs or outputs")
return
tr = {
'token_in': token_inputs[0]['mint'],
'token_out': token_outputs[0]['mint'],
'amount_in': float(token_inputs[0]['rawTokenAmount']['tokenAmount']) / 10**token_inputs[0]['rawTokenAmount']['decimals'],
'amount_out': float(token_outputs[0]['rawTokenAmount']['tokenAmount']) / 10**token_outputs[0]['rawTokenAmount']['decimals'],
}
usdcMint = LIQUIDITY_TOKENS[0]
tr = {}
if not token_inputs or len(token_inputs) == 0:
logging.info("Assumed USDC as first token. BUY transaction detected")
tr["token_in"] = usdcMint
tr["type"] = "BUY"
tr["amount_in"] = await SolanaAPI.DEX.get_token_prices(
token_outputs[0]["mint"],
int(token_outputs[0]["rawTokenAmount"]["tokenAmount"])
)
else:
token_in = token_inputs[0]
tr["token_in"] = token_in["mint"]
tr["token_in_decimals"] = token_in["rawTokenAmount"]["decimals"]
tr["amount_in"] = float(int(token_in["rawTokenAmount"]["tokenAmount"]) / 10**token_in["rawTokenAmount"]["decimals"])
# 'amount_in': float(token_inputs[0]['rawTokenAmount']['tokenAmount']) / 10**token_inputs[0]['rawTokenAmount']['decimals'],
if not token_outputs or len(token_outputs) == 0:
logging.info("Assumed USDC as second token. SELL transaction detected")
tr["token_out"] = usdcMint
tr["type"] = "SELL"
tr["amount_out"] = await SolanaAPI.DEX.get_token_prices(
token_inputs[0]["mint"],
int(token_inputs[0]["rawTokenAmount"]["tokenAmount"])
)
else:
token_out = token_outputs[0]
tr["token_out"] = token_out["mint"]
tr["token_out_decimals"] = token_out["rawTokenAmount"]["decimals"]
tr["amount_out"] = float(int(token_out["rawTokenAmount"]["tokenAmount"]) / 10**token_out["rawTokenAmount"]["decimals"])
#'amount_out': float(token_outputs[0]['rawTokenAmount']['tokenAmount']) / 10**token_outputs[0]['rawTokenAmount']['decimals'],
if not tr["token_in"] or not tr["token_out"] or tr["amount_in"] == 0 or tr["amount_out"] == 0:
logging.warning("Incomplete swap details found in logs. Getting details from transaction")
@ -150,7 +186,7 @@ def init_app(tr_handler=None):
tr['value_out_USD'] = prices.get(tr['token_out'], 0) * tr['amount_out']
notification = (
f"<b>Got WH notification:</b>: {tr['amount_in']} {tr['symbol_in']} swapped for {tr['symbol_out']} \n"
f"<b>Got WH notification:</b>: {tr['amount_in']} {tr['symbol_in']} swapped for {tr['amount_out']} {tr['symbol_out']} ${tr['value_out_USD']}\n"
)
logging.info(notification)
await utils.telegram_utils.send_telegram_message(notification)