try parsing swap events

This commit is contained in:
Dobromir Popov 2024-10-05 19:04:36 +03:00
parent 2d92dc6340
commit 1eeaec6e84
3 changed files with 138 additions and 47 deletions

2
.env
View File

@ -28,4 +28,4 @@ OPENAI_API_KEY=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
# List models available from Groq # List models available from Groq
# aider --models groq/ # aider --models groq/
SUBSCRIPTION_ID='1518430' SUBSCRIPTION_ID='2217755'

View File

@ -19,6 +19,7 @@ from dotenv import load_dotenv,set_key
import aiohttp import aiohttp
from typing import List, Dict from typing import List, Dict
import requests import requests
import threading
load_dotenv() load_dotenv()
@ -28,7 +29,10 @@ app = Flask(__name__)
def get_latest_log_file(): def get_latest_log_file():
log_dir = './logs' log_dir = './logs'
try: try:
files = [f for f in os.listdir(log_dir) if os.path.isfile(os.path.join(log_dir, f))] # files = [f for f in os.listdir(log_dir) if os.path.isfile(os.path.join(log_dir, f))]
# filter files mask log_20241005_004103_143116.json
files = [f for f in os.listdir(log_dir) if os.path.isfile(os.path.join(log_dir, f)) and f.startswith('log_')]
latest_file = max(files, key=lambda x: os.path.getctime(os.path.join(log_dir, x))) latest_file = max(files, key=lambda x: os.path.getctime(os.path.join(log_dir, x)))
return os.path.join(log_dir, latest_file) return os.path.join(log_dir, latest_file)
except Exception as e: except Exception as e:
@ -242,7 +246,7 @@ async def get_token_balance(wallet_address, token_address):
logging.debug(f"No account found for {token_address} in {wallet_address}") logging.debug(f"No account found for {token_address} in {wallet_address}")
return 0 return 0
except Exception as e: except Exception as e:
logging.error(f"Error getting balance for {token_address} in {wallet_address}: {str(e)}") logging.error(f"Error getting balance for {token_address} in {wallet_address}: {str(e)} \r\n {e}")
return 0 return 0
@ -498,15 +502,6 @@ async def get_transaction_details_rpc(tx_signature):
} }
] ]
} }
# request = {
# "jsonrpc": "2.0",
# "id": 1,
# "method": "getConfirmedTransaction",
# "params": [
# tx_signature,
# "json"
# ]
# }
try: try:
response = requests.post(url, headers=headers, data=json.dumps(data)) response = requests.post(url, headers=headers, data=json.dumps(data))
response.raise_for_status() # Raises an error for bad responses response.raise_for_status() # Raises an error for bad responses
@ -533,6 +528,35 @@ async def save_log(log):
logging.error(f"Error saving RPC log: {e}") logging.error(f"Error saving RPC log: {e}")
def determine_token(pubkey, watched_tokens):
# Check if the pubkey matches any watched token addresses
for token, address in watched_tokens.items():
if pubkey == address:
return token
return "Unknown"
def parse_amount_from_logs(logs):
amount_in = 0
amount_out = 0
for log in logs:
if 'SwapEvent' in log:
# Extract amounts from the log line
parts = log.split('amount_in: ')[1].split(', amount_out: ')
amount_in = int(parts[0])
amount_out = int(parts[1].split(' ')[0])
return amount_in, amount_out
def extract_swap_details(instruction, logs, watched_tokens):
# Extract source and target tokens along with amounts
from_pubkey = instruction['accounts'][0]
to_pubkey = instruction['accounts'][1]
amount_in, amount_out = parse_amount_from_logs(logs)
return from_pubkey, to_pubkey, amount_in, amount_out
async def process_log(log_result): async def process_log(log_result):
if log_result['value']['err']: if log_result['value']['err']:
return return
@ -541,45 +565,96 @@ async def process_log(log_result):
logs = log_result['value']['logs'] logs = log_result['value']['logs']
try: try:
# Detect swap operations in logs
try: swap_operations = ['Program log: Instruction: Swap', 'Program log: Instruction: Swap2']
transaction = await get_transaction_details_rpc(tx_signature_str)
except Exception as e:
logging.error(f"Error fetching transaction details: {e}")
return
# Convert the base58 signature string to bytes
tx_signature = Signature(b58decode(tx_signature_str))
# Fetch transaction details
tx_result = await solana_client.get_transaction(tx_signature, max_supported_transaction_version=0)
#tx_result = await get_transaction_details(tx_signature_str)
if tx_result.value is None:
logging.error(f"Transaction not found: {tx_signature_str}")
return
transaction = tx_result.value.transaction
for log_entry in logs: for log_entry in logs:
if 'Program log: Instruction: Swap' in log_entry: if any(op in log_entry for op in swap_operations):
for instruction in message.instructions: try:
if instruction.program_id == TOKEN_ADDRESSES['SOL']: # ++ OLD using solana-py
from_pubkey = instruction.accounts[0] # # Convert the base58 signature string to bytes
to_pubkey = instruction.accounts[1] # tx_signature = Signature(b58decode(tx_signature_str))
amount = int(instruction.data, 16) / 1e9 # # Fetch transaction details
# tx_result = await solana_client.get_transaction(tx_signature, max_supported_transaction_version=0)
# #tx_result = await get_transaction_details(tx_signature_str)
# if tx_result.value is None:
# logging.error(f"Transaction not found: {tx_signature_str}")
# return
# transaction2 = tx_result.value.transaction
# -- OLD using solana-py
watched_tokens = await get_non_zero_token_balances(FOLLOWED_WALLET)
details = parse_swap_logs(logs)
transaction = await get_transaction_details_rpc(tx_signature_str)
instructions = transaction['transaction']['message']['instructions']
for instruction in instructions:
from_pubkey, to_pubkey, amount_in, amount_out = extract_swap_details(instruction, logs, watched_tokens)
if from_pubkey in watched_tokens.values() or to_pubkey in watched_tokens.values():
from_token = determine_token(from_pubkey, watched_tokens)
to_token = determine_token(to_pubkey, watched_tokens)
if from_pubkey == FOLLOWED_WALLET:
move = { move = {
'token': 'SOL', 'token': from_token,
'amount': amount, 'amount': amount_in,
'to_token': 'Unknown' 'to_token': to_token
} }
message_text = f"Swap detected:\nFrom: {from_pubkey}\nTo: {to_pubkey}\nAmount: {amount} SOL" message_text = (
f"Swap detected:\n"
f"From: {from_pubkey} ({from_token})\n"
f"To: {to_pubkey} ({to_token})\n"
f"Amount In: {amount_in}\n"
f"Amount Out: {amount_out}"
)
await send_telegram_message(message_text) await send_telegram_message(message_text)
await follow_move(move) await follow_move(move)
except Exception as e:
logging.error(f"Error fetching transaction details: {e}")
return
except Exception as e: except Exception as e:
logging.error(f"Error processing log: {e}") logging.error(f"Error processing log: {e}")
def parse_swap_logs(logs):
swap_details = {
"source_token_address": "",
"destination_token_address": "",
"amount_in": 0,
"amount_out": 0
}
for log in logs:
if "SwapEvent" in log:
# Extract amounts from SwapEvent
parts = log.split("amount_in: ")[1].split(", amount_out: ")
swap_details["amount_in"] = int(parts[0])
swap_details["amount_out"] = int(parts[1].split(" ")[0])
if "source_token_change:" in log:
# Extract source and destination token changes
changes = log.split(", ")
for change in changes:
key, value = change.split(": ")
if key == "source_token_change":
swap_details["amount_in"] = int(value)
elif key == "destination_token_change":
swap_details["amount_out"] = int(value)
if "Program log:" in log and len(log.split()) == 2:
# Extract token addresses (assuming they are logged as single entries)
token_address = log.split(": ")[1]
if not swap_details["source_token_address"]:
swap_details["source_token_address"] = token_address
elif not swap_details["destination_token_address"]:
swap_details["destination_token_address"] = token_address
return swap_details
async def on_logs(log): async def on_logs(log):
logging.debug(f"Received log: {log}") logging.debug(f"Received log: {log}")
await save_log(log) await save_log(log)
@ -662,15 +737,28 @@ async def subscribe_to_wallet():
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
async def main(): async def main():
# Initialize logging # Initialize logging
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
# logging.basicConfig(level=logging.INFO)
await send_telegram_message("Solana Agent Started. Connecting to mainnet...") await send_telegram_message("Solana Agent Started. Connecting to mainnet...")
#await subscribe_to_wallet() await subscribe_to_wallet()
def run_flask():
# Run Flask app without the reloader, so we can run the async main function
app.run(debug=False, port=3001, use_reloader=False)
if __name__ == '__main__': if __name__ == '__main__':
# Start Flask in a separate thread
flask_thread = threading.Thread(target=run_flask)
flask_thread.start()
app.run(debug=True,port=3001) # Create an event loop for the async tasks
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
# Start Flask in a separate thread
flask_thread = threading.Thread(target=run_flask)
flask_thread.start()
# Run the async main function
asyncio.run(main()) asyncio.run(main())

