diff --git a/bin/cli/src/main.rs b/bin/cli/src/main.rs index 70c4e95d5..560840b1f 100644 --- a/bin/cli/src/main.rs +++ b/bin/cli/src/main.rs @@ -246,15 +246,8 @@ async fn main() -> Result<(), anyhow::Error> { let output_mint = pubkey_from_cli(&cmd.output_mint); let client = MangoClient::new_for_existing_account(client, account, owner).await?; let txsig = client - .jupiter_v4() - .swap( - input_mint, - output_mint, - cmd.amount, - cmd.slippage_bps, - mango_v4_client::JupiterSwapMode::ExactIn, - false, - ) + .jupiter_v6() + .swap(input_mint, output_mint, cmd.amount, cmd.slippage_bps, false) .await?; println!("{}", txsig); } diff --git a/bin/liquidator/src/main.rs b/bin/liquidator/src/main.rs index f489fabe8..047a7a169 100644 --- a/bin/liquidator/src/main.rs +++ b/bin/liquidator/src/main.rs @@ -57,7 +57,6 @@ enum BoolArg { #[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq, Eq)] enum JupiterVersionArg { Mock, - V4, V6, } @@ -65,7 +64,6 @@ impl From for jupiter::Version { fn from(a: JupiterVersionArg) -> Self { match a { JupiterVersionArg::Mock => jupiter::Version::Mock, - JupiterVersionArg::V4 => jupiter::Version::V4, JupiterVersionArg::V6 => jupiter::Version::V6, } } @@ -172,10 +170,6 @@ struct Cli { #[clap(long, env, value_enum, default_value = "v6")] jupiter_version: JupiterVersionArg, - /// override the url to jupiter v4 - #[clap(long, env, default_value = "https://quote-api.jup.ag/v4")] - jupiter_v4_url: String, - /// override the url to jupiter v6 #[clap(long, env, default_value = "https://quote-api.jup.ag/v6")] jupiter_v6_url: String, @@ -235,7 +229,6 @@ async fn main() -> anyhow::Result<()> { .commitment(commitment) .fee_payer(Some(liqor_owner.clone())) .timeout(rpc_timeout) - .jupiter_v4_url(cli.jupiter_v4_url) .jupiter_v6_url(cli.jupiter_v6_url) .jupiter_token(cli.jupiter_token) .transaction_builder_config( diff --git a/bin/liquidator/src/rebalance.rs b/bin/liquidator/src/rebalance.rs index 23757976a..eb982ec84 100644 --- a/bin/liquidator/src/rebalance.rs +++ b/bin/liquidator/src/rebalance.rs @@ -151,18 +151,7 @@ impl Rebalancer { let direct_sol_route_job = self.jupiter_quote(sol_mint, output_mint, in_amount_sol, true, jupiter_version); - let mut jobs = vec![full_route_job, direct_quote_route_job, direct_sol_route_job]; - - // for v6, add a v4 fallback - if self.config.jupiter_version == jupiter::Version::V6 { - jobs.push(self.jupiter_quote( - quote_mint, - output_mint, - in_amount_quote, - false, - jupiter::Version::V4, - )); - } + let jobs = vec![full_route_job, direct_quote_route_job, direct_sol_route_job]; let mut results = futures::future::join_all(jobs).await; let full_route = results.remove(0)?; @@ -211,18 +200,7 @@ impl Rebalancer { let direct_sol_route_job = self.jupiter_quote(input_mint, sol_mint, in_amount, true, jupiter_version); - let mut jobs = vec![full_route_job, direct_quote_route_job, direct_sol_route_job]; - - // for v6, add a v4 fallback - if self.config.jupiter_version == jupiter::Version::V6 { - jobs.push(self.jupiter_quote( - input_mint, - quote_mint, - in_amount, - false, - jupiter::Version::V4, - )); - } + let jobs = vec![full_route_job, direct_quote_route_job, direct_sol_route_job]; let mut results = futures::future::join_all(jobs).await; let full_route = results.remove(0)?; diff --git a/lib/client/src/client.rs b/lib/client/src/client.rs index 1ee3b2f90..5fe89a8aa 100644 --- a/lib/client/src/client.rs +++ b/lib/client/src/client.rs @@ -90,9 +90,6 @@ pub struct ClientConfig { #[builder(default = "ClientBuilder::default_rpc_confirm_transaction_config()")] pub rpc_confirm_transaction_config: RpcConfirmTransactionConfig, - #[builder(default = "\"https://quote-api.jup.ag/v4\".into()")] - pub jupiter_v4_url: String, - #[builder(default = "\"https://quote-api.jup.ag/v6\".into()")] pub jupiter_v6_url: String, @@ -1823,10 +1820,6 @@ impl MangoClient { // jupiter - pub fn jupiter_v4(&self) -> jupiter::v4::JupiterV4 { - jupiter::v4::JupiterV4 { mango_client: self } - } - pub fn jupiter_v6(&self) -> jupiter::v6::JupiterV6 { jupiter::v6::JupiterV6 { mango_client: self } } @@ -1869,54 +1862,6 @@ impl MangoClient { .await } - pub(crate) async fn deserialize_instructions_and_alts( - &self, - message: &solana_sdk::message::VersionedMessage, - ) -> anyhow::Result<(Vec, Vec)> { - let lookups = message.address_table_lookups().unwrap_or_default(); - let address_lookup_tables = self - .fetch_address_lookup_tables(lookups.iter().map(|a| &a.account_key)) - .await?; - - let mut account_keys = message.static_account_keys().to_vec(); - for (lookups, table) in lookups.iter().zip(address_lookup_tables.iter()) { - account_keys.extend( - lookups - .writable_indexes - .iter() - .map(|&index| table.addresses[index as usize]), - ); - } - for (lookups, table) in lookups.iter().zip(address_lookup_tables.iter()) { - account_keys.extend( - lookups - .readonly_indexes - .iter() - .map(|&index| table.addresses[index as usize]), - ); - } - - let compiled_ix = message - .instructions() - .iter() - .map(|ci| solana_sdk::instruction::Instruction { - program_id: *ci.program_id(&account_keys), - accounts: ci - .accounts - .iter() - .map(|&index| AccountMeta { - pubkey: account_keys[index as usize], - is_signer: message.is_signer(index.into()), - is_writable: message.is_maybe_writable(index.into()), - }) - .collect(), - data: ci.data.clone(), - }) - .collect(); - - Ok((compiled_ix, address_lookup_tables)) - } - fn instruction_cu(&self, health_cu: u32) -> u32 { self.context.compute_estimates.cu_per_mango_instruction + health_cu } diff --git a/lib/client/src/jupiter/mod.rs b/lib/client/src/jupiter/mod.rs index 85434c793..e8eeeb2ed 100644 --- a/lib/client/src/jupiter/mod.rs +++ b/lib/client/src/jupiter/mod.rs @@ -1,23 +1,21 @@ -pub mod v4; pub mod v6; use anchor_lang::prelude::*; use std::str::FromStr; -use crate::{JupiterSwapMode, MangoClient, TransactionBuilder}; +use crate::{MangoClient, TransactionBuilder}; use fixed::types::I80F48; #[derive(Clone, Copy, PartialEq, Eq)] pub enum Version { Mock, - V4, V6, } #[derive(Clone)] +#[allow(clippy::large_enum_variant)] pub enum RawQuote { Mock, - V4(v4::QueryRoute), V6(v6::QuoteResponse), } @@ -32,21 +30,6 @@ pub struct Quote { } impl Quote { - pub fn try_from_v4( - input_mint: Pubkey, - output_mint: Pubkey, - route: v4::QueryRoute, - ) -> anyhow::Result { - Ok(Quote { - input_mint, - output_mint, - price_impact_pct: route.price_impact_pct, - in_amount: route.in_amount.parse()?, - out_amount: route.out_amount.parse()?, - raw: RawQuote::V4(route), - }) - } - pub fn try_from_v6(query: v6::QuoteResponse) -> anyhow::Result { Ok(Quote { input_mint: Pubkey::from_str(&query.input_mint)?, @@ -65,7 +48,6 @@ impl Quote { pub fn first_route_label(&self) -> String { let label_maybe = match &self.raw { RawQuote::Mock => Some("mock".into()), - RawQuote::V4(raw) => raw.market_infos.first().map(|v| v.label.clone()), RawQuote::V6(raw) => raw .route_plan .first() @@ -129,21 +111,6 @@ impl<'a> Jupiter<'a> { ) -> anyhow::Result { Ok(match version { Version::Mock => self.quote_mock(input_mint, output_mint, amount).await?, - Version::V4 => Quote::try_from_v4( - input_mint, - output_mint, - self.mango_client - .jupiter_v4() - .quote( - input_mint, - output_mint, - amount, - slippage_bps, - JupiterSwapMode::ExactIn, - only_direct_routes, - ) - .await?, - )?, Version::V6 => Quote::try_from_v6( self.mango_client .jupiter_v6() @@ -165,12 +132,6 @@ impl<'a> Jupiter<'a> { ) -> anyhow::Result { match "e.raw { RawQuote::Mock => anyhow::bail!("can't prepare jupiter swap for the mock"), - RawQuote::V4(raw) => { - self.mango_client - .jupiter_v4() - .prepare_swap_transaction(quote.input_mint, quote.output_mint, raw) - .await - } RawQuote::V6(raw) => { self.mango_client .jupiter_v6() diff --git a/lib/client/src/jupiter/v4.rs b/lib/client/src/jupiter/v4.rs deleted file mode 100644 index 2c6dfb271..000000000 --- a/lib/client/src/jupiter/v4.rs +++ /dev/null @@ -1,376 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::str::FromStr; - -use anchor_lang::Id; -use anchor_spl::token::Token; - -use bincode::Options; - -use crate::{util, TransactionBuilder}; -use crate::{JupiterSwapMode, MangoClient}; - -use anyhow::Context; -use solana_sdk::instruction::Instruction; -use solana_sdk::signature::Signature; -use solana_sdk::{pubkey::Pubkey, signer::Signer}; - -#[derive(Deserialize, Serialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct QueryResult { - pub data: Vec, - pub time_taken: f64, - pub context_slot: u64, -} - -#[derive(Deserialize, Serialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct QueryRoute { - pub in_amount: String, - pub out_amount: String, - pub price_impact_pct: f64, - pub market_infos: Vec, - pub amount: String, - pub slippage_bps: u64, - pub other_amount_threshold: String, - pub swap_mode: String, - pub fees: Option, -} - -#[derive(Deserialize, Serialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct QueryMarketInfo { - pub id: String, - pub label: String, - pub input_mint: String, - pub output_mint: String, - pub not_enough_liquidity: bool, - pub in_amount: String, - pub out_amount: String, - pub min_in_amount: Option, - pub min_out_amount: Option, - pub price_impact_pct: Option, - pub lp_fee: QueryFee, - pub platform_fee: QueryFee, -} - -#[derive(Deserialize, Serialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct QueryFee { - pub amount: String, - pub mint: String, - pub pct: Option, -} - -#[derive(Deserialize, Serialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct QueryRouteFees { - pub signature_fee: f64, - pub open_orders_deposits: Vec, - pub ata_deposits: Vec, - pub total_fee_and_deposits: f64, - #[serde(rename = "minimalSOLForTransaction")] - pub minimal_sol_for_transaction: f64, -} - -#[derive(Deserialize, Serialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct SwapRequest { - pub route: QueryRoute, - pub user_public_key: String, - #[serde(rename = "wrapUnwrapSOL")] - pub wrap_unwrap_sol: bool, - pub compute_unit_price_micro_lamports: Option, -} - -#[derive(Deserialize, Serialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct SwapResponse { - pub setup_transaction: Option, - pub swap_transaction: String, - pub cleanup_transaction: Option, -} - -pub struct JupiterV4<'a> { - pub mango_client: &'a MangoClient, -} - -impl<'a> JupiterV4<'a> { - pub async fn quote( - &self, - input_mint: Pubkey, - output_mint: Pubkey, - amount: u64, - slippage_bps: u64, - swap_mode: JupiterSwapMode, - only_direct_routes: bool, - ) -> anyhow::Result { - let response = self - .mango_client - .http_client - .get(format!( - "{}/quote", - self.mango_client.client.config().jupiter_v4_url - )) - .query(&[ - ("inputMint", input_mint.to_string()), - ("outputMint", output_mint.to_string()), - ("amount", format!("{}", amount)), - ("onlyDirectRoutes", only_direct_routes.to_string()), - ("enforceSingleTx", "true".into()), - ("filterTopNResult", "10".into()), - ("slippageBps", format!("{}", slippage_bps)), - ( - "swapMode", - match swap_mode { - JupiterSwapMode::ExactIn => "ExactIn", - JupiterSwapMode::ExactOut => "ExactOut", - } - .into(), - ), - ]) - .send() - .await - .context("quote request to jupiter")?; - let quote: QueryResult = util::http_error_handling(response).await.with_context(|| { - format!("error requesting jupiter route between {input_mint} and {output_mint}") - })?; - - let route = quote.data.first().ok_or_else(|| { - anyhow::anyhow!( - "no route for swap. found {} routes, but none were usable", - quote.data.len() - ) - })?; - - Ok(route.clone()) - } - - /// Find the instructions and account lookup tables for a jupiter swap through mango - /// - /// It would be nice if we didn't have to pass input_mint/output_mint - the data is - /// definitely in QueryRoute - but it's unclear how. - pub async fn prepare_swap_transaction( - &self, - input_mint: Pubkey, - output_mint: Pubkey, - route: &QueryRoute, - ) -> anyhow::Result { - let source_token = self.mango_client.context.token_by_mint(&input_mint)?; - let target_token = self.mango_client.context.token_by_mint(&output_mint)?; - - let swap_response = self - .mango_client - .http_client - .post(format!( - "{}/swap", - self.mango_client.client.config().jupiter_v4_url - )) - .json(&SwapRequest { - route: route.clone(), - user_public_key: self.mango_client.owner.pubkey().to_string(), - wrap_unwrap_sol: false, - compute_unit_price_micro_lamports: None, // we already prioritize - }) - .send() - .await - .context("swap transaction request to jupiter")?; - - let swap: SwapResponse = util::http_error_handling(swap_response) - .await - .context("error requesting jupiter swap")?; - - if swap.setup_transaction.is_some() || swap.cleanup_transaction.is_some() { - anyhow::bail!( - "chosen jupiter route requires setup or cleanup transactions, can't execute" - ); - } - - let jup_tx = bincode::options() - .with_fixint_encoding() - .reject_trailing_bytes() - .deserialize::( - &base64::decode(&swap.swap_transaction) - .context("base64 decoding jupiter transaction")?, - ) - .context("parsing jupiter transaction")?; - let ata_program = anchor_spl::associated_token::ID; - let token_program = anchor_spl::token::ID; - let compute_budget_program = solana_sdk::compute_budget::ID; - // these setup instructions should be placed outside of flashloan begin-end - let is_setup_ix = |k: Pubkey| -> bool { - k == ata_program || k == token_program || k == compute_budget_program - }; - let (jup_ixs, jup_alts) = self - .mango_client - .deserialize_instructions_and_alts(&jup_tx.message) - .await?; - let jup_action_ix_begin = jup_ixs - .iter() - .position(|ix| !is_setup_ix(ix.program_id)) - .ok_or_else(|| { - anyhow::anyhow!("jupiter swap response only had setup-like instructions") - })?; - let jup_action_ix_end = jup_ixs.len() - - jup_ixs - .iter() - .rev() - .position(|ix| !is_setup_ix(ix.program_id)) - .unwrap(); - - let bank_ams = [source_token.first_bank(), target_token.first_bank()] - .into_iter() - .map(util::to_writable_account_meta) - .collect::>(); - - let vault_ams = [source_token.first_vault(), target_token.first_vault()] - .into_iter() - .map(util::to_writable_account_meta) - .collect::>(); - - let owner = self.mango_client.owner(); - let account = &self.mango_client.mango_account().await?; - - let token_ams = [source_token.mint, target_token.mint] - .into_iter() - .map(|mint| { - util::to_writable_account_meta( - anchor_spl::associated_token::get_associated_token_address(&owner, &mint), - ) - }) - .collect::>(); - - let source_loan = if route.swap_mode == "ExactIn" { - u64::from_str(&route.amount).unwrap() - } else if route.swap_mode == "ExactOut" { - u64::from_str(&route.other_amount_threshold).unwrap() - } else { - anyhow::bail!("unknown swap mode: {}", route.swap_mode); - }; - let loan_amounts = vec![source_loan, 0u64]; - let num_loans: u8 = loan_amounts.len().try_into().unwrap(); - - // This relies on the fact that health account banks will be identical to the first_bank above! - let (health_ams, _health_cu) = self - .mango_client - .derive_health_check_remaining_account_metas( - account, - vec![source_token.token_index, target_token.token_index], - vec![source_token.token_index, target_token.token_index], - vec![], - ) - .await - .context("building health accounts")?; - - let mut instructions = Vec::new(); - - for ix in &jup_ixs[..jup_action_ix_begin] { - instructions.push(ix.clone()); - } - - // Ensure the source token account is created (jupiter takes care of the output account) - instructions.push( - spl_associated_token_account::instruction::create_associated_token_account_idempotent( - &owner, - &owner, - &source_token.mint, - &Token::id(), - ), - ); - - instructions.push(Instruction { - program_id: mango_v4::id(), - accounts: { - let mut ams = anchor_lang::ToAccountMetas::to_account_metas( - &mango_v4::accounts::FlashLoanBegin { - account: self.mango_client.mango_account_address, - owner, - token_program: Token::id(), - instructions: solana_sdk::sysvar::instructions::id(), - }, - None, - ); - ams.extend(bank_ams); - ams.extend(vault_ams.clone()); - ams.extend(token_ams.clone()); - ams.push(util::to_readonly_account_meta(self.mango_client.group())); - ams - }, - data: anchor_lang::InstructionData::data(&mango_v4::instruction::FlashLoanBegin { - loan_amounts, - }), - }); - for ix in &jup_ixs[jup_action_ix_begin..jup_action_ix_end] { - instructions.push(ix.clone()); - } - instructions.push(Instruction { - program_id: mango_v4::id(), - accounts: { - let mut ams = anchor_lang::ToAccountMetas::to_account_metas( - &mango_v4::accounts::FlashLoanEnd { - account: self.mango_client.mango_account_address, - owner, - token_program: Token::id(), - }, - None, - ); - ams.extend(health_ams); - ams.extend(vault_ams); - ams.extend(token_ams); - ams.push(util::to_readonly_account_meta(self.mango_client.group())); - ams - }, - data: anchor_lang::InstructionData::data(&mango_v4::instruction::FlashLoanEndV2 { - num_loans, - flash_loan_type: mango_v4::accounts_ix::FlashLoanType::Swap, - }), - }); - for ix in &jup_ixs[jup_action_ix_end..] { - instructions.push(ix.clone()); - } - - let mut address_lookup_tables = self.mango_client.mango_address_lookup_tables().await?; - address_lookup_tables.extend(jup_alts.into_iter()); - - let payer = owner; // maybe use fee_payer? but usually it's the same - - Ok(TransactionBuilder { - instructions, - address_lookup_tables, - payer, - signers: vec![self.mango_client.owner.clone()], - config: self - .mango_client - .client - .config() - .transaction_builder_config - .clone(), - }) - } - - pub async fn swap( - &self, - input_mint: Pubkey, - output_mint: Pubkey, - amount: u64, - slippage_bps: u64, - swap_mode: JupiterSwapMode, - only_direct_routes: bool, - ) -> anyhow::Result { - let route = self - .quote( - input_mint, - output_mint, - amount, - slippage_bps, - swap_mode, - only_direct_routes, - ) - .await?; - - let tx_builder = self - .prepare_swap_transaction(input_mint, output_mint, &route) - .await?; - - tx_builder.send_and_confirm(&self.mango_client.client).await - } -}