use crate::{
quote_types::{QuoteResponse, RequestOption}, slippage::{FeeBps, Slippage}, utils::decompile_transaction_instructions
};
use anchor_lang::solana_program::pubkey::Pubkey;
use anchor_lang::InstructionData;
use anchor_lang::ToAccountMetas;
use anyhow::{anyhow, Result};
use juper_swap_cpi::JupiterIx;
use regex::RegexSet;
use solana_client::rpc_client::RpcClient;
use solana_client::rpc_config::RpcSendTransactionConfig;
use solana_sdk::transaction::Transaction;
use solana_sdk::{instruction::Instruction, signature::Signature};
use solana_sdk::{program_pack::Pack, signer::Signer};
#[cfg(test)]
use solana_client::rpc_client::serialize_and_encode;
use std::{collections::HashMap, sync::Arc};
use super::{replace_accounts, replace_by_account_pubkey, MARKET_BLACKLIST};
#[derive(Clone, Default)]
pub struct JupiterAnyIxSwap {
pub setup: Option<Vec<Instruction>>,
pub swap: Option<Instruction>,
pub cleanup: Option<Vec<Instruction>>,
}
pub fn new_anyix_swap_ix_with_quote(
swap_route: QuoteResponse,
rpc: &Arc<RpcClient>,
payer: &dyn Signer,
anyix_program: Pubkey,
management: Pubkey,
vault: Pubkey,
vault_pda: Pubkey,
replacements: &HashMap<Pubkey, Pubkey>,
fail_on_setup: bool,
input_mint: Pubkey,
output_mint: Pubkey,
) -> Result<JupiterAnyIxSwap> {
let jup_client = crate::Client::new()?;
let swap_response = jup_client.new_swap(
swap_route,
&vault_pda.to_string(),
false
)?;
let mut jup_any_ix = JupiterAnyIxSwap::default();
let mut swap_ix = swap_response.swap_instruction.to_instruction()?;
swap_ix.accounts.iter_mut().for_each(|acct| {
if acct.is_signer && acct.pubkey != payer.pubkey() {
acct.is_signer = false;
}
});
jup_any_ix.swap = Some(swap_ix);
Ok(jup_any_ix)
}
pub fn new_anyix_swap_with_quote(
swap_route: crate::quote_types::QuoteResponse,
rpc: &Arc<RpcClient>,
payer: &dyn Signer,
anyix_program: Pubkey,
management: Pubkey,
vault: Pubkey,
vault_pda: Pubkey,
skip_preflight: bool,
replacements: &HashMap<Pubkey, Pubkey>,
fail_on_setup: bool,
input_mint: Pubkey,
output_mint: Pubkey,
) -> Result<Signature> {
log::info!("generating jupiter swap ix");
let jup_any_ix = new_anyix_swap_ix_with_quote(
swap_route,
rpc,
payer,
anyix_program,
management,
vault,
vault_pda,
replacements,
fail_on_setup,
input_mint,
output_mint,
)?;
let jup_swap_ix = if let Some(swap_ix) = jup_any_ix.swap {
swap_ix
} else {
return Err(anyhow!("failed to create jupiter any ix swap"));
};
log::info!("constructing txn");
let mut tx = Transaction::new_with_payer(&[jup_swap_ix], Some(&payer.pubkey()));
#[cfg(test)]
println!(
"encoded jupiter tx {}",
serialize_and_encode(
&tx,
solana_transaction_status::UiTransactionEncoding::Base64
)
.unwrap()
);
log::info!("signing txn");
tx.sign(&vec![payer], rpc.get_latest_blockhash()?);
log::debug!("sending jupiter swap ix");
if skip_preflight {
match rpc.send_transaction_with_config(
&tx,
RpcSendTransactionConfig {
skip_preflight,
..Default::default()
},
) {
Ok(sig) => Ok(sig),
Err(err) => {
let error_msg = format!("failed to send jupiter swap ix {:#?}", err);
log::debug!("{}", error_msg);
Err(anyhow!("{}", error_msg))
}
}
} else {
match rpc.send_and_confirm_transaction(&tx) {
Ok(sig) => Ok(sig),
Err(err) => {
let error_msg = format!("failed to send jupiter swap ix {:#?}", err);
log::debug!("{}", error_msg);
Err(anyhow!("{}", error_msg))
}
}
}
}
pub fn new_anyix_swap(
client: Arc<crate::Client>,
rpc: &Arc<RpcClient>,
payer: &dyn Signer,
anyix_program: Pubkey,
management: Pubkey,
vault: Pubkey,
vault_pda: Pubkey,
input_mint: Pubkey,
output_mint: Pubkey,
input_amount: f64,
skip_preflight: bool,
max_tries: usize,
disallowed_market_list: Option<RegexSet>,
replacements: &HashMap<Pubkey, Pubkey>,
slippage: Slippage,
fail_on_setup: bool,
) -> Result<Signature> {
let quoter = crate::route_cache::Quoter::new(rpc, input_mint, output_mint)?;
let route = client.new_quote(
&input_mint.to_string(),
&output_mint.to_string(),
spl_token::ui_amount_to_amount(input_amount, quoter.input_mint_decimals),
&[RequestOption::AsLegacyTransaction]
)?;
match new_anyix_swap_with_quote(
route,
rpc,
payer,
anyix_program,
management,
vault,
vault_pda,
skip_preflight,
replacements,
fail_on_setup,
input_mint,
output_mint,
) {
Ok(sig) => return Ok(sig),
Err(err) => {
return Err(anyhow!("anyix swap failed {:#?}", err));
}
}
}
pub fn process_transaction(
rpc: &Arc<RpcClient>,
payer: &dyn Signer,
tx: &mut Transaction,
vault: Pubkey,
anyix_program: Pubkey,
management: Pubkey,
replacements: &HashMap<Pubkey, Pubkey>,
input_mint: Pubkey,
output_mint: Pubkey,
) -> Result<Instruction> {
let mut instructions = decompile_transaction_instructions(tx.clone())?;
for ix in instructions.iter() {
if ix.program_id.ne(&spl_associated_token_account::ID)
&& ix.program_id.ne(&juper_swap_cpi::JUPITER_V3_AGG_ID)
{
return Err(anyhow!("invalid program id {}", ix.program_id));
}
}
let mut expected_instructions = 0;
let any_ix_args = instructions
.iter_mut()
.filter_map(|ix| {
if ix.program_id.eq(&juper_swap_cpi::JUPITER_V3_AGG_ID) {
expected_instructions += 1;
ix.accounts = ix
.accounts
.iter_mut()
.map(|account| {
account.is_signer = false;
account.clone()
})
.collect();
let (jup_ix, mut swap_input) =
match juper_swap_cpi::decode_jupiter_instruction(&ix.data[..]) {
Ok(ix) => ix,
Err(err) => {
log::error!("failed to process jupiter ix {:#?}", err);
return None;
}
};
if jup_ix == JupiterIx::Whirlpool {
let token_vault_a = ix.accounts[5].pubkey;
match rpc.get_account_data(&token_vault_a) {
Ok(data) => match spl_token::state::Account::unpack_unchecked(&data[..]) {
Ok(token_vault_a_account) => {
if token_vault_a_account.mint.eq(&input_mint) {
log::debug!("whirlpool swap, setting side to 0 (ask)");
swap_input.side = 0;
} else if token_vault_a_account.mint.eq(&output_mint) {
log::debug!("whirlpool swap, setting side to 1 (bid)");
swap_input.side = 1;
}
}
Err(err) => {
log::error!("failed to process jupiter ix {:#?}", err);
return None;
}
},
Err(err) => {
log::error!("failed to process jupiter ix {:#?}", err);
return None;
}
}
}
if let Ok(args) = super::new_jupiter_swap_ix_data(ix.clone(), jup_ix, swap_input) {
Some(args)
} else {
None
}
} else {
None
}
})
.collect::<Vec<_>>();
if expected_instructions.ne(&any_ix_args.len()) {
return Err(anyhow!(
"unexpected instruction count. got {}, want {}",
any_ix_args.len(),
expected_instructions
));
}
let mut accounts = juper_swap_cpi::accounts::JupiterSwap {
vault,
authority: payer.pubkey(),
jupiter_program: juper_swap_cpi::JUPITER_V3_AGG_ID,
management,
}
.to_account_metas(Some(true));
let any_ix = ::anyix::AnyIx {
num_instructions: any_ix_args.len() as u8,
instruction_data_sizes: any_ix_args.iter().map(|ix| ix.data.len() as u8).collect(),
instruction_account_counts: any_ix_args
.iter()
.map(|ix| ix.accounts.len() as u8)
.collect(),
instruction_datas: any_ix_args.iter().map(|ix| ix.data.clone()).collect(),
}
.pack()?;
accounts.extend_from_slice(
&any_ix_args
.iter()
.flat_map(|ix| ix.accounts.clone())
.collect::<Vec<_>>()[..],
);
let mut ix = Instruction {
program_id: anyix_program,
accounts,
data: juper_swap_cpi::instructions::JupiterSwapArgs { input_data: any_ix }.data(),
};
replace_accounts(&mut ix, rpc, &mut replace_by_account_pubkey, replacements)?;
Ok(ix)
}