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            LocalSignerConfigStorage, RelayerSolanaPolicy, RelayerSolanaSwapConfig,
152            SignerConfigStorage, SignerRepoModel,
153        },
154        services::{MockSolanaProviderTrait, 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: SignerConfigStorage::Local(LocalSignerConfigStorage { 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(MockSolanaProviderTrait::new());
184
185        let signer_service = Arc::new(
186            SolanaSignerFactory::create_solana_signer(&create_test_signer_model().into()).unwrap(),
187        );
188        let jupiter_service = Arc::new(JupiterService::new_from_network(relayer.network.as_str()));
189
190        let result =
191            create_network_dex_generic(&relayer, provider, signer_service, jupiter_service);
192
193        match result {
194            Ok(NetworkDex::JupiterSwap { .. }) => {}
195            Ok(_) => panic!("Expected JupiterSwap strategy"),
196            Err(e) => panic!("Expected Ok with JupiterSwap, but got error: {:?}", e),
197        }
198    }
199
200    #[test]
201    fn test_create_network_dex_jupiter_ultra_explicit() {
202        let mut relayer = RelayerRepoModel::default();
203        let policy = crate::models::RelayerNetworkPolicy::Solana(RelayerSolanaPolicy {
204            swap_config: Some(RelayerSolanaSwapConfig {
205                strategy: Some(SolanaSwapStrategy::JupiterUltra),
206                cron_schedule: None,
207                min_balance_threshold: None,
208                jupiter_swap_options: None,
209            }),
210            ..Default::default()
211        });
212
213        relayer.policies = policy;
214
215        let provider = Arc::new(MockSolanaProviderTrait::new());
216
217        let signer_service = Arc::new(
218            SolanaSignerFactory::create_solana_signer(&create_test_signer_model().into()).unwrap(),
219        );
220        let jupiter_service = Arc::new(JupiterService::new_from_network(relayer.network.as_str()));
221
222        let result =
223            create_network_dex_generic(&relayer, provider, signer_service, jupiter_service);
224
225        match result {
226            Ok(NetworkDex::JupiterUltra { .. }) => {}
227            Ok(_) => panic!("Expected JupiterUltra strategy"),
228            Err(e) => panic!("Expected Ok with JupiterUltra, but got error: {:?}", e),
229        }
230    }
231
232    #[test]
233    fn test_create_network_dex_default_when_no_strategy() {
234        let mut relayer = RelayerRepoModel::default();
235        let policy = crate::models::RelayerNetworkPolicy::Solana(RelayerSolanaPolicy {
236            swap_config: Some(RelayerSolanaSwapConfig {
237                strategy: None,
238                cron_schedule: None,
239                min_balance_threshold: None,
240                jupiter_swap_options: None,
241            }),
242            ..Default::default()
243        });
244
245        relayer.policies = policy;
246
247        let provider = Arc::new(MockSolanaProviderTrait::new());
248
249        let signer_service = Arc::new(
250            SolanaSignerFactory::create_solana_signer(&create_test_signer_model().into()).unwrap(),
251        );
252        let jupiter_service = Arc::new(JupiterService::new_from_network(relayer.network.as_str()));
253
254        let result =
255            create_network_dex_generic(&relayer, provider, signer_service, jupiter_service);
256
257        match result {
258            Ok(NetworkDex::Noop { .. }) => {}
259            Ok(_) => panic!("Expected Noop strategy"),
260            Err(e) => panic!("Expected Ok with Noop, but got error: {:?}", e),
261        }
262    }
263
264    #[test]
265    fn test_create_network_dex_default_when_no_swap_config() {
266        let mut relayer = RelayerRepoModel::default();
267        let policy = crate::models::RelayerNetworkPolicy::Solana(RelayerSolanaPolicy {
268            swap_config: None,
269            ..Default::default()
270        });
271
272        relayer.policies = policy;
273
274        let provider = Arc::new(MockSolanaProviderTrait::new());
275
276        let signer_service = Arc::new(
277            SolanaSignerFactory::create_solana_signer(&create_test_signer_model().into()).unwrap(),
278        );
279        let jupiter_service = Arc::new(JupiterService::new_from_network(relayer.network.as_str()));
280
281        let result =
282            create_network_dex_generic(&relayer, provider, signer_service, jupiter_service);
283
284        match result {
285            Ok(NetworkDex::Noop { .. }) => {}
286            Ok(_) => panic!("Expected Noop strategy"),
287            Err(e) => panic!("Expected Ok with Noop, but got error: {:?}", e),
288        }
289    }
290}