openzeppelin_relayer/services/stellar_dex/mod.rs
1//! Stellar DEX service module
2//! Provides quote conversion services for Stellar tokens to XLM
3//! Supports native Stellar paths API and Soroswap integration for Soroban tokens
4
5mod order_book_service;
6mod soroswap_service;
7mod stellar_dex_service;
8
9pub use order_book_service::OrderBookService;
10pub use soroswap_service::SoroswapService;
11pub use stellar_dex_service::{DexServiceWrapper, StellarDexService};
12
13use async_trait::async_trait;
14#[cfg(test)]
15use mockall::automock;
16use serde::{Deserialize, Serialize};
17use thiserror::Error;
18
19#[derive(Error, Debug)]
20pub enum StellarDexServiceError {
21 #[error("HTTP request failed: {0}")]
22 HttpRequestError(#[from] reqwest::Error),
23 #[error("API returned an error: {message}")]
24 ApiError { message: String },
25 #[error("Failed to deserialize response: {0}")]
26 DeserializationError(#[from] serde_json::Error),
27 #[error("Invalid asset identifier: {0}")]
28 InvalidAssetIdentifier(String),
29 #[error("No path found for conversion")]
30 NoPathFound,
31 #[error("An unknown error occurred: {0}")]
32 UnknownError(String),
33}
34
35/// Quote response from DEX service
36#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
37pub struct StellarQuoteResponse {
38 /// Input asset identifier (e.g., "native" or "USDC:GA5Z...")
39 pub input_asset: String,
40 /// Output asset identifier (typically "native" for XLM)
41 pub output_asset: String,
42 /// Input amount in stroops
43 pub in_amount: u64,
44 /// Output amount in stroops
45 pub out_amount: u64,
46 /// Price impact percentage
47 pub price_impact_pct: f64,
48 /// Slippage in basis points
49 pub slippage_bps: u32,
50 /// Path information (optional route details)
51 pub path: Option<Vec<PathStep>>,
52}
53
54/// Path step in a conversion route
55#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
56pub struct PathStep {
57 /// Asset code
58 pub asset_code: Option<String>,
59 /// Asset issuer
60 pub asset_issuer: Option<String>,
61 /// Amount in this step
62 pub amount: u64,
63}
64
65/// Parameters for executing a swap transaction
66#[derive(Debug, Clone)]
67pub struct SwapTransactionParams {
68 /// Source account address (the account that will sign the transaction)
69 pub source_account: String,
70 /// Source asset identifier (e.g., "native" or "USDC:GA5Z...")
71 pub source_asset: String,
72 /// Destination asset identifier (typically "native" for XLM)
73 pub destination_asset: String,
74 /// Amount in stroops to swap
75 pub amount: u64,
76 /// Slippage percentage (e.g., 1.0 for 1%)
77 pub slippage_percent: f32,
78 /// Network passphrase for signing the transaction
79 pub network_passphrase: String,
80 /// Source asset decimals (defaults to 7 for native XLM)
81 pub source_asset_decimals: Option<u8>,
82 /// Destination asset decimals (defaults to 7 for native XLM)
83 pub destination_asset_decimals: Option<u8>,
84}
85
86/// Result of executing a swap transaction
87#[derive(Debug, Clone)]
88pub struct SwapExecutionResult {
89 /// Transaction hash of the submitted swap transaction
90 pub transaction_hash: String,
91 /// Destination amount received (in stroops)
92 pub destination_amount: u64,
93}
94
95/// Asset types supported by DEX services
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
97pub enum AssetType {
98 /// Native XLM
99 Native,
100 /// Classic assets (CODE:ISSUER format)
101 Classic,
102 /// Soroban contract tokens (C... format)
103 Contract,
104}
105
106/// Trait for Stellar DEX services
107#[async_trait]
108#[cfg_attr(test, automock)]
109pub trait StellarDexServiceTrait: Send + Sync {
110 /// Returns the asset types this DEX service can handle
111 ///
112 /// # Returns
113 /// A set of supported asset types (Native, Classic, Contract, or combinations)
114 fn supported_asset_types(&self) -> std::collections::HashSet<AssetType>;
115
116 /// Checks if this service can handle a specific asset
117 ///
118 /// # Arguments
119 /// * `asset_id` - Asset identifier (e.g., "native", "USDC:GA5Z...", "C...")
120 ///
121 /// # Returns
122 /// True if this service can process swaps for this asset
123 fn can_handle_asset(&self, asset_id: &str) -> bool {
124 let supported = self.supported_asset_types();
125
126 // Check native
127 if (asset_id == "native" || asset_id.is_empty()) && supported.contains(&AssetType::Native) {
128 return true;
129 }
130
131 // Check contract (C... format, 56 chars)
132 if asset_id.starts_with('C')
133 && asset_id.len() == 56
134 && !asset_id.contains(':')
135 && stellar_strkey::Contract::from_string(asset_id).is_ok()
136 && supported.contains(&AssetType::Contract)
137 {
138 return true;
139 }
140
141 // Check classic (CODE:ISSUER format)
142 if asset_id.contains(':') && supported.contains(&AssetType::Classic) {
143 return true;
144 }
145
146 false
147 }
148
149 /// Get a quote for converting a token to XLM
150 ///
151 /// # Arguments
152 ///
153 /// * `asset_id` - Asset identifier (e.g., "native" for XLM, or "USDC:GA5Z..." for credit assets)
154 /// * `amount` - Amount in stroops to convert
155 /// * `slippage` - Slippage percentage (e.g., 1.0 for 1%)
156 /// * `asset_decimals` - Number of decimal places for the asset (defaults to 7 if None)
157 ///
158 /// # Returns
159 ///
160 /// A quote response with conversion details
161 async fn get_token_to_xlm_quote(
162 &self,
163 asset_id: &str,
164 amount: u64,
165 slippage: f32,
166 asset_decimals: Option<u8>,
167 ) -> Result<StellarQuoteResponse, StellarDexServiceError>;
168
169 /// Get a quote for converting XLM to a token
170 ///
171 /// # Arguments
172 ///
173 /// * `asset_id` - Target asset identifier
174 /// * `amount` - Amount in stroops of XLM to convert
175 /// * `slippage` - Slippage percentage
176 /// * `asset_decimals` - Number of decimal places for the asset (defaults to 7 if None)
177 ///
178 /// # Returns
179 ///
180 /// A quote response with conversion details
181 async fn get_xlm_to_token_quote(
182 &self,
183 asset_id: &str,
184 amount: u64,
185 slippage: f32,
186 asset_decimals: Option<u8>,
187 ) -> Result<StellarQuoteResponse, StellarDexServiceError>;
188
189 /// Prepare a swap transaction (get quote and build XDR) without executing it
190 ///
191 /// This method creates an unsigned transaction XDR for a swap operation.
192 /// The transaction can then be queued for background processing.
193 ///
194 /// # Arguments
195 ///
196 /// * `params` - Swap transaction parameters including source account, assets, amounts, sequence number, and network passphrase
197 ///
198 /// # Returns
199 ///
200 /// A tuple containing:
201 /// * `String` - Unsigned transaction XDR (base64 encoded)
202 /// * `StellarQuoteResponse` - Quote information including destination amount
203 async fn prepare_swap_transaction(
204 &self,
205 params: SwapTransactionParams,
206 ) -> Result<(String, StellarQuoteResponse), StellarDexServiceError>;
207
208 /// Execute a swap transaction
209 ///
210 /// This method creates, signs, and submits a Stellar transaction with a path payment
211 /// operation based on the quote from `get_token_to_xlm_quote` or `get_xlm_to_token_quote`.
212 ///
213 /// # Arguments
214 ///
215 /// * `params` - Swap transaction parameters including source account, assets, amounts, sequence number, and network passphrase
216 ///
217 /// # Returns
218 ///
219 /// A `SwapExecutionResult` containing the transaction hash and destination amount received
220 async fn execute_swap(
221 &self,
222 params: SwapTransactionParams,
223 ) -> Result<SwapExecutionResult, StellarDexServiceError>;
224}
225
226/// Default implementation using Stellar Order Book service
227/// Note: This type alias cannot be used directly due to generic parameters.
228/// Use `OrderBookService<P, S>` where P implements `StellarProviderTrait` and S implements `StellarSignTrait`.
229pub type DefaultStellarDexService<P, S> = OrderBookService<P, S>;