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