swap works again reliably
This commit is contained in:
parent
291a5fed2c
commit
eebba5d6b4
@ -22,8 +22,8 @@ DEVELOPER_CHAT_ID="777826553"
|
||||
TELEGRAM_BOT_TOKEN="6749075936:AAHUHiPTDEIu6JH7S2fQdibwsu6JVG3FNG0"
|
||||
|
||||
DISPLAY_CURRENCY=USD
|
||||
FOLLOW_AMOUNT=3
|
||||
#FOLLOW_AMOUNT=percentage
|
||||
#FOLLOW_AMOUNT=3
|
||||
FOLLOW_AMOUNT=percentage
|
||||
|
||||
LIQUIDITY_TOKENS=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v,So11111111111111111111111111111111111111112
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import base64
|
||||
import struct
|
||||
import sys
|
||||
import os
|
||||
@ -50,7 +51,7 @@ from datetime import datetime
|
||||
from solana.rpc.types import TokenAccountOpts, TxOpts
|
||||
from typing import List, Dict, Any, Tuple
|
||||
import traceback
|
||||
import base64
|
||||
import httpx
|
||||
|
||||
# # # solders/solana libs (solana_client) # # #
|
||||
from spl.token._layouts import MINT_LAYOUT
|
||||
@ -234,6 +235,17 @@ class SolanaWS:
|
||||
class SolanaAPI:
|
||||
|
||||
pk = None
|
||||
|
||||
ENDPOINT_APIS_URL = {
|
||||
"QUOTE": "https://quote-api.jup.ag/v6/quote?",
|
||||
"SWAP": "https://quote-api.jup.ag/v6/swap",
|
||||
"OPEN_ORDER": "https://jup.ag/api/limit/v1/createOrder",
|
||||
"CANCEL_ORDERS": "https://jup.ag/api/limit/v1/cancelOrders",
|
||||
"QUERY_OPEN_ORDERS": "https://jup.ag/api/limit/v1/openOrders?wallet=",
|
||||
"QUERY_ORDER_HISTORY": "https://jup.ag/api/limit/v1/orderHistory",
|
||||
"QUERY_TRADE_HISTORY": "https://jup.ag/api/limit/v1/tradeHistory"
|
||||
}
|
||||
|
||||
def __init__(self, process_transaction_callback = None, on_initial_subscription_callback = None, on_bot_message=None):
|
||||
self.process_transaction = process_transaction_callback
|
||||
self.on_initial_subscription = on_initial_subscription_callback
|
||||
@ -247,6 +259,7 @@ class SolanaAPI:
|
||||
self.dex = DEX
|
||||
self.solana_ws = SolanaWS(on_message=self.process_transaction)
|
||||
|
||||
|
||||
async def process_messages(self, solana_ws):
|
||||
while True:
|
||||
message = await solana_ws.message_queue.get()
|
||||
@ -587,7 +600,7 @@ class SolanaAPI:
|
||||
except Exception as e:
|
||||
logging.error(f"Error fetching transaction details: {e}")
|
||||
|
||||
logging.info(f"({_} of {max_retries}) Waiting for transaction details for {transaction_id}. retry in {retry_delay} s.")
|
||||
logging.info(f"({_} of {max_retries}) Waiting for transaction details for {transaction_id} [ retry in {retry_delay} s.]")
|
||||
await asyncio.sleep(retry_delay)
|
||||
if backoff:
|
||||
retry_delay = retry_delay * 1.2
|
||||
@ -672,6 +685,100 @@ class SolanaAPI:
|
||||
logging.error(f"Error getting balance for {token_address} in {wallet_address}: {str(e)} \r\n {e}")
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
async def swap_on_jupiter(
|
||||
self,
|
||||
input_mint: str,
|
||||
output_mint: str,
|
||||
amount: int,
|
||||
slippage_bps: int = 1,
|
||||
swap_mode: str = "ExactIn",
|
||||
priority_fee: int = 0,
|
||||
only_direct_routes: bool = False,
|
||||
as_legacy_transaction: bool = False,
|
||||
exclude_dexes: list = None,
|
||||
max_accounts: int = None
|
||||
) -> str:
|
||||
"""Perform a swap on Jupiter with an option to set priority.
|
||||
|
||||
Args:
|
||||
input_mint (str): Input token mint.
|
||||
output_mint (str): Output token mint.
|
||||
amount (int): Amount to swap, considering token decimals.
|
||||
slippage_bps (int): Slippage in basis points.
|
||||
swap_mode (str): Swap mode, either 'ExactIn' or 'ExactOut'.
|
||||
priority_level (int): Priority level for the transaction fee.
|
||||
only_direct_routes (bool): Limit to direct routes only.
|
||||
as_legacy_transaction (bool): Use legacy transaction format.
|
||||
exclude_dexes (list): List of DEXes to exclude.
|
||||
max_accounts (int): Max number of accounts involved.
|
||||
|
||||
Returns:
|
||||
str: Serialized transaction data for the swap.
|
||||
"""
|
||||
# Get a quote from Jupiter
|
||||
quote_url = self.ENDPOINT_APIS_URL['QUOTE'] + "inputMint=" + input_mint + "&outputMint=" + output_mint + "&amount=" + str(amount) + "&swapMode=" + swap_mode + "&onlyDirectRoutes=" + str(only_direct_routes).lower() + "&asLegacyTransaction=" + str(as_legacy_transaction).lower()
|
||||
if slippage_bps:
|
||||
quote_url += "&slippageBps=" + str(slippage_bps)
|
||||
if exclude_dexes:
|
||||
quote_url += "&excludeDexes=" + ','.join(exclude_dexes).lower()
|
||||
if max_accounts:
|
||||
quote_url += "&maxAccounts=" + str(max_accounts)
|
||||
|
||||
quote_response = httpx.get(url=quote_url).json()
|
||||
|
||||
try:
|
||||
quote_response['routePlan']
|
||||
except:
|
||||
raise Exception(quote_response['error'])
|
||||
|
||||
|
||||
# Prepare transaction parameters
|
||||
fees = "auto" if priority_fee == 0 else priority_fee
|
||||
|
||||
pair = Keypair.from_bytes(base58.b58decode(self.pk))
|
||||
pubK = pair.pubkey().__str__()
|
||||
transaction_parameters = {
|
||||
"quoteResponse":quote_response,
|
||||
"userPublicKey": pubK,
|
||||
"wrapAndUnwrapSol": True,
|
||||
"computeUnitPriceMicroLamports":fees
|
||||
}
|
||||
# This will raise an error if data isn't JSON serializable
|
||||
json_string = json.dumps(transaction_parameters)
|
||||
validated_data = json.loads(json_string)
|
||||
|
||||
response = httpx.post(url=self.ENDPOINT_APIS_URL['SWAP'], json=validated_data)
|
||||
response_data = response.json()
|
||||
|
||||
# headers = {
|
||||
# 'Content-Type': 'application/json',
|
||||
# 'Accept': 'application/json'
|
||||
# }
|
||||
# response = requests.request("POST", self.ENDPOINT_APIS_URL['SWAP'], headers=headers, data=validated_data)
|
||||
# response_data = response.json()
|
||||
|
||||
# result = response_data['swapTransaction']
|
||||
|
||||
# # # Send the swap request to Jupiter
|
||||
# async with httpx.AsyncClient() as client:
|
||||
# response = await client.post(
|
||||
# self.ENDPOINT_APIS_URL['SWAP'],
|
||||
# json=validated_data
|
||||
# )
|
||||
# response_data = response.json()
|
||||
|
||||
result = response_data['swapTransaction']
|
||||
try:
|
||||
response_data['swapTransaction']
|
||||
return response_data['swapTransaction']
|
||||
except:
|
||||
raise Exception(response_data['error'])
|
||||
# Return the serialized transaction
|
||||
return result
|
||||
|
||||
|
||||
async def follow_move(self,move):
|
||||
try:
|
||||
your_balances = await DEX.get_wallet_balances(YOUR_WALLET, doGetTokenName=False)
|
||||
@ -756,101 +863,27 @@ class SolanaAPI:
|
||||
|
||||
if self.pk is None:
|
||||
self.pk = await get_pk()
|
||||
for retry in range(1):
|
||||
for retry in range(2):
|
||||
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(
|
||||
|
||||
# https://station.jup.ag/api-v6/post-swap
|
||||
#transaction_data = await jupiter.swap(
|
||||
transaction_data = await self.swap_on_jupiter(
|
||||
input_mint=move['token_in'],
|
||||
output_mint=move['token_out'],
|
||||
amount=amount_lamports,
|
||||
slippage_bps=300, # Increased to 3%
|
||||
#priority_fee= 100_000 commented for auto
|
||||
)
|
||||
logging.info(f"Initiating move. Transaction data:\n {transaction_data}")
|
||||
|
||||
raw_transaction = VersionedTransaction.from_bytes(base64.b64decode(transaction_data))
|
||||
fee = await async_client.get_fee_for_message(raw_transaction.message)
|
||||
priority_fee = fee.value or 100_000
|
||||
|
||||
|
||||
# fee = await async_client.get_fee_for_message(transaction_data)
|
||||
# priority_fee = 0
|
||||
# if PRIORITY:
|
||||
# priority_fee = 100 * PRIORITY # defalt if we can't get current rate
|
||||
# try:
|
||||
# priority_fee = await calculate_priority_fee(async_client, PRIORITY)
|
||||
# except:
|
||||
# logging.warning(f"Failed to get priority fee. Using default value: {priority_fee}")
|
||||
|
||||
# error_logger.info(f"Initiating move. Transaction data:\n {transaction_data}")
|
||||
# message = raw_transaction.message
|
||||
|
||||
# Add compute budget instruction to set priority fee
|
||||
# from solders.compute_budget import set_compute_unit_price
|
||||
# compute_budget_instruction = set_compute_unit_price(priority_fee)
|
||||
|
||||
# Add compute budget instruction to the transaction
|
||||
# raw_transaction.message.add_instruction(compute_budget_instruction)
|
||||
|
||||
# Create new instructions list with compute budget instruction first
|
||||
# new_instructions = [compute_budget_instruction] + list(raw_transaction.message.instructions)
|
||||
|
||||
# # Create a new message with the updated instructions
|
||||
# from solders.message import MessageV0
|
||||
# new_message = MessageV0(
|
||||
# instructions=new_instructions,
|
||||
# address_table_lookups=raw_transaction.message.address_table_lookups,
|
||||
# recent_blockhash=raw_transaction.message.recent_blockhash,
|
||||
# payer=raw_transaction.message.payer
|
||||
# )
|
||||
|
||||
# working - no priority fee
|
||||
signature = private_key.sign_message(message.to_bytes_versioned(raw_transaction.message))
|
||||
signed_txn = VersionedTransaction.populate(raw_transaction.message, [signature])
|
||||
# # # # # # # # # # # # # # # # # #
|
||||
# new - not working
|
||||
# signature = private_key.sign_message(new_message.to_bytes_versioned())
|
||||
# signed_txn = VersionedTransaction.populate(new_message, [signature])
|
||||
# from solders.compute_budget import set_compute_unit_price, ID as COMPUTE_BUDGET_ID
|
||||
|
||||
# priority_fee_ix = set_compute_unit_price(priority_fee)
|
||||
|
||||
# # Get the current message
|
||||
# msg = raw_transaction.message
|
||||
|
||||
# new_account_keys = msg.account_keys
|
||||
# program_id_index = 0
|
||||
# if COMPUTE_BUDGET_ID not in msg.account_keys:
|
||||
# new_account_keys = msg.account_keys + [COMPUTE_BUDGET_ID]
|
||||
# program_id_index = len(msg.account_keys) # Index of the newly added program ID
|
||||
# else:
|
||||
# new_account_keys = msg.account_keys
|
||||
# program_id_index = msg.account_keys.index(COMPUTE_BUDGET_ID)
|
||||
|
||||
# # Compile the priority fee instruction
|
||||
# compiled_priority_fee_ix = CompiledInstruction(
|
||||
# program_id_index=program_id_index,
|
||||
# accounts=bytes([]),
|
||||
# data=priority_fee_ix.data
|
||||
# )
|
||||
|
||||
# # Add priority fee instruction at the beginning
|
||||
# new_instructions = [compiled_priority_fee_ix] + msg.instructions
|
||||
|
||||
# # Create new message with updated instructions
|
||||
# new_message = Message.new_with_compiled_instructions(
|
||||
# num_required_signatures=msg.header.num_required_signatures,
|
||||
# num_readonly_signed_accounts=msg.header.num_readonly_signed_accounts,
|
||||
# num_readonly_unsigned_accounts=msg.header.num_readonly_unsigned_accounts,
|
||||
# account_keys=new_account_keys,
|
||||
# recent_blockhash=msg.recent_blockhash,
|
||||
# instructions=new_instructions
|
||||
# )
|
||||
|
||||
# signature = private_key.sign_message(message.to_bytes_versioned(new_message))
|
||||
# signed_txn = VersionedTransaction.populate(new_message, [signature])
|
||||
# # # # # # # # # # # # # # # # # #
|
||||
|
||||
opts = TxOpts(
|
||||
skip_preflight=False,
|
||||
@ -861,7 +894,7 @@ class SolanaAPI:
|
||||
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>"
|
||||
notification = f"Follow {move.get('type', 'SWAP').upper()} Success:\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=10, backoff=False)
|
||||
@ -879,7 +912,7 @@ class SolanaAPI:
|
||||
# error_logger.error(error_message)
|
||||
# error_logger.exception(e)
|
||||
await telegram_utils.send_telegram_message(error_message)
|
||||
amount = int(amount * 0.9)
|
||||
amount_to_swap = int(amount_to_swap * 0.9)
|
||||
|
||||
await DEX.get_wallet_balances(YOUR_WALLET, doGetTokenName=False)
|
||||
|
||||
@ -922,19 +955,20 @@ class SolanaAPI:
|
||||
except Exception as e:
|
||||
logging.error(f"Error following move: {e}")
|
||||
|
||||
async def calculate_priority_fee(async_client, priority_level=5):
|
||||
async def calculate_priority_fee(self, async_client, priority_level=5):
|
||||
try:
|
||||
recent_fees = await async_client.get_recent_prioritization_fees()
|
||||
|
||||
if not recent_fees:
|
||||
return 1000 # fallback value in microlamports
|
||||
if not recent_fees or len(recent_fees) == 0:
|
||||
return 100_000 # fallback value in microlamports
|
||||
|
||||
# Calculate average and max fees
|
||||
fees = [fee.prioritization_fee for fee in recent_fees]
|
||||
fees = [slot_fee.prioritization_fee for slot_fee in recent_fees]
|
||||
avg_fee = sum(fees) / len(fees)
|
||||
max_fee = max(fees)
|
||||
|
||||
# Calculate base fee (weighted average between mean and max)
|
||||
base_fee = (2 * avg_fee + max_fee) / 3 # You can adjust this weighting
|
||||
base_fee = (2 * avg_fee + max_fee) / 3
|
||||
|
||||
# Calculate scaling factor (priority_level / 5)
|
||||
# priority 5 = 1x base_fee
|
||||
@ -945,8 +979,11 @@ class SolanaAPI:
|
||||
final_fee = int(base_fee * scaling_factor)
|
||||
|
||||
# Set minimum fee to avoid too low values
|
||||
return max(final_fee, 100) # minimum 100 microlamports
|
||||
return max(final_fee, 100_001) # minimum 100,000 microlamports
|
||||
|
||||
except Exception as e:
|
||||
logging.warning(f"Error calculating priority fee: {str(e)}")
|
||||
return 100_000 # fallback value in microlamports
|
||||
|
||||
class SolanaDEX:
|
||||
def __init__(self, DISPLAY_CURRENCY: str):
|
||||
|
@ -10,7 +10,7 @@ from flask_login import LoginManager, UserMixin, login_user, login_required, log
|
||||
import secrets
|
||||
import json
|
||||
# from crypto.sol.config import LIQUIDITY_TOKENS
|
||||
from config import LIQUIDITY_TOKENS
|
||||
from config import LIQUIDITY_TOKENS, YOUR_WALLET
|
||||
|
||||
from modules import storage, utils, SolanaAPI
|
||||
from modules.utils import async_safe_call, decode_instruction_data
|
||||
@ -24,6 +24,8 @@ def init_app(tr_handler=None):
|
||||
global on_transaction
|
||||
on_transaction = tr_handler
|
||||
app = Flask(__name__, template_folder='../templates', static_folder='../static')
|
||||
|
||||
app.secret_key = secrets.token_hex(16)
|
||||
executor = ThreadPoolExecutor(max_workers=10) # Adjust the number of workers as needed
|
||||
login_manager = LoginManager(app)
|
||||
login_manager.login_view = 'login'
|
||||
@ -423,6 +425,11 @@ def init_app(tr_handler=None):
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
|
||||
@login_manager.unauthorized_handler
|
||||
def unauthorized():
|
||||
return redirect('/login?next=' + request.path)
|
||||
# return jsonify({'error': 'Unauthorized'}), 401
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
@ -460,12 +467,14 @@ def init_app(tr_handler=None):
|
||||
|
||||
@app.route('/wallet/<int:wallet_id>/transactions', methods=['GET'])
|
||||
@login_required
|
||||
@login_required
|
||||
def get_transactions(wallet_id):
|
||||
transactions = storage.get_transactions(wallet_id)
|
||||
return jsonify(transactions)
|
||||
|
||||
@app.route('/wallet/<int:wallet_id>/holdings', methods=['GET'])
|
||||
@login_required
|
||||
@login_required
|
||||
def get_holdings(wallet_id):
|
||||
holdings = storage.get_holdings(wallet_id)
|
||||
return jsonify(holdings)
|
||||
|
Loading…
x
Reference in New Issue
Block a user