Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

trades-work #6

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 49 additions & 10 deletions packages/cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use anyhow::Result;
use anyhow::{anyhow, Result};
use clap::Parser;
use libcheese::common::USDC_MINT;
use libcheese::common::{parse_other_token_name, CHEESE_MINT};
use libcheese::jupiter::fetch_jupiter_prices;
use libcheese::meteora::{fetch_meteora_cheese_pools, MeteoraPool};
Expand All @@ -14,10 +15,7 @@ use std::str::FromStr;
use std::time::Duration;
use tokio::time;

const WALLET_CHEESE_BALANCE: f64 = 5_000_000.0;
const WALLET_SOL_BALANCE: f64 = 1.0;
const SOL_PER_TX: f64 = 0.000005; // Approximate SOL cost per transaction
const USDC_MINT: &str = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
const LOOP_INTERVAL: Duration = Duration::from_secs(30);
const MIN_PROFIT_USD: f64 = 1.0; // Minimum profit in USD to execute trade

Expand Down Expand Up @@ -121,7 +119,7 @@ async fn main() -> Result<()> {

let keypair_path = args.keypair.unwrap();
let keypair = read_keypair_file(&keypair_path)
.map_err(|e| anyhow::anyhow!("Failed to read keypair file: {}", e))?;
.map_err(|e| anyhow!("Failed to read keypair file: {}", e))?;

let rpc_url = args
.rpc_url
Expand Down Expand Up @@ -414,20 +412,42 @@ async fn run_iteration(executor: &Option<TradeExecutor>) -> Result<()> {

// Execute trade if in hot mode
if let Some(executor) = executor {
println!("\nExecuting trade...");
println!("\n=== Starting Trade Execution ===");
println!("Trade details:");
println!("- Is sell: {}", opp.is_sell);
println!("- Max trade size: {}", opp.max_trade_size);
println!("- USDC price: {}", opp.usdc_price);
println!("- Implied price: {}", opp.implied_price);
println!("- Net profit USD: {}", opp.net_profit_usd);
println!("- Pool address: {}", opp.pool_address);
println!("- Symbol: {}", opp.symbol);

// Get the other token's index
let (_, other_ix) = if pool.pool_token_mints[0] == CHEESE_MINT {
(0, 1)
} else {
(1, 0)
};
println!("\nPool details:");
println!("- Pool token mints: {:?}", pool.pool_token_mints);
println!("- Pool token amounts: {:?}", pool.pool_token_amounts);
println!("- Other token index: {}", other_ix);

// Ensure all necessary token accounts exist before trading
println!("\nEnsuring token accounts exist...");
executor.ensure_token_account(USDC_MINT).await?;
executor.ensure_token_account(CHEESE_MINT).await?;
executor
.ensure_token_account(&pool.pool_token_mints[other_ix])
.await?;

if opp.is_sell {
// Path: USDC -> CHEESE -> Target -> CHEESE -> USDC
println!("\nExecuting sell path: USDC -> CHEESE -> Target -> CHEESE -> USDC");

// 1. USDC -> CHEESE on Meteora
let amount_in_usdc = (opp.max_trade_size * opp.usdc_price * 1_000_000.0) as u64;
let amount_in_usdc = ((opp.max_trade_size * opp.usdc_price) as u64) * 1_000_000; // Convert to USDC lamports (6 decimals)
println!("\nStep 1: USDC -> CHEESE");
println!("Amount in USDC: {}", amount_in_usdc as f64 / 1_000_000.0); // Display in human-readable USDC
let sig1 = executor
.execute_trade(
usdc_pool,
Expand All @@ -441,6 +461,8 @@ async fn run_iteration(executor: &Option<TradeExecutor>) -> Result<()> {

// 2. CHEESE -> Target token
let amount_in_cheese = (opp.max_trade_size * 1_000_000_000.0) as u64;
println!("\nStep 2: CHEESE -> {}", opp.symbol);
println!("Amount in CHEESE: {}", amount_in_cheese);
let sig2 = executor
.execute_trade(
pool,
Expand All @@ -454,6 +476,8 @@ async fn run_iteration(executor: &Option<TradeExecutor>) -> Result<()> {

// 3. Target -> CHEESE
let amount_in_target = (opp.other_qty * 0.1 * 1_000_000_000.0) as u64; // 10% of target token liquidity
println!("\nStep 3: {} -> CHEESE", opp.symbol);
println!("Amount in {}: {}", opp.symbol, amount_in_target);
let sig3 = executor
.execute_trade(
pool,
Expand All @@ -466,22 +490,28 @@ async fn run_iteration(executor: &Option<TradeExecutor>) -> Result<()> {
println!("3. {} -> CHEESE: {}", opp.symbol, sig3);

// 4. CHEESE -> USDC
println!("\nStep 4: CHEESE -> USDC");
println!("Amount in CHEESE: {}", amount_in_cheese);
let sig4 = executor
.execute_trade(usdc_pool, CHEESE_MINT, USDC_MINT, amount_in_cheese, 50)
.await?;
println!("4. CHEESE -> USDC: {}", sig4);
} else {
// Path: USDC -> CHEESE -> Target -> CHEESE -> USDC
println!("\nExecuting buy path: USDC -> CHEESE -> Target -> CHEESE -> USDC");

// 1. USDC -> CHEESE on Meteora
let amount_in_usdc = (opp.max_trade_size * opp.usdc_price * 1_000_000.0) as u64;
println!("\nStep 1: USDC -> CHEESE");
println!("Amount in USDC: {}", amount_in_usdc);
let sig1 = executor
.execute_trade(usdc_pool, USDC_MINT, CHEESE_MINT, amount_in_usdc, 50)
.await?;
println!("1. USDC -> CHEESE: {}", sig1);

// 2. CHEESE -> Target token
let amount_in_cheese = (opp.max_trade_size * 1_000_000_000.0) as u64;
println!("\nStep 2: CHEESE -> {}", opp.symbol);
println!("Amount in CHEESE: {}", amount_in_cheese);
let sig2 = executor
.execute_trade(
pool,
Expand All @@ -495,6 +525,8 @@ async fn run_iteration(executor: &Option<TradeExecutor>) -> Result<()> {

// 3. Target -> CHEESE
let amount_in_target = (opp.other_qty * 0.1 * 1_000_000_000.0) as u64; // 10% of target token liquidity
println!("\nStep 3: {} -> CHEESE", opp.symbol);
println!("Amount in {}: {}", opp.symbol, amount_in_target);
let sig3 = executor
.execute_trade(
pool,
Expand All @@ -507,6 +539,8 @@ async fn run_iteration(executor: &Option<TradeExecutor>) -> Result<()> {
println!("3. {} -> CHEESE: {}", opp.symbol, sig3);

// 4. CHEESE -> USDC
println!("\nStep 4: CHEESE -> USDC");
println!("Amount in CHEESE: {}", amount_in_cheese);
let sig4 = executor
.execute_trade(usdc_pool, CHEESE_MINT, USDC_MINT, amount_in_cheese, 50)
.await?;
Expand Down Expand Up @@ -538,6 +572,7 @@ fn find_arbitrage_opportunities(

let cheese_qty: f64 = pool.pool_token_amounts[cheese_ix].parse()?;
let other_qty: f64 = pool.pool_token_amounts[other_ix].parse()?;
let is_usdc_pool = pool.pool_token_mints.contains(&USDC_MINT.to_string());
let fee_percent: f64 = pool.total_fee_pct.trim_end_matches('%').parse::<f64>()? / 100.0;

if cheese_qty <= 0.0 || other_qty <= 0.0 {
Expand All @@ -549,7 +584,11 @@ fn find_arbitrage_opportunities(

// If price difference is significant (>1%)
if price_diff_pct.abs() > 1.0 {
let max_trade_size = cheese_qty * 0.1; // 10% of pool liquidity
let max_trade_size = if is_usdc_pool {
cheese_qty * 0.1
} else {
cheese_qty * 0.05
}; // 10% of pool liquidity
let price_diff_per_cheese = (implied_price - cheese_usdc_price).abs();
let gross_profit = max_trade_size * price_diff_per_cheese;

Expand Down
1 change: 1 addition & 0 deletions packages/libcheese/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ anyhow = "1.0"
solana-sdk = "2.1.7"
solana-client = "2.1.7"
spl-associated-token-account = "6.0.0"
spl-token = "4.0.0"
bincode = "1.3"
base64 = "0.22.1"
1 change: 1 addition & 0 deletions packages/libcheese/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use serde::de::{self, Deserializer};
use serde::Deserialize;

pub const CHEESE_MINT: &str = "A3hzGcTxZNSc7744CWB2LR5Tt9VTtEaQYpP6nwripump";
pub const USDC_MINT: &str = "27VkFr6b6DHoR6hSYZjUDbwJsV6MPSFqPavXLg8nduHW";

pub fn de_string_to_f64<'de, D>(deserializer: D) -> std::result::Result<f64, D::Error>
where
Expand Down
117 changes: 106 additions & 11 deletions packages/libcheese/src/solana.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use solana_sdk::{
signer::Signer,
transaction::Transaction,
};
use spl_token;
use std::{str::FromStr, time::Duration};
use tokio::time::sleep;

Expand Down Expand Up @@ -108,7 +109,18 @@ impl TradeExecutor {
.await?;

// 3. Deserialize and sign transaction
let tx: Transaction = bincode::deserialize(&base64::decode(swap_tx)?)?;
let mut tx: Transaction = bincode::deserialize(&base64::decode(swap_tx)?)?;

// Verify and update blockhash if needed
let recent_blockhash = self.rpc_client.get_latest_blockhash()?;
if tx.message.recent_blockhash != recent_blockhash {
tx.message.recent_blockhash = recent_blockhash;
}

// Sign the transaction if not already signed
if tx.signatures.is_empty() || tx.signatures[0] == Signature::default() {
tx.sign(&[&self.wallet], tx.message.recent_blockhash);
}

// 4. Simulate transaction with detailed error reporting
match self.simulate_transaction(&tx).await {
Expand All @@ -126,20 +138,65 @@ impl TradeExecutor {
/// Check if the wallet has sufficient balance for the trade
async fn check_token_balance(&self, mint: &str, amount: u64) -> Result<()> {
let token_account = self.find_token_account(mint)?;

// Check if token account exists
match self.rpc_client.get_account(&token_account) {
Ok(_) => (),
Err(_) => {
println!(
"Token account {} does not exist, creating...",
token_account
);
self.create_token_account(mint).await?;
}
}

let balance = self.rpc_client.get_token_account_balance(&token_account)?;
println!(
"Current balance of {}: {} (need {})",
mint,
balance.ui_amount.unwrap_or(0.0),
amount as f64 / 10f64.powi(balance.decimals as i32)
);

if balance.ui_amount.unwrap_or(0.0) * 10f64.powi(balance.decimals as i32) < amount as f64 {
// Compare raw amounts (lamports) instead of UI amounts
if balance.amount.parse::<u64>().unwrap_or(0) < amount {
return Err(anyhow!(
"Insufficient balance: have {} {}, need {}",
balance.ui_amount.unwrap_or(0.0),
mint,
amount
amount as f64 / 10f64.powi(balance.decimals as i32)
));
}

Ok(())
}

/// Create token account if it doesn't exist
async fn create_token_account(&self, mint: &str) -> Result<()> {
let mint_pubkey = Pubkey::from_str(mint)?;
let owner = self.wallet.pubkey();

let create_ix = spl_associated_token_account::instruction::create_associated_token_account(
&owner,
&owner,
&mint_pubkey,
&spl_token::id(),
);

let recent_blockhash = self.rpc_client.get_latest_blockhash()?;
let tx = Transaction::new_signed_with_payer(
&[create_ix],
Some(&owner),
&[&self.wallet],
recent_blockhash,
);

self.send_and_confirm_transaction(&tx).await?;
println!("Created token account for mint {}", mint);
Ok(())
}

/// Find the associated token account for a given mint
fn find_token_account(&self, mint: &str) -> Result<Pubkey> {
let mint_pubkey = Pubkey::from_str(mint)?;
Expand All @@ -153,19 +210,57 @@ impl TradeExecutor {

/// Simulate a transaction before sending
async fn simulate_transaction(&self, transaction: &Transaction) -> Result<()> {
self.rpc_client
.simulate_transaction(transaction)
.map_err(|e| anyhow!("Transaction simulation failed: {}", e))?;
let sim_result = self.rpc_client.simulate_transaction(transaction)?;

if let Some(err) = sim_result.value.err {
println!("Simulation error: {:?}", err);
if let Some(logs) = sim_result.value.logs {
println!("Transaction logs:");
for log in logs {
println!(" {}", log);
}
}
return Err(anyhow!("Transaction simulation failed: {:?}", err));
}
Ok(())
}

/// Send and confirm a transaction
async fn send_and_confirm_transaction(&self, transaction: &Transaction) -> Result<Signature> {
let signature = self
.rpc_client
.send_and_confirm_transaction(transaction)
.map_err(|e| anyhow!("Failed to send transaction: {}", e))?;
Ok(signature)
let signature = transaction.signatures[0];
println!("Sending transaction with signature: {}", signature);

match self.rpc_client.send_and_confirm_transaction(transaction) {
Ok(_) => {
println!("Transaction confirmed successfully");
Ok(signature)
}
Err(e) => {
println!("Transaction failed: {}", e);
// Try to get more details about the error
if let Ok(status) = self.rpc_client.get_signature_status(&signature) {
println!("Transaction status: {:?}", status);
}
Err(anyhow!("Failed to send transaction: {}", e))
}
}
}

/// Ensure a token account exists for the given mint
pub async fn ensure_token_account(&self, mint: &str) -> Result<()> {
let token_account = self.find_token_account(mint)?;

// Check if token account exists
match self.rpc_client.get_account(&token_account) {
Ok(_) => {
println!("Token account {} exists", token_account);
Ok(())
}
Err(_) => {
println!("Creating token account for mint {}", mint);
self.create_token_account(mint).await
}
}
}
}

Expand Down
Loading