This commit is contained in:
Dobromir Popov 2024-11-11 22:38:05 +02:00
parent efd7d8df5a
commit 5851af8f80
8 changed files with 115 additions and 199 deletions

View File

@ -1,3 +1,10 @@
#https://dev.d-popov.com/wh
#7keSmTZozjmuX66gd9GBSJYEHnMqsyutWpvuuKtXZKDH
#9U7D916zuQ8qcL9kQZqkcroWhHGho5vD8VNekvztrutN
#3EZkyU9zQRrHPnrNovDiRCA9Yg3wLK35u9cdrcGcszi1
#7QXGLRjvyFAmxdRaP9Wk18KwWTMfspF4Na2sr3o3PzxV
SOLANA_WS_URL="wss://api.mainnet-beta.solana.com"
SOLANA_WS_URL2="wss://mainnet.rpcpool.com"
@ -23,11 +30,11 @@ LIQUIDITY_TOKENS=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v,So1111111111111111
PRIORITY=1 # 0-10, 5 = market cap, 10 twice market cap
DO_WATCH_WALLET=True
# Niki's to Sync: [PROD]
FOLLOWED_WALLET="3EZkyU9zQRrHPnrNovDiRCA9Yg3wLK35u9cdrcGcszi1"
YOUR_WALLET="7QXGLRjvyFAmxdRaP9Wk18KwWTMfspF4Na2sr3o3PzxV"
PK=3FxXjNrtEqwAKYj4BpkuLAJPzuKRWykkvjeBYQEVuFqRFWRm9eVcWrrYKbns2M31ESMoASG2WV39w9Dpx532sPUH
#FOLLOWED_WALLET="3EZkyU9zQRrHPnrNovDiRCA9Yg3wLK35u9cdrcGcszi1"
#YOUR_WALLET="7QXGLRjvyFAmxdRaP9Wk18KwWTMfspF4Na2sr3o3PzxV"
#PK=3FxXjNrtEqwAKYj4BpkuLAJPzuKRWykkvjeBYQEVuFqRFWRm9eVcWrrYKbns2M31ESMoASG2WV39w9Dpx532sPUH
# Sync to main [DEV]
#FOLLOWED_WALLET="7QXGLRjvyFAmxdRaP9Wk18KwWTMfspF4Na2sr3o3PzxV"
#YOUR_WALLET="65nzyZXTLC81MthTo52a2gRJjqryTizWVqpK2fDKLye5"
#PK=5ccrMf3BFFE1HMsXt17btK1tMSNay7aBoY27saPHrqg2JEjxKBmBbxUABD9Jh7Gisf1bhM51oGzWdyLUgHdrUJPw
FOLLOWED_WALLET="7QXGLRjvyFAmxdRaP9Wk18KwWTMfspF4Na2sr3o3PzxV"
YOUR_WALLET="65nzyZXTLC81MthTo52a2gRJjqryTizWVqpK2fDKLye5"
PK=5ccrMf3BFFE1HMsXt17btK1tMSNay7aBoY27saPHrqg2JEjxKBmBbxUABD9Jh7Gisf1bhM51oGzWdyLUgHdrUJPw

View File

@ -205,167 +205,6 @@ async def process_log(log_result):
return tr_details
# async def follow_move_legacy(move):
# global pk
# if pk is None:
# pk = await get_pk()
# your_balances = await SAPI.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 = SAPI.dex.TOKENS_INFO.get(move['token_in'])
# token_name_in = token_info.get('symbol') or await SAPI.get_token_metadata(move['token_in'])
# token_name_out = SAPI.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."
# 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}")
# for retry in range(3):
# try:
# private_key = Keypair.from_bytes(base58.b58decode(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,
# 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(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
# 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 = amount * 0.75
# await SAPI.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 ()
# )
# 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.")
# else:
# await telegram_utils.send_telegram_message(error_message)
# Helper functions
async def process_messages(websocket):
try:
while True:

