one_inch/swap/
types.rs

1use crate::builder_setter;
2
3use serde::{Deserialize, Serialize};
4use thiserror::Error;
5use crate::common::token::TokenInfo;
6
7/// Enumerates potential errors when constructing `SwapDetails`.
8#[derive(Error, Debug, Eq, PartialEq)]
9pub enum SwapDetailsBuilderError {
10    /// Indicates a required field is missing its value.
11    #[error("Missing {0}")]
12    MissingField(&'static str),
13
14    /// Indicates the provided slippage value is outside the allowable range.
15    #[error("Invalid slippage value. It should be between 0 and 50.")]
16    InvalidSlippage,
17
18    #[error("Invalid fee value. It should be between 0 and 3.")]
19    InvalidFee
20}
21
22/// Enumerates potential errors when constructing `QuoteDetails`.
23#[derive(Error, Debug, Eq, PartialEq)]
24pub enum QuoteDetailsBuilderError {
25    /// Indicates a required field is missing its value.
26    #[error("Missing {0}")]
27    MissingField(&'static str),
28
29    #[error("Invalid fee value. It should be between 0 and 3.")]
30    InvalidFee
31}
32/// Represents the details required for performing a token swap.
33#[derive(Debug, Clone)]
34pub struct SwapDetails {
35    pub src: String,              // Source token address.
36    pub dst: String,              // Destination token address.
37    pub amount: String,           // Amount to be swapped.
38    pub from: String,             // Address of the user initiating the swap.
39    pub slippage: usize,          // Permitted slippage percentage.
40
41    // Optional fields
42    pub fee : Option<u8>,
43    pub protocols : Option<String>,
44    pub gas_price : Option<String>,
45    pub complexity_level : Option<u128>,
46    pub parts : Option<u128>,
47    pub main_route_parts : Option<u128>,
48    pub gas_limit : Option<u128>,
49
50    pub include_tokens_info : Option<bool>,
51    pub include_protocols : Option<bool>,
52    pub include_gas : Option<bool>,
53    pub connector_tokens : Option<String>,
54    pub permit : Option<String>,
55    pub receiver : Option<String>,
56    pub referrer : Option<String>,
57
58    pub disable_estimate: Option<bool>,   // If true, disables estimation.
59    pub allow_partial_fill: Option<bool>, // If true, allows the swap to be partially filled.
60}
61
62/// A builder pattern implementation for creating a `SwapDetails`.
63#[derive(Default)]
64pub struct SwapDetailsBuilder {
65    src: Option<String>,
66    dst: Option<String>,
67    amount: Option<String>,
68    from_addr: Option<String>,
69    slippage: Option<usize>,
70
71    // Optional fields
72    fee : Option<u8>,
73    protocols : Option<String>,
74    gas_price : Option<String>,
75    complexity_level : Option<u128>,
76    parts : Option<u128>,
77    main_route_parts : Option<u128>,
78    gas_limit : Option<u128>,
79
80    include_tokens_info : Option<bool>,
81    include_protocols : Option<bool>,
82    include_gas : Option<bool>,
83    connector_tokens : Option<String>,
84    permit : Option<String>,
85    receiver : Option<String>,
86    referrer : Option<String>,
87
88    disable_estimate: Option<bool>,   // If true, disables estimation.
89    allow_partial_fill: Option<bool>, // If true, allows the swap to be partially filled.
90}
91
92
93/// SwapResponse is a struct to deserialize data we can get on swap request.
94#[derive(Deserialize, Debug)]
95pub struct SwapResponse {
96    #[serde(rename = "fromToken")]
97    pub from_token: Option<TokenInfo>,
98
99    #[serde(rename = "toToken")]
100    pub to_token: Option<TokenInfo>,
101
102    #[serde(rename = "toAmount")]
103    pub to_amount: String,
104
105    pub protocols: Option<Vec<Vec<Vec<SelectedProtocol>>>>,
106
107
108    #[serde(rename = "tx")]
109    pub transaction : SwapTranactionData
110}
111
112
113/// SwapTranactionData is a struct contains some information and a binary representation of raw_tranaction to perform swap on blockchain.
114#[derive(Deserialize, Debug)]
115pub struct SwapTranactionData {
116    pub from : String,
117    pub to : String,
118    pub data : String,
119    pub value : String,
120
121    #[serde(rename = "gasPrice")]
122    pub gas_price : String,
123
124
125    pub gas : u128
126}
127
128/// Represents errors that can occur during both swap or quote request.
129/// We use the same struct to handle errors that may occur with `swap` and `quote` requests because the possible errors are almost identical.
130/// This enum aggregates various types of errors related to these operations,
131/// including HTTP requests, JSON parsing, and swap API specific errors.
132#[derive(Error, Debug)]
133pub enum SwapError {
134    /// Error related to network requests.
135    ///
136    /// Used for handling issues with network requests, such as server
137    /// unavailability, network connectivity problems, etc.
138    #[error("Network error: {0}")]
139    Network(reqwest::Error),
140
141    /// Error while parsing JSON.
142    ///
143    /// Occurs when the server's response cannot be correctly deserialized from JSON.
144    /// This could happen if the response format is different than expected.
145    #[error("JSON parsing error: {0}")]
146    JsonParse(serde_json::Error),
147
148    /// Specific error related to swap/quote API.
149    ///
150    /// Represents errors specific to the swap API, like insufficient funds or invalid
151    /// request parameters.
152    #[error("Swap request error: {description}")]
153    SwapRequest {
154        description: String,
155        error: String,
156        status_code: u16,
157        request_id: String,
158    },
159
160    /// A general error.
161    ///
162    /// Used for other types of errors that do not fit into the above categories.
163    #[error("Other error: {0}")]
164    Other(String),
165}
166
167
168/// Represents an error response from the swap/quote API.
169///
170/// This structure is used to deserialize the JSON error response from the both swap/quote API.
171/// It contains details about the error that occurred during a request.
172#[derive(serde::Deserialize)]
173pub struct SwapRequestError {
174    /// A brief description of the error.
175    pub error: String,
176
177    /// A more detailed description of the error.
178    pub description: String,
179
180    /// The HTTP status code associated with the error.
181    #[serde(rename = "statusCode")]
182    pub status_code: u16,
183
184    /// A unique identifier for the request, useful for debugging.
185    #[serde(rename = "requestId")]
186    pub request_id: String,
187
188    /// Additional metadata related to the error, if any.
189    pub meta: Vec<HttpExceptionMeta>,
190}
191
192
193/// Represents additional metadata in the swap API error response.
194///
195/// Each item in the `meta` field of `SwapRequestError` will be deserialized into this structure.
196/// It provides more context about the error, such as the affected parameters or values.
197#[derive(serde::Deserialize)]
198pub struct HttpExceptionMeta {
199    /// The type of metadata.
200    #[serde(rename = "type")]
201    pub type_field: String,
202
203    /// The value associated with this metadata.
204    pub value: String,
205}
206
207#[derive(Debug, Serialize, Deserialize)]
208pub struct SelectedProtocol {
209    pub name: String,
210    pub part: f64,
211
212    #[serde(rename = "fromTokenAddress")]
213    pub from_token_address: String,
214
215    #[serde(rename = "toTokenAddress")]
216    pub to_token_address: String,
217}
218
219
220impl SwapDetailsBuilder {
221    /// Constructs a new `SwapDetailsBuilder` with all fields uninitialized.
222    pub fn new() -> Self {
223        SwapDetailsBuilder::default()
224    }
225
226    builder_setter!(src, String);
227    builder_setter!(dst, String);
228    builder_setter!(amount, String);
229    builder_setter!(from_addr, String);
230
231    builder_setter!(protocols, String);
232    builder_setter!(gas_price, String);
233    builder_setter!(complexity_level, u128);
234    builder_setter!(parts, u128);
235    builder_setter!(main_route_parts, u128);
236    builder_setter!(gas_limit, u128);
237
238    builder_setter!(include_tokens_info, bool);
239    builder_setter!(include_protocols, bool);
240    builder_setter!(include_gas, bool);
241
242
243    builder_setter!(connector_tokens, String);
244    builder_setter!(permit, String);
245    builder_setter!(receiver, String);
246    builder_setter!(referrer, String);
247
248    builder_setter!(disable_estimate, bool);
249    builder_setter!(allow_partial_fill, bool);
250
251    /// Special setter for fee that ensures value is within allowable range.
252    pub fn fee(mut self, fee: u8) -> Result<Self, SwapDetailsBuilderError> {
253        if fee > 3 {
254            return Err(SwapDetailsBuilderError::InvalidFee);
255        }
256        self.fee = Some(fee);
257        Ok(self)
258    }
259
260
261
262    /// Special setter for slippage that ensures value is within allowable range.
263    pub fn slippage(mut self, slippage: usize) -> Result<Self, SwapDetailsBuilderError> {
264        if slippage > 50 {
265            return Err(SwapDetailsBuilderError::InvalidSlippage);
266        }
267        self.slippage = Some(slippage);
268        Ok(self)
269    }
270
271    /// Attempts to construct a ['SwapDetails'](crate::swap::types::SwapDetails) from the builder, returning errors if required fields are missing or if some of values are incorrect.
272    pub fn build(self) -> Result<SwapDetails, SwapDetailsBuilderError> {
273        Ok(SwapDetails {
274            src: self
275                .src
276                .ok_or(SwapDetailsBuilderError::MissingField("src"))?,
277            dst: self
278                .dst
279                .ok_or(SwapDetailsBuilderError::MissingField("dst"))?,
280            amount: self
281                .amount
282                .ok_or(SwapDetailsBuilderError::MissingField("amount"))?
283                .to_string(),
284            from: self
285                .from_addr
286                .ok_or(SwapDetailsBuilderError::MissingField("from_addr"))?,
287            slippage: self
288                .slippage
289                .ok_or(SwapDetailsBuilderError::MissingField("slippage"))?,
290
291
292            fee: self.fee,
293            protocols: self.protocols,
294            gas_price: self.gas_price,
295            complexity_level: self.complexity_level,
296            parts: self.parts,
297            main_route_parts: self.main_route_parts,
298            gas_limit: self.gas_limit,
299            include_tokens_info: self.include_tokens_info,
300            include_protocols: self.include_protocols,
301            include_gas: self.include_gas,
302            connector_tokens: self.connector_tokens,
303            permit: self.permit,
304            receiver: self.receiver,
305            referrer: self.referrer,
306            disable_estimate: self.disable_estimate,
307            allow_partial_fill: self.allow_partial_fill,
308        })
309    }
310}
311
312
313
314/// QuoteDetails is struct that contains data we need to perform /quote request.
315#[derive(Debug, Clone)]
316pub struct QuoteDetails {
317    pub src: String,              // Source token address.
318    pub dst: String,              // Destination token address.
319    pub amount: String,           // Amount to be swapped.
320
321    // Optional fields
322    pub fee : Option<u8>,
323    pub protocols : Option<String>,
324    pub gas_price : Option<String>,
325    pub complexity_level : Option<u128>,
326    pub parts : Option<u128>,
327    pub main_route_parts : Option<u128>,
328    pub gas_limit : Option<u128>,
329
330    pub include_tokens_info : Option<bool>,
331    pub include_protocols : Option<bool>,
332    pub include_gas : Option<bool>,
333    pub connector_tokens : Option<String>,
334}
335
336
337/// QuoteDetailsBuilder is struct to create instance of `QuoteDetails`
338#[derive(Default)]
339pub struct QuoteDetailsBuilder {
340    pub src: Option<String>,
341    pub dst: Option<String>,
342    pub amount: Option<String>,
343
344    // Optional fields
345    pub fee : Option<u8>,
346    pub protocols : Option<String>,
347    pub gas_price : Option<String>,
348    pub complexity_level : Option<u128>,
349    pub parts : Option<u128>,
350    pub main_route_parts : Option<u128>,
351    pub gas_limit : Option<u128>,
352
353    pub include_tokens_info : Option<bool>,
354    pub include_protocols : Option<bool>,
355    pub include_gas : Option<bool>,
356    pub connector_tokens : Option<String>,
357
358}
359
360impl QuoteDetailsBuilder {
361    pub fn new() -> Self {
362        QuoteDetailsBuilder::default()
363    }
364
365
366    builder_setter!(src, String);
367    builder_setter!(dst, String);
368    builder_setter!(amount, String);
369
370    builder_setter!(protocols, String);
371    builder_setter!(gas_price, String);
372    builder_setter!(complexity_level, u128);
373    builder_setter!(parts, u128);
374    builder_setter!(main_route_parts, u128);
375    builder_setter!(gas_limit, u128);
376
377    builder_setter!(include_tokens_info, bool);
378    builder_setter!(include_protocols, bool);
379    builder_setter!(include_gas, bool);
380    builder_setter!(connector_tokens, String);
381
382    /// Special setter for fee that ensures value is within allowable range.
383    pub fn fee(mut self, fee: u8) -> Result<Self, QuoteDetailsBuilderError> {
384        if fee > 3 {
385            return Err(QuoteDetailsBuilderError::InvalidFee);
386        }
387        self.fee = Some(fee);
388        Ok(self)
389    }
390
391
392    /// Attempts to construct a [`QuoteDetails`](crate::swap::QuoteDetails) from the builder, returning errors if required fields are missing or if some of values are incorrect.
393    pub fn build(self) -> Result<QuoteDetails, QuoteDetailsBuilderError> {
394        Ok(QuoteDetails {
395            src: self
396                .src
397                .ok_or(QuoteDetailsBuilderError::MissingField("src"))?,
398            dst: self
399                .dst
400                .ok_or(QuoteDetailsBuilderError::MissingField("dst"))?,
401            amount: self
402                .amount
403                .ok_or(QuoteDetailsBuilderError::MissingField("amount"))?
404                .to_string(),
405
406
407            fee: self.fee,
408            protocols: self.protocols,
409            gas_price: self.gas_price,
410            complexity_level: self.complexity_level,
411            parts: self.parts,
412            main_route_parts: self.main_route_parts,
413            gas_limit: self.gas_limit,
414            include_tokens_info: self.include_tokens_info,
415            include_protocols: self.include_protocols,
416            include_gas: self.include_gas,
417            connector_tokens: self.connector_tokens,
418        })
419    }
420
421}
422
423
424/// SwapResponse is a struct to deserialize data we can get on quote request.
425#[derive(Deserialize, Debug)]
426pub struct QuoteResponse {
427    #[serde(rename = "fromToken")]
428    pub from_token: Option<TokenInfo>,
429
430    #[serde(rename = "toToken")]
431    pub to_token: Option<TokenInfo>,
432
433    #[serde(rename = "toAmount")]
434    pub to_amount: String,
435    pub protocols: Option<Vec<Vec<Vec<SelectedProtocol>>>>,
436}
437
438
439
440/// Tests for the `SwapDetailsBuilder` and related components.
441#[cfg(test)]
442mod tests {
443    use super::*;
444
445    /// Tests a successful construction of `SwapDetails` using the builder.
446    #[test]
447    fn test_valid_swap_details_builder() {
448        let swap_details = SwapDetailsBuilder::new()
449            .src("from_token".to_string())
450            .dst("to_token".to_string())
451            .amount("1000".to_string())
452            .from_addr("from_addr".to_string())
453            .slippage(5)
454            .expect("Invalid slippage")
455            .disable_estimate(false)
456            .allow_partial_fill(false)
457            .build()
458            .expect("Failed to build SwapDetails");
459
460        assert_eq!(swap_details.src, "from_token");
461        assert_eq!(swap_details.dst, "to_token");
462        assert_eq!(swap_details.amount, "1000");
463        assert_eq!(swap_details.from, "from_addr");
464        assert_eq!(swap_details.slippage, 5);
465        assert!(!swap_details.disable_estimate.unwrap());
466        assert!(!swap_details.allow_partial_fill.unwrap());
467    }
468
469    /// Tests the builder's response to an invalid slippage value.
470    #[test]
471    fn test_invalid_slippage_in_builder() {
472        let result = SwapDetailsBuilder::new()
473            .src("from_token".to_string())
474            .dst("to_token".to_string())
475            .amount("1000".to_string())
476            .from_addr("from_addr".to_string())
477            .slippage(102);
478
479        assert!(result.is_err());
480        if let Err(err) = result {
481            assert_eq!(err, SwapDetailsBuilderError::InvalidSlippage);
482        }
483    }
484}