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