openzeppelin_relayer/domain/relayer/solana/dex/
mod.rs

1//! DEX integration module for Solana token swaps
2
3use std::sync::Arc;
4
5use crate::domain::relayer::RelayerError;
6use crate::models::{RelayerRepoModel, SolanaSwapStrategy};
7use crate::services::{
8    provider::{SolanaProvider, SolanaProviderTrait},
9    signer::{SolanaSignTrait, SolanaSigner},
10    JupiterService, JupiterServiceTrait,
11};
12use async_trait::async_trait;
13#[cfg(test)]
14use mockall::automock;
15use serde::{Deserialize, Serialize};
16/// Result of a swap operation
17#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
18pub struct SwapResult {
19    pub mint: String,
20    pub source_amount: u64,
21    pub destination_amount: u64,
22    pub transaction_signature: String,
23    pub error: Option<String>,
24}
25
26impl Default for SwapResult {
27    fn default() -> Self {
28        Self {
29            mint: "".into(),
30            source_amount: 0,
31            destination_amount: 0,
32            transaction_signature: "".into(),
33            error: None,
34        }
35    }
36}
37
38/// Parameters for a swap operation
39#[derive(Debug)]
40pub struct SwapParams {
41    pub owner_address: String,
42    pub source_mint: String,
43    pub destination_mint: String,
44    pub amount: u64,
45    pub slippage_percent: f64,
46}
47
48/// Trait defining DEX swap functionality
49#[async_trait]
50#[cfg_attr(test, automock)]
51pub trait DexStrategy: Send + Sync {
52    /// Execute a token swap operation
53    async fn execute_swap(&self, params: SwapParams) -> Result<SwapResult, RelayerError>;
54}
55
56// Re-export the specific implementations
57pub mod jupiter_swap;
58pub mod jupiter_ultra;
59
60pub enum NetworkDex<P, S, J>
61where
62    P: SolanaProviderTrait + 'static,
63    S: SolanaSignTrait + Send + Sync + 'static,
64    J: JupiterServiceTrait + Send + Sync + 'static,
65{
66    JupiterSwap {
67        dex: jupiter_swap::JupiterSwapDex<P, S, J>,
68    },
69    JupiterUltra {
70        dex: jupiter_ultra::JupiterUltraDex<S, J>,
71    },
72    Noop {
73        dex: NoopDex,
74    },
75}
76
77pub type DefaultNetworkDex = NetworkDex<SolanaProvider, SolanaSigner, JupiterService>;
78
79#[async_trait]
80impl<P, S, J> DexStrategy for NetworkDex<P, S, J>
81where
82    P: SolanaProviderTrait + Send + Sync + 'static,
83    S: SolanaSignTrait + Send + Sync + 'static,
84    J: JupiterServiceTrait + Send + Sync + 'static,
85{
86    async fn execute_swap(&self, params: SwapParams) -> Result<SwapResult, RelayerError> {
87        match self {
88            NetworkDex::JupiterSwap { dex } => dex.execute_swap(params).await,
89            NetworkDex::JupiterUltra { dex } => dex.execute_swap(params).await,
90            NetworkDex::Noop { dex } => dex.execute_swap(params).await,
91        }
92    }
93}
94
95fn resolve_strategy(relayer: &RelayerRepoModel) -> SolanaSwapStrategy {
96    relayer
97        .policies
98        .get_solana_policy()
99        .get_swap_config()
100        .and_then(|cfg| cfg.strategy)
101        .unwrap_or(SolanaSwapStrategy::Noop) // Provide a default strategy
102}
103
104pub struct NoopDex;
105#[async_trait]
106impl DexStrategy for NoopDex {
107    async fn execute_swap(&self, _params: SwapParams) -> Result<SwapResult, RelayerError> {
108        Ok(SwapResult::default())
109    }
110}
111
112// Helper function to create the appropriate DEX implementation
113pub fn create_network_dex_generic<P, S, J>(
114    relayer: &RelayerRepoModel,
115    provider: Arc<P>,
116    signer_service: Arc<S>,
117    jupiter_service: Arc<J>,
118) -> Result<NetworkDex<P, S, J>, RelayerError>
119where
120    P: SolanaProviderTrait + Send + Sync + 'static,
121    S: SolanaSignTrait + Send + Sync + 'static,
122    J: JupiterServiceTrait + Send + Sync + 'static,
123{
124    let jupiter_swap_options = relayer
125        .policies
126        .get_solana_policy()
127        .get_swap_config()
128        .and_then(|cfg| cfg.jupiter_swap_options.clone());
129
130    match resolve_strategy(relayer) {
131        SolanaSwapStrategy::JupiterSwap => Ok(NetworkDex::JupiterSwap {
132            dex: jupiter_swap::JupiterSwapDex::<P, S, J>::new(
133                provider,
134                signer_service,
135                jupiter_service,
136                jupiter_swap_options,
137            ),
138        }),
139        SolanaSwapStrategy::JupiterUltra => Ok(NetworkDex::JupiterUltra {
140            dex: jupiter_ultra::JupiterUltraDex::<S, J>::new(signer_service, jupiter_service),
141        }),
142        _ => Ok(NetworkDex::Noop { dex: NoopDex }),
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use secrets::SecretVec;
149
150    use crate::{
151        models::{
152            LocalSignerConfigStorage, RelayerSolanaPolicy, RelayerSolanaSwapConfig,
153            SignerConfigStorage, SignerRepoModel,
154        },
155        services::{provider::MockSolanaProviderTrait, signer::SolanaSignerFactory},
156    };
157
158    use super::*;
159
160    fn create_test_signer_model() -> SignerRepoModel {
161        let seed = vec![1u8; 32];
162        let raw_key = SecretVec::new(32, |v| v.copy_from_slice(&seed));
163        SignerRepoModel {
164            id: "test".to_string(),
165            config: SignerConfigStorage::Local(LocalSignerConfigStorage { raw_key }),
166        }
167    }
168
169    #[tokio::test]
170    async fn test_create_network_dex_jupiter_swap_explicit() {
171        let mut relayer = RelayerRepoModel::default();
172        let policy = crate::models::RelayerNetworkPolicy::Solana(RelayerSolanaPolicy {
173            swap_config: Some(RelayerSolanaSwapConfig {
174                strategy: Some(SolanaSwapStrategy::JupiterSwap),
175                cron_schedule: None,
176                min_balance_threshold: None,
177                jupiter_swap_options: None,
178            }),
179            ..Default::default()
180        });
181
182        relayer.policies = policy;
183
184        let provider = Arc::new(MockSolanaProviderTrait::new());
185
186        let signer_service = Arc::new(
187            SolanaSignerFactory::create_solana_signer(create_test_signer_model().into())
188                .await
189                .unwrap(),
190        );
191        let jupiter_service = Arc::new(JupiterService::new_from_network(relayer.network.as_str()));
192
193        let result =
194            create_network_dex_generic(&relayer, provider, signer_service, jupiter_service);
195
196        match result {
197            Ok(NetworkDex::JupiterSwap { .. }) => {}
198            Ok(_) => panic!("Expected JupiterSwap strategy"),
199            Err(e) => panic!("Expected Ok with JupiterSwap, but got error: {e:?}"),
200        }
201    }
202
203    #[tokio::test]
204    async fn test_create_network_dex_jupiter_ultra_explicit() {
205        let mut relayer = RelayerRepoModel::default();
206        let policy = crate::models::RelayerNetworkPolicy::Solana(RelayerSolanaPolicy {
207            swap_config: Some(RelayerSolanaSwapConfig {
208                strategy: Some(SolanaSwapStrategy::JupiterUltra),
209                cron_schedule: None,
210                min_balance_threshold: None,
211                jupiter_swap_options: None,
212            }),
213            ..Default::default()
214        });
215
216        relayer.policies = policy;
217
218        let provider = Arc::new(MockSolanaProviderTrait::new());
219
220        let signer_service = Arc::new(
221            SolanaSignerFactory::create_solana_signer(create_test_signer_model().into())
222                .await
223                .unwrap(),
224        );
225        let jupiter_service = Arc::new(JupiterService::new_from_network(relayer.network.as_str()));
226
227        let result =
228            create_network_dex_generic(&relayer, provider, signer_service, jupiter_service);
229
230        match result {
231            Ok(NetworkDex::JupiterUltra { .. }) => {}
232            Ok(_) => panic!("Expected JupiterUltra strategy"),
233            Err(e) => panic!("Expected Ok with JupiterUltra, but got error: {e:?}"),
234        }
235    }
236
237    #[tokio::test]
238    async fn test_create_network_dex_default_when_no_strategy() {
239        let mut relayer = RelayerRepoModel::default();
240        let policy = crate::models::RelayerNetworkPolicy::Solana(RelayerSolanaPolicy {
241            swap_config: Some(RelayerSolanaSwapConfig {
242                strategy: None,
243                cron_schedule: None,
244                min_balance_threshold: None,
245                jupiter_swap_options: None,
246            }),
247            ..Default::default()
248        });
249
250        relayer.policies = policy;
251
252        let provider = Arc::new(MockSolanaProviderTrait::new());
253
254        let signer_service = Arc::new(
255            SolanaSignerFactory::create_solana_signer(create_test_signer_model().into())
256                .await
257                .unwrap(),
258        );
259        let jupiter_service = Arc::new(JupiterService::new_from_network(relayer.network.as_str()));
260
261        let result =
262            create_network_dex_generic(&relayer, provider, signer_service, jupiter_service);
263
264        match result {
265            Ok(NetworkDex::Noop { .. }) => {}
266            Ok(_) => panic!("Expected Noop strategy"),
267            Err(e) => panic!("Expected Ok with Noop, but got error: {e:?}"),
268        }
269    }
270
271    #[tokio::test]
272    async fn test_create_network_dex_default_when_no_swap_config() {
273        let mut relayer = RelayerRepoModel::default();
274        let policy = crate::models::RelayerNetworkPolicy::Solana(RelayerSolanaPolicy {
275            swap_config: None,
276            ..Default::default()
277        });
278
279        relayer.policies = policy;
280
281        let provider = Arc::new(MockSolanaProviderTrait::new());
282
283        let signer_service = Arc::new(
284            SolanaSignerFactory::create_solana_signer(create_test_signer_model().into())
285                .await
286                .unwrap(),
287        );
288        let jupiter_service = Arc::new(JupiterService::new_from_network(relayer.network.as_str()));
289
290        let result =
291            create_network_dex_generic(&relayer, provider, signer_service, jupiter_service);
292
293        match result {
294            Ok(NetworkDex::Noop { .. }) => {}
295            Ok(_) => panic!("Expected Noop strategy"),
296            Err(e) => panic!("Expected Ok with Noop, but got error: {e:?}"),
297        }
298    }
299}