View File

@ -84,7 +84,18 @@ class SolanaWS:
self.on_message = on_message
self.websocket = None
self.last_msg_responded = False
async def save_log(log):
try:
os.makedirs('./logs', exist_ok=True)
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S_%f")
filename = f"./logs/ws_response_{timestamp}.json"
with open(filename, 'w') as f:
json.dump(log, f, indent=2)
except Exception as e:
logging.error(f"Error saving RPC log: {e}")
async def connect(self):
while True:
try:
@ -116,7 +127,7 @@ class SolanaWS:
response = await self.websocket.recv()
response_data = json.loads(response)
self.last_msg_responded = True
await self.save_log(response_data)
if 'result' in response_data:
return response_data['result']
elif 'error' in response_data:
@ -158,7 +169,7 @@ class SolanaWS:
async def receive_messages(self, one = False):
while True:
try:
response = await self.websocket.recv()
response_data = json.loads(response)
self.last_msg_responded = True

View File

@ -34,7 +34,19 @@ async def process_log_file(file_path):
if not existing_transaction:
# Store the transaction if it doesn't exist
await store_transaction(wallet_id, transaction_type, sell_currency, sell_amount, sell_value, buy_currency, buy_amount, buy_value, solana_signature, details)
transaction_data = {
'wallet_id': wallet_id,
'type': transaction_type,
'sell_currency': sell_currency,
'sell_amount': sell_amount,
'sell_value': sell_value,
'buy_currency': buy_currency,
'buy_amount': buy_amount,
'buy_value': buy_value,
'solana_signature': solana_signature,
'details': details
}
await store_transaction(transaction_data)
# Rename the file to append '_saved'
new_file_path = file_path.with_name(file_path.stem + "_saved" + file_path.suffix)

View File

@ -13,25 +13,26 @@ prisma_client = Prisma()
async def init_db():
await prisma_client.connect()
async def store_transaction(wallet_id, transaction_type, sell_currency, sell_amount, sell_value, buy_currency, buy_amount, buy_value, solana_signature, details=None):
async def store_transaction(transaction_data):
"""
Store a transaction record in the database.
Store a transaction record in the database using a dictionary.
"""
await prisma_client.transaction.create(
data={
'wallet_id': wallet_id,
'timestamp': datetime.now().isoformat(),
'type': transaction_type,
'sell_currency': sell_currency,
'sell_amount': sell_amount,
'sell_value': sell_value,
'buy_currency': buy_currency,
'buy_amount': buy_amount,
'buy_value': buy_value,
'solana_signature': solana_signature,
'details': json.dumps(details or {})
}
)
default_data = {
'wallet_id': None,
'timestamp': datetime.now().isoformat(),
'type': None,
'sell_currency': None,
'sell_amount': 0.0,
'sell_value': 0.0,
'buy_currency': None,
'buy_amount': 0.0,
'buy_value': 0.0,
'solana_signature': None,
'details': json.dumps({}),
'status': prisma_client.transactionStatus.ORIGINAL
}
default_data.update(transaction_data)
await prisma_client.transaction.create(data=default_data)
async def update_holdings(wallet_id, currency, amount_change):
holding = await prisma_client.holding.find_first(

View File

@ -1,6 +1,8 @@
# telegram_utils.py
import sys
import os
from base58 import b58decode
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import aiohttp
@ -65,7 +67,8 @@ class CSVFormatter(logging.Formatter):
record.wallet_address
])
return ''
class Log:
class Logging:
# Set up success logger for accounting CSV
def __init__(self):
@ -105,6 +108,26 @@ class Log:
})
def decode_instruction_data(data: str) -> dict:
try:
# Decode base58 data
decoded = b58decode(data)
# First byte usually represents instruction type
instruction_type = decoded[0] if decoded else None
# Rest of the data might contain amounts, token info etc
# Exact parsing depends on the program (Raydium, Orca, etc)
params = decoded[1:] if len(decoded) > 1 else None
return {
"instruction_type": instruction_type,
"params": params.hex() if params else None
}
except Exception as e:
return {"error": str(e)}
def safe_get_property(info, property_name, default='Unknown'):
if not isinstance(info, dict):
return str(default)
@ -154,7 +177,7 @@ async def async_safe_call(
# Create a global instance of TelegramUtils
telegram_utils = TelegramUtils()
log = Log().logger
log = Logging().logger
# You can add more Telegram-related methods to the TelegramUtils class if needed

View File

@ -12,7 +12,7 @@ import json
from config import LIQUIDITY_TOKENS
from modules import storage, utils, SolanaAPI
from modules.utils import async_safe_call
from modules.utils import async_safe_call, decode_instruction_data
import os
import logging
from datetime import datetime
@ -75,7 +75,7 @@ def init_app(tr_handler=None):
await utils.telegram_utils.send_telegram_message(notification)
# Store the notified transaction in the database
await storage.store_transaction(wallet, "SWAP", tr['symbol_in'] , tr['amount_in'], tr['value_in_USD'], tr['symbol_out'], tr['amount_out'], tr['value_out_USD'],tx_signature)
await storage.store_transaction(wallet, "SWAP", tr['symbol_in'] , tr['amount_in'], tr['value_in_USD'], tr['symbol_out'], tr['amount_out'], tr['value_out_USD'], tx_signature)
# Attempt to execute the copytrade transaction
try:
await SolanaAPI.SAPI.follow_move(tr)
@ -100,12 +100,34 @@ def init_app(tr_handler=None):
request_data = request.get_json() if request.is_json else None
if not request_data:
return jsonify({"error": "No data in request"}), 400
logger.info(f"Webhook data: {request_data}")
if "description" in request_data[0] and request_data[0]["description"]:
logger.info(request_data[0]["description"])
else:
logger.info(f"Webhook data: {request_data}")
# save dump to /cache/last-webhook-{datetime}.json
with open( os.path.join(SolanaAPI.root_path, 'logs', f'wh_{current_time}.json') , 'w') as f:
json.dump(request_data, f)
if "meta" in request_data[0]:
meta = request_data[0]["meta"]
# Parse inner instructions
for inner_ix in meta.get("innerInstructions", []):
for instruction in inner_ix.get("instructions", []):
decoded = decode_instruction_data(instruction["data"])
logger.info(f"Instruction data decoded: {decoded}")
# Example of pattern matching for specific instruction types
if decoded["instruction_type"] == 1: # Example: swap instruction
# Parse parameters based on program type
# Different DEXes will have different parameter layouts
pass
# await process_wh(request_data)
# don't wait for the process to finish
asyncio.create_task(process_wh(request_data ))
@ -214,7 +236,8 @@ def init_app(tr_handler=None):
logging.error(f"Copytrade transaction failed: {e}")
# ToDo - probably optimize
await SolanaAPI.DEX.save_token_info()
else:
logger.info("wh transaction is not a swap. skipping...")
except Exception as e:
logging.error(f"Error processing transaction notification: {str(e)}")
# Log the full traceback for debugging

View File

@ -75,14 +75,14 @@ enum TransactionStatus {
model Transaction {
id Int @id @default(autoincrement())
transactionId String
amount Float
date DateTime
amountUSD Float
transactionId String
amount Float @default(0.0)
date DateTime @default(now())
amountUSD Float @default(0.0)
holdingId Int
holding Holding @relation(fields: [holdingId], references: [id])
isClosed Boolean @default(false)
status TransactionStatus
status TransactionStatus @default(ORIGINAL)
originalId Int?
original Transaction? @relation("OriginalTransaction", fields: [originalId], references: [id])
relatedTo Transaction[] @relation("OriginalTransaction")