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>;