solagent_plugin_jupiter/
trade.rs1use crate::JUP_API;
16use anyhow::Result;
17use base64::{engine::general_purpose, Engine as _};
18use serde::{Deserialize, Serialize};
19use solagent_core::{
20 solana_sdk::{
21 commitment_config::CommitmentConfig, program_pack::Pack, pubkey::Pubkey,
22 transaction::VersionedTransaction,
23 },
24 SolanaAgentKit,
25};
26use spl_token::state::Mint;
27use std::str::FromStr;
28
29#[derive(Serialize)]
30struct SwapRequest {
31 #[serde(rename = "quoteResponse")]
32 quote_response: QuoteResponse,
33 #[serde(rename = "userPublicKey")]
34 user_public_key: String,
35 #[serde(rename = "wrapAndUnwrapSol")]
36 wrap_and_unwrap_sol: bool,
37 #[serde(rename = "dynamicComputeUnitLimit")]
38 dynamic_compute_unit_limit: bool,
39 #[serde(rename = "prioritizationFeeLamports")]
40 prioritization_fee_lamports: String,
41 #[serde(rename = "feeAccount")]
42 fee_account: Option<String>,
43}
44#[derive(Deserialize)]
45struct SwapResponse {
46 #[serde(rename = "swapTransaction")]
47 swap_transaction: String,
48}
49
50#[derive(Deserialize, Serialize)]
51struct QuoteResponse {
52 #[serde(flatten)]
53 extra: serde_json::Value,
54}
55
56pub async fn trade(
70 agent: &SolanaAgentKit,
71 output_mint: &str,
72 input_amount: f64,
73 input_mint: Option<String>,
74 slippage_bps: Option<u32>,
75) -> Result<String, Box<dyn std::error::Error>> {
76 let output_mint = Pubkey::from_str(output_mint)?;
78 let input_mint = input_mint
79 .as_deref()
80 .map(Pubkey::from_str)
81 .transpose()?
82 .unwrap_or(spl_token::native_mint::id());
83
84 let slippage_bps = slippage_bps.unwrap_or(300);
86
87 let is_native_sol = input_mint == spl_token::native_mint::id();
89
90 let input_decimals = if is_native_sol {
92 9
93 } else {
94 let account = agent.connection.get_account(&input_mint)?;
95 let mint = Mint::unpack(&account.data)?;
96 mint.decimals
97 };
98
99 let scaled_amount = (input_amount * 10f64.powf(input_decimals as f64)) as u64;
101
102 let quote_url = format!(
104 "{}/quote?inputMint={}&outputMint={}&amount={}&slippageBps={}&onlyDirectRoutes=true&maxAccounts=20",
105 JUP_API, input_mint, output_mint, scaled_amount, slippage_bps
106 );
107
108 let client = reqwest::Client::new();
110 let quote_response: QuoteResponse = client.get("e_url).send().await?.json().await?;
111
112 let swap_request = SwapRequest {
114 quote_response,
115 user_public_key: agent.wallet.pubkey.to_string(),
116 wrap_and_unwrap_sol: true,
117 dynamic_compute_unit_limit: true,
118 prioritization_fee_lamports: "auto".to_string(),
119 fee_account: None,
120 };
121
122 let swap_response: SwapResponse = client
123 .post(format!("{}/swap", JUP_API))
124 .json(&swap_request)
125 .send()
126 .await?
127 .json()
128 .await?;
129
130 let swap_transaction = general_purpose::STANDARD
131 .decode(&swap_response.swap_transaction)
132 .expect("decode swap_transaction");
133
134 let versioned_transaction: VersionedTransaction = bincode::deserialize(&swap_transaction)?;
135
136 let signed_transaction =
137 VersionedTransaction::try_new(versioned_transaction.message, &[&agent.wallet.keypair])?;
138
139 let signature = agent.connection.send_transaction(&signed_transaction)?;
140
141 let latest_blockhash = agent.connection.get_latest_blockhash()?;
142
143 agent.connection.confirm_transaction_with_spinner(
144 &signature,
145 &latest_blockhash,
146 CommitmentConfig::confirmed(),
147 )?;
148
149 Ok(signature.to_string())
150}