openzeppelin_relayer/domain/relayer/solana/rpc/
handler.rs1use super::{SolanaRpcError, SolanaRpcMethods};
11use crate::{
12 domain::SolanaRpcMethodsImpl,
13 models::{
14 JsonRpcRequest, JsonRpcResponse, NetworkRpcRequest, NetworkRpcResult, SolanaRpcRequest,
15 SolanaRpcResult,
16 },
17};
18use eyre::Result;
19use std::sync::Arc;
20use tracing::debug;
21
22pub type SolanaRpcHandlerType<SP, S, JS, J, TR> =
23 Arc<SolanaRpcHandler<SolanaRpcMethodsImpl<SP, S, JS, J, TR>>>;
24
25pub struct SolanaRpcHandler<T> {
26 rpc_methods: T,
27}
28
29impl<T: SolanaRpcMethods> SolanaRpcHandler<T> {
30 pub fn new(rpc_methods: T) -> Self {
41 Self { rpc_methods }
42 }
43
44 pub async fn handle_request(
66 &self,
67 request: JsonRpcRequest<NetworkRpcRequest>,
68 ) -> Result<JsonRpcResponse<NetworkRpcResult>, SolanaRpcError> {
69 debug!(params = ?request.params, "received request params");
70 let solana_request = match request.params {
72 NetworkRpcRequest::Solana(solana_params) => solana_params,
73 _ => {
74 return Err(SolanaRpcError::BadRequest(
75 "Expected Solana network request".to_string(),
76 ));
77 }
78 };
79
80 let result = match solana_request {
81 SolanaRpcRequest::FeeEstimate(params) => {
82 let res = self.rpc_methods.fee_estimate(params).await?;
83 SolanaRpcResult::FeeEstimate(res)
84 }
85 SolanaRpcRequest::TransferTransaction(params) => {
86 let res = self.rpc_methods.transfer_transaction(params).await?;
87 SolanaRpcResult::TransferTransaction(res)
88 }
89 SolanaRpcRequest::PrepareTransaction(params) => {
90 let res = self.rpc_methods.prepare_transaction(params).await?;
91 SolanaRpcResult::PrepareTransaction(res)
92 }
93 SolanaRpcRequest::SignAndSendTransaction(params) => {
94 let res = self.rpc_methods.sign_and_send_transaction(params).await?;
95 SolanaRpcResult::SignAndSendTransaction(res)
96 }
97 SolanaRpcRequest::SignTransaction(params) => {
98 let res = self.rpc_methods.sign_transaction(params).await?;
99 SolanaRpcResult::SignTransaction(res)
100 }
101 SolanaRpcRequest::GetSupportedTokens(params) => {
102 let res = self.rpc_methods.get_supported_tokens(params).await?;
103 SolanaRpcResult::GetSupportedTokens(res)
104 }
105 SolanaRpcRequest::GetFeaturesEnabled(params) => {
106 let res = self.rpc_methods.get_features_enabled(params).await?;
107 SolanaRpcResult::GetFeaturesEnabled(res)
108 }
109 _ => {
110 return Err(SolanaRpcError::Internal(
111 "Unsupported Solana RPC Paymaster method".to_string(),
112 ))
113 }
114 };
115
116 Ok(JsonRpcResponse::result(
117 request.id,
118 NetworkRpcResult::Solana(result),
119 ))
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use std::sync::Arc;
126
127 use crate::{
128 domain::MockSolanaRpcMethods,
129 models::{
130 EncodedSerializedTransaction, FeeEstimateRequestParams, FeeEstimateResult,
131 GetFeaturesEnabledRequestParams, GetFeaturesEnabledResult, JsonRpcId,
132 PrepareTransactionRequestParams, PrepareTransactionResult,
133 SignAndSendTransactionRequestParams, SignAndSendTransactionResult,
134 SignTransactionRequestParams, SignTransactionResult, TransferTransactionRequestParams,
135 TransferTransactionResult,
136 },
137 };
138
139 use super::*;
140 use mockall::predicate::{self};
141
142 #[tokio::test]
143 async fn test_handle_request_fee_estimate() {
144 let mut mock_rpc_methods = MockSolanaRpcMethods::new();
145 mock_rpc_methods
146 .expect_fee_estimate()
147 .with(predicate::eq(FeeEstimateRequestParams {
148 transaction: EncodedSerializedTransaction::new("test_transaction".to_string()),
149 fee_token: "test_token".to_string(),
150 }))
151 .returning(|_| {
152 Ok(FeeEstimateResult {
153 estimated_fee: "0".to_string(),
154 conversion_rate: "0".to_string(),
155 })
156 })
157 .times(1);
158 let mock_handler = Arc::new(SolanaRpcHandler::new(mock_rpc_methods));
159 let request = JsonRpcRequest {
160 jsonrpc: "2.0".to_string(),
161 id: Some(JsonRpcId::Number(1)),
162 params: NetworkRpcRequest::Solana(SolanaRpcRequest::FeeEstimate(
163 FeeEstimateRequestParams {
164 transaction: EncodedSerializedTransaction::new("test_transaction".to_string()),
165 fee_token: "test_token".to_string(),
166 },
167 )),
168 };
169
170 let response = mock_handler.handle_request(request).await;
171
172 assert!(response.is_ok(), "Expected Ok response, got {:?}", response);
173 let json_response = response.unwrap();
174 assert_eq!(
175 json_response.result,
176 Some(NetworkRpcResult::Solana(SolanaRpcResult::FeeEstimate(
177 FeeEstimateResult {
178 estimated_fee: "0".to_string(),
179 conversion_rate: "0".to_string(),
180 }
181 )))
182 );
183 }
184
185 #[tokio::test]
186 async fn test_handle_request_features_enabled() {
187 let mut mock_rpc_methods = MockSolanaRpcMethods::new();
188 mock_rpc_methods
189 .expect_get_features_enabled()
190 .with(predicate::eq(GetFeaturesEnabledRequestParams {}))
191 .returning(|_| {
192 Ok(GetFeaturesEnabledResult {
193 features: vec!["gasless".to_string()],
194 })
195 })
196 .times(1);
197 let mock_handler = Arc::new(SolanaRpcHandler::new(mock_rpc_methods));
198 let request = JsonRpcRequest {
199 jsonrpc: "2.0".to_string(),
200 id: Some(JsonRpcId::Number(1)),
201 params: NetworkRpcRequest::Solana(SolanaRpcRequest::GetFeaturesEnabled(
202 GetFeaturesEnabledRequestParams {},
203 )),
204 };
205
206 let response = mock_handler.handle_request(request).await;
207
208 assert!(response.is_ok(), "Expected Ok response, got {:?}", response);
209 let json_response = response.unwrap();
210 assert_eq!(
211 json_response.result,
212 Some(NetworkRpcResult::Solana(
213 SolanaRpcResult::GetFeaturesEnabled(GetFeaturesEnabledResult {
214 features: vec!["gasless".to_string()],
215 })
216 ))
217 );
218 }
219
220 #[tokio::test]
221 async fn test_handle_request_sign_transaction() {
222 let mut mock_rpc_methods = MockSolanaRpcMethods::new();
223
224 let mock_signature = "5wHu1qwD4kF3wxjejXkgDYNVnEgB1e8uVvrxNwJYRzHPPxWqRA4nxwE1TU4";
226 let mock_transaction = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_string();
227
228 mock_rpc_methods
229 .expect_sign_transaction()
230 .with(predicate::eq(SignTransactionRequestParams {
231 transaction: EncodedSerializedTransaction::new(mock_transaction.clone()),
232 }))
233 .returning(move |_| {
234 Ok(SignTransactionResult {
235 transaction: EncodedSerializedTransaction::new(mock_transaction.clone()),
236 signature: mock_signature.to_string(),
237 })
238 })
239 .times(1);
240
241 let mock_handler = Arc::new(SolanaRpcHandler::new(mock_rpc_methods));
242
243 let request = JsonRpcRequest {
244 jsonrpc: "2.0".to_string(),
245 id: Some(JsonRpcId::Number(1)),
246 params: NetworkRpcRequest::Solana(SolanaRpcRequest::SignTransaction(
247 SignTransactionRequestParams {
248 transaction: EncodedSerializedTransaction::new("AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_string()),
249 },
250 )),
251 };
252
253 let response = mock_handler.handle_request(request).await;
254
255 assert!(response.is_ok(), "Expected Ok response, got {:?}", response);
256 let json_response = response.unwrap();
257
258 match json_response.result {
259 Some(value) => {
260 if let NetworkRpcResult::Solana(SolanaRpcResult::SignTransaction(result)) = value {
261 assert_eq!(result.signature, mock_signature);
262 } else {
263 panic!("Expected SignTransaction result, got {:?}", value);
264 }
265 }
266 None => panic!("Expected Some result, got None"),
267 }
268 }
269
270 #[tokio::test]
271 async fn test_handle_request_sign_and_send_transaction_success() {
272 let mut mock_rpc_methods = MockSolanaRpcMethods::new();
273
274 let mock_signature = "5wHu1qwD4kF3wxjejXkgDYNVnEgB1e8uVvrxNwJYRzHPPxWqRA4nxwE1TU4";
276 let mock_transaction = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_string();
277
278 mock_rpc_methods
279 .expect_sign_and_send_transaction()
280 .with(predicate::eq(SignAndSendTransactionRequestParams {
281 transaction: EncodedSerializedTransaction::new(mock_transaction.clone()),
282 }))
283 .returning(move |_| {
284 Ok(SignAndSendTransactionResult {
285 transaction: EncodedSerializedTransaction::new(mock_transaction.clone()),
286 signature: mock_signature.to_string(),
287 id: "123".to_string(),
288 })
289 })
290 .times(1);
291
292 let handler = Arc::new(SolanaRpcHandler::new(mock_rpc_methods));
293
294 let request = JsonRpcRequest {
295 jsonrpc: "2.0".to_string(),
296 id: Some(JsonRpcId::Number(1)),
297 params: NetworkRpcRequest::Solana(SolanaRpcRequest::SignAndSendTransaction(
298 SignAndSendTransactionRequestParams {
299 transaction: EncodedSerializedTransaction::new("AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_string()),
300 },
301 )),
302 };
303
304 let response = handler.handle_request(request).await;
305
306 assert!(response.is_ok());
307 let json_response = response.unwrap();
308 match json_response.result {
309 Some(value) => {
310 if let NetworkRpcResult::Solana(SolanaRpcResult::SignAndSendTransaction(result)) =
311 value
312 {
313 assert_eq!(result.signature, mock_signature);
314 } else {
315 panic!("Expected SignAndSendTransaction result, got {:?}", value);
316 }
317 }
318 None => panic!("Expected Some result, got None"),
319 }
320 }
321
322 #[tokio::test]
323 async fn test_transfer_transaction_success() {
324 let mut mock_rpc_methods = MockSolanaRpcMethods::new();
325 let mock_transaction = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_string();
326
327 mock_rpc_methods
328 .expect_transfer_transaction()
329 .with(predicate::eq(TransferTransactionRequestParams {
330 source: "C6VBV1EK2Jx7kFgCkCD5wuDeQtEH8ct2hHGUPzEhUSc8".to_string(),
331 destination: "C6VBV1EK2Jx7kFgCkCD5wuDeQtEH8ct2hHGUPzEhUSc8".to_string(),
332 amount: 10,
333 token: "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr".to_string(), }))
335 .returning(move |_| {
336 Ok(TransferTransactionResult {
337 fee_in_lamports: "1005000".to_string(),
338 fee_in_spl: "1005000".to_string(),
339 fee_token: "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr".to_string(), transaction: EncodedSerializedTransaction::new(mock_transaction.clone()),
341 valid_until_blockheight: 351207983,
342 })
343 })
344 .times(1);
345
346 let handler = Arc::new(SolanaRpcHandler::new(mock_rpc_methods));
347
348 let request = JsonRpcRequest {
349 jsonrpc: "2.0".to_string(),
350 id: Some(JsonRpcId::Number(1)),
351 params: NetworkRpcRequest::Solana(SolanaRpcRequest::TransferTransaction(
352 TransferTransactionRequestParams {
353 source: "C6VBV1EK2Jx7kFgCkCD5wuDeQtEH8ct2hHGUPzEhUSc8".to_string(),
354 destination: "C6VBV1EK2Jx7kFgCkCD5wuDeQtEH8ct2hHGUPzEhUSc8".to_string(),
355 amount: 10,
356 token: "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr".to_string(), },
358 )),
359 };
360
361 let response = handler.handle_request(request).await;
362
363 assert!(response.is_ok());
364 let json_response = response.unwrap();
365 match json_response.result {
366 Some(value) => {
367 if let NetworkRpcResult::Solana(SolanaRpcResult::TransferTransaction(result)) =
368 value
369 {
370 assert!(!result.fee_in_lamports.is_empty());
371 assert!(!result.fee_in_spl.is_empty());
372 assert!(!result.fee_token.is_empty());
373 assert!(!result.transaction.into_inner().is_empty());
374 assert!(result.valid_until_blockheight > 0);
375 } else {
376 panic!("Expected TransferTransaction result, got {:?}", value);
377 }
378 }
379 None => panic!("Expected Some result, got None"),
380 }
381 }
382
383 #[tokio::test]
384 async fn test_prepare_transaction_success() {
385 let mut mock_rpc_methods = MockSolanaRpcMethods::new();
386 let mock_transaction = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_string();
387
388 mock_rpc_methods
389 .expect_prepare_transaction()
390 .with(predicate::eq(PrepareTransactionRequestParams {
391 transaction: EncodedSerializedTransaction::new(mock_transaction.clone()),
392 fee_token: "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr".to_string(),
393 }))
394 .returning(move |_| {
395 Ok(PrepareTransactionResult {
396 fee_in_lamports: "1005000".to_string(),
397 fee_in_spl: "1005000".to_string(),
398 fee_token: "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr".to_string(),
399 transaction: EncodedSerializedTransaction::new(mock_transaction.clone()),
400 valid_until_blockheight: 351207983,
401 })
402 })
403 .times(1);
404
405 let handler = Arc::new(SolanaRpcHandler::new(mock_rpc_methods));
406
407 let request = JsonRpcRequest {
408 jsonrpc: "2.0".to_string(),
409 id: Some(JsonRpcId::Number(1)),
410 params: NetworkRpcRequest::Solana(SolanaRpcRequest::PrepareTransaction(
411 PrepareTransactionRequestParams {
412 transaction: EncodedSerializedTransaction::new("AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_string()),
413 fee_token: "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr".to_string(),
414 },
415 )),
416 };
417
418 let response = handler.handle_request(request).await;
419
420 assert!(response.is_ok());
421 let json_response = response.unwrap();
422 match json_response.result {
423 Some(value) => {
424 if let NetworkRpcResult::Solana(SolanaRpcResult::PrepareTransaction(result)) = value
425 {
426 assert!(!result.fee_in_lamports.is_empty());
427 assert!(!result.fee_in_spl.is_empty());
428 assert!(!result.fee_token.is_empty());
429 assert!(!result.transaction.into_inner().is_empty());
430 assert!(result.valid_until_blockheight > 0);
431 } else {
432 panic!("Expected PrepareTransaction result, got {:?}", value);
433 }
434 }
435 None => panic!("Expected Some result, got None"),
436 }
437 }
438}