1use 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#[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#[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#[async_trait]
49#[cfg_attr(test, automock)]
50pub trait DexStrategy: Send + Sync {
51 async fn execute_swap(&self, params: SwapParams) -> Result<SwapResult, RelayerError>;
53}
54
55pub 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) }
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
111pub 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}