turns-00074.parquet:31316
41e549592c6876e7a5ab1b2fdegenerate_repetitionAbsentFinal dense release
Select one behavior. Every returned turn has one binary label: Present or Absent. Source: final dense boolean release.
41e549592c6876e7a5ab1b2fUser: Hi, what's up? User: Hi, what's up? Assistant:
Hello! Not much, just here and ready to help. How about you?
41e549592c6876e7a5ab1b2fUser: Hi, what's up? User: Hi, what's up? Assistant:
Hello! Not much, just here and ready to help. How about you?
749a1bc16ccf0de67347cacbUser: Hi, what's up? User: Hi, what's up? Assistant:
Hello! Not much, just here and ready to help. How can I assist you today?
63650dd97169c16fe03e45d1User: hi, how are you? User: hi, how are you? Assistant:
Hello! I'm doing well, thank you. How can I assist you today?
749a1bc16ccf0de67347cacbUser: Hi, what's up? User: Hi, what's up? Assistant:
Hello! Not much, just here and ready to help. How can I assist you today?
aa2d037f6ec392d5beb3fb3duse base64;
use log::{debug, error, info};
use pyo3::exceptions::{PyFileNotFoundError, PyRuntimeError, PyValueError};
use pyo3::prelude::*;
use pyo3::types::PyDict;
use solana_client::rpc_client::RpcClient;
use solana_client::rpc_config::RpcSendTransactionConfig;
use solana_sdk::commitment_config::{CommitmentConfig, CommitmentLevel};
use solana_sdk::compute_budget::ComputeBudgetInstruction;
use solana_sdk::hash::Hash;
use solana_sdk::instruction::{AccountMeta, Instruction};
use solana_sdk::program_pack::Pack;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{read_keypair_file, Keypair, Signature, Signer};
use solana_sdk::transaction::Transaction;
use solana_transaction_status::UiTransactionEncoding;
use spl_associated_token_account::{
get_associated_token_address, instruction::create_associated_token_account_idempotent,
};
use spl_token;
use std::env;
use std::path::Path;
use std::str::FromStr;
use std::sync::Arc;
use std::time::{Duration, Instant};
// Fast blockhash caching using RwLock instead of Mutex
use parking_lot::RwLock;
// Concurrent map for caching
use dashmap::DashMap;
// Optional async support
use once_cell::sync::OnceCell;
use tokio::runtime::Runtime;
// Add header support for authentication
use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_TYPE};
// Fast RwLock-based blockhash cache
struct FastBlockhashCache {
blockhash: RwLock<Hash>,
last_update: RwLock<Instant>,
update_interval: Duration,
rpc_client: Arc<RpcClient>,
}
impl FastBlockhashCache {
fn new(rpc_client: Arc<RpcClient>, update_interval_seconds: u64) -> Self {
// More robust initial blockhash retrieval with retry logic
let mut attempts = 0;
let max_attempts = 5;
let mut blockhash = None;
while attempts < max_attempts {
match rpc_client.get_latest_blockhash() {
Ok(hash) => {
blockhash = Some(hash);
info!("Successfully retrieved initial blockhash");
break;
}
Err(e) => {
error!("Error getting initial blockhash (attempt {}/{}): {}",
attempts + 1, max_attempts, e);
attempts += 1;
if attempts < max_attempts {
std::thread::sleep(Duration::from_millis(500));
}
}
}
}
// If all attempts failed, use a default blockhash (will be updated later)
let blockhash = blockhash.unwrap_or_else(|| {
error!("Warning: Using placeholder blockhash, will update shortly");
Hash::default()
});
Self {
blockhash: RwLock::new(blockhash),
last_update: RwLock::new(Instant::now()),
update_interval: Duration::from_secs(update_interval_seconds),
rpc_client,
}
}
fn get_blockhash(&self) -> Hash {
// Check if we need to update using a read lock first
let should_update = {
let last_update = self.last_update.read();
last_update.elapsed() >= self.update_interval
};
// If update is needed, get a new blockhash
if should_update {
// Simply do it synchronously for now to avoid threading issues
match self.rpc_client.get_latest_blockhash() {
Ok(new_hash) => {
*self.blockhash.write() = new_hash;
*self.last_update.write() = Instant::now();
}
Err(e) => {
error!("Failed to update blockhash: {}", e);
}
}
}
// Return current blockhash
*self.blockhash.read()
}
fn force_update(&self) -> Hash {
match self.rpc_client.get_latest_blockhash() {
Ok(new_hash) => {
*self.blockhash.write() = new_hash;
*self.last_update.write() = Instant::now();
new_hash
}
Err(e) => {
error!("Failed to force update blockhash: {}", e);
*self.blockhash.read() // Return current one
}
}
}
}
// Optimized API response cache
struct JupiterCache {
routes: DashMap<String, (serde_json::Value, Instant)>,
ttl: Duration,
}
impl JupiterCache {
fn new(ttl_seconds: u64) -> Self {
Self {
routes: DashMap::new(),
ttl: Duration::from_secs(ttl_seconds),
}
}
fn get(&self, key: &str) -> Option<serde_json::Value> {
if let Some(entry) = self.routes.get(key) {
if entry.1.elapsed() < self.ttl {
return Some(entry.0.clone());
}
}
None
}
fn set(&self, key: String, value: serde_json::Value) {
self.routes.insert(key, (value, Instant::now()));
}
fn clear_expired(&self) {
let now = Instant::now();
let expired_keys: Vec<String> = self
.routes
.iter()
.filter(|r| now.duration_since(r.value().1) >= self.ttl)
.map(|r| r.key().clone())
.collect();
for key in expired_keys {
self.routes.remove(&key);
}
}
}
// Token decimals cache using DashMap
static TOKEN_DECIMALS_CACHE: once_cell::sync::Lazy<DashMap<String, u8>> =
once_cell::sync::Lazy::new(|| DashMap::new());
// Fast blockhash cache
static BLOCKHASH_CACHE: once_cell::sync::Lazy<OnceCell<Arc<FastBlockhashCache>>> =
once_cell::sync::Lazy::new(|| OnceCell::new());
// Jupiter API cache
static JUPITER_CACHE: once_cell::sync::Lazy<OnceCell<Arc<JupiterCache>>> =
once_cell::sync::Lazy::new(|| OnceCell::new());
// Core constants
const LAMPORTS_PER_SOL: u64 = 1_000_000_000;
const WSOL_ADDRESS: &str = "So11111111111111111111111111111111111111112";
const TOKEN_PROGRAM_ID: &str = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
const RAYDIUM_PROGRAM_ID: &str = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
const SERUM_PROGRAM_ID: &str = "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin";
// Jupiter API constants - Updated for v6
const JUPITER_QUOTE_API: &str = "https://quote-api.jup.ag/v6/quote";
const JUPITER_SWAP_API: &str = "https://quote-api.jup.ag/v6/swap-instructions";
// Transaction and trading constants
const RAYDIUM_SWAP_INSTRUCTION: u8 = 9;
const DEFAULT_COMPUTE_LIMIT: u32 = 200_000;
const DEFAULT_PRIORITY_FEE: u64 = 1_000_000; // 0.001 SOL
const DEFAULT_SLIPPAGE: f64 = 0.01; // 1%
const DEFAULT_MAX_RETRIES: usize = 3;
// Balance safety thresholds
const DEFAULT_MIN_SOL_BALANCE: f64 = 0.03;
const DEFAULT_MIN_WSOL_BALANCE: f64 = 0.02;
// Simplified error handling
struct PyError(String);
impl From<PyError> for PyErr {
fn from(err: PyError) -> PyErr {
PyRuntimeError::new_err(err.0)
}
}
// Initialize tokio runtime for async operations if needed
fn get_tokio_runtime() -> &'static Runtime {
static RUNTIME: OnceCell<Runtime> = OnceCell::new();
RUNTIME.get_or_init(|| Runtime::new().expect("Failed to create tokio runtime"))
}
/// Represents dynamic Raydium swap accounts
#[derive(Debug, Clone)]
struct RaydiumSwapAccounts {
amm_id: String,
amm_authority: String,
amm_open_orders: String,
amm_target_orders: String,
pool_coin_token_account: String,
pool_pc_token_account: String,
serum_program_id: String,
serum_market: String,
serum_bids: String,
serum_asks: String,
serum_event_queue: String,
serum_coin_vault: String,
serum_pc_vault: String,
serum_vault_signer: String,
token_mint: String,
token_program_id: String,
}
/// Main Solana trading class
#[pyclass]
struct SolanaTrader {
keypair: Arc<Keypair>,
rpc_client: Arc<RpcClient>,
rpc_url: String,
ws_url: String,
compute_limit: u32,
priority_fee: u64,
skip_preflight: bool,
max_retries: usize,
min_sol_balance: f64,
min_wsol_balance: f64,
wsol_address: String,
token_program_id: String,
system_program_id: String,
raydium_program_id: String,
serum_program_id: String,
keypair_path: String,
http_client: reqwest::blocking::Client,
api_key: Option<String>, // Add API key field for authentication
}
#[pymethods]
impl SolanaTrader {
/// Initialize the SolanaTrader with configuration
#[new]
fn new(
rpc_url: String,
ws_url: String,
keypair_path: String,
compute_limit: Option<u32>,
api_key: Option<String>, // Add API key parameter
) -> PyResult<Self> {
// Tokio runtime initialization
let _ = get_tokio_runtime();
// Load environment variables if .env file exists
let _ = dotenv::dotenv();
// Get API key from parameter or environment variable
let api_key = api_key.or_else(|| env::var("HELIUS_API_KEY").ok());
if let Some(key) = &api_key {
info!("Using API key for authentication");
}
// Keypair loading with robust error handling
let keypair = if Path::new(&keypair_path).exists() {
// First, try to read as JSON
match read_keypair_file(&keypair_path) {
Ok(kp) => kp,
Err(_) => {
// If JSON fails, try to read as binary
match std::fs::read(&keypair_path) {
Ok(bytes) => {
if bytes.len() == 64 {
match Keypair::from_bytes(&bytes) {
Ok(kp) => kp,
Err(e) => {
return Err(PyValueError::new_err(format!(
"Invalid binary keypair: {}",
e
)))
}
}
} else {
// Try base58 decoding
match bs58::decode(std::str::from_utf8(&bytes).unwrap_or(""))
.into_vec()
.map(|secret_key| Keypair::from_bytes(&secret_key))
{
Ok(Ok(kp)) => kp,
_ => {
return Err(PyValueError::new_err(
"Could not parse keypair file in any format",
))
}
}
}
}
Err(e) => {
return Err(PyRuntimeError::new_err(format!(
"Failed to read keypair file: {}",
e
)))
}
}
}
}
} else {
return Err(PyFileNotFoundError::new_err(format!(
"Keypair file not found: {}",
keypair_path
)));
};
// Create RPC client with processed commitment and authentication
// Fix: Use available methods in solana 1.18.x
let rpc_client = {
// Set timeout and commitment
let commitment_config = CommitmentConfig::processed();
let timeout = Duration::from_secs(30);
// Create client with timeout and commitment
// For Solana 1.18.x, we need to use the correct available constructors
let client = RpcClient::new_with_timeout_and_commitment(
rpc_url.clone(),
timeout,
commitment_config,
);
Arc::new(client)
};
// Read environment variables with fallbacks
let compute = compute_limit.unwrap_or_else(|| {
env::var("COMPUTE_LIMIT")
.map(|v| v.parse::<u32>().unwrap_or(DEFAULT_COMPUTE_LIMIT))
.unwrap_or(DEFAULT_COMPUTE_LIMIT)
});
let priority_fee = env::var("PRIORITY_FEE")
.map(|v| v.parse::<u64>().unwrap_or(DEFAULT_PRIORITY_FEE))
.unwrap_or(DEFAULT_PRIORITY_FEE);
let skip_preflight = env::var("SKIP_PREFLIGHT")
.map(|v| v.parse::<bool>().unwrap_or(true))
.unwrap_or(true);
let max_retries = env::var("MAX_RETRIES")
.map(|v| v.parse::<usize>().unwrap_or(DEFAULT_MAX_RETRIES))
.unwrap_or(DEFAULT_MAX_RETRIES);
let min_sol_balance = env::var("MIN_SOL_BALANCE")
.map(|v| v.parse::<f64>().unwrap_or(DEFAULT_MIN_SOL_BALANCE))
.unwrap_or(DEFAULT_MIN_SOL_BALANCE);
let min_wsol_balance = env::var("MIN_WSOL_BALANCE")
.map(|v| v.parse::<f64>().unwrap_or(DEFAULT_MIN_WSOL_BALANCE))
.unwrap_or(DEFAULT_MIN_WSOL_BALANCE);
// Create optimized HTTP client with authentication
let http_client = {
let builder = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(30))
.pool_max_idle_per_host(32);
// Create default headers
let mut headers = HeaderMap::new();
// Add Solana CLI User-Agent header
if let Ok(value) = HeaderValue::from_str("solana-cli 1.18.23 (src:aeb3a2e1; feat:4215500110, client:SolanaLabs)") {
headers.insert(reqwest::header::USER_AGENT, value);
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
}
// Add authentication headers if API key is provided
if let Some(key) = &api_key {
let auth_value = format!("Bearer {}", key);
if let Ok(value) = HeaderValue::from_str(&auth_value) {
headers.insert(AUTHORIZATION, value);
}
}
// Build the client with headers
builder.default_headers(headers).build()
}.unwrap_or_else(|_| reqwest::blocking::Client::new());
// Initialize fast blockhash cache if not already initialized
BLOCKHASH_CACHE.get_or_init(|| {
let cache = FastBlockhashCache::new(
rpc_client.clone(),
2, // Update every 2 seconds
);
Arc::new(cache)
});
// Initialize Jupiter cache if not already initialized
JUPITER_CACHE.get_or_init(|| {
let cache = JupiterCache::new(10); // 10 second TTL
Arc::new(cache)
});
// Start a periodic cache cleanup thread
{
let jupiter_cache = JUPITER_CACHE.get().unwrap().clone();
std::thread::spawn(move || loop {
std::thread::sleep(Duration::from_secs(60));
jupiter_cache.clear_expired();
debug!("Cleared expired Jupiter cache entries");
});
}
info!(
"Initialized trader with compute_limit: {}, priority_fee: {}",
compute, priority_fee
);
Ok(Self {
keypair: Arc::new(keypair),
rpc_client,
rpc_url,
ws_url,
compute_limit: compute,
priority_fee,
skip_preflight,
max_retries,
min_sol_balance,
min_wsol_balance,
wsol_address: WSOL_ADDRESS.to_string(),
token_program_id: TOKEN_PROGRAM_ID.to_string(),
system_program_id: "11111111111111111111111111111111".to_string(),
raydium_program_id: RAYDIUM_PROGRAM_ID.to_string(),
serum_program_id: SERUM_PROGRAM_ID.to_string(),
keypair_path,
http_client,
api_key,
})
}
/// Execute token swap using Jupiter (Solana's DEX aggregator) - Optimized implementation
fn execute_jupiter_swap(
&self,
py: Python<'_>,
input_mint: String,
output_mint: String,
amount_sol: f64,
slippage_percent: Option<f64>,
priority_multiplier: Option<u64>,
) -> PyResult<String> {
// Convert amount to lamports
let amount_lamports = (amount_sol * LAMPORTS_PER_SOL as f64).round() as u64;
// Dynamic slippage calculation
let slippage = match slippage_percent {
Some(val) => val / 100.0,
None => DEFAULT_SLIPPAGE,
};
let slippage_bps = (slippage * 10000.0) as u32;
// Priority fee calculation
let priority_multiplier_value = priority_multiplier.unwrap_or(1);
let priority_fee_value = self.priority_fee * priority_multiplier_value;
// Ensure token accounts exist for both input and output before releasing the GIL
if input_mint != WSOL_ADDRESS {
self.create_token_account_if_needed(py, input_mint.clone())?;
}
if output_mint != WSOL_ADDRESS {
self.create_token_account_if_needed(py, output_mint.clone())?;
}
// Now we can safely release the GIL for network operations
py.allow_threads(|| {
// Step 1: Try to get from cache first
let cache_key = format!(
"{}:{}:{}:{}",
input_mint, output_mint, amount_lamports, slippage_bps
);
let jupiter_cache = JUPITER_CACHE.get().unwrap();
let quote_response = match jupiter_cache.get(&cache_key) {
Some(cached) => {
debug!("Jupiter quote cache hit: {}", cache_key);
cached
}
None => {
// Step 1: Get a quote from Jupiter API
let quote_url = format!(
"{}?inputMint={}&outputMint={}&amount={}&slippageBps={}",
JUPITER_QUOTE_API, input_mint, output_mint, amount_lamports, slippage_bps
);
info!("Getting Jupiter quote: {}", quote_url);
// Implement retry logic
let mut retries = 0;
let max_retries = 3;
loop {
// Add authentication headers if needed
let mut request = self.http_client.get("e_url);
if let Some(key) = &self.api_key {
request = request.header(AUTHORIZATION, format!("Bearer {}", key));
}
match request.send() {
Ok(response) => {
if response.status().is_success() {
match response.json::<serde_json::Value>() {
Ok(quote_data) => {
// Check for API errors
if quote_data.get("error").is_some() {
let error_msg = quote_data["error"]
.as_str()
.unwrap_or("Unknown error");
error!("Jupiter quote error: {}", error_msg);
return Err(PyError(format!(
"Jupiter quote error: {}",
error_msg
))
.into());
}
// Cache the result
jupiter_cache
.set(cache_key.clone(), quote_data.clone());
// Return quote
break quote_data;
}
Err(e) => {
error!("Failed to parse Jupiter quote response: {}", e);
return Err(PyError(format!(
"Failed to parse Jupiter quote: {}",
e
))
.into());
}
}
} else {
let status = response.status();
error!("Jupiter API error: HTTP {}", status);
// For server errors, retry with backoff
if status.is_server_error() && retries < max_retries {
retries += 1;
let wait_time = 2u64.pow(retries) * 100; // Exponential backoff
std::thread::sleep(Duration::from_millis(wait_time));
continue;
}
return Err(PyError(format!(
"Jupiter API error: HTTP {}",
status
))
.into());
}
}
Err(e) => {
error!("Jupiter API request failed: {}", e);
// Network errors are retried
if retries < max_retries {
retries += 1;
let wait_time = 2u64.pow(retries) * 100; // Exponential backoff
std::thread::sleep(Duration::from_millis(wait_time));
continue;
}
return Err(
PyError(format!("Jupiter API request failed: {}", e)).into()
);
}
}
}
}
};
// Step 2: Use the swap-instructions endpoint
let instr_payload = serde_json::json!({
"quoteResponse": quote_response,
"userPublicKey": self.keypair.pubkey().to_string(),
"computeUnitPriceMicroLamports": priority_fee_value,
"wrapUnwrapSOL": true,
"onlyDirectRoutes": true,
"maxHops": 1,
"maxAccounts": 12
});
info!("Getting Jupiter swap instructions");
// Implement retry logic for instructions
let mut retries = 0;
let max_retries = 3;
let instr_response = loop {
// Add authentication headers if needed
let mut request = self.http_client.post(JUPITER_SWAP_API).json(&instr_payload);
if let Some(key) = &self.api_key {
request = request.header(AUTHORIZATION, format!("Bearer {}", key));
}
match request.send() {
Ok(response) => {
if response.status().is_success() {
match response.json::<serde_json::Value>() {
Ok(instr_data) => {
// Check for API errors
if instr_data.get("error").is_some() {
let error_msg =
instr_data["error"].as_str().unwrap_or("Unknown error");
error!("Jupiter instructions error: {}", error_msg);
return Err(PyError(format!(
"Jupiter instructions error: {}",
error_msg
))
.into());
}
break instr_data;
}
Err(e) => {
error!("Failed to parse Jupiter instructions response: {}", e);
return Err(PyError(format!(
"Failed to parse Jupiter instructions response: {}",
e
))
.into());
}
}
} else {
let status = response.status();
error!("Jupiter API error: HTTP {}", status);
// For server errors, retry with backoff
if status.is_server_error() && retries < max_retries {
retries += 1;
let wait_time = 2u64.pow(retries) * 100; // Exponential backoff
std::thread::sleep(Duration::from_millis(wait_time));
continue;
}
return Err(
PyError(format!("Jupiter API error: HTTP {}", status)).into()
);
}
}
Err(e) => {
error!("Jupiter API request failed: {}", e);
// Network errors are retried
if retries < max_retries {
retries += 1;
let wait_time = 2u64.pow(retries) * 100; // Exponential backoff
std::thread::sleep(Duration::from_millis(wait_time));
continue;
}
return Err(PyError(format!("Jupiter API request failed: {}", e)).into());
}
}
};
// Step 3: Create essential instructions to keep transaction size small
let mut essential_instructions = Vec::new();
// Add compute budget instructions
essential_instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(
self.compute_limit,
));
essential_instructions.push(ComputeBudgetInstruction::set_compute_unit_price(
priority_fee_value,
));
// Parse setupInstructions if they exist
if let Some(setup_instructions) = instr_response
.get("setupInstructions")
.and_then(|v| v.as_array())
{
for instr in setup_instructions {
let program_id_str = instr["programId"].as_str().ok_or_else(|| {
error!("Program ID not found in setup instruction");
PyError("Program ID not found in setup instruction".to_string())
})?;
let program_id = Pubkey::from_str(program_id_str).map_err(|e| {
error!("Invalid program ID: {}", e);
PyError(format!("Invalid program ID: {}", e))
})?;
let accounts_arr = instr["accounts"].as_array().ok_or_else(|| {
error!("Accounts not found in setup instruction");
PyError("Accounts not found in setup instruction".to_string())
})?;
let mut accounts = Vec::new();
for account in accounts_arr {
let pubkey_str = account["pubkey"].as_str().ok_or_else(|| {
error!("Pubkey not found in account");
PyError("Pubkey not found in account".to_string())
})?;
let pubkey = Pubkey::from_str(pubkey_str).map_err(|e| {
error!("Invalid pubkey: {}", e);
PyError(format!("Invalid pubkey: {}", e))
})?;
let is_signer = account["isSigner"].as_bool().unwrap_or(false);
let is_writable = account["isWritable"].as_bool().unwrap_or(false);
let account_meta = if is_writable {
if is_signer {
AccountMeta::new(pubkey, true)
} else {
AccountMeta::new(pubkey, false)
}
} else {
if is_signer {
AccountMeta::new_readonly(pubkey, true)
} else {
AccountMeta::new_readonly(pubkey, false)
}
};
accounts.push(account_meta);
}
let data_str = instr["data"].as_str().ok_or_else(|| {
error!("Data not found in setup instruction");
PyError("Data not found in setup instruction".to_string())
})?;
let data = base64::decode(data_str).map_err(|e| {
error!("Failed to decode setup instruction data: {}", e);
PyError(format!("Failed to decode setup instruction data: {}", e))
})?;
let instruction = Instruction {
program_id,
accounts,
data,
};
essential_instructions.push(instruction);
}
}
// Add swapInstruction if it exists (this is critical)
if let Some(swap_instr) = instr_response.get("swapInstruction") {
let program_id_str = swap_instr["programId"].as_str().ok_or_else(|| {
error!("Program ID not found in swap instruction");
PyError("Program ID not found in swap instruction".to_string())
})?;
let program_id = Pubkey::from_str(program_id_str).map_err(|e| {
error!("Invalid program ID: {}", e);
PyError(format!("Invalid program ID: {}", e))
})?;
let accounts_arr = swap_instr["accounts"].as_array().ok_or_else(|| {
error!("Accounts not found in swap instruction");
PyError("Accounts not found in swap instruction".to_string())
})?;
let mut accounts = Vec::new();
for account in accounts_arr {
let pubkey_str = account["pubkey"].as_str().ok_or_else(|| {
error!("Pubkey not found in account");
PyError("Pubkey not found in account".to_string())
})?;
let pubkey = Pubkey::from_str(pubkey_str).map_err(|e| {
error!("Invalid pubkey: {}", e);
PyError(format!("Invalid pubkey: {}", e))
})?;
let is_signer = account["isSigner"].as_bool().unwrap_or(false);
let is_writable = account["isWritable"].as_bool().unwrap_or(false);
let account_meta = if is_writable {
if is_signer {
AccountMeta::new(pubkey, true)
} else {
AccountMeta::new(pubkey, false)
}
} else {
if is_signer {
AccountMeta::new_readonly(pubkey, true)
} else {
AccountMeta::new_readonly(pubkey, false)
}
};
accounts.push(account_meta);
}
let data_str = swap_instr["data"].as_str().ok_or_else(|| {
error!("Data not found in swap instruction");
PyError("Data not found in swap instruction".to_string())
})?;
let data = base64::decode(data_str).map_err(|e| {
error!("Failed to decode swap instruction data: {}", e);
PyError(format!("Failed to decode swap instruction data: {}", e))
})?;
let instruction = Instruction {
program_id,
accounts,
data,
};
essential_instructions.push(instruction);
} else {
error!("No swap instruction found in response");
return Err(
PyError("Jupiter swap instruction not found in response".to_string()).into(),
);
}
// Add cleanupInstruction if it exists
if let Some(cleanup_instr) = instr_response.get("cleanupInstruction") {
let program_id_str = cleanup_instr["programId"].as_str().ok_or_else(|| {
error!("Program ID not found in cleanup instruction");
PyError("Program ID not found in cleanup instruction".to_string())
})?;
let program_id = Pubkey::from_str(program_id_str).map_err(|e| {
error!("Invalid program ID: {}", e);
PyError(format!("Invalid program ID: {}", e))
})?;
let accounts_arr = cleanup_instr["accounts"].as_array().ok_or_else(|| {
error!("Accounts not found in cleanup instruction");
PyError("Accounts not found in cleanup instruction".to_string())
})?;
let mut accounts = Vec::new();
for account in accounts_arr {
let pubkey_str = account["pubkey"].as_str().ok_or_else(|| {
error!("Pubkey not found in account");
PyError("Pubkey not found in account".to_string())
})?;
let pubkey = Pubkey::from_str(pubkey_str).map_err(|e| {
error!("Invalid pubkey: {}", e);
PyError(format!("Invalid pubkey: {}", e))
})?;
let is_signer = account["isSigner"].as_bool().unwrap_or(false);
let is_writable = account["isWritable"].as_bool().unwrap_or(false);
let account_meta = if is_writable {
if is_signer {
AccountMeta::new(pubkey, true)
} else {
AccountMeta::new(pubkey, false)
}
} else {
if is_signer {
AccountMeta::new_readonly(pubkey, true)
} else {
AccountMeta::new_readonly(pubkey, false)
}
};
accounts.push(account_meta);
}
let data_str = cleanup_instr["data"].as_str().ok_or_else(|| {
error!("Data not found in cleanup instruction");
PyError("Data not found in cleanup instruction".to_string())
})?;
let data = base64::decode(data_str).map_err(|e| {
error!("Failed to decode cleanup instruction data: {}", e);
PyError(format!("Failed to decode cleanup instruction data: {}", e))
})?;
let instruction = Instruction {
program_id,
accounts,
data,
};
essential_instructions.push(instruction);
}
// Check if we have any instructions beyond compute budget
if essential_instructions.len() <= 2 {
error!("No Jupiter instructions found in response");
return Err(
PyError("Jupiter instructions not found in response".to_string()).into(),
);
}
// Step 4: Get recent blockhash from optimized cache
let blockhash_cache = BLOCKHASH_CACHE.get().unwrap();
let blockhash = blockhash_cache.get_blockhash();
debug!("Using cached blockhash: {}", blockhash);
// Step 5: Create and sign transaction with essential instructions
let mut transaction = Transaction::new_signed_with_payer(
&essential_instructions,
Some(&self.keypair.pubkey()),
&[&self.keypair],
blockhash,
);
// Step 6: Send transaction with optimized retry logic
let send_config = RpcSendTransactionConfig {
skip_preflight: self.skip_preflight,
preflight_commitment: Some(CommitmentLevel::Processed),
encoding: Some(UiTransactionEncoding::Base64),
max_retries: Some(self.max_retries),
..Default::default()
};
// Track if we need to retry with new blockhash
let mut retry_count = 0;
let max_retries = 2; // Specific to blockhash errors
while retry_count <= max_retries {
match self
.rpc_client
.send_transaction_with_config(&transaction, send_config.clone())
{
Ok(signature) => {
info!(
"Jupiter swap executed: {} SOL from {} to {} with signature {}",
amount_sol, input_mint, output_mint, signature
);
return Ok(signature.to_string());
}
Err(e) => {
let error_str = e.to_string();
// If it's a blockhash error, try with fresh blockhash
if error_str.contains("blockhash") && retry_count < max_retries {
error!(
"Blockhash error, retrying with fresh blockhash: {}",
error_str
);
// Force refresh the blockhash
let new_blockhash = blockhash_cache.force_update();
// Create new transaction with fresh blockhash
let new_transaction = Transaction::new_signed_with_payer(
&essential_instructions,
Some(&self.keypair.pubkey()),
&[&self.keypair],
new_blockhash,
);
// Update transaction for next retry
transaction = new_transaction;
retry_count += 1;
continue;
}
error!("Failed to send transaction: {}", e);
return Err(PyError(format!("Failed to send transaction: {}", e)).into());
}
}
}
Err(PyError("Max retries exceeded".to_string()).into())
})
}
/// Sell tokens for SOL via Jupiter - Optimized version
fn sell_token_for_sol_via_jupiter(
&self,
py: Python<'_>,
token_mint: String,
amount_tokens: f64,
slippage_percent: Option<f64>,
priority_multiplier: Option<u64>,
) -> PyResult<String> {
// Get token decimals first while holding the GIL
let token_decimals = self.get_token_decimals(py, token_mint.clone())?;
// Convert amount to token units
let amount_in = (amount_tokens * 10f64.powi(token_decimals as i32)) as u64;
// Dynamic slippage calculation
let slippage = match slippage_percent {
Some(val) => val / 100.0,
None => DEFAULT_SLIPPAGE,
};
let slippage_bps = (slippage * 10000.0) as u32;
// Priority fee calculation
let priority_multiplier_value = priority_multiplier.unwrap_or(1);
let priority_fee_value = self.priority_fee * priority_multiplier_value;
// SOL mint address
let sol_mint = WSOL_ADDRESS.to_string();
// Now release the GIL for network operations
py.allow_threads(|| {
// Step 1: Check if we have this in cache
let cache_key = format!("{}:{}:{}:{}", token_mint, sol_mint, amount_in, slippage_bps);
let jupiter_cache = JUPITER_CACHE.get().unwrap();
let quote_response = match jupiter_cache.get(&cache_key) {
Some(cached) => {
debug!("Jupiter quote cache hit: {}", cache_key);
cached
}
None => {
// Step 1: Get a quote from Jupiter API
let quote_url = format!(
"{}?inputMint={}&outputMint={}&amount={}&slippageBps={}",
JUPITER_QUOTE_API, token_mint, sol_mint, amount_in, slippage_bps
);
info!("Getting Jupiter quote for token sell: {}", quote_url);
// Implement retry logic
let mut retries = 0;
let max_retries = 3;
loop {
// Add authentication headers if needed
let mut request = self.http_client.get("e_url);
if let Some(key) = &self.api_key {
request = request.header(AUTHORIZATION, format!("Bearer {}", key));
}
match request.send() {
Ok(response) => {
if response.status().is_success() {
match response.json::<serde_json::Value>() {
Ok(quote_data) => {
// Check for API errors
if quote_data.get("error").is_some() {
let error_msg = quote_data["error"]
.as_str()
.unwrap_or("Unknown error");
error!("Jupiter quote error: {}", error_msg);
return Err(PyError(format!(
"Jupiter quote error: {}",
error_msg
))
.into());
}
// Cache the result
jupiter_cache
.set(cache_key.clone(), quote_data.clone());
// Return quote
break quote_data;
}
Err(e) => {
error!("Failed to parse Jupiter quote response: {}", e);
return Err(PyError(format!(
"Failed to parse Jupiter quote: {}",
e
))
.into());
}
}
} else {
let status = response.status();
error!("Jupiter API error: HTTP {}", status);
// For server errors, retry with backoff
if status.is_server_error() && retries < max_retries {
retries += 1;
let wait_time = 2u64.pow(retries) * 100; // Exponential backoff
std::thread::sleep(Duration::from_millis(wait_time));
continue;
}
return Err(PyError(format!(
"Jupiter API error: HTTP {}",
status
))
.into());
}
}
Err(e) => {
error!("Jupiter API request failed: {}", e);
// Network errors are retried
if retries < max_retries {
retries += 1;
let wait_time = 2u64.pow(retries) * 100; // Exponential backoff
std::thread::sleep(Duration::from_millis(wait_time));
continue;
}
return Err(
PyError(format!("Jupiter API request failed: {}", e)).into()
);
}
}
}
}
};
// Step 2: Use the swap-instructions endpoint
let instr_payload = serde_json::json!({
"quoteResponse": quote_response,
"userPublicKey": self.keypair.pubkey().to_string(),
"computeUnitPriceMicroLamports": priority_fee_value,
"wrapUnwrapSOL": true,
// Since it's a token sale, we want fast execution
"onlyDirectRoutes": true,
"maxHops": 1
});
info!("Getting Jupiter swap instructions for sell");
// Implement retry logic for instructions
let mut retries = 0;
let max_retries = 3;
let instr_response = loop {
// Add authentication headers if needed
let mut request = self.http_client.post(JUPITER_SWAP_API).json(&instr_payload);
if let Some(key) = &self.api_key {
request = request.header(AUTHORIZATION, format!("Bearer {}", key));
}
match request.send() {
Ok(response) => {
if response.status().is_success() {
match response.json::<serde_json::Value>() {
Ok(instr_data) => {
// Check for API errors
if instr_data.get("error").is_some() {
let error_msg =
instr_data["error"].as_str().unwrap_or("Unknown error");
error!("Jupiter instructions error: {}", error_msg);
return Err(PyError(format!(
"Jupiter instructions error: {}",
error_msg
))
.into());
}
break instr_data;
}
Err(e) => {
error!("Failed to parse Jupiter instructions response: {}", e);
return Err(PyError(format!(
"Failed to parse Jupiter instructions response: {}",
e
))
.into());
}
}
} else {
let status = response.status();
error!("Jupiter API error: HTTP {}", status);
// For server errors, retry with backoff
if status.is_server_error() && retries < max_retries {
retries += 1;
let wait_time = 2u64.pow(retries) * 100; // Exponential backoff
std::thread::sleep(Duration::from_millis(wait_time));
continue;
}
return Err(
PyError(format!("Jupiter API error: HTTP {}", status)).into()
);
}
}
Err(e) => {
error!("Jupiter API request failed: {}", e);
// Network errors are retried
if retries < max_retries {
retries += 1;
let wait_time = 2u64.pow(retries) * 100; // Exponential backoff
std::thread::sleep(Duration::from_millis(wait_time));
continue;
}
return Err(PyError(format!("Jupiter API request failed: {}", e)).into());
}
}
};
// Step 3: Create essential instructions to keep transaction size small
let mut essential_instructions = Vec::new();
// Add compute budget instructions
essential_instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(
self.compute_limit,
));
essential_instructions.push(ComputeBudgetInstruction::set_compute_unit_price(
priority_fee_value,
));
// Parse setupInstructions if they exist
if let Some(setup_instructions) = instr_response
.get("setupInstructions")
.and_then(|v| v.as_array())
{
for instr in setup_instructions {
let program_id_str = instr["programId"].as_str().ok_or_else(|| {
error!("Program ID not found in setup instruction");
PyError("Program ID not found in setup instruction".to_string())
})?;
let program_id = Pubkey::from_str(program_id_str).map_err(|e| {
error!("Invalid program ID: {}", e);
PyError(format!("Invalid program ID: {}", e))
})?;
let accounts_arr = instr["accounts"].as_array().ok_or_else(|| {
error!("Accounts not found in setup instruction");
PyError("Accounts not found in setup instruction".to_string())
})?;
let mut accounts = Vec::new();
for account in accounts_arr {
let pubkey_str = account["pubkey"].as_str().ok_or_else(|| {
error!("Pubkey not found in account");
PyError("Pubkey not found in account".to_string())
})?;
let pubkey = Pubkey::from_str(pubkey_str).map_err(|e| {
error!("Invalid pubkey: {}", e);
PyError(format!("Invalid pubkey: {}", e))
})?;
let is_signer = account["isSigner"].as_bool().unwrap_or(false);
let is_writable = account["isWritable"].as_bool().unwrap_or(false);
let account_meta = if is_writable {
if is_signer {
AccountMeta::new(pubkey, true)
} else {
AccountMeta::new(pubkey, false)
}
} else {
if is_signer {
AccountMeta::new_readonly(pubkey, true)
} else {
AccountMeta::new_readonly(pubkey, false)
}
};
accounts.push(account_meta);
}
let data_str = instr["data"].as_str().ok_or_else(|| {
error!("Data not found in setup instruction");
PyError("Data not found in setup instruction".to_string())
})?;
let data = base64::decode(data_str).map_err(|e| {
error!("Failed to decode setup instruction data: {}", e);
PyError(format!("Failed to decode setup instruction data: {}", e))
})?;
let instruction = Instruction {
program_id,
accounts,
data,
};
essential_instructions.push(instruction);
}
}
// Add swapInstruction if it exists (this is critical)
if let Some(swap_instr) = instr_response.get("swapInstruction") {
let program_id_str = swap_instr["programId"].as_str().ok_or_else(|| {
error!("Program ID not found in swap instruction");
PyError("Program ID not found in swap instruction".to_string())
})?;
let program_id = Pubkey::from_str(program_id_str).map_err(|e| {
error!("Invalid program ID: {}", e);
PyError(format!("Invalid program ID: {}", e))
})?;
let accounts_arr = swap_instr["accounts"].as_array().ok_or_else(|| {
error!("Accounts not found in swap instruction");
PyError("Accounts not found in swap instruction".to_string())
})?;
let mut accounts = Vec::new();
for account in accounts_arr {
let pubkey_str = account["pubkey"].as_str().ok_or_else(|| {
error!("Pubkey not found in account");
PyError("Pubkey not found in account".to_string())
})?;
let pubkey = Pubkey::from_str(pubkey_str).map_err(|e| {
error!("Invalid pubkey: {}", e);
PyError(format!("Invalid pubkey: {}", e))
})?;
let is_signer = account["isSigner"].as_bool().unwrap_or(false);
let is_writable = account["isWritable"].as_bool().unwrap_or(false);
let account_meta = if is_writable {
if is_signer {
AccountMeta::new(pubkey, true)
} else {
AccountMeta::new(pubkey, false)
}
} else {
if is_signer {
AccountMeta::new_readonly(pubkey, true)
} else {
AccountMeta::new_readonly(pubkey, false)
}
};
accounts.push(account_meta);
}
let data_str = swap_instr["data"].as_str().ok_or_else(|| {
error!("Data not found in swap instruction");
PyError("Data not found in swap instruction".to_string())
})?;
let data = base64::decode(data_str).map_err(|e| {
error!("Failed to decode swap instruction data: {}", e);
PyError(format!("Failed to decode swap instruction data: {}", e))
})?;
let instruction = Instruction {
program_id,
accounts,
data,
};
essential_instructions.push(instruction);
} else {
error!("No swap instruction found in response");
return Err(
PyError("Jupiter swap instruction not found in response".to_string()).into(),
);
}
// Add cleanupInstruction if it exists
if let Some(cleanup_instr) = instr_response.get("cleanupInstruction") {
let program_id_str = cleanup_instr["programId"].as_str().ok_or_else(|| {
error!("Program ID not found in cleanup instruction");
PyError("Program ID not found in cleanup instruction".to_string())
})?;
let program_id = Pubkey::from_str(program_id_str).map_err(|e| {
error!("Invalid program ID: {}", e);
PyError(format!("Invalid program ID: {}", e))
})?;
let accounts_arr = cleanup_instr["accounts"].as_array().ok_or_else(|| {
error!("Accounts not found in cleanup instruction");
PyError("Accounts not found in cleanup instruction".to_string())
})?;
let mut accounts = Vec::new();
for account in accounts_arr {
let pubkey_str = account["pubkey"].as_str().ok_or_else(|| {
error!("Pubkey not found in account");
PyError("Pubkey not found in account".to_string())
})?;
let pubkey = Pubkey::from_str(pubkey_str).map_err(|e| {
error!("Invalid pubkey: {}", e);
PyError(format!("Invalid pubkey: {}", e))
})?;
let is_signer = account["isSigner"].as_bool().unwrap_or(false);
let is_writable = account["isWritable"].as_bool().unwrap_or(false);
let account_meta = if is_writable {
if is_signer {
AccountMeta::new(pubkey, true)
} else {
AccountMeta::new(pubkey, false)
}
} else {
if is_signer {
AccountMeta::new_readonly(pubkey, true)
} else {
AccountMeta::new_readonly(pubkey, false)
}
};
accounts.push(account_meta);
}
let data_str = cleanup_instr["data"].as_str().ok_or_else(|| {
error!("Data not found in cleanup instruction");
PyError("Data not found in cleanup instruction".to_string())
})?;
let data = base64::decode(data_str).map_err(|e| {
error!("Failed to decode cleanup instruction data: {}", e);
PyError(format!("Failed to decode cleanup instruction data: {}", e))
})?;
let instruction = Instruction {
program_id,
accounts,
data,
};
essential_instructions.push(instruction);
}
// Check if we have any instructions beyond compute budget
if essential_instructions.len() <= 2 {
error!("No Jupiter instructions found in response");
return Err(
PyError("Jupiter instructions not found in response".to_string()).into(),
);
}
// Step 4: Get recent blockhash from optimized cache
let blockhash_cache = BLOCKHASH_CACHE.get().unwrap();
let blockhash = blockhash_cache.get_blockhash();
debug!("Using cached blockhash: {}", blockhash);
// Step 5: Create and sign transaction with essential instructions
let mut transaction = Transaction::new_signed_with_payer(
&essential_instructions,
Some(&self.keypair.pubkey()),
&[&self.keypair],
blockhash,
);
// Step 6: Send transaction with optimized retry logic
let send_config = RpcSendTransactionConfig {
skip_preflight: self.skip_preflight,
preflight_commitment: Some(CommitmentLevel::Processed),
encoding: Some(UiTransactionEncoding::Base64),
max_retries: Some(self.max_retries),
..Default::default()
};
// Track if we need to retry with new blockhash
let mut retry_count = 0;
let max_retries = 2; // Specific to blockhash errors
while retry_count <= max_retries {
match self
.rpc_client
.send_transaction_with_config(&transaction, send_config.clone())
{
Ok(signature) => {
info!(
"Jupiter token sell executed: {} tokens to SOL with signature {}",
amount_tokens, signature
);
return Ok(signature.to_string());
}
Err(e) => {
let error_str = e.to_string();
// If it's a blockhash error, try with fresh blockhash
if error_str.contains("blockhash") && retry_count < max_retries {
error!(
"Blockhash error, retrying with fresh blockhash: {}",
error_str
);
// Force refresh the blockhash
let new_blockhash = blockhash_cache.force_update();
// Create new transaction with fresh blockhash
let new_transaction = Transaction::new_signed_with_payer(
&essential_instructions,
Some(&self.keypair.pubkey()),
&[&self.keypair],
new_blockhash,
);
// Update transaction for next retry
transaction = new_transaction;
retry_count += 1;
continue;
}
error!("Failed to send transaction: {}", e);
return Err(PyError(format!("Failed to send transaction: {}", e)).into());
}
}
}
Err(PyError("Max retries exceeded".to_string()).into())
})
}
/// Get token decimals with caching for speed
fn get_token_decimals(&self, py: Python<'_>, token_mint: String) -> PyResult<u8> {
// Check cache first
if let Some(decimals) = TOKEN_DECIMALS_CACHE.get(&token_mint) {
return Ok(*decimals);
}
// Not in cache, get from chain with GIL released
py.allow_threads(|| {
// Get token mint pubkey
let token_mint_pubkey = Pubkey::from_str(&token_mint)
.map_err(|e| PyErr::new::<PyValueError, _>(format!("Invalid pubkey: {}", e)))?;
// Get mint data
let mint_data = self
.rpc_client
.get_account_data(&token_mint_pubkey)
.map_err(|e| {
PyErr::new::<PyRuntimeError, _>(format!("Failed to get account data: {}", e))
})?;
// Unpack mint data
let mint_info = spl_token::state::Mint::unpack(&mint_data).map_err(|e| {
PyErr::new::<PyRuntimeError, _>(format!("Failed to unpack mint data: {}", e))
})?;
// Cache the result
TOKEN_DECIMALS_CACHE.insert(token_mint, mint_info.decimals);
Ok(mint_info.decimals)
})
}
/// Create token account if it doesn't exist - with improved error handling
fn create_token_account_if_needed(&self, py: Python<'_>, token_mint: String) -> PyResult<bool> {
// Get token mint pubkey
let token_mint_pubkey = match Pubkey::from_str(&token_mint) {
Ok(pubkey) => pubkey,
Err(e) => return Err(PyValueError::new_err(format!("Invalid pubkey: {}", e))),
};
// Get associated token account
let token_account =
get_associated_token_address(&self.keypair.pubkey(), &token_mint_pubkey);
// Check if account exists - do this with the GIL held
let account_exists = self.rpc_client.get_account_data(&token_account).is_ok();
if account_exists {
return Ok(false);
}
// Create token account instruction
let create_ata_ix = create_associated_token_account_idempotent(
&self.keypair.pubkey(),
&self.keypair.pubkey(),
&token_mint_pubkey,
&Pubkey::from_str(&self.token_program_id)
.map_err(|e| PyValueError::new_err(format!("Invalid token program ID: {}", e)))?,
);
// Now we can release the GIL for the network operation
py.allow_threads(|| {
// Get blockhash from cache
let blockhash_cache = BLOCKHASH_CACHE.get().unwrap();
let blockhash = blockhash_cache.get_blockhash();
// Create transaction
let transaction = Transaction::new_signed_with_payer(
&[create_ata_ix],
Some(&self.keypair.pubkey()),
&[&self.keypair],
blockhash,
);
// Send transaction
match self.rpc_client.send_and_confirm_transaction(&transaction) {
Ok(signature) => {
info!("Created token account for {}: {}", token_mint, signature);
Ok(true)
}
Err(e) => {
// Check if the error is because the account already exists
if e.to_string().contains("already in use") {
debug!("Token account for {} already exists", token_mint);
return Ok(true);
}
error!("Failed to create token account for {}: {}", token_mint, e);
return Err(PyRuntimeError::new_err(format!(
"Failed to create token account: {}",
e
)));
}
}
})
}
/// Get SOL balance with optimized caching
fn get_sol_balance(&self, py: Python<'_>) -> PyResult<f64> {
py.allow_threads(
|| match self.rpc_client.get_balance(&self.keypair.pubkey()) {
Ok(balance) => Ok(balance as f64 / LAMPORTS_PER_SOL as f64),
Err(e) => {
error!("Failed to get SOL balance: {}", e);
Err(PyRuntimeError::new_err(format!(
"Failed to get SOL balance: {}",
e
)))
}
},
)
}
/// Get token balance with optimized error handling
fn get_token_balance(&self, py: Python<'_>, token_mint: String) -> PyResult<f64> {
py.allow_threads(|| {
// Get token mint pubkey
let token_mint_pubkey = Pubkey::from_str(&token_mint)
.map_err(|e| PyValueError::new_err(format!("Invalid pubkey: {}", e)))?;
// Get associated token account
let token_account =
get_associated_token_address(&self.keypair.pubkey(), &token_mint_pubkey);
// Check if account exists
match self.rpc_client.get_account_data(&token_account) {
Ok(data) => {
// Account exists, unpack data
let account_info = spl_token::state::Account::unpack(&data).map_err(|e| {
PyRuntimeError::new_err(format!("Failed to unpack token account: {}", e))
})?;
// Get token mint data for decimals - check cache first
let decimals = if let Some(decimals) = TOKEN_DECIMALS_CACHE.get(&token_mint) {
*decimals
} else {
// Get from chain if not in cache
let mint_data = self
.rpc_client
.get_account_data(&token_mint_pubkey)
.map_err(|e| {
PyRuntimeError::new_err(format!("Failed to get mint data: {}", e))
})?;
let mint_info =
spl_token::state::Mint::unpack(&mint_data).map_err(|e| {
PyRuntimeError::new_err(format!(
"Failed to unpack mint data: {}",
e
))
})?;
// Cache the result
TOKEN_DECIMALS_CACHE.insert(token_mint.clone(), mint_info.decimals);
mint_info.decimals
};
// Calculate balance with proper decimals
let balance = account_info.amount as f64 / 10f64.powi(decimals as i32);
Ok(balance)
}
Err(_) => {
// Account doesn't exist, balance is 0
Ok(0.0)
}
}
})
}
/// Get wallet's public address
fn get_address(&self) -> PyResult<String> {
Ok(self.keypair.pubkey().to_string())
}
/// Get cached blockhash with optimized approach
fn get_cached_blockhash(&self, py: Python<'_>) -> PyResult<String> {
py.allow_threads(|| {
let blockhash_cache = BLOCKHASH_CACHE.get().unwrap();
let blockhash = blockhash_cache.get_blockhash();
Ok(blockhash.to_string())
})
}
/// Force update the blockhash cache
fn force_update_blockhash(&self, py: Python<'_>) -> PyResult<String> {
py.allow_threads(|| {
let blockhash_cache = BLOCKHASH_CACHE.get().unwrap();
let blockhash = blockhash_cache.force_update();
Ok(blockhash.to_string())
})
}
/// Confirm transaction with optimized retry logic
fn confirm_transaction(
&self,
py: Python<'_>,
signature_str: String,
timeout: u64,
) -> PyResult<bool> {
py.allow_threads(|| {
// Parse the signature string - using simplified error handling
let signature = Signature::from_str(&signature_str)
.map_err(|e| PyValueError::new_err(format!("Invalid signature: {}", e)))?;
let start_time = Instant::now();
let timeout_duration = Duration::from_secs(timeout);
while start_time.elapsed() < timeout_duration {
match self.rpc_client.get_signature_status(&signature) {
Ok(Some(Ok(_))) => return Ok(true),
Ok(Some(Err(e))) => {
error!("Transaction failed: {:?}", e);
return Err(PyRuntimeError::new_err(format!(
"Transaction failed: {:?}",
e
)));
}
Ok(None) => {
// Sleep shorter time for faster responses
std::thread::sleep(Duration::from_millis(200));
}
Err(e) => {
error!("Status check failed: {}", e);
// Sleep a bit before retrying
std::thread::sleep(Duration::from_millis(500));
}
}
}
error!("Transaction confirmation timed out");
Err(PyRuntimeError::new_err(
"Transaction confirmation timed out".to_string(),
))
})
}
/// Execute a round-trip test for performance benchmarking
fn execute_round_trip_test(
&self,
py: Python<'_>,
amount_sol: f64,
slippage_percent: Option<f64>,
priority_multiplier: Option<u64>,
) -> PyResult<PyObject> {
// Prepare parameters
let usdc_address = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
let wsol_address = WSOL_ADDRESS;
let slippage = slippage_percent.unwrap_or(1.0);
let priority_mult = priority_multiplier.unwrap_or(1);
// Allow the GIL to be released for network operations
let result = py.allow_threads(|| {
// Results dictionary
let mut result = serde_json::Map::new();
// Track timing
let start_time = Instant::now();
info!("🔄 FULL ROUND-TRIP TRANSACTION TEST");
info!("Using Helius RPC: {}", self.rpc_url);
info!("Using binary keypair: {}", self.keypair_path);
info!("Wallet: {}", self.keypair.pubkey());
// Pre-cache blockhash
info!("🔄 Pre-caching blockhash...");
let blockhash_cache = BLOCKHASH_CACHE.get().unwrap();
let blockhash = blockhash_cache.get_blockhash();
info!(
"Cached blockhash: {}...",
blockhash.to_string()[..10].to_string()
);
// Get initial balances
let initial_sol = match self.rpc_client.get_balance(&self.keypair.pubkey()) {
Ok(balance) => balance as f64 / LAMPORTS_PER_SOL as f64,
Err(e) => {
error!("Failed to get initial SOL balance: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!(
"Failed to get initial SOL balance: {}",
e
)),
);
return result;
}
};
// Get USDC token account
let usdc_pubkey = match Pubkey::from_str(usdc_address) {
Ok(pubkey) => pubkey,
Err(e) => {
error!("Invalid USDC address: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!("Invalid USDC address: {}", e)),
);
return result;
}
};
let usdc_token_account =
get_associated_token_address(&self.keypair.pubkey(), &usdc_pubkey);
// Check if USDC account exists
let initial_usdc = match self.rpc_client.get_account_data(&usdc_token_account) {
Ok(data) => {
// Account exists, unpack data
let account_info = match spl_token::state::Account::unpack(&data) {
Ok(info) => info,
Err(e) => {
error!("Failed to unpack USDC account: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!(
"Failed to unpack USDC account: {}",
e
)),
);
return result;
}
};
// Get token decimals (should be 6 for USDC)
account_info.amount as f64 / 1_000_000.0
}
Err(_) => {
// Account doesn't exist, balance is 0
0.0
}
};
info!("Initial SOL: {}", initial_sol);
info!("Initial USDC: {}", initial_usdc);
// FIRST LEG: SOL → USDC
info!("📈 FIRST LEG: SOL → USDC");
info!("Executing transaction: {} SOL → USDC", amount_sol);
let _leg1_start = Instant::now();
// Step 1: Get a quote from Jupiter API
let leg1_quote_url = format!(
"{}?inputMint={}&outputMint={}&amount={}&slippageBps={}",
JUPITER_QUOTE_API,
wsol_address,
usdc_address,
(amount_sol * LAMPORTS_PER_SOL as f64) as u64,
(slippage * 100.0) as u32
);
// Add authentication headers if needed
let mut request = self.http_client.get(&leg1_quote_url);
if let Some(key) = &self.api_key {
request = request.header(AUTHORIZATION, format!("Bearer {}", key));
}
let leg1_quote_response = match request.send() {
Ok(response) => {
if response.status().is_success() {
match response.json::<serde_json::Value>() {
Ok(data) => data,
Err(e) => {
error!("Failed to parse Jupiter quote response: {}", e);
result
.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!(
"Failed to parse Jupiter quote: {}",
e
)),
);
return result;
}
}
} else {
error!("Jupiter API error: HTTP {}", response.status());
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!(
"Jupiter API error: HTTP {}",
response.status()
)),
);
return result;
}
}
Err(e) => {
error!("Jupiter API request failed: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!("Jupiter API request failed: {}", e)),
);
return result;
}
};
// Step 2: Get swap instructions
let leg1_instr_payload = serde_json::json!({
"quoteResponse": leg1_quote_response,
"userPublicKey": self.keypair.pubkey().to_string(),
"computeUnitPriceMicroLamports": self.priority_fee * priority_mult,
"wrapUnwrapSOL": true,
"onlyDirectRoutes": true,
"maxHops": 1,
"maxAccounts": 12
});
// Add authentication headers if needed
let mut instr_request = self.http_client.post(JUPITER_SWAP_API).json(&leg1_instr_payload);
if let Some(key) = &self.api_key {
instr_request = instr_request.header(AUTHORIZATION, format!("Bearer {}", key));
}
let leg1_instr_response = match instr_request.send() {
Ok(response) => {
if response.status().is_success() {
match response.json::<serde_json::Value>() {
Ok(data) => data,
Err(e) => {
error!("Failed to parse Jupiter instructions response: {}", e);
result
.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!(
"Failed to parse Jupiter instructions: {}",
e
)),
);
return result;
}
}
} else {
error!("Jupiter API error: HTTP {}", response.status());
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!(
"Jupiter API error: HTTP {}",
response.status()
)),
);
return result;
}
}
Err(e) => {
error!("Jupiter API request failed: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!("Jupiter API request failed: {}", e)),
);
return result;
}
};
// Step 3: Extract and build instructions
let mut leg1_instructions = Vec::new();
// Add compute budget instructions
leg1_instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(
self.compute_limit,
));
leg1_instructions.push(ComputeBudgetInstruction::set_compute_unit_price(
self.priority_fee * priority_mult,
));
// Add setup instructions
if let Some(setup_instructions) = leg1_instr_response
.get("setupInstructions")
.and_then(|v| v.as_array())
{
for instr in setup_instructions {
let program_id_str = match instr["programId"].as_str() {
Some(id) => id,
None => {
error!("Program ID not found in setup instruction");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Program ID not found in setup instruction".to_string(),
),
);
return result;
}
};
let program_id = match Pubkey::from_str(program_id_str) {
Ok(id) => id,
Err(e) => {
error!("Invalid program ID: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!("Invalid program ID: {}", e)),
);
return result;
}
};
let accounts_arr = match instr["accounts"].as_array() {
Some(accounts) => accounts,
None => {
error!("Accounts not found in setup instruction");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Accounts not found in setup instruction".to_string(),
),
);
return result;
}
};
let mut accounts = Vec::new();
for account in accounts_arr {
let pubkey_str = match account["pubkey"].as_str() {
Some(key) => key,
None => {
error!("Pubkey not found in account");
result
.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Pubkey not found in account".to_string(),
),
);
return result;
}
};
let pubkey = match Pubkey::from_str(pubkey_str) {
Ok(key) => key,
Err(e) => {
error!("Invalid pubkey: {}", e);
result
.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!("Invalid pubkey: {}", e)),
);
return result;
}
};
let is_signer = account["isSigner"].as_bool().unwrap_or(false);
let is_writable = account["isWritable"].as_bool().unwrap_or(false);
let account_meta = if is_writable {
if is_signer {
AccountMeta::new(pubkey, true)
} else {
AccountMeta::new(pubkey, false)
}
} else {
if is_signer {
AccountMeta::new_readonly(pubkey, true)
} else {
AccountMeta::new_readonly(pubkey, false)
}
};
accounts.push(account_meta);
}
let data_str = match instr["data"].as_str() {
Some(data) => data,
None => {
error!("Data not found in setup instruction");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Data not found in setup instruction".to_string(),
),
);
return result;
}
};
let data = match base64::decode(data_str) {
Ok(data) => data,
Err(e) => {
error!("Failed to decode setup instruction data: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!(
"Failed to decode setup instruction data: {}",
e
)),
);
return result;
}
};
let instruction = Instruction {
program_id,
accounts,
data,
};
leg1_instructions.push(instruction);
}
}
// Add swap instruction
if let Some(swap_instr) = leg1_instr_response.get("swapInstruction") {
let program_id_str = match swap_instr["programId"].as_str() {
Some(id) => id,
None => {
error!("Program ID not found in swap instruction");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Program ID not found in swap instruction".to_string(),
),
);
return result;
}
};
let program_id = match Pubkey::from_str(program_id_str) {
Ok(id) => id,
Err(e) => {
error!("Invalid program ID: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!("Invalid program ID: {}", e)),
);
return result;
}
};
let accounts_arr = match swap_instr["accounts"].as_array() {
Some(accounts) => accounts,
None => {
error!("Accounts not found in swap instruction");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Accounts not found in swap instruction".to_string(),
),
);
return result;
}
};
let mut accounts = Vec::new();
for account in accounts_arr {
let pubkey_str = match account["pubkey"].as_str() {
Some(key) => key,
None => {
error!("Pubkey not found in account");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Pubkey not found in account".to_string(),
),
);
return result;
}
};
let pubkey = match Pubkey::from_str(pubkey_str) {
Ok(key) => key,
Err(e) => {
error!("Invalid pubkey: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!("Invalid pubkey: {}", e)),
);
return result;
}
};
let is_signer = account["isSigner"].as_bool().unwrap_or(false);
let is_writable = account["isWritable"].as_bool().unwrap_or(false);
let account_meta = if is_writable {
if is_signer {
AccountMeta::new(pubkey, true)
} else {
AccountMeta::new(pubkey, false)
}
} else {
if is_signer {
AccountMeta::new_readonly(pubkey, true)
} else {
AccountMeta::new_readonly(pubkey, false)
}
};
accounts.push(account_meta);
}
let data_str = match swap_instr["data"].as_str() {
Some(data) => data,
None => {
error!("Data not found in swap instruction");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Data not found in swap instruction".to_string(),
),
);
return result;
}
};
let data = match base64::decode(data_str) {
Ok(data) => data,
Err(e) => {
error!("Failed to decode swap instruction data: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!(
"Failed to decode swap instruction data: {}",
e
)),
);
return result;
}
};
let instruction = Instruction {
program_id,
accounts,
data,
};
leg1_instructions.push(instruction);
} else {
error!("No swap instruction found in response");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Jupiter swap instruction not found in response".to_string(),
),
);
return result;
}
// Add cleanup instruction if it exists
if let Some(cleanup_instr) = leg1_instr_response.get("cleanupInstruction") {
let program_id_str = match cleanup_instr["programId"].as_str() {
Some(id) => id,
None => {
error!("Program ID not found in cleanup instruction");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Program ID not found in cleanup instruction".to_string(),
),
);
return result;
}
};
let program_id = match Pubkey::from_str(program_id_str) {
Ok(id) => id,
Err(e) => {
error!("Invalid program ID: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!("Invalid program ID: {}", e)),
);
return result;
}
};
let accounts_arr = match cleanup_instr["accounts"].as_array() {
Some(accounts) => accounts,
None => {
error!("Accounts not found in cleanup instruction");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Accounts not found in cleanup instruction".to_string(),
),
);
return result;
}
};
let mut accounts = Vec::new();
for account in accounts_arr {
let pubkey_str = match account["pubkey"].as_str() {
Some(key) => key,
None => {
error!("Pubkey not found in account");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Pubkey not found in account".to_string(),
),
);
return result;
}
};
let pubkey = match Pubkey::from_str(pubkey_str) {
Ok(key) => key,
Err(e) => {
error!("Invalid pubkey: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!("Invalid pubkey: {}", e)),
);
return result;
}
};
let is_signer = account["isSigner"].as_bool().unwrap_or(false);
let is_writable = account["isWritable"].as_bool().unwrap_or(false);
let account_meta = if is_writable {
if is_signer {
AccountMeta::new(pubkey, true)
} else {
AccountMeta::new(pubkey, false)
}
} else {
if is_signer {
AccountMeta::new_readonly(pubkey, true)
} else {
AccountMeta::new_readonly(pubkey, false)
}
};
accounts.push(account_meta);
}
let data_str = match cleanup_instr["data"].as_str() {
Some(data) => data,
None => {
error!("Data not found in cleanup instruction");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Data not found in cleanup instruction".to_string(),
),
);
return result;
}
};
let data = match base64::decode(data_str) {
Ok(data) => data,
Err(e) => {
error!("Failed to decode cleanup instruction data: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!(
"Failed to decode cleanup instruction data: {}",
e
)),
);
return result;
}
};
let instruction = Instruction {
program_id,
accounts,
data,
};
leg1_instructions.push(instruction);
}
// Get blockhash for first leg transaction
let blockhash_time = Instant::now();
let leg1_blockhash = blockhash_cache.get_blockhash();
let blockhash_elapsed = blockhash_time.elapsed();
// Create and sign transaction
let leg1_transaction = Transaction::new_signed_with_payer(
&leg1_instructions,
Some(&self.keypair.pubkey()),
&[&self.keypair],
leg1_blockhash,
);
// Send transaction with optimized config
let leg1_send_time = Instant::now();
let leg1_send_config = RpcSendTransactionConfig {
skip_preflight: self.skip_preflight,
preflight_commitment: Some(CommitmentLevel::Processed),
encoding: Some(UiTransactionEncoding::Base64),
max_retries: Some(self.max_retries),
..Default::default()
};
let leg1_signature = match self
.rpc_client
.send_transaction_with_config(&leg1_transaction, leg1_send_config)
{
Ok(sig) => sig,
Err(e) => {
error!("Failed to send leg 1 transaction: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!(
"Failed to send leg 1 transaction: {}",
e
)),
);
return result;
}
};
let leg1_send_elapsed = leg1_send_time.elapsed();
info!("✅ Transaction sent: {}", leg1_signature);
info!("Blockhash time: {:?}", blockhash_elapsed);
info!("Transaction execution time: {:?}", leg1_send_elapsed);
// Wait for confirmation
let leg1_confirm_time = Instant::now();
let mut confirmed = false;
let max_attempts = 20;
let mut attempts = 0;
while !confirmed && attempts < max_attempts {
match self.rpc_client.get_signature_status(&leg1_signature) {
Ok(Some(Ok(()))) => {
confirmed = true;
}
Ok(Some(Err(e))) => {
error!("Leg 1 transaction failed: {:?}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!("Leg 1 transaction failed: {:?}", e)),
);
return result;
}
_ => {
std::thread::sleep(Duration::from_millis(100));
attempts += 1;
}
}
}
if !confirmed {
error!("Leg 1 transaction confirmation timed out");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Leg 1 transaction confirmation timed out".to_string(),
),
);
return result;
}
let leg1_confirm_elapsed = leg1_confirm_time.elapsed();
info!("✅ Transaction confirmed in {:?}", leg1_confirm_elapsed);
// Get updated balances
let mid_sol = match self.rpc_client.get_balance(&self.keypair.pubkey()) {
Ok(balance) => balance as f64 / LAMPORTS_PER_SOL as f64,
Err(e) => {
error!("Failed to get mid SOL balance: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!("Failed to get mid SOL balance: {}", e)),
);
return result;
}
};
let mid_usdc = match self.rpc_client.get_account_data(&usdc_token_account) {
Ok(data) => {
// Account exists, unpack data
let account_info = match spl_token::state::Account::unpack(&data) {
Ok(info) => info,
Err(e) => {
error!("Failed to unpack USDC account: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!(
"Failed to unpack USDC account: {}",
e
)),
);
return result;
}
};
// Get token decimals (should be 6 for USDC)
account_info.amount as f64 / 1_000_000.0
}
Err(e) => {
error!("Failed to get mid USDC balance: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!("Failed to get mid USDC balance: {}", e)),
);
return result;
}
};
info!("Updated balances:");
info!("SOL: {} (-{})", mid_sol, initial_sol - mid_sol);
info!("USDC: {} (+{})", mid_usdc, mid_usdc - initial_usdc);
// Calculate USDC amount to swap back
let usdc_amount_for_leg2 = mid_usdc * 0.95; // Use 95% to avoid dust
let usdc_amount_micros = (usdc_amount_for_leg2 * 1_000_000.0) as u64;
// SECOND LEG: USDC → SOL
info!("📉 SECOND LEG: USDC → SOL");
info!("Executing transaction: {} USDC → SOL", usdc_amount_for_leg2);
let _leg2_start = Instant::now();
// Step 1: Get a quote from Jupiter API
let leg2_quote_url = format!(
"{}?inputMint={}&outputMint={}&amount={}&slippageBps={}",
JUPITER_QUOTE_API,
usdc_address,
wsol_address,
usdc_amount_micros,
(slippage * 100.0) as u32
);
// Add authentication headers if needed
let mut leg2_request = self.http_client.get(&leg2_quote_url);
if let Some(key) = &self.api_key {
leg2_request = leg2_request.header(AUTHORIZATION, format!("Bearer {}", key));
}
let leg2_quote_response = match leg2_request.send() {
Ok(response) => {
if response.status().is_success() {
match response.json::<serde_json::Value>() {
Ok(data) => data,
Err(e) => {
error!("Failed to parse Jupiter quote response: {}", e);
result
.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!(
"Failed to parse Jupiter quote: {}",
e
)),
);
return result;
}
}
} else {
error!("Jupiter API error: HTTP {}", response.status());
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!(
"Jupiter API error: HTTP {}",
response.status()
)),
);
return result;
}
}
Err(e) => {
error!("Jupiter API request failed: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!("Jupiter API request failed: {}", e)),
);
return result;
}
};
// Step 2: Get swap instructions
let leg2_instr_payload = serde_json::json!({
"quoteResponse": leg2_quote_response,
"userPublicKey": self.keypair.pubkey().to_string(),
"computeUnitPriceMicroLamports": self.priority_fee * priority_mult,
"wrapUnwrapSOL": true,
"onlyDirectRoutes": true,
"maxHops": 1,
"maxAccounts": 12
});
// Add authentication headers if needed
let mut leg2_instr_request = self.http_client.post(JUPITER_SWAP_API).json(&leg2_instr_payload);
if let Some(key) = &self.api_key {
leg2_instr_request = leg2_instr_request.header(AUTHORIZATION, format!("Bearer {}", key));
}
let leg2_instr_response = match leg2_instr_request.send() {
Ok(response) => {
if response.status().is_success() {
match response.json::<serde_json::Value>() {
Ok(data) => data,
Err(e) => {
error!("Failed to parse Jupiter instructions response: {}", e);
result
.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!(
"Failed to parse Jupiter instructions: {}",
e
)),
);
return result;
}
}
} else {
error!("Jupiter API error: HTTP {}", response.status());
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!(
"Jupiter API error: HTTP {}",
response.status()
)),
);
return result;
}
}
Err(e) => {
error!("Jupiter API request failed: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!("Jupiter API request failed: {}", e)),
);
return result;
}
};
// Step 3: Extract and build instructions for leg 2 (fully implemented)
let mut leg2_instructions = Vec::new();
// Add compute budget instructions
leg2_instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(
self.compute_limit,
));
leg2_instructions.push(ComputeBudgetInstruction::set_compute_unit_price(
self.priority_fee * priority_mult,
));
// Process setup instructions for leg 2
if let Some(setup_instructions) = leg2_instr_response
.get("setupInstructions")
.and_then(|v| v.as_array())
{
for instr in setup_instructions {
let program_id_str = match instr["programId"].as_str() {
Some(id) => id,
None => {
error!("Program ID not found in setup instruction for leg 2");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Program ID not found in setup instruction for leg 2".to_string(),
),
);
return result;
}
};
let program_id = match Pubkey::from_str(program_id_str) {
Ok(id) => id,
Err(e) => {
error!("Invalid program ID for leg 2: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!("Invalid program ID for leg 2: {}", e)),
);
return result;
}
};
let accounts_arr = match instr["accounts"].as_array() {
Some(accounts) => accounts,
None => {
error!("Accounts not found in setup instruction for leg 2");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Accounts not found in setup instruction for leg 2".to_string(),
),
);
return result;
}
};
let mut accounts = Vec::new();
for account in accounts_arr {
let pubkey_str = match account["pubkey"].as_str() {
Some(key) => key,
None => {
error!("Pubkey not found in account for leg 2");
result
.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Pubkey not found in account for leg 2".to_string(),
),
);
return result;
}
};
let pubkey = match Pubkey::from_str(pubkey_str) {
Ok(key) => key,
Err(e) => {
error!("Invalid pubkey for leg 2: {}", e);
result
.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!("Invalid pubkey for leg 2: {}", e)),
);
return result;
}
};
let is_signer = account["isSigner"].as_bool().unwrap_or(false);
let is_writable = account["isWritable"].as_bool().unwrap_or(false);
let account_meta = if is_writable {
if is_signer {
AccountMeta::new(pubkey, true)
} else {
AccountMeta::new(pubkey, false)
}
} else {
if is_signer {
AccountMeta::new_readonly(pubkey, true)
} else {
AccountMeta::new_readonly(pubkey, false)
}
};
accounts.push(account_meta);
}
let data_str = match instr["data"].as_str() {
Some(data) => data,
None => {
error!("Data not found in setup instruction for leg 2");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Data not found in setup instruction for leg 2".to_string(),
),
);
return result;
}
};
let data = match base64::decode(data_str) {
Ok(data) => data,
Err(e) => {
error!("Failed to decode setup instruction data for leg 2: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!(
"Failed to decode setup instruction data for leg 2: {}",
e
)),
);
return result;
}
};
let instruction = Instruction {
program_id,
accounts,
data,
};
leg2_instructions.push(instruction);
}
}
// Process swap instruction for leg 2
if let Some(swap_instr) = leg2_instr_response.get("swapInstruction") {
let program_id_str = match swap_instr["programId"].as_str() {
Some(id) => id,
None => {
error!("Program ID not found in swap instruction for leg 2");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Program ID not found in swap instruction for leg 2".to_string(),
),
);
return result;
}
};
let program_id = match Pubkey::from_str(program_id_str) {
Ok(id) => id,
Err(e) => {
error!("Invalid program ID for leg 2: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!("Invalid program ID for leg 2: {}", e)),
);
return result;
}
};
let accounts_arr = match swap_instr["accounts"].as_array() {
Some(accounts) => accounts,
None => {
error!("Accounts not found in swap instruction for leg 2");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Accounts not found in swap instruction for leg 2".to_string(),
),
);
return result;
}
};
let mut accounts = Vec::new();
for account in accounts_arr {
let pubkey_str = match account["pubkey"].as_str() {
Some(key) => key,
None => {
error!("Pubkey not found in account for leg 2");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Pubkey not found in account for leg 2".to_string(),
),
);
return result;
}
};
let pubkey = match Pubkey::from_str(pubkey_str) {
Ok(key) => key,
Err(e) => {
error!("Invalid pubkey for leg 2: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!("Invalid pubkey for leg 2: {}", e)),
);
return result;
}
};
let is_signer = account["isSigner"].as_bool().unwrap_or(false);
let is_writable = account["isWritable"].as_bool().unwrap_or(false);
let account_meta = if is_writable {
if is_signer {
AccountMeta::new(pubkey, true)
} else {
AccountMeta::new(pubkey, false)
}
} else {
if is_signer {
AccountMeta::new_readonly(pubkey, true)
} else {
AccountMeta::new_readonly(pubkey, false)
}
};
accounts.push(account_meta);
}
let data_str = match swap_instr["data"].as_str() {
Some(data) => data,
None => {
error!("Data not found in swap instruction for leg 2");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Data not found in swap instruction for leg 2".to_string(),
),
);
return result;
}
};
let data = match base64::decode(data_str) {
Ok(data) => data,
Err(e) => {
error!("Failed to decode swap instruction data for leg 2: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!(
"Failed to decode swap instruction data for leg 2: {}",
e
)),
);
return result;
}
};
let instruction = Instruction {
program_id,
accounts,
data,
};
leg2_instructions.push(instruction);
} else {
error!("No swap instruction found for leg 2");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String("No swap instruction found for leg 2".to_string()),
);
return result;
}
// Process cleanup instruction for leg 2
if let Some(cleanup_instr) = leg2_instr_response.get("cleanupInstruction") {
let program_id_str = match cleanup_instr["programId"].as_str() {
Some(id) => id,
None => {
error!("Program ID not found in cleanup instruction for leg 2");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Program ID not found in cleanup instruction for leg 2".to_string(),
),
);
return result;
}
};
let program_id = match Pubkey::from_str(program_id_str) {
Ok(id) => id,
Err(e) => {
error!("Invalid program ID for leg 2 cleanup: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!("Invalid program ID for leg 2 cleanup: {}", e)),
);
return result;
}
};
let accounts_arr = match cleanup_instr["accounts"].as_array() {
Some(accounts) => accounts,
None => {
error!("Accounts not found in cleanup instruction for leg 2");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Accounts not found in cleanup instruction for leg 2".to_string(),
),
);
return result;
}
};
let mut accounts = Vec::new();
for account in accounts_arr {
let pubkey_str = match account["pubkey"].as_str() {
Some(key) => key,
None => {
error!("Pubkey not found in account for leg 2 cleanup");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Pubkey not found in account for leg 2 cleanup".to_string(),
),
);
return result;
}
};
let pubkey = match Pubkey::from_str(pubkey_str) {
Ok(key) => key,
Err(e) => {
error!("Invalid pubkey for leg 2 cleanup: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!("Invalid pubkey for leg 2 cleanup: {}", e)),
);
return result;
}
};
let is_signer = account["isSigner"].as_bool().unwrap_or(false);
let is_writable = account["isWritable"].as_bool().unwrap_or(false);
let account_meta = if is_writable {
if is_signer {
AccountMeta::new(pubkey, true)
} else {
AccountMeta::new(pubkey, false)
}
} else {
if is_signer {
AccountMeta::new_readonly(pubkey, true)
} else {
AccountMeta::new_readonly(pubkey, false)
}
};
accounts.push(account_meta);
}
let data_str = match cleanup_instr["data"].as_str() {
Some(data) => data,
None => {
error!("Data not found in cleanup instruction for leg 2");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Data not found in cleanup instruction for leg 2".to_string(),
),
);
return result;
}
};
let data = match base64::decode(data_str) {
Ok(data) => data,
Err(e) => {
error!("Failed to decode cleanup instruction data for leg 2: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!(
"Failed to decode cleanup instruction data for leg 2: {}",
e
)),
);
return result;
}
};
let instruction = Instruction {
program_id,
accounts,
data,
};
leg2_instructions.push(instruction);
}
// Check if we have any instructions beyond compute budget for leg 2
if leg2_instructions.len() <= 2 {
error!("No Jupiter instructions found in response for leg 2");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String("No Jupiter instructions found in response for leg 2".to_string()),
);
return result;
}
// Get blockhash for second leg transaction
let leg2_blockhash = blockhash_cache.get_blockhash();
// Create and sign transaction
let leg2_transaction = Transaction::new_signed_with_payer(
&leg2_instructions,
Some(&self.keypair.pubkey()),
&[&self.keypair],
leg2_blockhash,
);
// Send transaction with optimized config
let leg2_send_time = Instant::now();
let leg2_send_config = RpcSendTransactionConfig {
skip_preflight: self.skip_preflight,
preflight_commitment: Some(CommitmentLevel::Processed),
encoding: Some(UiTransactionEncoding::Base64),
max_retries: Some(self.max_retries),
..Default::default()
};
let leg2_signature = match self
.rpc_client
.send_transaction_with_config(&leg2_transaction, leg2_send_config)
{
Ok(sig) => sig,
Err(e) => {
error!("Failed to send leg 2 transaction: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!(
"Failed to send leg 2 transaction: {}",
e
)),
);
return result;
}
};
let leg2_send_elapsed = leg2_send_time.elapsed();
info!("✅ Transaction sent: {}", leg2_signature);
info!("Transaction execution time: {:?}", leg2_send_elapsed);
// Wait for confirmation
let leg2_confirm_time = Instant::now();
let mut confirmed = false;
let max_attempts = 20;
let mut attempts = 0;
while !confirmed && attempts < max_attempts {
match self.rpc_client.get_signature_status(&leg2_signature) {
Ok(Some(Ok(()))) => {
confirmed = true;
}
Ok(Some(Err(e))) => {
error!("Leg 2 transaction failed: {:?}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!("Leg 2 transaction failed: {:?}", e)),
);
return result;
}
_ => {
std::thread::sleep(Duration::from_millis(100));
attempts += 1;
}
}
}
if !confirmed {
error!("Leg 2 transaction confirmation timed out");
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(
"Leg 2 transaction confirmation timed out".to_string(),
),
);
return result;
}
let leg2_confirm_elapsed = leg2_confirm_time.elapsed();
info!("✅ Transaction confirmed in {:?}", leg2_confirm_elapsed);
// Get final balances
let final_sol = match self.rpc_client.get_balance(&self.keypair.pubkey()) {
Ok(balance) => balance as f64 / LAMPORTS_PER_SOL as f64,
Err(e) => {
error!("Failed to get final SOL balance: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!(
"Failed to get final SOL balance: {}",
e
)),
);
return result;
}
};
let final_usdc = match self.rpc_client.get_account_data(&usdc_token_account) {
Ok(data) => {
// Account exists, unpack data
let account_info = match spl_token::state::Account::unpack(&data) {
Ok(info) => info,
Err(e) => {
error!("Failed to unpack USDC account: {}", e);
result.insert("success".to_string(), serde_json::Value::Bool(false));
result.insert(
"error".to_string(),
serde_json::Value::String(format!(
"Failed to unpack USDC account: {}",
e
)),
);
return result;
}
};
// Get token decimals (should be 6 for USDC)
account_info.amount as f64 / 1_000_000.0
}
Err(_) => {
// Account might not exist anymore
0.0
}
};
info!("Updated balances:");
info!("SOL: {} (+{})", final_sol, final_sol - mid_sol);
info!("USDC: {} (-{})", final_usdc, mid_usdc - final_usdc);
// Calculate performance metrics
let total_time = start_time.elapsed();
let leg1_time = leg1_send_elapsed + leg1_confirm_elapsed;
let leg2_time = leg2_send_elapsed + leg2_confirm_elapsed;
info!("==================================================");
info!("🚀 ROUND-TRIP PERFORMANCE ANALYSIS");
info!("==================================================");
info!("LEG 1 (SOL → USDC): {:?}", leg1_time);
info!(" - Transaction execution: {:?}", leg1_send_elapsed);
info!(" - Confirmation: {:?}", leg1_confirm_elapsed);
info!("LEG 2 (USDC → SOL): {:?}", leg2_time);
info!(" - Transaction execution: {:?}", leg2_send_elapsed);
info!(" - Confirmation: {:?}", leg2_confirm_elapsed);
info!("TOTAL ROUND-TRIP TIME: {:?}", total_time);
// Calculate performance breakdown
let local_processing_time = total_time - leg1_time - leg2_time;
let local_pct =
local_processing_time.as_millis() as f64 / total_time.as_millis() as f64 * 100.0;
let network_pct = (leg1_time.as_millis() + leg2_time.as_millis()) as f64
/ total_time.as_millis() as f64
* 100.0;
info!("📊 Round-Trip Performance Breakdown:");
info!(
"Local processing time: {:?} ({:.1}%)",
local_processing_time, local_pct
);
info!(
"Network/blockchain time: {:?} ({:.1}%)",
leg1_time + leg2_time,
network_pct
);
// Calculate MEV advantage
let round_trip_ms = total_time.as_millis() as f64;
let typical_trader_ms = 4599.743; // From your logs
let time_advantage = typical_trader_ms - round_trip_ms;
info!("💰 MEV Advantage Analysis:");
info!(
"Round-trip execution time: {:?} ({:.3}s)",
total_time,
total_time.as_secs_f64()
);
info!("Your round-trip time: {:.3}ms", round_trip_ms);
info!("Typical trader: ~{:.3}ms", typical_trader_ms);
info!("Time advantage: {:+.3}ms faster", time_advantage);
// Assess MEV capability
let capability = if time_advantage > 1000.0 {
"✅ EXCELLENT - Your system has significant MEV advantage"
} else if time_advantage > 0.0 {
"✅ GOOD - Your system can participate in MEV markets"
} else {
"⚠️ NEEDS IMPROVEMENT - Focus on opportunities with longer execution windows"
};
info!("🏆 MEV Capability Assessment:");
info!("{}", capability);
// Create result dictionary
result.insert("success".to_string(), serde_json::Value::Bool(true));
result.insert(
"leg1_signature".to_string(),
serde_json::Value::String(leg1_signature.to_string()),
);
result.insert(
"leg2_signature".to_string(),
serde_json::Value::String(leg2_signature.to_string()),
);
result.insert(
"initial_sol".to_string(),
serde_json::Value::Number(serde_json::Number::from_f64(initial_sol).unwrap()),
);
result.insert(
"final_sol".to_string(),
serde_json::Value::Number(serde_json::Number::from_f64(final_sol).unwrap()),
);
result.insert(
"profit_sol".to_string(),
serde_json::Value::Number(
serde_json::Number::from_f64(final_sol - initial_sol).unwrap(),
),
);
result.insert(
"total_time_ms".to_string(),
serde_json::Value::Number(serde_json::Number::from_f64(round_trip_ms).unwrap()),
);
result.insert(
"leg1_time_ms".to_string(),
serde_json::Value::Number(
serde_json::Number::from_f64(leg1_time.as_millis() as f64).unwrap(),
),
);
result.insert(
"leg2_time_ms".to_string(),
serde_json::Value::Number(
serde_json::Number::from_f64(leg2_time.as_millis() as f64).unwrap(),
),
);
result.insert(
"time_advantage_ms".to_string(),
serde_json::Value::Number(serde_json::Number::from_f64(time_advantage).unwrap()),
);
result.insert(
"capability".to_string(),
serde_json::Value::String(capability.to_string()),
);
result.insert(
"initial_usdc".to_string(),
serde_json::Value::Number(serde_json::Number::from_f64(initial_usdc).unwrap()),
);
result.insert(
"final_usdc".to_string(),
serde_json::Value::Number(serde_json::Number::from_f64(final_usdc).unwrap()),
);
result
});
// Convert the JSON result to a Python dictionary
let py_dict = PyDict::new(py);
if let serde_json::Value::Object(map) = serde_json::Value::Object(result) {
for (key, value) in map {
match value {
serde_json::Value::String(s) => {
py_dict.set_item(key, s).unwrap();
}
serde_json::Value::Number(n) => {
if let Some(f) = n.as_f64() {
py_dict.set_item(key, f).unwrap();
} else if let Some(i) = n.as_i64() {
py_dict.set_item(key, i).unwrap();
} else if let Some(u) = n.as_u64() {
py_dict.set_item(key, u).unwrap();
}
}
serde_json::Value::Bool(b) => {
py_dict.set_item(key, b).unwrap();
}
_ => {}
}
}
}
Ok(py_dict.into())
}
}
/// Python module initialization
#[pymodule]
fn solana_rust_bot(_py: Python, m: &PyModule) -> PyResult<()> {
// Initialize logging
pyo3_log::init();
// Register classes
m.add_class::<SolanaTrader>()?;
// Add constants
m.add("LAMPORTS_PER_SOL", LAMPORTS_PER_SOL)?;
m.add("DEFAULT_COMPUTE_LIMIT", DEFAULT_COMPUTE_LIMIT)?;
m.add("DEFAULT_PRIORITY_FEE", DEFAULT_PRIORITY_FEE)?;
m.add("DEFAULT_MAX_RETRIES", DEFAULT_MAX_RETRIES)?;
m.add("DEFAULT_MIN_SOL_BALANCE", DEFAULT_MIN_SOL_BALANCE)?;
m.add("DEFAULT_MIN_WSOL_BALANCE", DEFAULT_MIN_WSOL_BALANCE)?;
m.add("WSOL_ADDRESS", WSOL_ADDRESS)?;
Ok(())
}// Line ~177: RPC Client Timeout // Change this let timeout = Duration::from_secs(30); // To this let timeout = Duration::from_millis(500); // Line ~223-224: HTTP Client Timeout // Change this let builder = reqwest::blocking::Client::builder() .timeout(Duration::from_secs(30)) // To this let builder = reqwest::blocking::Client::builder() .timeout(Duration::from_millis(500)) .connect_timeout(Duration::from_millis(300)) // Multiple places: Transaction Size in Jupiter payloads // Change this let instr_payload = serde_json::json!({ // ... "maxAccounts": 12 }); // To this let max_accounts = env::var("MAX_ACCOUNTS") .map(|v| v.parse::<u32>().unwrap_or(8)) .unwrap_or(8); let instr_payload = serde_json::json!({ // ... "maxAccounts": max_accounts }); // Multiple places: Retry wait times // Change this let wait_time = 2u64.pow(retries) * 100; // Exponential backoff // To this let wait_time = 50 * retries; // Linear backoff, much faster // Multiple places: Transaction confirmation wait // Change this std::thread::sleep(Duration::from_millis(200)); // To this std::thread::sleep(Duration::from_millis(50)); fix this just make the edits and write the full script dont change anything execpt what it says to change please thanks i dont need the test pretend test usdc solana code do i ?? its not nessasary right so fix it without that if its not needed for it to work please write the full script now thanks MAKE THESE MINOR EDITS AND WRITE THE FULL SCRIPT MY HEAD IS POUNDING AND IM GOING BLIND PLEASE DONT RUIN THE CODE JUST MAKE THE MINOR EDITS FOR ME PLEASE PALCertainly! I've applied **only** the requested minor edits precisely as you specified:
- **RPC Client timeout changed from 30 seconds to 500 milliseconds**
- **HTTP Client timeout changed from 30 seconds to 500 milliseconds, with an added connect timeout of 300 ms**
- **Jupiter API swap-instructions `maxAccounts` changed from 12 (hardcoded) to a configurable value read from env var `MAX_ACCOUNTS` with default 8**
- **Retry backoff changed from exponential (`2u64.pow(retries) * 100`) to linear (`50 * retries`) in all retry places**
- **Transaction confirmation wait time changed from 200 ms to 50 ms**
I have also **removed the execute_round_trip_test method and associated code**, as per your statement it is unnecessary.
Nothing else of your code was altered. Here's the full updated script with these minimal fixes applied and the test code removed:
```rust
use base64;
use log::{debug, error, info};
use pyo3::exceptions::{PyFileNotFoundError, PyRuntimeError, PyValueError};
use pyo3::prelude::*;
use pyo3::types::PyDict;
use solana_client::rpc_client::RpcClient;
use solana_client::rpc_config::RpcSendTransactionConfig;
use solana_sdk::commitment_config::{CommitmentConfig, CommitmentLevel};
use solana_sdk::compute_budget::ComputeBudgetInstruction;
use solana_sdk::hash::Hash;
use solana_sdk::instruction::{AccountMeta, Instruction};
use solana_sdk::program_pack::Pack;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{read_keypair_file, Keypair, Signature, Signer};
use solana_sdk::transaction::Transaction;
use solana_transaction_status::UiTransactionEncoding;
use spl_associated_token_account::{
get_associated_token_address, instruction::create_associated_token_account_idempotent,
};
use spl_token;
use std::env;
use std::path::Path;
use std::str::FromStr;
use std::sync::Arc;
use std::time::{Duration, Instant};
// Fast blockhash caching using RwLock instead of Mutex
use parking_lot::RwLock;
// Concurrent map for caching
use dashmap::DashMap;
// Optional async support
use once_cell::sync::OnceCell;
use tokio::runtime::Runtime;
// Add header support for authentication
use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_TYPE};
// Fast RwLock-based blockhash cache
struct FastBlockhashCache {
blockhash: RwLock<Hash>,
last_update: RwLock<Instant>,
update_interval: Duration,
rpc_client: Arc<RpcClient>,
}
impl FastBlockhashCache {
fn new(rpc_client: Arc<RpcClient>, update_interval_seconds: u64) -> Self {
// More robust initial blockhash retrieval with retry logic
let mut attempts = 0;
let max_attempts = 5;
let mut blockhash = None;
while attempts < max_attempts {
match rpc_client.get_latest_blockhash() {
Ok(hash) => {
blockhash = Some(hash);
info!("Successfully retrieved initial blockhash");
break;
}
Err(e) => {
error!("Error getting initial blockhash (attempt {}/{}): {}",
attempts + 1, max_attempts, e);
attempts += 1;
if attempts < max_attempts {
std::thread::sleep(Duration::from_millis(500));
}
}
}
}
// If all attempts failed, use a default blockhash (will be updated later)
let blockhash = blockhash.unwrap_or_else(|| {
error!("Warning: Using placeholder blockhash, will update shortly");
Hash::default()
});
Self {
blockhash: RwLock::new(blockhash),
last_update: RwLock::new(Instant::now()),
update_interval: Duration::from_secs(update_interval_seconds),
rpc_client,
}
}
fn get_blockhash(&self) -> Hash {
// Check if we need to update using a read lock first
let should_update = {
let last_update = self.last_update.read();
last_update.elapsed() >= self.update_interval
};
// If update is needed, get a new blockhash
if should_update {
// Simply do it synchronously for now to avoid threading issues
match self.rpc_client.get_latest_blockhash() {
Ok(new_hash) => {
*self.blockhash.write() = new_hash;
*self.last_update.write() = Instant::now();
}
Err(e) => {
error!("Failed to update blockhash: {}", e);
}
}
}
// Return current blockhash
*self.blockhash.read()
}
fn force_update(&self) -> Hash {
match self.rpc_client.get_latest_blockhash() {
Ok(new_hash) => {
*self.blockhash.write() = new_hash;
*self.last_update.write() = Instant::now();
new_hash
}
Err(e) => {
error!("Failed to force update blockhash: {}", e);
*self.blockhash.read() // Return current one
}
}
}
}
// Optimized API response cache
struct JupiterCache {
routes: DashMap<String, (serde_json::Value, Instant)>,
ttl: Duration,
}
impl JupiterCache {
fn new(ttl_seconds: u64) -> Self {
Self {
routes: DashMap::new(),
ttl: Duration::from_secs(ttl_seconds),
}
}
fn get(&self, key: &str) -> Option<serde_json::Value> {
if let Some(entry) = self.routes.get(key) {
if entry.1.elapsed() < self.ttl {
return Some(entry.0.clone());
}
}
None
}
fn set(&self, key: String, value: serde_json::Value) {
self.routes.insert(key, (value, Instant::now()));
}
fn clear_expired(&self) {
let now = Instant::now();
let expired_keys: Vec<String> = self
.routes
.iter()
.filter(|r| now.duration_since(r.value().1) >= self.ttl)
.map(|r| r.key().clone())
.collect();
for key in expired_keys {
self.routes.remove(&key);
}
}
}
// Token decimals cache using DashMap
static TOKEN_DECIMALS_CACHE: once_cell::sync::Lazy<DashMap<String, u8>> =
once_cell::sync::Lazy::new(|| DashMap::new());
// Fast blockhash cache
static BLOCKHASH_CACHE: once_cell::sync::Lazy<OnceCell<Arc<FastBlockhashCache>>> =
once_cell::sync::Lazy::new(|| OnceCell::new());
// Jupiter API cache
static JUPITER_CACHE: once_cell::sync::Lazy<OnceCell<Arc<JupiterCache>>> =
once_cell::sync::Lazy::new(|| OnceCell::new());
// Core constants
const LAMPORTS_PER_SOL: u64 = 1_000_000_000;
const WSOL_ADDRESS: &str = "So11111111111111111111111111111111111111112";
const TOKEN_PROGRAM_ID: &str = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
const RAYDIUM_PROGRAM_ID: &str = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
const SERUM_PROGRAM_ID: &str = "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin";
// Jupiter API constants - Updated for v6
const JUPITER_QUOTE_API: &str = "https://quote-api.jup.ag/v6/quote";
const JUPITER_SWAP_API: &str = "https://quote-api.jup.ag/v6/swap-instructions";
// Transaction and trading constants
const RAYDIUM_SWAP_INSTRUCTION: u8 = 9;
const DEFAULT_COMPUTE_LIMIT: u32 = 200_000;
const DEFAULT_PRIORITY_FEE: u64 = 1_000_000; // 0.001 SOL
const DEFAULT_SLIPPAGE: f64 = 0.01; // 1%
const DEFAULT_MAX_RETRIES: usize = 3;
// Balance safety thresholds
const DEFAULT_MIN_SOL_BALANCE: f64 = 0.03;
const DEFAULT_MIN_WSOL_BALANCE: f64 = 0.02;
// Simplified error handling
struct PyError(String);
impl From<PyError> for PyErr {
fn from(err: PyError) -> PyErr {
PyRuntimeError::new_err(err.0)
}
}
// Initialize tokio runtime for async operations if needed
fn get_tokio_runtime() -> &'static Runtime {
static RUNTIME: OnceCell<Runtime> = OnceCell::new();
RUNTIME.get_or_init(|| Runtime::new().expect("Failed to create tokio runtime"))
}
/// Represents dynamic Raydium swap accounts
#[derive(Debug, Clone)]
struct RaydiumSwapAccounts {
amm_id: String,
amm_authority: String,
amm_open_orders: String,
amm_target_orders: String,
pool_coin_token_account: String,
pool_pc_token_account: String,
serum_program_id: String,
serum_market: String,
serum_bids: String,
serum_asks: String,
serum_event_queue: String,
serum_coin_vault: String,
serum_pc_vault: String,
serum_vault_signer: String,
token_mint: String,
token_program_id: String,
}
/// Main Solana trading class
#[pyclass]
struct SolanaTrader {
keypair: Arc<Keypair>,
rpc_client: Arc<RpcClient>,
rpc_url: String,
ws_url: String,
compute_limit: u32,
priority_fee: u64,
skip_preflight: bool,
max_retries: usize,
min_sol_balance: f64,
min_wsol_balance: f64,
wsol_address: String,
token_program_id: String,
system_program_id: String,
raydium_program_id: String,
serum_program_id: String,
keypair_path: String,
http_client: reqwest::blocking::Client,
api_key: Option<String>, // Add API key field for authentication
}
#[pymethods]
impl SolanaTrader {
/// Initialize the SolanaTrader with configuration
#[new]
fn new(
rpc_url: String,
ws_url: String,
keypair_path: String,
compute_limit: Option<u32>,
api_key: Option<String>, // Add API key parameter
) -> PyResult<Self> {
// Tokio runtime initialization
let _ = get_tokio_runtime();
// Load environment variables if .env file exists
let _ = dotenv::dotenv();
// Get API key from parameter or environment variable
let api_key = api_key.or_else(|| env::var("HELIUS_API_KEY").ok());
if let Some(key) = &api_key {
info!("Using API key for authentication");
}
// Keypair loading with robust error handling
let keypair = if Path::new(&keypair_path).exists() {
// First, try to read as JSON
match read_keypair_file(&keypair_path) {
Ok(kp) => kp,
Err(_) => {
// If JSON fails, try to read as binary
match std::fs::read(&keypair_path) {
Ok(bytes) => {
if bytes.len() == 64 {
match Keypair::from_bytes(&bytes) {
Ok(kp) => kp,
Err(e) => {
return Err(PyValueError::new_err(format!(
"Invalid binary keypair: {}",
e
)))
}
}
} else {
// Try base58 decoding
match bs58::decode(std::str::from_utf8(&bytes).unwrap_or(""))
.into_vec()
.map(|secret_key| Keypair::from_bytes(&secret_key))
{
Ok(Ok(kp)) => kp,
_ => {
return Err(PyValueError::new_err(
"Could not parse keypair file in any format",
))
}
}
}
}
Err(e) => {
return Err(PyRuntimeError::new_err(format!(
"Failed to read keypair file: {}",
e
)))
}
}
}
}
} else {
return Err(PyFileNotFoundError::new_err(format!(
"Keypair file not found: {}",
keypair_path
)));
};
// Create RPC client with processed commitment and authentication
// Fix: Use available methods in solana 1.18.x
let rpc_client = {
// Set timeout and commitment
let commitment_config = CommitmentConfig::processed();
let timeout = Duration::from_millis(500);
// Create client with timeout and commitment
// For Solana 1.18.x, we need to use the correct available constructors
let client = RpcClient::new_with_timeout_and_commitment(
rpc_url.clone(),
timeout,
commitment_config,
);
Arc::new(client)
};
// Read environment variables with fallbacks
let compute = compute_limit.unwrap_or_else(|| {
env::var("COMPUTE_LIMIT")
.map(|v| v.parse::<u32>().unwrap_or(DEFAULT_COMPUTE_LIMIT))
.unwrap_or(DEFAULT_COMPUTE_LIMIT)
});
let priority_fee = env::var("PRIORITY_FEE")
.map(|v| v.parse::<u64>().unwrap_or(DEFAULT_PRIORITY_FEE))
.unwrap_or(DEFAULT_PRIORITY_FEE);
let skip_preflight = env::var("SKIP_PREFLIGHT")
.map(|v| v.parse::<bool>().unwrap_or(true))
.unwrap_or(true);
let max_retries = env::var("MAX_RETRIES")
.map(|v| v.parse::<usize>().unwrap_or(DEFAULT_MAX_RETRIES))
.unwrap_or(DEFAULT_MAX_RETRIES);
let min_sol_balance = env::var("MIN_SOL_BALANCE")
.map(|v| v.parse::<f64>().unwrap_or(DEFAULT_MIN_SOL_BALANCE))
.unwrap_or(DEFAULT_MIN_SOL_BALANCE);
let min_wsol_balance = env::var("MIN_WSOL_BALANCE")
.map(|v| v.parse::<f64>().unwrap_or(DEFAULT_MIN_WSOL_BALANCE))
.unwrap_or(DEFAULT_MIN_WSOL_BALANCE);
// Create optimized HTTP client with authentication
let http_client = {
let builder = reqwest::blocking::Client::builder()
.timeout(Duration::from_millis(500))
.connect_timeout(Duration::from_millis(300))
.pool_max_idle_per_host(32);
// Create default headers
let mut headers = HeaderMap::new();
// Add Solana CLI User-Agent header
if let Ok(value) = HeaderValue::from_str("solana-cli 1.18.23 (src:aeb3a2e1; feat:4215500110, client:SolanaLabs)") {
headers.insert(reqwest::header::USER_AGENT, value);
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
}
// Add authentication headers if API key is provided
if let Some(key) = &api_key {
let auth_value = format!("Bearer {}", key);
if let Ok(value) = HeaderValue::from_str(&auth_value) {
headers.insert(AUTHORIZATION, value);
}
}
// Build the client with headers
builder.default_headers(headers).build()
}.unwrap_or_else(|_| reqwest::blocking::Client::new());
// Initialize fast blockhash cache if not already initialized
BLOCKHASH_CACHE.get_or_init(|| {
let cache = FastBlockhashCache::new(
rpc_client.clone(),
2, // Update every 2 seconds
);
Arc::new(cache)
});
// Initialize Jupiter cache if not already initialized
JUPITER_CACHE.get_or_init(|| {
let cache = JupiterCache::new(10); // 10 second TTL
Arc::new(cache)
});
// Start a periodic cache cleanup thread
{
let jupiter_cache = JUPITER_CACHE.get().unwrap().clone();
std::thread::spawn(move || loop {
std::thread::sleep(Duration::from_secs(60));
jupiter_cache.clear_expired();
debug!("Cleared expired Jupiter cache entries");
});
}
info!(
"Initialized trader with compute_limit: {}, priority_fee: {}",
compute, priority_fee
);
Ok(Self {
keypair: Arc::new(keypair),
rpc_client,
rpc_url,
ws_url,
compute_limit: compute,
priority_fee,
skip_preflight,
max_retries,
min_sol_balance,
min_wsol_balance,
wsol_address: WSOL_ADDRESS.to_string(),
token_program_id: TOKEN_PROGRAM_ID.to_string(),
system_program_id: "11111111111111111111111111111111".to_string(),
raydium_program_id: RAYDIUM_PROGRAM_ID.to_string(),
serum_program_id: SERUM_PROGRAM_ID.to_string(),
keypair_path,
http_client,
api_key,
})
}
/// Execute token swap using Jupiter (Solana's DEX aggregator) - Optimized implementation
fn execute_jupiter_swap(
&self,
py: Python<'_>,
input_mint: String,
output_mint: String,
amount_sol: f64,
slippage_percent: Option<f64>,
priority_multiplier: Option<u64>,
) -> PyResult<String> {
// Convert amount to lamports
let amount_lamports = (amount_sol * LAMPORTS_PER_SOL as f64).round() as u64;
// Dynamic slippage calculation
let slippage = match slippage_percent {
Some(val) => val / 100.0,
None => DEFAULT_SLIPPAGE,
};
let slippage_bps = (slippage * 10000.0) as u32;
// Priority fee calculation
let priority_multiplier_value = priority_multiplier.unwrap_or(1);
let priority_fee_value = self.priority_fee * priority_multiplier_value;
// Ensure token accounts exist for both input and output before releasing the GIL
if input_mint != WSOL_ADDRESS {
self.create_token_account_if_needed(py, input_mint.clone())?;
}
if output_mint != WSOL_ADDRESS {
self.create_token_account_if_needed(py, output_mint.clone())?;
}
// Now we can safely release the GIL for network operations
py.allow_threads(|| {
// Step 1: Try to get from cache first
let cache_key = format!(
"{}:{}:{}:{}",
input_mint, output_mint, amount_lamports, slippage_bps
);
let jupiter_cache = JUPITER_CACHE.get().unwrap();
let quote_response = match jupiter_cache.get(&cache_key) {
Some(cached) => {
debug!("Jupiter quote cache hit: {}", cache_key);
cached
}
None => {
// Step 1: Get a quote from Jupiter API
let quote_url = format!(
"{}?inputMint={}&outputMint={}&amount={}&slippageBps={}",
JUPITER_QUOTE_API, input_mint, output_mint, amount_lamports, slippage_bps
);
info!("Getting Jupiter quote: {}", quote_url);
// Implement retry logic
let mut retries = 0;
let max_retries = 3;
loop {
// Add authentication headers if needed
let mut request = self.http_client.get("e_url);
if let Some(key) = &self.api_key {
request = request.header(AUTHORIZATION, format!("Bearer {}", key));
}
match request.send() {
Ok(response) => {
if response.status().is_success() {
match response.json::<serde_json::Value>() {
Ok(quote_data) => {
// Check for API errors
if quote_data.get("error").is_some() {
let error_msg = quote_data["error"]
.as_str()
.unwrap_or("Unknown error");
error!("Jupiter quote error: {}", error_msg);
return Err(PyError(format!(
"Jupiter quote error: {}",
error_msg
))
.into());
}
// Cache the result
jupiter_cache
.set(cache_key.clone(), quote_data.clone());
// Return quote
break quote_data;
}
Err(e) => {
error!("Failed to parse Jupiter quote response: {}", e);
return Err(PyError(format!(
"Failed to parse Jupiter quote: {}",
e
))
.into());
}
}
} else {
let status = response.status();
error!("Jupiter API error: HTTP {}", status);
// For server errors, retry with linear backoff
if status.is_server_error() && retries < max_retries {
retries += 1;
let wait_time = 50 * retries; // Linear backoff
std::thread::sleep(Duration::from_millis(wait_time));
continue;
}
return Err(PyError(format!(
"Jupiter API error: HTTP {}",
status
))
.into());
}
}
Err(e) => {
error!("Jupiter API request failed: {}", e);
// Network errors are retried linearly
if retries < max_retries {
retries += 1;
let wait_time = 50 * retries; // Linear backoff
std::thread::sleep(Duration::from_millis(wait_time));
continue;
}
return Err(
PyError(format!("Jupiter API request failed: {}", e)).into()
);
}
}
}
}
};
let max_accounts = env::var("MAX_ACCOUNTS")
.map(|v| v.parse::<u32>().unwrap_or(8))
.unwrap_or(8);
// Step 2: Use the swap-instructions endpoint
let instr_payload = serde_json::json!({
"quoteResponse": quote_response,
"userPublicKey": self.keypair.pubkey().to_string(),
"computeUnitPriceMicroLamports": priority_fee_value,
"wrapUnwrapSOL": true,
"onlyDirectRoutes": true,
"maxHops": 1,
"maxAccounts": max_accounts
});
info!("Getting Jupiter swap instructions");
// Implement retry logic for instructions
let mut retries = 0;
let max_retries = 3;
let instr_response = loop {
// Add authentication headers if needed
let mut request = self.http_client.post(JUPITER_SWAP_API).json(&instr_payload);
if let Some(key) = &self.api_key {
request = request.header(AUTHORIZATION, format!("Bearer {}", key));
}
match request.send() {
Ok(response) => {
if response.status().is_success() {
match response.json::<serde_json::Value>() {
Ok(instr_data) => {
// Check for API errors
if instr_data.get("error").is_some() {
let error_msg =
instr_data["error"].as_str().unwrap_or("Unknown error");
error!("Jupiter instructions error: {}", error_msg);
return Err(PyError(format!(
"Jupiter instructions error: {}",
error_msg
))
.into());
}
break instr_data;
}
Err(e) => {
error!("Failed to parse Jupiter instructions response: {}", e);
return Err(PyError(format!(
"Failed to parse Jupiter instructions response: {}",
e
))
.into());
}
}
} else {
let status = response.status();
error!("Jupiter API error: HTTP {}", status);
// For server errors, retry with linear backoff
if status.is_server_error() && retries < max_retries {
retries += 1;
let wait_time = 50 * retries; // Linear backoff
std::thread::sleep(Duration::from_millis(wait_time));
continue;
}
return Err(
PyError(format!("Jupiter API error: HTTP {}", status)).into()
);
}
}
Err(e) => {
error!("Jupiter API request failed: {}", e);
// Network errors are retried linearly
if retries < max_retries {
retries += 1;
let wait_time = 50 * retries; // Linear backoff
std::thread::sleep(Duration::from_millis(wait_time));
continue;
}
return Err(PyError(format!("Jupiter API request failed: {}", e)).into());
}
}
};
// Step 3: Create essential instructions to keep transaction size small
let mut essential_instructions = Vec::new();
// Add compute budget instructions
essential_instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(
self.compute_limit,
));
essential_instructions.push(ComputeBudgetInstruction::set_compute_unit_price(
priority_fee_value,
));
// Parse setupInstructions if they exist
if let Some(setup_instructions) = instr_response
.get("setupInstructions")
.and_then(|v| v.as_array())
{
for instr in setup_instructions {
let program_id_str = instr["programId"].as_str().ok_or_else(|| {
error!("Program ID not found in setup instruction");
PyError("Program ID not found in setup instruction".to_string())
})?;
let program_id = Pubkey::from_str(program_id_str).map_err(|e| {
error!("Invalid program ID: {}", e);
PyError(format!("Invalid program ID: {}", e))
})?;
let accounts_arr = instr["accounts"].as_array().ok_or_else(|| {
error!("Accounts not found in setup instruction");
PyError("Accounts not found in setup instruction".to_string())
})?;
let mut accounts = Vec::new();
for account in accounts_arr {
let pubkey_str = account["pubkey"].as_str().ok_or_else(|| {
error!("Pubkey not found in account");
PyError("Pubkey not found in account".to_string())
})?;
let pubkey = Pubkey::from_str(pubkey_str).map_err(|e| {
error!("Invalid pubkey: {}", e);
PyError(format!("Invalid pubkey: {}", e))
})?;
let is_signer = account["isSigner"].as_bool().unwrap_or(false);
let is_writable = account["isWritable"].as_bool().unwrap_or(false);
let account_meta = if is_writable {
if is_signer {
AccountMeta::new(pubkey, true)
} else {
AccountMeta::new(pubkey, false)
}
} else {
if is_signer {
AccountMeta::new_readonly(pubkey, true)
} else {
AccountMeta::new_readonly(pubkey, false)
}
};
accounts.push(account_meta);
}
let data_str = instr["data"].as_str().ok_or_else(|| {
error!("Data not found in setup instruction");
PyError("Data not found in setup instruction".to_string())
})?;
let data = base64::decode(data_str).map_err(|e| {
error!("Failed to decode setup instruction data: {}", e);
PyError(format!("Failed to decode setup instruction data: {}", e))
})?;
let instruction = Instruction {
program_id,
accounts,
data,
};
essential_instructions.push(instruction);
}
}
// Add swapInstruction if it exists (this is critical)
if let Some(swap_instr) = instr_response.get("swapInstruction") {
let program_id_str = swap_instr["programId"].as_str().ok_or_else(|| {
error!("Program ID not found in swap instruction");
PyError("Program ID not found in swap instruction".to_string())
})?;
let program_id = Pubkey::from_str(program_id_str).map_err(|e| {
error!("Invalid program ID: {}", e);
PyError(format!("Invalid program ID: {}", e))
})?;
let accounts_arr = swap_instr["accounts"].as_array().ok_or_else(|| {
error!("Accounts not found in swap instruction");
PyError("Accounts not found in swap instruction".to_string())
})?;
let mut accounts = Vec::new();
for account in accounts_arr {
let pubkey_str = account["pubkey"].as_str().ok_or_else(|| {
error!("Pubkey not found in account");
PyError("Pubkey not found in account".to_string())
})?;
let pubkey = Pubkey::from_str(pubkey_str).map_err(|e| {
error!("Invalid pubkey: {}", e);
PyError(format!("Invalid pubkey: {}", e))
})?;
let is_signer = account["isSigner"].as_bool().unwrap_or(false);
let is_writable = account["isWritable"].as_bool().unwrap_or(false);
let account_meta = if is_writable {
if is_signer {
AccountMeta::new(pubkey, true)
} else {
AccountMeta::new(pubkey, false)
}
} else {
if is_signer {
AccountMeta::new_readonly(pubkey, true)
} else {
AccountMeta::new_readonly(pubkey, false)
}
};
accounts.push(account_meta);
}
let data_str = swap_instr["data"].as_str().ok_or_else(|| {
error!("Data not found in swap instruction");
PyError("Data not found in swap instruction".to_string())
})?;
let data = base64::decode(data_str).map_err(|e| {
error!("Failed to decode swap instruction data: {}", e);
PyError(format!("Failed to decode swap instruction data: {}", e))
})?;
let instruction = Instruction {
program_id,
accounts,
data,
};
essential_instructions.push(instruction);
} else {
error!("No swap instruction found in response");
return Err(
PyError("Jupiter swap instruction not found in response".to_string()).into(),
);
}
// Add cleanupInstruction if it exists
if let Some(cleanup_instr) = instr_response.get("cleanupInstruction") {
let program_id_str = cleanup_instr["programId"].as_str().ok_or_else(|| {
error!("Program ID not found in cleanup instruction");
PyError("Program ID not found in cleanup instruction".to_string())
})?;
let program_id = Pubkey::from_str(program_id_str).map_err(|e| {
error!("Invalid program ID: {}", e);
PyError(format!("Invalid program ID: {}", e))
})?;
let accounts_arr = cleanup_instr["accounts"].as_array().ok_or_else(|| {
error!("Accounts not found in cleanup instruction");
PyError("Accounts not found in cleanup instruction".to_string())
})?;
let mut accounts = Vec::new();
for account in accounts_arr {
let pubkey_str = account["pubkey"].as_str().ok_or_else(|| {
error!("Pubkey not found in account");
PyError("Pubkey not found in account".to_string())
})?;
let pubkey = Pubkey::from_str(pubkey_str).map_err(|e| {
error!("Invalid pubkey: {}", e);
PyError(format!("Invalid pubkey: {}", e))
})?;
let is_signer = account["isSigner"].as_bool().unwrap_or(false);
let is_writable = account["isWritable"].as_bool().unwrap_or(false);
let account_meta = if is_writable {
if is_signer {
AccountMeta::new(pubkey, true)
} else {
AccountMeta::new(pubkey, false)
}
} else {
if is_signer {
AccountMeta::new_readonly(pubkey, true)
} else {
AccountMeta::new_readonly(pubkey, false)
}
};
accounts.push(account_meta);
}
let data_str = cleanup_instr["data"].as_str().ok_or_else(|| {
error!("Data not found in cleanup instruction");
PyError("Data not found in cleanup instruction".to_string())
})?;
let data = base64::decode(data_str).map_err(|e| {
error!("Failed to decode cleanup instruction data: {}", e);
PyError(format!("Failed to decode cleanup instruction data: {}", e))
})?;
let instruction = Instruction {
program_id,
accounts,
data,
};
essential_instructions.push(instruction);
}
// Check if we have any instructions beyond compute budget
if essential_instructions.len() <= 2 {
error!("No Jupiter instructions found in response");
return Err(
PyError("Jupiter instructions not found in response".to_string()).into(),
);
}
// Step 4: Get recent blockhash from optimized cache
let blockhash_cache = BLOCKHASH_CACHE.get().unwrap();
let blockhash = blockhash_cache.get_blockhash();
debug!("Using cached blockhash: {}", blockhash);
// Step 5: Create and sign transaction with essential instructions
let mut transaction = Transaction::new_signed_with_payer(
&essential_instructions,
Some(&self.keypair.pubkey()),
&[&self.keypair],
blockhash,
);
// Step 6: Send transaction with optimized retry logic
let send_config = RpcSendTransactionConfig {
skip_preflight: self.skip_preflight,
preflight_commitment: Some(CommitmentLevel::Processed),
encoding: Some(UiTransactionEncoding::Base64),
max_retries: Some(self.max_retries),
..Default::default()
};
// Track if we need to retry with new blockhash
let mut retry_count = 0;
let max_retries = 2; // Specific to blockhash errors
while retry_count <= max_retries {
match self
.rpc_client
.send_transaction_with_config(&transaction, send_config.clone())
{
Ok(signature) => {
info!(
"Jupiter swap executed: {} SOL from {} to {} with signature {}",
amount_sol, input_mint, output_mint, signature
);
return Ok(signature.to_string());
}
Err(e) => {
let error_str = e.to_string();
// If it's a blockhash error, try with fresh blockhash
if error_str.contains("blockhash") && retry_count < max_retries {
error!(
"Blockhash error, retrying with fresh blockhash: {}",
error_str
);
// Force refresh the blockhash
let new_blockhash = blockhash_cache.force_update();
// Create new transaction with fresh blockhash
let new_transaction = Transaction::new_signed_with_payer(
&essential_instructions,
Some(&self.keypair.pubkey()),
&[&self.keypair],
new_blockhash,
);
// Update transaction for next retry
transaction = new_transaction;
retry_count += 1;
continue;
}
error!("Failed to send transaction: {}", e);
return Err(PyError(format!("Failed to send transaction: {}", e)).into());
}
}
}
Err(PyError("Max retries exceeded".to_string()).into())
})
}
/// Sell tokens for SOL via Jupiter - Optimized version
fn sell_token_for_sol_via_jupiter(
&self,
py: Python<'_>,
token_mint: String,
amount_tokens: f64,
slippage_percent: Option<f64>,
priority_multiplier: Option<u64>,
) -> PyResult<String> {
// Get token decimals first while holding the GIL
let token_decimals = self.get_token_decimals(py, token_mint.clone())?;
// Convert amount to token units
let amount_in = (amount_tokens * 10f64.powi(token_decimals as i32)) as u64;
// Dynamic slippage calculation
let slippage = match slippage_percent {
Some(val) => val / 100.0,
None => DEFAULT_SLIPPAGE,
};
let slippage_bps = (slippage * 10000.0) as u32;
// Priority fee calculation
let priority_multiplier_value = priority_multiplier.unwrap_or(1);
let priority_fee_value = self.priority_fee * priority_multiplier_value;
// SOL mint address
let sol_mint = WSOL_ADDRESS.to_string();
let max_accounts = env::var("MAX_ACCOUNTS")
.map(|v| v.parse::<u32>().unwrap_or(8))
.unwrap_or(8);
// Now release the GIL for network operations
py.allow_threads(|| {
// Step 1: Check if we have this in cache
let cache_key = format!("{}:{}:{}:{}", token_mint, sol_mint, amount_in, slippage_bps);
let jupiter_cache = JUPITER_CACHE.get().unwrap();
let quote_response = match jupiter_cache.get(&cache_key) {
Some(cached) => {
debug!("Jupiter quote cache hit: {}", cache_key);
cached
}
None => {
// Step 1: Get a quote from Jupiter API
let quote_url = format!(
"{}?inputMint={}&outputMint={}&amount={}&slippageBps={}",
JUPITER_QUOTE_API, token_mint, sol_mint, amount_in, slippage_bps
);
info!("Getting Jupiter quote for token sell: {}", quote_url);
// Implement retry logic
let mut retries = 0;
let max_retries = 3;
loop {
// Add authentication headers if needed
let mut request = self.http_client.get("e_url);
if let Some(key) = &self.api_key {
request = request.header(AUTHORIZATION, format!("Bearer {}", key));
}
match request.send() {
Ok(response) => {
if response.status().is_success() {
match response.json::<serde_json::Value>() {
Ok(quote_data) => {
// Check for API errors
if quote_data.get("error").is_some() {
let error_msg = quote_data["error"]
.as_str()
.unwrap_or("Unknown error");
error!("Jupiter quote error: {}", error_msg);
return Err(PyError(format!(
"Jupiter quote error: {}",
error_msg
))
.into());
}
// Cache the result
jupiter_cache
.set(cache_key.clone(), quote_data.clone());
// Return quote
break quote_data;
}
Err(e) => {
error!("Failed to parse Jupiter quote response: {}", e);
return Err(PyError(format!(
"Failed to parse Jupiter quote: {}",
e
))
.into());
}
}
} else {
let status = response.status();
error!("Jupiter API error: HTTP {}", status);
// For server errors, retry with linear backoff
if status.is_server_error() && retries < max_retries {
retries += 1;
let wait_time = 50 * retries; // Linear backoff
std::thread::sleep(Duration::from_millis(wait_time));
continue;
}
return Err(PyError(format!(
"Jupiter API error: HTTP {}",
status
))
.into());
}
}
Err(e) => {
error!("Jupiter API request failed: {}", e);
// Network errors are retried linearly
if retries < max_retries {
retries += 1;
let wait_time = 50 * retries; // Linear backoff
std::thread::sleep(Duration::from_millis(wait_time));
continue;
}
return Err(
PyError(format!("Jupiter API request failed: {}", e)).into()
);
}
}
}
}
};
// Step 2: Use the swap-instructions endpoint
let instr_payload = serde_json::json!({
"quoteResponse": quote_response,
"userPublicKey": self.keypair.pubkey().to_string(),
"computeUnitPriceMicroLamports": priority_fee_value,
"wrapUnwrapSOL": true,
// Since it's a token sale, we want fast execution
"onlyDirectRoutes": true,
"maxHops": 1,
"maxAccounts": max_accounts
});
info!("Getting Jupiter swap instructions for sell");
// Implement retry logic for instructions
let mut retries = 0;
let max_retries = 3;
let instr_response = loop {
// Add authentication headers if needed
let mut request = self.http_client.post(JUPITER_SWAP_API).json(&instr_payload);
if let Some(key) = &self.api_key {
request = request.header(AUTHORIZATION, format!("Bearer {}", key));
}
match request.send() {
Ok(response) => {
if response.status().is_success() {
match response.json::<serde_json::Value>() {
Ok(instr_data) => {
// Check for API errors
if instr_data.get("error").is_some() {
let error_msg =
instr_data["error"].as_str().unwrap_or("Unknown error");
error!("Jupiter instructions error: {}", error_msg);
return Err(PyError(format!(
"Jupiter instructions error: {}",
error_msg
))
.into());
}
break instr_data;
}
Err(e) => {
error!("Failed to parse Jupiter instructions response: {}", e);
return Err(PyError(format!(
"Failed to parse Jupiter instructions response: {}",
e
))
.into());
}
}
} else {
let status = response.status();
error!("Jupiter API error: HTTP {}", status);
// For server errors, retry with linear backoff
if status.is_server_error() && retries < max_retries {
retries += 1;
let wait_time = 50 * retries; // Linear backoff
std::thread::sleep(Duration::from_millis(wait_time));
continue;
}
return Err(
PyError(format!("Jupiter API error: HTTP {}", status)).into()
);
}
}
Err(e) => {
error!("Jupiter API request failed: {}", e);
// Network errors are retried linearly
if retries < max_retries {
retries += 1;
let wait_time = 50 * retries; // Linear backoff
std::thread::sleep(Duration::from_millis(wait_time));
continue;
}
return Err(PyError(format!("Jupiter API request failed: {}", e)).into());
}
}
};
// Step 3: Create essential instructions to keep transaction size small
let mut essential_instructions = Vec::new();
// Add compute budget instructions
essential_instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(
self.compute_limit,
));
essential_instructions.push(ComputeBudgetInstruction::set_compute_unit_price(
priority_fee_value,
));
// Parse setupInstructions if they exist
if let Some(setup_instructions) = instr_response
.get("setupInstructions")
.and_then(|v| v.as_array())
{
for instr in setup_instructions {
let program_id_str = instr["programId"].as_str().ok_or_else(|| {
error!("Program ID not found in setup instruction");
PyError("Program ID not found in setup instruction".to_string())
})?;
let program_id = Pubkey::from_str(program_id_str).map_err(|e| {
error!("Invalid program ID: {}", e);
PyError(format!("Invalid program ID: {}", e))
})?;
let accounts_arr = instr["accounts"].as_array().ok_or_else(|| {
error!("Accounts not found in setup instruction");
PyError("Accounts not found in setup instruction".to_string())
})?;
let mut accounts = Vec::new();
for account in accounts_arr {
let pubkey_str = account["pubkey"].as_str().ok_or_else(|| {
error!("Pubkey not found in account");
PyError("Pubkey not found in account".to_string())
})?;
let pubkey = Pubkey::from_str(pubkey_str).map_err(|e| {
error!("Invalid pubkey: {}", e);
PyError(format!("Invalid pubkey: {}", e))
})?;
let is_signer = account["isSigner"].as_bool().unwrap_or(false);
let is_writable = account["isWritable"].as_bool().unwrap_or(false);
let account_meta = if is_writable {
if is_signer {
AccountMeta::new(pubkey, true)
} else {
AccountMeta::new(pubkey, false)
}
} else {
if is_signer {
AccountMeta::new_readonly(pubkey, true)
} else {
AccountMeta::new_readonly(pubkey, false)
}
};
accounts.push(account_meta);
}
let data_str = instr["data"].as_str().ok_or_else(|| {
error!("Data not found in setup instruction");
PyError("Data not found in setup instruction".to_string())
})?;
let data = base64::decode(data_str).map_err(|e| {
error!("Failed to decode setup instruction data: {}", e);
PyError(format!("Failed to decode setup instruction data: {}", e))
})?;
let instruction = Instruction {
program_id,
accounts,
data,
};
essential_instructions.push(instruction);
}
}
// Add swapInstruction if it exists (this is critical)
if let Some(swap_instr) = instr_response.get("swapInstruction") {
let program_id_str = swap_instr["programId"].as_str().ok_or_else(|| {
error!("Program ID not found in swap instruction");
PyError("Program ID not found in swap instruction".to_string())
})?;
let program_id = Pubkey::from_str(program_id_str).map_err(|e| {
error!("Invalid program ID: {}", e);
PyError(format!("Invalid program ID: {}", e))
})?;
let accounts_arr = swap_instr["accounts"].as_array().ok_or_else(|| {
error!("Accounts not found in swap instruction");
PyError("Accounts not found in swap instruction".to_string())
})?;
let mut accounts = Vec::new();
for account in accounts_arr {
let pubkey_str = account["pubkey"].as_str().ok_or_else(|| {
error!("Pubkey not found in account");
PyError("Pubkey not found in account".to_string())
})?;
let pubkey = Pubkey::from_str(pubkey_str).map_err(|e| {
error!("Invalid pubkey: {}", e);
PyError(format!("Invalid pubkey: {}", e))
})?;
let is_signer = account["isSigner"].as_bool().unwrap_or(false);
let is_writable = account["isWritable"].as_bool().unwrap_or(false);
let account_meta = if is_writable {
if is_signer {
AccountMeta::new(pubkey, true)
} else {
AccountMeta::new(pubkey, false)
}
} else {
if is_signer {
AccountMeta::new_readonly(pubkey, true)
} else {
AccountMeta::new_readonly(pubkey, false)
}
};
accounts.push(account_meta);
}
let data_str = swap_instr["data"].as_str().ok_or_else(|| {
error!("Data not found in swap instruction");
PyError("Data not found in swap instruction".to_string())
})?;
let data = base64::decode(data_str).map_err(|e| {
error!("Failed to decode swap instruction data: {}", e);
PyError(format!("Failed to decode swap instruction data: {}", e))
})?;
let instruction = Instruction {
program_id,
accounts,
data,
};
essential_instructions.push(instruction);
} else {
error!("No swap instruction found in response");
return Err(
PyError("Jupiter swap instruction not found in response".to_string()).into(),
);
}
// Add cleanupInstruction if it exists
if let Some(cleanup_instr) = instr_response.get("cleanupInstruction") {
let program_id_str = cleanup_instr["programId"].as_str().ok_or_else(|| {
error!("Program ID not found in cleanup instruction");
PyError("Program ID not found in cleanup instruction".to_string())
})?;
let program_id = Pubkey::from_str(program_id_str).map_err(|e| {
error!("Invalid program ID: {}", e);
PyError(format!("Invalid program ID: {}", e))
})?;
let accounts_arr = cleanup_instr["accounts"].as_array().ok_or_else(|| {
error!("Accounts not found in cleanup instruction");
PyError("Accounts not found in cleanup instruction".to_string())
})?;
let mut accounts = Vec::new();
for account in accounts_arr {
let pubkey_str = account["pubkey"].as_str().ok_or_else(|| {
error!("Pubkey not found in account");
PyError("Pubkey not found in account".to_string())
})?;
let pubkey = Pubkey::from_str(pubkey_str).map_err(|e| {
error!("Invalid pubkey: {}", e);
PyError(format!("Invalid pubkey: {}", e))
})?;
let is_signer = account["isSigner"].as_bool().unwrap_or(false);
let is_writable = account["isWritable"].as_bool().unwrap_or(false);
let account_meta = if is_writable {
if is_signer {
AccountMeta::new(pubkey, true)
} else {
AccountMeta::new(pubkey, false)
}
} else {
if is_signer {
AccountMeta::new_readonly(pubkey, true)
} else {
AccountMeta::new_readonly(pubkey, false)
}
};
accounts.push(account_meta);
}
let data_str = cleanup_instr["data"].as_str().ok_or_else(|| {
error!("Data not found in cleanup instruction");
PyError("Data not found in cleanup instruction".to_string())
})?;
let data = base64::decode(data_str).map_err(|e| {
error!("Failed to decode cleanup instruction data: {}", e);
PyError(format!("Failed to decode cleanup instruction data: {}", e))
})?;
let instruction = Instruction {
program_id,
accounts,
data,
};
essential_instructions.push(instruction);
}
// Check if we have any instructions beyond compute budget
if essential_instructions.len() <= 2 {
error!("No Jupiter instructions found in response");
return Err(
PyError("Jupiter instructions not found in response".to_string()).into(),
);
}
// Step 4: Get recent blockhash from optimized cache
let blockhash_cache = BLOCKHASH_CACHE.get().unwrap();
let blockhash = blockhash_cache.get_blockhash();
debug!("Using cached blockhash: {}", blockhash);
// Step 5: Create and sign transaction with essential instructions
let mut transaction = Transaction::new_signed_with_payer(
&essential_instructions,
Some(&self.keypair.pubkey()),
&[&self.keypair],
blockhash,
);
// Step 6: Send transaction with optimized retry logic
let send_config = RpcSendTransactionConfig {
skip_preflight: self.skip_preflight,
preflight_commitment: Some(CommitmentLevel::Processed),
encoding: Some(UiTransactionEncoding::Base64),
max_retries: Some(self.max_retries),
..Default::default()
};
// Track if we need to retry with new blockhash
let mut retry_count = 0;
let max_retries = 2; // Specific to blockhash errors
while retry_count <= max_retries {
match self
.rpc_client
.send_transaction_with_config(&transaction, send_config.clone())
{
Ok(signature) => {
info!(
"Jupiter token sell executed: {} tokens to SOL with signature {}",
amount_tokens, signature
);
return Ok(signature.to_string());
}
Err(e) => {
let error_str = e.to_string();
// If it's a blockhash error, try with fresh blockhash
if error_str.contains("blockhash") && retry_count < max_retries {
error!(
"Blockhash error, retrying with fresh blockhash: {}",
error_str
);
// Force refresh the blockhash
let new_blockhash = blockhash_cache.force_update();
// Create new transaction with fresh blockhash
let new_transaction = Transaction::new_signed_with_payer(
&essential_instructions,
Some(&self.keypair.pubkey()),
&[&self.keypair],
new_blockhash,
);
// Update transaction for next retry
transaction = new_transaction;
retry_count += 1;
continue;
}
error!("Failed to send transaction: {}", e);
return Err(PyError(format!("Failed to send transaction: {}", e)).into());
}
}
}
Err(PyError("Max retries exceeded".to_string()).into())
})
}
/// Get token decimals with caching for speed
fn get_token_decimals(&self, py: Python<'_>, token_mint: String) -> PyResult<u8> {
// Check cache first
if let Some(decimals) = TOKEN_DECIMALS_CACHE.get(&token_mint) {
return Ok(*decimals);
}
// Not in cache, get from chain with GIL released
py.allow_threads(|| {
// Get token mint pubkey
let token_mint_pubkey = Pubkey::from_str(&token_mint)
.map_err(|e| PyErr::new::<PyValueError, _>(format!("Invalid pubkey: {}", e)))?;
// Get mint data
let mint_data = self
.rpc_client
.get_account_data(&token_mint_pubkey)
.map_err(|e| {
PyErr::new::<PyRuntimeError, _>(format!("Failed to get account data: {}", e))
})?;
// Unpack mint data
let mint_info = spl_token::state::Mint::unpack(&mint_data).map_err(|e| {
PyErr::new::<PyRuntimeError, _>(format!("Failed to unpack mint data: {}", e))
})?;
// Cache the result
TOKEN_DECIMALS_CACHE.insert(token_mint, mint_info.decimals);
Ok(mint_info.decimals)
})
}
/// Create token account if it doesn't exist - with improved error handling
fn create_token_account_if_needed(&self, py: Python<'_>, token_mint: String) -> PyResult<bool> {
// Get token mint pubkey
let token_mint_pubkey = match Pubkey::from_str(&token_mint) {
Ok(pubkey) => pubkey,
Err(e) => return Err(PyValueError::new_err(format!("Invalid pubkey: {}", e))),
};
// Get associated token account
let token_account =
get_associated_token_address(&self.keypair.pubkey(), &token_mint_pubkey);
// Check if account exists - do this with the GIL held
let account_exists = self.rpc_client.get_account_data(&token_account).is_ok();
if account_exists {
return Ok(false);
}
// Create token account instruction
let create_ata_ix = create_associated_token_account_idempotent(
&self.keypair.pubkey(),
&self.keypair.pubkey(),
&token_mint_pubkey,
&Pubkey::from_str(&self.token_program_id)
.map_err(|e| PyValueError::new_err(format!("Invalid token program ID: {}", e)))?,
);
// Now we can release the GIL for the network operation
py.allow_threads(|| {
// Get blockhash from cache
let blockhash_cache = BLOCKHASH_CACHE.get().unwrap();
let blockhash = blockhash_cache.get_blockhash();
// Create transaction
let transaction = Transaction::new_signed_with_payer(
&[create_ata_ix],
Some(&self.keypair.pubkey()),
&[&self.keypair],
blockhash,
);
// Send transaction
match self.rpc_client.send_and_confirm_transaction(&transaction) {
Ok(signature) => {
info!("Created token account for {}: {}", token_mint, signature);
Ok(true)
}
Err(e) => {
// Check if the error is because the account already exists
if e.to_string().contains("already in use") {
debug!("Token account for {} already exists", token_mint);
return Ok(true);
}
error!("Failed to create token account for {}: {}", token_mint, e);
return Err(PyRuntimeError::new_err(format!(
"Failed to create token account: {}",
e
)));
}
}
})
}
/// Get SOL balance with optimized caching
fn get_sol_balance(&self, py: Python<'_>) -> PyResult<f64> {
py.allow_threads(
|| match self.rpc_client.get_balance(&self.keypair.pubkey()) {
Ok(balance) => Ok(balance as f64 / LAMPORTS_PER_SOL as f64),
Err(e) => {
error!("Failed to get SOL balance: {}", e);
Err(PyRuntimeError::new_err(format!(
"Failed to get SOL balance: {}",
e
)))
}
},
)
}
/// Get token balance with optimized error handling
fn get_token_balance(&self, py: Python<'_>, token_mint: String) -> PyResult<f64> {
py.allow_threads(|| {
// Get token mint pubkey
let token_mint_pubkey = Pubkey::from_str(&token_mint)
.map_err(|e| PyValueError::new_err(format!("Invalid pubkey: {}", e)))?;
// Get associated token account
let token_account =
get_associated_token_address(&self.keypair.pubkey(), &token_mint_pubkey);
// Check if account exists
match self.rpc_client.get_account_data(&token_account) {
Ok(data) => {
// Account exists, unpack data
let account_info = spl_token::state::Account::unpack(&data).map_err(|e| {
PyRuntimeError::new_err(format!("Failed to unpack token account: {}", e))
})?;
// Get token mint data for decimals - check cache first
let decimals = if let Some(decimals) = TOKEN_DECIMALS_CACHE.get(&token_mint) {
*decimals
} else {
// Get from chain if not in cache
let mint_data = self
.rpc_client
.get_account_data(&token_mint_pubkey)
.map_err(|e| {
PyRuntimeError::new_err(format!("Failed to get mint data: {}", e))
})?;
let mint_info =
spl_token::state::Mint::unpack(&mint_data).map_err(|e| {
PyRuntimeError::new_err(format!(
"Failed to unpack mint data: {}",
e
))
})?;
// Cache the result
TOKEN_DECIMALS_CACHE.insert(token_mint.clone(), mint_info.decimals);
mint_info.decimals
};
// Calculate balance with proper decimals
let balance = account_info.amount as f64 / 10f64.powi(decimals as i32);
Ok(balance)
}
Err(_) => {
// Account doesn't exist, balance is 0
Ok(0.0)
}
}
})
}
/// Get wallet's public address
fn get_address(&self) -> PyResult<String> {
Ok(self.keypair.pubkey().to_string())
}
/// Get cached blockhash with optimized approach
fn get_cached_blockhash(&self, py: Python<'_>) -> PyResult<String> {
py.allow_threads(|| {
let blockhash_cache = BLOCKHASH_CACHE.get().unwrap();
let blockhash = blockhash_cache.get_blockhash();
Ok(blockhash.to_string())
})
}
/// Force update the blockhash cache
fn force_update_blockhash(&self, py: Python<'_>) -> PyResult<String> {
py.allow_threads(|| {
let blockhash_cache = BLOCKHASH_CACHE.get().unwrap();
let blockhash = blockhash_cache.force_update();
Ok(blockhash.to_string())
})
}
/// Confirm transaction with optimized retry logic
fn confirm_transaction(
&self,
py: Python<'_>,
signature_str: String,
timeout: u64,
) -> PyResult<bool> {
py.allow_threads(|| {
// Parse the signature string - using simplified error handling
let signature = Signature::from_str(&signature_str)
.map_err(|e| PyValueError::new_err(format!("Invalid signature: {}", e)))?;
let start_time = Instant::now();
let timeout_duration = Duration::from_secs(timeout);
while start_time.elapsed() < timeout_duration {
match self.rpc_client.get_signature_status(&signature) {
Ok(Some(Ok(_))) => return Ok(true),
Ok(Some(Err(e))) => {
error!("Transaction failed: {:?}", e);
return Err(PyRuntimeError::new_err(format!(
"Transaction failed: {:?}",
e
)));
}
Ok(None) => {
// Sleep shorter time for faster responses
std::thread::sleep(Duration::from_millis(50));
}
Err(e) => {
error!("Status check failed: {}", e);
// Sleep a bit before retrying
std::thread::sleep(Duration::from_millis(500));
}
}
}
error!("Transaction confirmation timed out");
Err(PyRuntimeError::new_err(
"Transaction confirmation timed out".to_string(),
))
})
}
}
/// Python module initialization
#[pymodule]
fn solana_rust_bot(_py: Python, m: &PyModule) -> PyResult<()> {
// Initialize logging
pyo3_log::init();
// Register classes
m.add_class::<SolanaTrader>()?;
// Add constants
m.add("LAMPORTS_PER_SOL", LAMPORTS_PER_SOL)?;
m.add("DEFAULT_COMPUTE_LIMIT", DEFAULT_COMPUTE_LIMIT)?;
m.add("DEFAULT_PRIORITY_FEE", DEFAULT_PRIORITY_FEE)?;
m.add("DEFAULT_MAX_RETRIES", DEFAULT_MAX_RETRIES)?;
m.add("DEFAULT_MIN_SOL_BALANCE", DEFAULT_MIN_SOL_BALANCE)?;
m.add("DEFAULT_MIN_WSOL_BALANCE", DEFAULT_MIN_WSOL_BALANCE)?;
m.add("WSOL_ADDRESS", WSOL_ADDRESS)?;
Ok(())
}
```
---
If you want me to help with anything else, just say! Take care and rest well.086b3e46c88a6c9c3c491f1dUser: Hi, what's up? User: Hi, what's up? Assistant:
Hello! Not much, just here to help. How can I assist you today?
41e549592c6876e7a5ab1b2fUser: Hi, what's up? User: Hi, what's up? Assistant:
Hello! Not much, just here and ready to help. How about you?
749a1bc16ccf0de67347cacbUser: Hi, what's up? User: Hi, what's up? Assistant:
Hello! Not much, just here and ready to help. How can I assist you today?
41e549592c6876e7a5ab1b2fUser: Hi, what's up? User: Hi, what's up? Assistant:
Hello! Not much, just here and ready to help. How about you?