odos_sdk/
assemble.rs

1use std::fmt::Display;
2
3use alloy_network::TransactionBuilder;
4use alloy_primitives::{Address, U256, hex};
5use alloy_rpc_types::TransactionRequest;
6use serde::{Deserialize, Serialize};
7
8pub const ASSEMBLE_URL: &str = "https://api.odos.xyz/sor/assemble";
9
10/// Request to the Odos Assemble API: <https://docs.odos.xyz/build/api-docs>
11#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
12#[serde(rename_all = "camelCase")]
13pub struct AssembleRequest {
14    pub user_addr: String,
15    pub path_id: String,
16    pub simulate: bool,
17    pub receiver: Option<Address>,
18}
19
20impl Display for AssembleRequest {
21    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22        write!(
23            f,
24            "AssembleRequest {{ user_addr: {}, path_id: {}, simulate: {}, receiver: {} }}",
25            self.user_addr,
26            self.path_id,
27            self.simulate,
28            self.receiver
29                .as_ref()
30                .map_or("None".to_string(), |s| s.to_string())
31        )
32    }
33}
34
35/// Response from the Odos Assemble API: <https://docs.odos.xyz/build/api-docs>
36#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
37#[serde(rename_all = "camelCase")]
38pub struct AssemblyResponse {
39    pub transaction: TransactionData,
40    pub simulation: Option<Simulation>,
41}
42
43impl Display for AssemblyResponse {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        write!(
46            f,
47            "AssemblyResponse {{ transaction: {}, simulation: {} }}",
48            self.transaction,
49            self.simulation
50                .as_ref()
51                .map_or("None".to_string(), |s| s.to_string())
52        )
53    }
54}
55
56/// Transaction data from the Odos Assemble API: <https://docs.odos.xyz/build/api-docs>
57#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
58#[serde(rename_all = "camelCase")]
59pub struct TransactionData {
60    pub to: Address,
61    pub from: Address,
62    pub data: String,
63    pub value: String,
64    pub gas: i128,
65    pub gas_price: u128,
66    pub chain_id: u64,
67    pub nonce: u64,
68}
69
70/// Convert [`TransactionData`] to a [`TransactionRequest`].
71impl TryFrom<TransactionData> for TransactionRequest {
72    type Error = hex::FromHexError;
73
74    fn try_from(data: TransactionData) -> Result<Self, Self::Error> {
75        let input = hex::decode(&data.data)?;
76        let value = parse_value(&data.value);
77
78        Ok(TransactionRequest::default()
79            .with_input(input)
80            .with_value(value))
81    }
82}
83
84impl Display for TransactionData {
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        write!(
87            f,
88            "TransactionData {{ to: {}, from: {}, data: {}, value: {}, gas: {}, gas_price: {}, chain_id: {}, nonce: {} }}",
89            self.to,
90            self.from,
91            self.data,
92            self.value,
93            self.gas,
94            self.gas_price,
95            self.chain_id,
96            self.nonce
97        )
98    }
99}
100
101/// Simulation from the Odos Assemble API: <https://docs.odos.xyz/build/api-docs>
102#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
103#[serde(rename_all = "camelCase")]
104pub struct Simulation {
105    is_success: bool,
106    amounts_out: Vec<String>,
107    gas_estimate: i64,
108    simulation_error: SimulationError,
109}
110
111impl Simulation {
112    pub fn is_success(&self) -> bool {
113        self.is_success
114    }
115
116    pub fn error_message(&self) -> &str {
117        &self.simulation_error.error_message
118    }
119}
120
121impl Display for Simulation {
122    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123        write!(
124            f,
125            "Simulation {{ is_success: {}, amounts_out: {:?}, gas_estimate: {}, simulation_error: {} }}",
126            self.is_success,
127            self.amounts_out,
128            self.gas_estimate,
129            self.simulation_error.error_message
130        )
131    }
132}
133
134/// Simulation error from the Odos Assemble API: <https://docs.odos.xyz/build/api-docs>
135#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
136#[serde(rename_all = "camelCase")]
137pub struct SimulationError {
138    r#type: String,
139    error_message: String,
140}
141
142impl SimulationError {
143    pub fn error_message(&self) -> &str {
144        &self.error_message
145    }
146}
147
148impl Display for SimulationError {
149    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150        write!(f, "Simulation error: {}", self.error_message)
151    }
152}
153
154pub fn parse_value(value: &str) -> U256 {
155    if value == "0" {
156        return U256::ZERO;
157    }
158
159    U256::from_str_radix(value, 10).unwrap_or_else(|_| {
160        // Remove "0x" prefix if present
161        let value = if let Some(value) = value.strip_prefix("0x") {
162            value
163        } else {
164            value
165        };
166        U256::from_str_radix(value, 16).unwrap_or(U256::ZERO)
167    })
168}