scematica_executor/
jupiter.rs1use crate::SwapInstructionBuilder;
2use anyhow::Result;
3use async_trait::async_trait;
4use base64::{Engine as _, engine::general_purpose};
5use scematica_core::types::DexKind;
6use solana_sdk::{instruction::Instruction, pubkey::Pubkey, transaction::VersionedTransaction};
7
8pub struct JupiterBuilder {
11 http_client: reqwest::Client,
12 api_url: String,
13}
14
15impl JupiterBuilder {
16 pub fn new() -> Self {
17 Self {
18 http_client: reqwest::Client::new(),
19 api_url: "https://quote-api.jup.ag/v6".into(),
20 }
21 }
22
23 pub async fn get_quote(
25 &self,
26 input_mint: &Pubkey,
27 output_mint: &Pubkey,
28 amount: u64,
29 slippage_bps: u16,
30 ) -> Result<serde_json::Value> {
31 let url = format!(
32 "{}/quote?inputMint={}&outputMint={}&amount={}&slippageBps={}",
33 self.api_url, input_mint, output_mint, amount, slippage_bps
34 );
35 let resp = self.http_client.get(&url).send().await?.json().await?;
36 Ok(resp)
37 }
38
39 pub async fn get_swap_transaction(
41 &self,
42 quote: &serde_json::Value,
43 user_public_key: &Pubkey,
44 ) -> Result<Vec<u8>> {
45 let payload = serde_json::json!({
46 "quoteResponse": quote,
47 "userPublicKey": user_public_key.to_string(),
48 "wrapAndUnwrapSol": true,
49 "dynamicComputeUnitLimit": true,
50 "prioritizationFeeLamports": "auto"
51 });
52
53 let response = self
54 .http_client
55 .post(format!("{}/swap", self.api_url))
56 .json(&payload)
57 .send()
58 .await?;
59
60 let status = response.status();
61 if !status.is_success() {
62 let body = response.text().await.unwrap_or_default();
63 anyhow::bail!(
64 "Jupiter /swap returned HTTP {}: {}",
65 status.as_u16(),
66 body
67 );
68 }
69
70 let resp: serde_json::Value = response.json().await?;
71
72 let tx_b64 = resp["swapTransaction"]
73 .as_str()
74 .ok_or_else(|| anyhow::anyhow!("No swapTransaction in Jupiter response"))?;
75
76 Ok(general_purpose::STANDARD.decode(tx_b64)?)
77 }
78
79 pub fn deserialize_transaction(&self, tx_bytes: &[u8]) -> Result<VersionedTransaction> {
83 bincode::deserialize::<VersionedTransaction>(tx_bytes).map_err(Into::into)
84 }
85}
86
87#[async_trait]
88impl SwapInstructionBuilder for JupiterBuilder {
89 fn dex(&self) -> DexKind {
90 DexKind::Jupiter
91 }
92
93 async fn build_swap(
94 &self,
95 _pool: &Pubkey,
96 _owner: &Pubkey,
97 _token_in: &Pubkey,
98 _token_out: &Pubkey,
99 _ata_in: &Pubkey,
100 _ata_out: &Pubkey,
101 _amount_in: u64,
102 _min_amount_out: u64,
103 ) -> Result<Vec<Instruction>> {
104 tracing::warn!("JupiterBuilder::build_swap called — use get_swap_transaction() for Jupiter swaps");
109 Ok(vec![])
110 }
111}