openzeppelin_relayer/domain/relayer/solana/dex/
mod.rs1use 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#[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#[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#[async_trait]
50#[cfg_attr(test, automock)]
51pub trait DexStrategy: Send + Sync {
52 async fn execute_swap(&self, params: SwapParams) -> Result<SwapResult, RelayerError>;
54}
55
56pub 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) }
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
112pub 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}