wp_solana_jupiter_swap_client/
lib.rs1use std::collections::HashMap;
7
8use quote::{InternalQuoteRequest, QuoteRequest, QuoteResponse};
9use reqwest::{Client, Response};
10use serde::de::DeserializeOwned;
11use swap::{SwapInstructionsResponse, SwapInstructionsResponseInternal, SwapRequest, SwapResponse};
12use thiserror::Error;
13
14pub mod quote;
15pub mod route_plan_with_metadata;
16pub mod serde_helpers;
17pub mod swap;
18pub mod transaction_config;
19
20pub const DEFAULT_API_BASE_URL: &str = "https://api.jup.ag/swap/v1";
22
23#[derive(Clone)]
25pub struct JupiterSwapApiClient {
26 pub base_path: String,
28 pub api_key: String,
30}
31
32#[derive(Debug, Error)]
34pub enum ClientError {
35 #[error("Request failed with status {status}: {body}")]
37 RequestFailed {
38 status: reqwest::StatusCode,
40 body: String,
42 },
43 #[error("Failed to deserialize response: {0}")]
45 DeserializationError(#[from] reqwest::Error),
46}
47
48async fn check_is_success(response: Response) -> Result<Response, ClientError> {
49 if !response.status().is_success() {
50 let status = response.status();
51 let body = response.text().await.unwrap_or_default();
52 return Err(ClientError::RequestFailed { status, body });
53 }
54 Ok(response)
55}
56
57async fn check_status_code_and_deserialize<T: DeserializeOwned>(
58 response: Response,
59) -> Result<T, ClientError> {
60 let response = check_is_success(response).await?;
61 response.json::<T>().await.map_err(ClientError::DeserializationError)
62}
63
64impl JupiterSwapApiClient {
65 pub fn new(base_path: String, api_key: String) -> Self {
67 Self { base_path, api_key }
68 }
69
70 pub fn mainnet(api_key: String) -> Self {
72 Self::new(DEFAULT_API_BASE_URL.to_string(), api_key)
73 }
74
75 pub async fn quote(&self, quote_request: &QuoteRequest) -> Result<QuoteResponse, ClientError> {
77 let url = format!("{}/quote", self.base_path);
78 let extra_args = quote_request.quote_args.clone();
79 let internal_quote_request = InternalQuoteRequest::from(quote_request.clone());
80 let response = Client::new()
81 .get(url)
82 .query(&internal_quote_request)
83 .query(&extra_args)
84 .header("x-api-key", self.api_key.clone())
85 .send()
86 .await?;
87 check_status_code_and_deserialize(response).await
90 }
91
92 pub async fn swap(
94 &self,
95 swap_request: &SwapRequest,
96 extra_args: Option<HashMap<String, String>>,
97 ) -> Result<SwapResponse, ClientError> {
98 let response = Client::new()
99 .post(format!("{}/swap", self.base_path))
100 .query(&extra_args)
101 .json(swap_request)
102 .header("x-api-key", self.api_key.clone())
103 .send()
104 .await?;
105 check_status_code_and_deserialize(response).await
106 }
107
108 pub async fn swap_instructions(
110 &self,
111 swap_request: &SwapRequest,
112 ) -> Result<SwapInstructionsResponse, ClientError> {
113 let response = Client::new()
114 .post(format!("{}/swap-instructions", self.base_path))
115 .json(swap_request)
116 .header("x-api-key", self.api_key.clone())
117 .send()
118 .await?;
119 check_status_code_and_deserialize::<SwapInstructionsResponseInternal>(response)
120 .await
121 .map(Into::into)
122 }
123}