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