initial follow move implemented!

This commit is contained in:
Dobromir Popov 2024-10-05 22:39:35 +03:00
parent 413e8399d2
commit dc256e119a

View File

@ -7,12 +7,16 @@ from solana.transaction import Signature
from solana.rpc.websocket_api import connect from solana.rpc.websocket_api import connect
from solana.rpc.types import TokenAccountOpts, TxOpts from solana.rpc.types import TokenAccountOpts, TxOpts
from solana.rpc.commitment import Confirmed from solana.rpc.commitment import Confirmed
from base58 import b58decode
from solders.signature import Signature
from solders.pubkey import Pubkey from solders.pubkey import Pubkey
from solders.keypair import Keypair from solders.keypair import Keypair
from solders.transaction import VersionedTransaction
from solders.transaction import Transaction from solders.transaction import Transaction
from solders.message import Message from solders.message import Message
from solders.instruction import Instruction from solders.instruction import Instruction
from solders.hash import Hash from solders.hash import Hash
from jupiter_python_sdk.jupiter import Jupiter, Jupiter_DCA
from dexscreener import DexscreenerClient from dexscreener import DexscreenerClient
from telegram import Bot from telegram import Bot
from telegram.constants import ParseMode from telegram.constants import ParseMode
@ -25,7 +29,7 @@ import aiohttp
from typing import List, Dict from typing import List, Dict
import requests import requests
import threading import threading
import re
load_dotenv() load_dotenv()
app = Flask(__name__) app = Flask(__name__)
@ -245,11 +249,6 @@ async def get_token_balance(wallet_address, token_address):
commitment=Confirmed commitment=Confirmed
) )
# response = await solana_client.get_token_accounts_by_owner(
# Pubkey.from_string(wallet_address),
# {"mint": Pubkey.from_string(token_address)}
# )
if response['result']['value']: if response['result']['value']:
balance = await solana_client.get_token_account_balance( balance = await solana_client.get_token_account_balance(
response['result']['value'][0]['pubkey'] response['result']['value'][0]['pubkey']
@ -367,56 +366,9 @@ async def get_wallet_balances(wallet_address):
return balances, token_addresses return balances, token_addresses
async def get_converted_balances(wallet_address):
balances, token_addresses = await get_wallet_balances(wallet_address)
token_prices = await get_token_prices(token_addresses)
sol_price = await get_sol_price()
converted_balances = await convert_balances_to_currency(balances, token_prices, sol_price)
return converted_balances
async def send_initial_wallet_states(followed_wallet, your_wallet):
followed_balances = await get_converted_balances(followed_wallet)
your_balances = await get_converted_balances(your_wallet)
message = f"Initial Wallet States (Non-zero balances in {DISPLAY_CURRENCY}):\n\n"
message += f"Followed Wallet ({followed_wallet}):\n"
for token, amount in followed_balances.items():
if amount and amount > 0:
message += f"{token}: {amount:.2f}\n"
message += f"\nYour Wallet ({your_wallet}):\n"
for token, amount in your_balances.items():
if amount and amount > 0:
message += f"{token}: {amount:.2f}\n"
message += "\nMonitored Tokens:\n"
# Add monitored tokens logic here if needed
await bot.send_message(chat_id=CHAT_ID, text=message)
async def get_non_zero_token_balances(wallet_address):
followed_wallet_balances, followed_token_addresses = await get_wallet_balances(FOLLOWED_WALLET)
# return non zero balances for followed wallet
non_zero_balances = {token: address for token, address in {**followed_wallet_balances}.items() if address is not None and address > 0}
logging.info(f"Getting non-zero balances for wallet: {wallet_address}")
return non_zero_balances
# non_zero_balances = {}
# logging.info(f"Getting non-zero balances for wallet: {wallet_address}")
# for token, address in TOKEN_ADDRESSES.items():
# balance = await get_token_balance_rpc(wallet_address, address)
# if balance > 0:
# non_zero_balances[token] = address
# logging.debug(f"Non-zero balance for {token}: {balance}")
# return non_zero_balances
async def list_initial_wallet_states(): async def list_initial_wallet_states():
global TOKEN_ADDRESSES, FOLLOWED_WALLET_VALUE, YOUR_WALLET_VALUE global TOKEN_ADDRESSES, FOLLOWED_WALLET_VALUE, YOUR_WALLET_VALUE
@ -463,101 +415,9 @@ async def list_initial_wallet_states():
await send_telegram_message(message) await send_telegram_message(message)
async def follow_move(move):
followed_balances = await get_wallet_balances(FOLLOWED_WALLET)
your_balances = await get_wallet_balances(YOUR_WALLET)
if move['token'] not in followed_balances or move['token'] not in your_balances:
logging.error(f"Invalid token: {move['token']}")
return
followed_balance = followed_balances[move['token']]
your_balance = your_balances[move['token']]
proportion = your_balance / followed_balance if followed_balance > 0 else 0
amount_to_swap = move['amount'] * proportion
if your_balance >= amount_to_swap:
# Perform the swap using Jupiter API
try:
swap_result = perform_swap(move['token'], move['to_token'], amount_to_swap)
if swap_result['success']:
message = (
f"<b>Move Followed:</b>\n"
f"Swapped {amount_to_swap:.6f} {move['token']} "
f"for {swap_result['outputAmount']:.6f} {move['to_token']}"
)
logging.info(message)
else:
message = (
f"<b>Swap Failed:</b>\n"
f"Error: {swap_result['error']}"
)
logging.warning(message)
await send_telegram_message(message)
except Exception as e:
error_message = f"<b>Swap Error:</b>\n{str(e)}"
logging.error(error_message)
await send_telegram_message(error_message)
else:
message = (
f"<b>Move Failed:</b>\n"
f"Insufficient balance to swap {amount_to_swap:.6f} {move['token']}"
)
logging.warning(message)
await send_telegram_message(message)
def perform_swap(input_token, output_token, amount):
# Jupiter API endpoint
url = "https://quote-api.jup.ag/v4/quote"
# Parameters for the API request
params = {
"inputMint": input_token,
"outputMint": output_token,
"amount": int(amount * 10**9), # Convert to lamports
"slippageBps": 50, # 0.5% slippage
}
try:
response = requests.get(url, params=params)
response.raise_for_status()
quote = response.json()
# Get the best route
route = quote['data'][0]
# Perform the swap
swap_url = "https://quote-api.jup.ag/v4/swap"
swap_data = {
"quoteResponse": route,
"userPublicKey": YOUR_WALLET,
"wrapUnwrapSOL": True
}
swap_response = requests.post(swap_url, json=swap_data)
swap_response.raise_for_status()
swap_result = swap_response.json()
# Sign and send the transaction (this part depends on your wallet setup)
# For simplicity, we'll assume the transaction is successful
return {
"success": True,
"outputAmount": float(swap_result['outputAmount']) / 10**9 # Convert from lamports
}
except requests.exceptions.RequestException as e:
return {
"success": False,
"error": str(e)
}
from base58 import b58decode
from solders.pubkey import Pubkey
from solders.transaction import Transaction
from solders.signature import Signature
async def get_transaction_details_rpc(tx_signature, readfromDump=False): async def get_transaction_details_rpc(tx_signature, readfromDump=False):
url = SOLANA_HTTP_URL url = SOLANA_HTTP_URL
@ -609,34 +469,6 @@ 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']:
@ -651,69 +483,18 @@ async def process_log(log_result):
for log_entry in logs: for log_entry in logs:
if any(op in log_entry for op in swap_operations): if any(op in log_entry for op in swap_operations):
try: try:
watched_tokens = await get_non_zero_token_balances(FOLLOWED_WALLET)
details = await parse_swap_logs(logs) details = await parse_swap_logs(logs)
# transaction = await get_transaction_details_rpc(tx_signature_str, True)
# tokens = []
# source_token = None
# target_token = None
# # Check inner instructions for transfers and mints
# for instruction_set in transaction.get('meta', {}).get('innerInstructions', []):
# for instruction in instruction_set.get('instructions', []):
# if 'parsed' in instruction and 'info' in instruction['parsed']:
# info = instruction['parsed']['info']
# amount = None
# mint = 'Unknown'
# # Check for amount in transfer and transferChecked instructions
# if 'amount' in info:
# amount = info['amount']
# elif 'tokenAmount' in info and 'amount' in info['tokenAmount']:
# amount = info['tokenAmount']['amount']
# # Get mint if available
# if 'mint' in info:
# mint = info['mint']
# if amount is not None:
# tokens.append({'amount': amount, 'mint': mint})
# # Identify source and target tokens
# if 'source' in info:
# source_token = info['source']
# if 'destination' in info:
# target_token = info['destination']
# # Check post token balances for final token states
# for balance in transaction.get('postTokenBalances', []):
# amount = balance['uiTokenAmount']['amount']
# mint = balance['mint']
# tokens.append({'amount': amount, 'mint': mint})
# Get amount_in, amount_out, tokens, and USD value
swap_details = {
'amount_in': details['total_amount_in'],
'amount_out': details['total_amount_out'],
'tokens': tokens,
'source_token': source_token,
'target_token': target_token
}
message_text = ( message_text = (
f"Swap detected:\n" f"Swap detected:\n"
f"Amount In: {swap_details['amount_in']}\n" f"Order ID: {details['order_id']}\n"
f"Amount Out: {swap_details['amount_out']}\n" f"Token In: {details['token_in']}\n"
f"Source Token: {swap_details['source_token']}\n" f"Token Out: {details['token_out']}\n"
f"Target Token: {swap_details['target_token']}\n" f"Amount In USD: {details['amount_in_USD']}\n"
f"Tokens: {tokens}"
) )
await send_telegram_message(message_text) await send_telegram_message(message_text)
await follow_move(swap_details) await follow_move(details)
except Exception as e: except Exception as e:
logging.error(f"Error fetching transaction details: {e}") logging.error(f"Error fetching transaction details: {e}")
@ -724,59 +505,123 @@ async def process_log(log_result):
# "Program log: Instruction: Swap2", # "Program log: Instruction: Swap2",
# "Program log: order_id: 13985890735038016", # "Program log: order_id: 13985890735038016",
# "Program log: AbrMJWfDVRZ2EWCQ1xSCpoVeVgZNpq1U2AoYG98oRXfn", source # "Program log: AbrMJWfDVRZ2EWCQ1xSCpoVeVgZNpq1U2AoYG98oRXfn", source
# "Program log: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", target # "Program log: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", target
# "Program log: before_source_balance: 58730110139, before_destination_balance: 202377778, amount_in: 58730110139, expect_amount_out: 270109505, min_return: 267408410",
# "Program log: after_source_balance: 0, after_destination_balance: 472509072",
# "Program log: source_token_change: 58730110139, destination_token_change: 270131294",
async def parse_swap_logs(logs): async def parse_swap_logs(logs):
global TOKEN_ADDRESSES
token_in = None token_in = None
token_out = None token_out = None
amount_in = 0 amount_in = 0
amount_out_expected = 0 amount_out_expected = 0
amount_out_actual = 0 amount_out_actual = 0
order_id = None
for log in logs: for log in logs:
# Check for token identifiers
if "Program log:" in log: if "Program log:" in log:
if "Swap2" in log: if "order_id:" in log:
order_id = log.split("order_id: ")[-1].strip()
elif "Swap2" in log:
token_in = None token_in = None
token_out = None token_out = None
elif "order_id" in log: elif not token_in:
order_id = log.split("order_id: ")[-1] token_in = log.split("Program log: ")[-1].strip()
else: elif not token_out:
if not token_in: token_out = log.split("Program log: ")[-1].strip()
token_in = log.split("Program log: ")[-1].strip()
elif not token_out:
token_out = log.split("Program log: ")[-1].strip()
elif "source_token_change:" in log: # Use regex to find amount_in and amount_out patterns
if "amount_in" in log or "amount_out" in log:
amount_matches = re.findall(r"(amount_in|amount_out): (\d+)", log)
for amount_type, value in amount_matches:
if amount_type == "amount_in":
amount_in = int(value)/1e6
elif amount_type == "amount_out":
amount_out_expected = int(value)/1e6
# Check for source_token_change and destination_token_change for additional details
elif "source_token_change:" in log or "destination_token_change:" in log:
changes = log.split(", ") changes = log.split(", ")
for change in changes: for change in changes:
if "source_token_change" in change: if "source_token_change" in change:
amount_in = int(change.split(": ")[-1]) amount_in = int(change.split(": ")[-1])/1e6
elif "destination_token_change" in change: elif "destination_token_change" in change:
amount_out_expected = int(change.split(": ")[-1]) amount_out_actual = int(change.split(": ")[-1])/1e6
amount_out_actual = amount_out_expected # Modify if actual is derived separately
token_prices = await get_token_prices([token_in, token_out]) token_prices = await get_token_prices([token_in, token_out])
amount_in_usd = amount_in * token_prices.get(token_in, 0) amount_in_usd = amount_in * token_prices.get(token_in, 0)
amount_out_usd = amount_out_actual * token_prices.get(token_out, 0) amount_out_usd = amount_out_actual * token_prices.get(token_out, 0)
return { return {
"order_id": order_id,
"token_in": token_in, "token_in": token_in,
"token_out": token_out, "token_out": token_out,
"amount_in": amount_in, "amount_in": amount_in,
"amount_out_expected": amount_out_expected, "amount_out_expected": amount_out_expected,
"amount_out_actual": amount_out_actual, "amount_out_actual": amount_out_actual,
"amount_in_USD": amount_in_usd, "amount_in_USD": amount_in_usd ,
"amount_out_USD": amount_out_usd, "amount_out_USD": amount_out_usd,
} }
async def follow_move(move):
followed_balances = await get_wallet_balances(FOLLOWED_WALLET)
your_balances = await get_wallet_balances(YOUR_WALLET)
followed_balance = followed_balances[move.token_in]
your_balance = your_balances[move.token_in]
proportion = your_balance / followed_balance if followed_balance > 0 else 0
amount_to_swap = move.amount_in * proportion
if your_balance >= amount_to_swap:
try:
# Initialize Jupiter client
private_key = Keypair.from_bytes(base58.b58decode(os.getenv("PK")))
async_client = AsyncClient(SOLANA_WS_URL)
jupiter = Jupiter(async_client, private_key)
# Perform the swap
transaction_data = await jupiter.swap(
input_mint=move.token_in,
output_mint=move.token_out,
amount=int(amount_to_swap),
slippage_bps=1,
)
raw_transaction = VersionedTransaction.from_bytes(base64.b64decode(transaction_data))
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)
result = await async_client.send_raw_transaction(txn=bytes(signed_txn), opts=opts)
transaction_id = json.loads(result.to_json())['result']
message = (
f"<b>Move Followed:</b>\n"
f"Swapped {amount_to_swap:.6f} {move['token']} "
f"for {transaction_data['outputAmount']:.6f} {move['to_token']}"
)
logging.info(message)
await send_telegram_message(message)
except Exception as e:
error_message = f"<b>Swap Error:</b>\n{str(e)}"
logging.error(error_message)
await send_telegram_message(error_message)
else:
message = (
f"<b>Move Failed:</b>\n"
f"Insufficient balance to swap {amount_to_swap:.6f} {move['token']}"
)
logging.warning(message)
await send_telegram_message(message)
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)