rust client: remove jupiter v4 (#879)
* rust client: remove jupiter v4 * rust client: remove dead code * rust client: allow large enum variant for RawQuote enum
This commit is contained in:
parent
3993a3fa66
commit
7a8d46e362
|
@ -246,15 +246,8 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||||
let output_mint = pubkey_from_cli(&cmd.output_mint);
|
let output_mint = pubkey_from_cli(&cmd.output_mint);
|
||||||
let client = MangoClient::new_for_existing_account(client, account, owner).await?;
|
let client = MangoClient::new_for_existing_account(client, account, owner).await?;
|
||||||
let txsig = client
|
let txsig = client
|
||||||
.jupiter_v4()
|
.jupiter_v6()
|
||||||
.swap(
|
.swap(input_mint, output_mint, cmd.amount, cmd.slippage_bps, false)
|
||||||
input_mint,
|
|
||||||
output_mint,
|
|
||||||
cmd.amount,
|
|
||||||
cmd.slippage_bps,
|
|
||||||
mango_v4_client::JupiterSwapMode::ExactIn,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
println!("{}", txsig);
|
println!("{}", txsig);
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,6 @@ enum BoolArg {
|
||||||
#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
enum JupiterVersionArg {
|
enum JupiterVersionArg {
|
||||||
Mock,
|
Mock,
|
||||||
V4,
|
|
||||||
V6,
|
V6,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +64,6 @@ impl From<JupiterVersionArg> for jupiter::Version {
|
||||||
fn from(a: JupiterVersionArg) -> Self {
|
fn from(a: JupiterVersionArg) -> Self {
|
||||||
match a {
|
match a {
|
||||||
JupiterVersionArg::Mock => jupiter::Version::Mock,
|
JupiterVersionArg::Mock => jupiter::Version::Mock,
|
||||||
JupiterVersionArg::V4 => jupiter::Version::V4,
|
|
||||||
JupiterVersionArg::V6 => jupiter::Version::V6,
|
JupiterVersionArg::V6 => jupiter::Version::V6,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,10 +170,6 @@ struct Cli {
|
||||||
#[clap(long, env, value_enum, default_value = "v6")]
|
#[clap(long, env, value_enum, default_value = "v6")]
|
||||||
jupiter_version: JupiterVersionArg,
|
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
|
/// override the url to jupiter v6
|
||||||
#[clap(long, env, default_value = "https://quote-api.jup.ag/v6")]
|
#[clap(long, env, default_value = "https://quote-api.jup.ag/v6")]
|
||||||
jupiter_v6_url: String,
|
jupiter_v6_url: String,
|
||||||
|
@ -235,7 +229,6 @@ async fn main() -> anyhow::Result<()> {
|
||||||
.commitment(commitment)
|
.commitment(commitment)
|
||||||
.fee_payer(Some(liqor_owner.clone()))
|
.fee_payer(Some(liqor_owner.clone()))
|
||||||
.timeout(rpc_timeout)
|
.timeout(rpc_timeout)
|
||||||
.jupiter_v4_url(cli.jupiter_v4_url)
|
|
||||||
.jupiter_v6_url(cli.jupiter_v6_url)
|
.jupiter_v6_url(cli.jupiter_v6_url)
|
||||||
.jupiter_token(cli.jupiter_token)
|
.jupiter_token(cli.jupiter_token)
|
||||||
.transaction_builder_config(
|
.transaction_builder_config(
|
||||||
|
|
|
@ -151,18 +151,7 @@ impl Rebalancer {
|
||||||
let direct_sol_route_job =
|
let direct_sol_route_job =
|
||||||
self.jupiter_quote(sol_mint, output_mint, in_amount_sol, true, jupiter_version);
|
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];
|
let 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 mut results = futures::future::join_all(jobs).await;
|
let mut results = futures::future::join_all(jobs).await;
|
||||||
let full_route = results.remove(0)?;
|
let full_route = results.remove(0)?;
|
||||||
|
@ -211,18 +200,7 @@ impl Rebalancer {
|
||||||
let direct_sol_route_job =
|
let direct_sol_route_job =
|
||||||
self.jupiter_quote(input_mint, sol_mint, in_amount, true, jupiter_version);
|
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];
|
let 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 mut results = futures::future::join_all(jobs).await;
|
let mut results = futures::future::join_all(jobs).await;
|
||||||
let full_route = results.remove(0)?;
|
let full_route = results.remove(0)?;
|
||||||
|
|
|
@ -90,9 +90,6 @@ pub struct ClientConfig {
|
||||||
#[builder(default = "ClientBuilder::default_rpc_confirm_transaction_config()")]
|
#[builder(default = "ClientBuilder::default_rpc_confirm_transaction_config()")]
|
||||||
pub rpc_confirm_transaction_config: RpcConfirmTransactionConfig,
|
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()")]
|
#[builder(default = "\"https://quote-api.jup.ag/v6\".into()")]
|
||||||
pub jupiter_v6_url: String,
|
pub jupiter_v6_url: String,
|
||||||
|
|
||||||
|
@ -1823,10 +1820,6 @@ impl MangoClient {
|
||||||
|
|
||||||
// jupiter
|
// jupiter
|
||||||
|
|
||||||
pub fn jupiter_v4(&self) -> jupiter::v4::JupiterV4 {
|
|
||||||
jupiter::v4::JupiterV4 { mango_client: self }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn jupiter_v6(&self) -> jupiter::v6::JupiterV6 {
|
pub fn jupiter_v6(&self) -> jupiter::v6::JupiterV6 {
|
||||||
jupiter::v6::JupiterV6 { mango_client: self }
|
jupiter::v6::JupiterV6 { mango_client: self }
|
||||||
}
|
}
|
||||||
|
@ -1869,54 +1862,6 @@ impl MangoClient {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn deserialize_instructions_and_alts(
|
|
||||||
&self,
|
|
||||||
message: &solana_sdk::message::VersionedMessage,
|
|
||||||
) -> anyhow::Result<(Vec<Instruction>, Vec<AddressLookupTableAccount>)> {
|
|
||||||
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 {
|
fn instruction_cu(&self, health_cu: u32) -> u32 {
|
||||||
self.context.compute_estimates.cu_per_mango_instruction + health_cu
|
self.context.compute_estimates.cu_per_mango_instruction + health_cu
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,21 @@
|
||||||
pub mod v4;
|
|
||||||
pub mod v6;
|
pub mod v6;
|
||||||
|
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use crate::{JupiterSwapMode, MangoClient, TransactionBuilder};
|
use crate::{MangoClient, TransactionBuilder};
|
||||||
use fixed::types::I80F48;
|
use fixed::types::I80F48;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Version {
|
pub enum Version {
|
||||||
Mock,
|
Mock,
|
||||||
V4,
|
|
||||||
V6,
|
V6,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
pub enum RawQuote {
|
pub enum RawQuote {
|
||||||
Mock,
|
Mock,
|
||||||
V4(v4::QueryRoute),
|
|
||||||
V6(v6::QuoteResponse),
|
V6(v6::QuoteResponse),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,21 +30,6 @@ pub struct Quote {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Quote {
|
impl Quote {
|
||||||
pub fn try_from_v4(
|
|
||||||
input_mint: Pubkey,
|
|
||||||
output_mint: Pubkey,
|
|
||||||
route: v4::QueryRoute,
|
|
||||||
) -> anyhow::Result<Self> {
|
|
||||||
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<Self> {
|
pub fn try_from_v6(query: v6::QuoteResponse) -> anyhow::Result<Self> {
|
||||||
Ok(Quote {
|
Ok(Quote {
|
||||||
input_mint: Pubkey::from_str(&query.input_mint)?,
|
input_mint: Pubkey::from_str(&query.input_mint)?,
|
||||||
|
@ -65,7 +48,6 @@ impl Quote {
|
||||||
pub fn first_route_label(&self) -> String {
|
pub fn first_route_label(&self) -> String {
|
||||||
let label_maybe = match &self.raw {
|
let label_maybe = match &self.raw {
|
||||||
RawQuote::Mock => Some("mock".into()),
|
RawQuote::Mock => Some("mock".into()),
|
||||||
RawQuote::V4(raw) => raw.market_infos.first().map(|v| v.label.clone()),
|
|
||||||
RawQuote::V6(raw) => raw
|
RawQuote::V6(raw) => raw
|
||||||
.route_plan
|
.route_plan
|
||||||
.first()
|
.first()
|
||||||
|
@ -129,21 +111,6 @@ impl<'a> Jupiter<'a> {
|
||||||
) -> anyhow::Result<Quote> {
|
) -> anyhow::Result<Quote> {
|
||||||
Ok(match version {
|
Ok(match version {
|
||||||
Version::Mock => self.quote_mock(input_mint, output_mint, amount).await?,
|
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(
|
Version::V6 => Quote::try_from_v6(
|
||||||
self.mango_client
|
self.mango_client
|
||||||
.jupiter_v6()
|
.jupiter_v6()
|
||||||
|
@ -165,12 +132,6 @@ impl<'a> Jupiter<'a> {
|
||||||
) -> anyhow::Result<TransactionBuilder> {
|
) -> anyhow::Result<TransactionBuilder> {
|
||||||
match "e.raw {
|
match "e.raw {
|
||||||
RawQuote::Mock => anyhow::bail!("can't prepare jupiter swap for the mock"),
|
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) => {
|
RawQuote::V6(raw) => {
|
||||||
self.mango_client
|
self.mango_client
|
||||||
.jupiter_v6()
|
.jupiter_v6()
|
||||||
|
|
|
@ -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<QueryRoute>,
|
|
||||||
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<QueryMarketInfo>,
|
|
||||||
pub amount: String,
|
|
||||||
pub slippage_bps: u64,
|
|
||||||
pub other_amount_threshold: String,
|
|
||||||
pub swap_mode: String,
|
|
||||||
pub fees: Option<QueryRouteFees>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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<String>,
|
|
||||||
pub min_out_amount: Option<String>,
|
|
||||||
pub price_impact_pct: Option<f64>,
|
|
||||||
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<f64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct QueryRouteFees {
|
|
||||||
pub signature_fee: f64,
|
|
||||||
pub open_orders_deposits: Vec<f64>,
|
|
||||||
pub ata_deposits: Vec<f64>,
|
|
||||||
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<u64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct SwapResponse {
|
|
||||||
pub setup_transaction: Option<String>,
|
|
||||||
pub swap_transaction: String,
|
|
||||||
pub cleanup_transaction: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<QueryRoute> {
|
|
||||||
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<TransactionBuilder> {
|
|
||||||
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::<solana_sdk::transaction::VersionedTransaction>(
|
|
||||||
&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::<Vec<_>>();
|
|
||||||
|
|
||||||
let vault_ams = [source_token.first_vault(), target_token.first_vault()]
|
|
||||||
.into_iter()
|
|
||||||
.map(util::to_writable_account_meta)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
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::<Vec<_>>();
|
|
||||||
|
|
||||||
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<Signature> {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue