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