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, Default, 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    #[builder(default)]
95    source_blacklist: Vec<String>,
96}
97
98/// Single quote response from the Odos quote API: <https://docs.odos.xyz/build/api-docs>
99#[derive(Clone, Debug, PartialEq, PartialOrd, Deserialize, Serialize)]
100#[serde(rename_all = "camelCase")]
101pub struct SingleQuoteResponse {
102    block_number: u64,
103    data_gas_estimate: u64,
104    gas_estimate: f64,
105    gas_estimate_value: f64,
106    gwei_per_gas: f64,
107    in_amounts: Vec<String>,
108    in_tokens: Vec<Address>,
109    in_values: Vec<f64>,
110    net_out_value: f64,
111    out_amounts: Vec<String>,
112    out_tokens: Vec<Address>,
113    out_values: Vec<f64>,
114    partner_fee_percent: f64,
115    path_id: String,
116    path_viz: Option<String>,
117    percent_diff: f64,
118    price_impact: f64,
119}
120
121impl SingleQuoteResponse {
122    /// Get the data gas estimate of the quote
123    pub fn data_gas_estimate(&self) -> u64 {
124        self.data_gas_estimate
125    }
126
127    /// Get the block number of the quote
128    pub fn get_block_number(&self) -> u64 {
129        self.block_number
130    }
131
132    /// Get the gas estimate of the quote
133    pub fn gas_estimate(&self) -> f64 {
134        self.gas_estimate
135    }
136
137    /// Get the in amounts of the quote
138    pub fn in_amounts_iter(&self) -> impl Iterator<Item = &String> {
139        self.in_amounts.iter()
140    }
141
142    /// Get the in amount of the quote
143    pub fn in_amount_u256(&self) -> anyhow::Result<U256> {
144        let amount_str = self
145            .in_amounts_iter()
146            .next()
147            .ok_or(anyhow::anyhow!("Missing input amount"))?;
148        let amount: u128 = amount_str.parse()?;
149        Ok(U256::from(amount))
150    }
151
152    /// Get the out amount of the quote
153    pub fn out_amount(&self) -> Option<&String> {
154        self.out_amounts.first()
155    }
156
157    /// Get the out amounts of the quote
158    pub fn out_amounts_iter(&self) -> impl Iterator<Item = &String> {
159        self.out_amounts.iter()
160    }
161
162    /// Get the in tokens of the quote
163    pub fn in_tokens_iter(&self) -> impl Iterator<Item = &Address> {
164        self.in_tokens.iter()
165    }
166
167    /// Get the in token of the quote
168    pub fn first_in_token(&self) -> Option<&Address> {
169        self.in_tokens.first()
170    }
171
172    pub fn out_tokens_iter(&self) -> impl Iterator<Item = &Address> {
173        self.out_tokens.iter()
174    }
175
176    /// Get the out token of the quote
177    pub fn first_out_token(&self) -> Option<&Address> {
178        self.out_tokens.first()
179    }
180
181    /// Get the out values of the quote
182    pub fn out_values_iter(&self) -> impl Iterator<Item = &f64> {
183        self.out_values.iter()
184    }
185
186    /// Get the path id of the quote
187    pub fn path_id(&self) -> &str {
188        &self.path_id
189    }
190
191    /// Get the path id as a vector of bytes
192    pub fn path_definition_as_vec_u8(&self) -> Vec<u8> {
193        self.path_id().as_bytes().to_vec()
194    }
195
196    /// Get the swap input token and amount
197    pub fn swap_input_token_and_amount(&self) -> anyhow::Result<(Address, U256)> {
198        let input_token = *self
199            .in_tokens_iter()
200            .next()
201            .ok_or(anyhow::anyhow!("Missing input token"))?;
202        let input_amount_in_u256 = self.in_amount_u256()?;
203
204        Ok((input_token, input_amount_in_u256))
205    }
206
207    /// Get the price impact of the quote
208    pub fn price_impact(&self) -> f64 {
209        self.price_impact
210    }
211}
212
213#[derive(Clone, Debug)]
214pub struct SwapInputs {
215    executor: Address,
216    path_definition: Bytes,
217    input_token_info: inputTokenInfo,
218    output_token_info: outputTokenInfo,
219    value_out_min: U256,
220}
221
222impl TryFrom<OdosV2RouterCalls> for SwapInputs {
223    type Error = anyhow::Error;
224
225    fn try_from(swap: OdosV2RouterCalls) -> Result<Self, Self::Error> {
226        match swap {
227            OdosV2RouterCalls::swap(call) => {
228                debug!(call = ?call);
229
230                let swapCall {
231                    executor,
232                    pathDefinition,
233                    referralCode,
234                    tokenInfo,
235                } = call;
236
237                let _referral_code = referralCode;
238
239                let swapTokenInfo {
240                    inputToken,
241                    inputAmount,
242                    inputReceiver,
243                    outputMin,
244                    outputQuote,
245                    outputReceiver,
246                    outputToken,
247                } = tokenInfo;
248
249                let _output_quote = outputQuote;
250
251                Ok(Self {
252                    executor,
253                    path_definition: pathDefinition,
254                    input_token_info: inputTokenInfo {
255                        tokenAddress: inputToken,
256                        amountIn: inputAmount,
257                        receiver: inputReceiver,
258                    },
259                    output_token_info: outputTokenInfo {
260                        tokenAddress: outputToken,
261                        relativeValue: U256::from(1),
262                        receiver: outputReceiver,
263                    },
264                    value_out_min: outputMin,
265                })
266            }
267            _ => Err(anyhow::anyhow!("Unexpected OdosV2RouterCalls")),
268        }
269    }
270}
271
272impl SwapInputs {
273    /// Get the executor of the swap
274    pub fn executor(&self) -> Address {
275        self.executor
276    }
277
278    /// Get the path definition of the swap
279    pub fn path_definition(&self) -> &Bytes {
280        &self.path_definition
281    }
282
283    /// Get the token address of the swap
284    pub fn token_address(&self) -> Address {
285        self.input_token_info.tokenAddress
286    }
287
288    /// Get the amount in of the swap
289    pub fn amount_in(&self) -> U256 {
290        self.input_token_info.amountIn
291    }
292
293    /// Get the receiver of the swap
294    pub fn receiver(&self) -> Address {
295        self.input_token_info.receiver
296    }
297
298    /// Get the relative value of the swap
299    pub fn relative_value(&self) -> U256 {
300        self.output_token_info.relativeValue
301    }
302
303    /// Get the output token address of the swap
304    pub fn output_token_address(&self) -> Address {
305        self.output_token_info.tokenAddress
306    }
307
308    /// Get the value out min of the swap
309    pub fn value_out_min(&self) -> U256 {
310        self.value_out_min
311    }
312}