diff --git a/crypto/sol/.env b/crypto/sol/.env
index 3e225b7..68173bd 100644
--- a/crypto/sol/.env
+++ b/crypto/sol/.env
@@ -16,6 +16,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"
diff --git a/crypto/sol/config.py b/crypto/sol/config.py
index d8a9dc8..770d50a 100644
--- a/crypto/sol/config.py
+++ b/crypto/sol/config.py
@@ -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",
diff --git a/crypto/sol/modules/SolanaAPI.py b/crypto/sol/modules/SolanaAPI.py
index 82c1435..9dc8a76 100644
--- a/crypto/sol/modules/SolanaAPI.py
+++ b/crypto/sol/modules/SolanaAPI.py
@@ -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
-
- your_balance = your_balance_info['amount']
+ 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']
- 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"Move not followed:\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"Move not followed:\nInvalid FOLLOW_AMOUNT '{FOLLOW_AMOUNT}'. Must be 'percentage' or a number."
+ if not your_balance:
+ msg = f"Move not followed:\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"Warning:\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"Initiating move:\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\nTransaction: {transaction_id}"
-
- 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"Move Failed:\n{str(e)}\n{transaction_data}\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"Move Followed, failed to get transaction details.\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\nTransaction: {transaction_id}"
- # log_successful_swap ()
- )
-
+ #fallback to 100 USD
+ amount_to_swap = 100
else:
- notification = (
- f"Move Followed:\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\nTransaction: {transaction_id}"
- )
- 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"Swap Follow Error:\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"Move not followed:\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"Warning:\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"Initiating move:\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:\nTransaction: swapping {amount_to_swap:.2f} {token_name_in}"
+ 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"Move Failed:\n{str(e)}\n{decoded_data}\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"Move Followed, failed to get transaction details.\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\nTransaction: solscan.io"
+ # log_successful_swap ()
+ )
+
+ else:
+ notification = (
+ f"Move Followed:\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\nTransaction: solscan.io"
+ )
+ 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"Swap Follow Error:\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}")
diff --git a/crypto/sol/modules/webui.py b/crypto/sol/modules/webui.py
index 142d0c6..06f5310 100644
--- a/crypto/sol/modules/webui.py
+++ b/crypto/sol/modules/webui.py
@@ -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
@@ -123,14 +133,40 @@ def init_app(tr_handler=None):
if not token_inputs or not token_outputs:
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")
tx_signature = data[0].get('signature')
@@ -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"Got WH notification:: {tr['amount_in']} {tr['symbol_in']} swapped for {tr['symbol_out']} \n"
+ f"Got WH notification:: {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)