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 error_code::TraceId,
10 OdosError,
11 OdosRouterV2::{inputTokenInfo, outputTokenInfo, swapTokenInfo},
12 OdosV2Router::{swapCall, OdosV2RouterCalls},
13 Result,
14};
15
16#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
18#[serde(rename_all = "camelCase")]
19pub struct InputToken {
20 token_address: String,
22 amount: String,
24}
25
26impl InputToken {
27 pub fn new(token_address: Address, amount: U256) -> Self {
28 Self {
29 token_address: token_address.to_string(),
30 amount: amount.to_string(),
31 }
32 }
33}
34
35impl From<(Address, U256)> for InputToken {
36 fn from((token_address, amount): (Address, U256)) -> Self {
37 Self::new(token_address, amount)
38 }
39}
40
41impl Display for InputToken {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 write!(
44 f,
45 "InputToken {{ token_address: {}, amount: {} }}",
46 self.token_address, self.amount
47 )
48 }
49}
50
51#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
53#[serde(rename_all = "camelCase")]
54pub struct OutputToken {
55 token_address: String,
57 proportion: u32,
58}
59
60impl OutputToken {
61 pub fn new(token_address: Address, proportion: u32) -> Self {
62 Self {
63 token_address: token_address.to_string(),
64 proportion,
65 }
66 }
67}
68
69impl From<(Address, u32)> for OutputToken {
70 fn from((token_address, proportion): (Address, u32)) -> Self {
71 Self::new(token_address, proportion)
72 }
73}
74
75impl Display for OutputToken {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 write!(
78 f,
79 "OutputToken {{ token_address: {}, proportion: {} }}",
80 self.token_address, self.proportion
81 )
82 }
83}
84
85#[derive(Builder, Clone, Debug, Default, PartialEq, PartialOrd, Deserialize, Serialize)]
87#[serde(rename_all = "camelCase")]
88pub struct QuoteRequest {
89 chain_id: u64,
90 input_tokens: Vec<InputToken>,
91 output_tokens: Vec<OutputToken>,
92 slippage_limit_percent: f64,
93 user_addr: String,
95 compact: bool,
96 simple: bool,
97 referral_code: u32,
98 disable_rfqs: bool,
99 #[builder(default)]
100 source_blacklist: Vec<String>,
101}
102
103#[derive(Clone, Debug, PartialEq, PartialOrd, Deserialize, Serialize)]
105#[serde(rename_all = "camelCase")]
106pub struct SingleQuoteResponse {
107 block_number: u64,
108 data_gas_estimate: u64,
109 gas_estimate: f64,
110 gas_estimate_value: f64,
111 gwei_per_gas: f64,
112 in_amounts: Vec<String>,
113 in_tokens: Vec<Address>,
114 in_values: Vec<f64>,
115 net_out_value: f64,
116 out_amounts: Vec<String>,
117 out_tokens: Vec<Address>,
118 out_values: Vec<f64>,
119 partner_fee_percent: f64,
120 path_id: String,
121 path_viz: Option<String>,
122 percent_diff: f64,
123 price_impact: f64,
124}
125
126impl SingleQuoteResponse {
127 pub fn data_gas_estimate(&self) -> u64 {
129 self.data_gas_estimate
130 }
131
132 pub fn get_block_number(&self) -> u64 {
134 self.block_number
135 }
136
137 pub fn gas_estimate(&self) -> f64 {
139 self.gas_estimate
140 }
141
142 pub fn in_amounts_iter(&self) -> impl Iterator<Item = &String> {
144 self.in_amounts.iter()
145 }
146
147 pub fn in_amount_u256(&self) -> Result<U256> {
149 let amount_str = self
150 .in_amounts_iter()
151 .next()
152 .ok_or_else(|| OdosError::missing_data("Missing input amount"))?;
153 let amount: u128 = amount_str
154 .parse()
155 .map_err(|_| OdosError::invalid_input("Invalid input amount format"))?;
156 Ok(U256::from(amount))
157 }
158
159 pub fn out_amount(&self) -> Option<&String> {
161 self.out_amounts.first()
162 }
163
164 pub fn out_amounts_iter(&self) -> impl Iterator<Item = &String> {
166 self.out_amounts.iter()
167 }
168
169 pub fn in_tokens_iter(&self) -> impl Iterator<Item = &Address> {
171 self.in_tokens.iter()
172 }
173
174 pub fn first_in_token(&self) -> Option<&Address> {
176 self.in_tokens.first()
177 }
178
179 pub fn out_tokens_iter(&self) -> impl Iterator<Item = &Address> {
180 self.out_tokens.iter()
181 }
182
183 pub fn first_out_token(&self) -> Option<&Address> {
185 self.out_tokens.first()
186 }
187
188 pub fn out_values_iter(&self) -> impl Iterator<Item = &f64> {
190 self.out_values.iter()
191 }
192
193 pub fn path_id(&self) -> &str {
195 &self.path_id
196 }
197
198 pub fn path_definition_as_vec_u8(&self) -> Vec<u8> {
200 self.path_id().as_bytes().to_vec()
201 }
202
203 pub fn swap_input_token_and_amount(&self) -> Result<(Address, U256)> {
205 let input_token = *self
206 .in_tokens_iter()
207 .next()
208 .ok_or_else(|| OdosError::missing_data("Missing input token"))?;
209 let input_amount_in_u256 = self.in_amount_u256()?;
210
211 Ok((input_token, input_amount_in_u256))
212 }
213
214 pub fn price_impact(&self) -> f64 {
216 self.price_impact
217 }
218}
219
220#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
236#[serde(rename_all = "camelCase")]
237pub struct OdosApiErrorResponse {
238 pub detail: String,
240 pub trace_id: TraceId,
242 pub error_code: u16,
244}
245
246#[derive(Clone, Debug)]
248pub struct SwapInputs {
249 executor: Address,
250 path_definition: Bytes,
251 input_token_info: inputTokenInfo,
252 output_token_info: outputTokenInfo,
253 value_out_min: U256,
254}
255
256impl TryFrom<OdosV2RouterCalls> for SwapInputs {
257 type Error = OdosError;
258
259 fn try_from(swap: OdosV2RouterCalls) -> std::result::Result<Self, Self::Error> {
260 match swap {
261 OdosV2RouterCalls::swap(call) => {
262 debug!(
263 input = %call.tokenInfo.inputToken,
264 output = %call.tokenInfo.outputToken,
265 amount = %call.tokenInfo.inputAmount
266 );
267
268 let swapCall {
269 executor,
270 pathDefinition,
271 referralCode,
272 tokenInfo,
273 } = call;
274
275 let _referral_code = referralCode;
276
277 let swapTokenInfo {
278 inputToken,
279 inputAmount,
280 inputReceiver,
281 outputMin,
282 outputQuote,
283 outputReceiver,
284 outputToken,
285 } = tokenInfo;
286
287 let _output_quote = outputQuote;
288
289 Ok(Self {
290 executor,
291 path_definition: pathDefinition,
292 input_token_info: inputTokenInfo {
293 tokenAddress: inputToken,
294 amountIn: inputAmount,
295 receiver: inputReceiver,
296 },
297 output_token_info: outputTokenInfo {
298 tokenAddress: outputToken,
299 relativeValue: U256::from(1),
300 receiver: outputReceiver,
301 },
302 value_out_min: outputMin,
303 })
304 }
305 _ => Err(OdosError::invalid_input("Unexpected OdosV2RouterCalls")),
306 }
307 }
308}
309
310impl SwapInputs {
311 pub fn executor(&self) -> Address {
313 self.executor
314 }
315
316 pub fn path_definition(&self) -> &Bytes {
318 &self.path_definition
319 }
320
321 pub fn token_address(&self) -> Address {
323 self.input_token_info.tokenAddress
324 }
325
326 pub fn amount_in(&self) -> U256 {
328 self.input_token_info.amountIn
329 }
330
331 pub fn receiver(&self) -> Address {
333 self.input_token_info.receiver
334 }
335
336 pub fn relative_value(&self) -> U256 {
338 self.output_token_info.relativeValue
339 }
340
341 pub fn output_token_address(&self) -> Address {
343 self.output_token_info.tokenAddress
344 }
345
346 pub fn value_out_min(&self) -> U256 {
348 self.value_out_min
349 }
350}