odos_sdk/
api.rs

1use std::fmt::Display;
2
3use alloy_primitives::{Address, Bytes, U256};
4use bon::Builder;
5use serde::{Deserialize, Serialize};
6use tracing::debug;
7
8use crate::{
9    OdosRouterV2::{inputTokenInfo, outputTokenInfo, swapTokenInfo},
10    OdosV2Router::{OdosV2RouterCalls, swapCall},
11};
12
13#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
14#[serde(rename_all = "camelCase")]
15pub struct InputToken {
16    // Haven't looked much into it, but there's trouble if you try to make this a `Address`
17    token_address: String,
18    // Odos API error message: "Input Amount should be positive integer in string form with < 64 digits[0x6]"
19    amount: String,
20}
21
22impl InputToken {
23    pub fn new(token_address: Address, amount: U256) -> Self {
24        Self {
25            token_address: token_address.to_string(),
26            amount: amount.to_string(),
27        }
28    }
29}
30
31impl From<(Address, U256)> for InputToken {
32    fn from((token_address, amount): (Address, U256)) -> Self {
33        Self::new(token_address, amount)
34    }
35}
36
37impl Display for InputToken {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        write!(
40            f,
41            "InputToken {{ token_address: {}, amount: {} }}",
42            self.token_address, self.amount
43        )
44    }
45}
46
47#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
48#[serde(rename_all = "camelCase")]
49pub struct OutputToken {
50    // Haven't looked much into it, but there's trouble if you try to make this a `Address`
51    token_address: String,
52    proportion: u32,
53}
54
55impl OutputToken {
56    pub fn new(token_address: Address, proportion: u32) -> Self {
57        Self {
58            token_address: token_address.to_string(),
59            proportion,
60        }
61    }
62}
63
64impl From<(Address, u32)> for OutputToken {
65    fn from((token_address, proportion): (Address, u32)) -> Self {
66        Self::new(token_address, proportion)
67    }
68}
69
70impl Display for OutputToken {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        write!(
73            f,
74            "OutputToken {{ token_address: {}, proportion: {} }}",
75            self.token_address, self.proportion
76        )
77    }
78}
79
80/// Request to the Odos quote API: <https://docs.odos.xyz/build/api-docs>
81#[derive(Builder, Clone, Debug, PartialEq, PartialOrd, Deserialize, Serialize)]
82#[serde(rename_all = "camelCase")]
83pub struct QuoteRequest {
84    chain_id: u64,
85    input_tokens: Vec<InputToken>,
86    output_tokens: Vec<OutputToken>,
87    slippage_limit_percent: f64,
88    // Haven't looked much into it, but there's trouble if you try to make this a `Address`
89    user_addr: String,
90    compact: bool,
91    simple: bool,
92    referral_code: u32,
93    disable_rfqs: bool,
94}
95
96/// Single quote response from the Odos quote API: <https://docs.odos.xyz/build/api-docs>
97#[derive(Clone, Debug, PartialEq, PartialOrd, Deserialize, Serialize)]
98#[serde(rename_all = "camelCase")]
99pub struct SingleQuoteResponse {
100    block_number: u64,
101    data_gas_estimate: u64,
102    gas_estimate: f64,
103    gas_estimate_value: f64,
104    gwei_per_gas: f64,
105    in_amounts: Vec<String>,
106    in_tokens: Vec<Address>,
107    in_values: Vec<f64>,
108    net_out_value: f64,
109    out_amounts: Vec<String>,
110    out_tokens: Vec<Address>,
111    out_values: Vec<f64>,
112    partner_fee_percent: f64,
113    path_id: String,
114    path_viz: Option<String>,
115    percent_diff: f64,
116    price_impact: f64,
117}
118
119impl SingleQuoteResponse {
120    /// Get the data gas estimate of the quote
121    pub fn data_gas_estimate(&self) -> u64 {
122        self.data_gas_estimate
123    }
124
125    /// Get the block number of the quote
126    pub fn get_block_number(&self) -> u64 {
127        self.block_number
128    }
129
130    /// Get the gas estimate of the quote
131    pub fn gas_estimate(&self) -> f64 {
132        self.gas_estimate
133    }
134
135    /// Get the in amounts of the quote
136    pub fn in_amounts_iter(&self) -> impl Iterator<Item = &String> {
137        self.in_amounts.iter()
138    }
139
140    /// Get the in amount of the quote
141    pub fn in_amount_u256(&self) -> anyhow::Result<U256> {
142        let amount_str = self
143            .in_amounts_iter()
144            .next()
145            .ok_or(anyhow::anyhow!("Missing input amount"))?;
146        let amount: u128 = amount_str.parse()?;
147        Ok(U256::from(amount))
148    }
149
150    /// Get the out amount of the quote
151    pub fn out_amount(&self) -> Option<&String> {
152        self.out_amounts.first()
153    }
154
155    /// Get the out amounts of the quote
156    pub fn out_amounts_iter(&self) -> impl Iterator<Item = &String> {
157        self.out_amounts.iter()
158    }
159
160    /// Get the in tokens of the quote
161    pub fn in_tokens_iter(&self) -> impl Iterator<Item = &Address> {
162        self.in_tokens.iter()
163    }
164
165    /// Get the in token of the quote
166    pub fn first_in_token(&self) -> Option<&Address> {
167        self.in_tokens.first()
168    }
169
170    pub fn out_tokens_iter(&self) -> impl Iterator<Item = &Address> {
171        self.out_tokens.iter()
172    }
173
174    /// Get the out token of the quote
175    pub fn first_out_token(&self) -> Option<&Address> {
176        self.out_tokens.first()
177    }
178
179    /// Get the out values of the quote
180    pub fn out_values_iter(&self) -> impl Iterator<Item = &f64> {
181        self.out_values.iter()
182    }
183
184    /// Get the path id of the quote
185    pub fn path_id(&self) -> &str {
186        &self.path_id
187    }
188
189    /// Get the path id as a vector of bytes
190    pub fn path_definition_as_vec_u8(&self) -> Vec<u8> {
191        self.path_id().as_bytes().to_vec()
192    }
193
194    /// Get the swap input token and amount
195    pub fn swap_input_token_and_amount(&self) -> anyhow::Result<(Address, U256)> {
196        let input_token = *self
197            .in_tokens_iter()
198            .next()
199            .ok_or(anyhow::anyhow!("Missing input token"))?;
200        let input_amount_in_u256 = self.in_amount_u256()?;
201
202        Ok((input_token, input_amount_in_u256))
203    }
204
205    /// Get the price impact of the quote
206    pub fn price_impact(&self) -> f64 {
207        self.price_impact
208    }
209}
210
211#[derive(Clone, Debug)]
212pub struct SwapInputs {
213    executor: Address,
214    path_definition: Bytes,
215    input_token_info: inputTokenInfo,
216    output_token_info: outputTokenInfo,
217    value_out_min: U256,
218}
219
220impl TryFrom<OdosV2RouterCalls> for SwapInputs {
221    type Error = anyhow::Error;
222
223    fn try_from(swap: OdosV2RouterCalls) -> Result<Self, Self::Error> {
224        match swap {
225            OdosV2RouterCalls::swap(call) => {
226                debug!(call = ?call);
227
228                let swapCall {
229                    executor,
230                    pathDefinition,
231                    referralCode,
232                    tokenInfo,
233                } = call;
234
235                let _referral_code = referralCode;
236
237                let swapTokenInfo {
238                    inputToken,
239                    inputAmount,
240                    inputReceiver,
241                    outputMin,
242                    outputQuote,
243                    outputReceiver,
244                    outputToken,
245                } = tokenInfo;
246
247                let _output_quote = outputQuote;
248
249                Ok(Self {
250                    executor,
251                    path_definition: pathDefinition,
252                    input_token_info: inputTokenInfo {
253                        tokenAddress: inputToken,
254                        amountIn: inputAmount,
255                        receiver: inputReceiver,
256                    },
257                    output_token_info: outputTokenInfo {
258                        tokenAddress: outputToken,
259                        relativeValue: U256::from(1),
260                        receiver: outputReceiver,
261                    },
262                    value_out_min: outputMin,
263                })
264            }
265            _ => Err(anyhow::anyhow!("Unexpected OdosV2RouterCalls")),
266        }
267    }
268}
269
270impl SwapInputs {
271    /// Get the executor of the swap
272    pub fn executor(&self) -> Address {
273        self.executor
274    }
275
276    /// Get the path definition of the swap
277    pub fn path_definition(&self) -> &Bytes {
278        &self.path_definition
279    }
280
281    /// Get the token address of the swap
282    pub fn token_address(&self) -> Address {
283        self.input_token_info.tokenAddress
284    }
285
286    /// Get the amount in of the swap
287    pub fn amount_in(&self) -> U256 {
288        self.input_token_info.amountIn
289    }
290
291    /// Get the receiver of the swap
292    pub fn receiver(&self) -> Address {
293        self.input_token_info.receiver
294    }
295
296    /// Get the relative value of the swap
297    pub fn relative_value(&self) -> U256 {
298        self.output_token_info.relativeValue
299    }
300
301    /// Get the output token address of the swap
302    pub fn output_token_address(&self) -> Address {
303        self.output_token_info.tokenAddress
304    }
305
306    /// Get the value out min of the swap
307    pub fn value_out_min(&self) -> U256 {
308        self.value_out_min
309    }
310}