View File

@ -1,4 +1,7 @@
{'blockTime': 1728035375, 'meta': {'computeUnitsConsumed': 128099, 'err': None, 'fee': 97127, 'innerInstructions': [ {
'blockTime': 1728035375,
'meta': {'computeUnitsConsumed': 128099,
'err': None, 'fee': 97127, 'innerInstructions': [
{'index': 4, 'instructions': [ {'index': 4, 'instructions': [
{'accounts': ['7QXGLRjvyFAmxdRaP9Wk18KwWTMfspF4Na2sr3o3PzxV', 'A1BBtTYJd4i3xU8D6Tc2FzU6ZN4oXZWXKZnCxwbHXr8x', '7LVHkVPVosXoWgH1PykWa5PjsfnyBviCdxC8ZUHJDy5U', '8Ggb8th8pZv7eJbRmwkoyrDbSvwLPvMQN7QuHwjm113z', 'icV5K6iC8J7yyeP9YnJLH2jPRJYT7dBmzm844STxHkP', 'BAxX7eMuSoxF8E4s1Xz5ukcLPHjvfC8Wv9JjSZci7tZ7', 'Gvjgjv63zcQxLbcG2iYF3vcJ2nudRLEffN6hoo8p6Ewy', 'HtsJ5S6K4NM2vXaWZ8k49JAggBgPGrryJ21ezdPBoUC6', 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb', 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr', 'Faf89929Ni9fbg4gmVZTca7eW6NFg877Jqn6MizT3Gvw', 'So11111111111111111111111111111111111111112', 'DeRo9XMsizey3KWBWS3LHomFC7ZDny1NkFMZUEHgyX8a', 'E6ef1fYRvZ8ejtbdwaK1CDNAgpJxkWZ2pQsL4jaShjNU', '423toqoYT7obTRx8qmwVAeYZfMFRxkwwDzRAwJBd86K3' {'accounts': ['7QXGLRjvyFAmxdRaP9Wk18KwWTMfspF4Na2sr3o3PzxV', 'A1BBtTYJd4i3xU8D6Tc2FzU6ZN4oXZWXKZnCxwbHXr8x', '7LVHkVPVosXoWgH1PykWa5PjsfnyBviCdxC8ZUHJDy5U', '8Ggb8th8pZv7eJbRmwkoyrDbSvwLPvMQN7QuHwjm113z', 'icV5K6iC8J7yyeP9YnJLH2jPRJYT7dBmzm844STxHkP', 'BAxX7eMuSoxF8E4s1Xz5ukcLPHjvfC8Wv9JjSZci7tZ7', 'Gvjgjv63zcQxLbcG2iYF3vcJ2nudRLEffN6hoo8p6Ewy', 'HtsJ5S6K4NM2vXaWZ8k49JAggBgPGrryJ21ezdPBoUC6', 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb', 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr', 'Faf89929Ni9fbg4gmVZTca7eW6NFg877Jqn6MizT3Gvw', 'So11111111111111111111111111111111111111112', 'DeRo9XMsizey3KWBWS3LHomFC7ZDny1NkFMZUEHgyX8a', 'E6ef1fYRvZ8ejtbdwaK1CDNAgpJxkWZ2pQsL4jaShjNU', '423toqoYT7obTRx8qmwVAeYZfMFRxkwwDzRAwJBd86K3'
], 'data': 'ASCsAbe1UnDmvdhmjnwFdPMzcfkrayiNaFZ61j7FiXYFR5MbydQDnzNL', 'programId': 'CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK', 'stackHeight': 2 ], 'data': 'ASCsAbe1UnDmvdhmjnwFdPMzcfkrayiNaFZ61j7FiXYFR5MbydQDnzNL', 'programId': 'CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK', 'stackHeight': 2