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