1use async_trait::async_trait;
22use base64::{engine::general_purpose, Engine as _};
23use reqwest_middleware::ClientBuilder;
24use std::{str, time::Duration};
25use thiserror::Error;
26
27use crate::constants::{
28 DEFAULT_HTTP_CLIENT_CONNECT_TIMEOUT_SECONDS,
29 DEFAULT_HTTP_CLIENT_HTTP2_KEEP_ALIVE_INTERVAL_SECONDS,
30 DEFAULT_HTTP_CLIENT_HTTP2_KEEP_ALIVE_TIMEOUT_SECONDS,
31 DEFAULT_HTTP_CLIENT_POOL_IDLE_TIMEOUT_SECONDS, DEFAULT_HTTP_CLIENT_POOL_MAX_IDLE_PER_HOST,
32 DEFAULT_HTTP_CLIENT_TCP_KEEPALIVE_SECONDS, DEFAULT_HTTP_CLIENT_TIMEOUT_SECONDS,
33};
34use crate::models::{Address, CdpSignerConfig};
35
36use cdp_sdk::{auth::WalletAuth, types, Client, CDP_BASE_URL};
37
38#[derive(Error, Debug, serde::Serialize)]
39pub enum CdpError {
40 #[error("HTTP error: {0}")]
41 HttpError(String),
42
43 #[error("Authentication failed: {0}")]
44 AuthenticationFailed(String),
45
46 #[error("Configuration error: {0}")]
47 ConfigError(String),
48
49 #[error("Signing error: {0}")]
50 SigningError(String),
51
52 #[error("Serialization error: {0}")]
53 SerializationError(String),
54
55 #[error("Invalid signature: {0}")]
56 SignatureError(String),
57
58 #[error("Other error: {0}")]
59 OtherError(String),
60}
61
62pub type CdpResult<T> = Result<T, CdpError>;
64
65#[cfg(test)]
66use mockall::automock;
67
68#[async_trait]
69#[cfg_attr(test, automock)]
70pub trait CdpServiceTrait: Send + Sync {
71 async fn account_address(&self) -> Result<Address, CdpError>;
73
74 async fn sign_evm_message(&self, message: String) -> Result<Vec<u8>, CdpError>;
76
77 async fn sign_evm_transaction(&self, message: &[u8]) -> Result<Vec<u8>, CdpError>;
79
80 async fn sign_solana_message(&self, message: &[u8]) -> Result<Vec<u8>, CdpError>;
82
83 async fn sign_solana_transaction(&self, message: String) -> Result<Vec<u8>, CdpError>;
85}
86
87#[derive(Clone, Debug)]
88pub struct CdpService {
89 pub config: CdpSignerConfig,
90 pub client: Client,
91}
92
93impl CdpService {
94 pub fn new(config: CdpSignerConfig) -> Result<Self, CdpError> {
95 let wallet_auth = WalletAuth::builder()
97 .api_key_id(config.api_key_id.clone())
98 .api_key_secret(config.api_key_secret.to_str().to_string())
99 .wallet_secret(config.wallet_secret.to_str().to_string())
100 .source("openzeppelin-relayer".to_string())
101 .source_version(env!("CARGO_PKG_VERSION").to_string())
102 .build()
103 .map_err(|e| CdpError::ConfigError(format!("Invalid CDP configuration: {e}")))?;
104
105 let inner = reqwest::Client::builder()
106 .connect_timeout(Duration::from_secs(
107 DEFAULT_HTTP_CLIENT_CONNECT_TIMEOUT_SECONDS,
108 ))
109 .timeout(Duration::from_secs(DEFAULT_HTTP_CLIENT_TIMEOUT_SECONDS))
110 .pool_max_idle_per_host(DEFAULT_HTTP_CLIENT_POOL_MAX_IDLE_PER_HOST)
111 .pool_idle_timeout(Duration::from_secs(
112 DEFAULT_HTTP_CLIENT_POOL_IDLE_TIMEOUT_SECONDS,
113 ))
114 .tcp_keepalive(Duration::from_secs(
115 DEFAULT_HTTP_CLIENT_TCP_KEEPALIVE_SECONDS,
116 ))
117 .http2_keep_alive_interval(Some(Duration::from_secs(
118 DEFAULT_HTTP_CLIENT_HTTP2_KEEP_ALIVE_INTERVAL_SECONDS,
119 )))
120 .http2_keep_alive_timeout(Duration::from_secs(
121 DEFAULT_HTTP_CLIENT_HTTP2_KEEP_ALIVE_TIMEOUT_SECONDS,
122 ))
123 .build()
124 .map_err(|e| CdpError::ConfigError(format!("Failed to build HTTP client: {e}")))?;
125 let wallet_client = ClientBuilder::new(inner).with(wallet_auth).build();
126 let client = Client::new_with_client(CDP_BASE_URL, wallet_client);
127 Ok(Self { config, client })
128 }
129
130 fn get_account_address(&self) -> &str {
132 &self.config.account_address
133 }
134
135 fn is_evm_address(&self) -> bool {
137 self.config.account_address.starts_with("0x")
138 }
139
140 fn is_solana_address(&self) -> bool {
142 !self.config.account_address.starts_with("0x")
143 }
144
145 fn address_from_string(&self, address_str: &str) -> Result<Address, CdpError> {
147 if address_str.starts_with("0x") {
148 let hex_str = address_str.strip_prefix("0x").unwrap();
150
151 let bytes = hex::decode(hex_str)
153 .map_err(|e| CdpError::ConfigError(format!("Invalid hex address: {e}")))?;
154
155 if bytes.len() != 20 {
156 return Err(CdpError::ConfigError(format!(
157 "EVM address should be 20 bytes, got {} bytes",
158 bytes.len()
159 )));
160 }
161
162 let mut array = [0u8; 20];
163 array.copy_from_slice(&bytes);
164
165 Ok(Address::Evm(array))
166 } else {
167 Ok(Address::Solana(address_str.to_string()))
169 }
170 }
171}
172
173#[async_trait]
174impl CdpServiceTrait for CdpService {
175 async fn account_address(&self) -> Result<Address, CdpError> {
176 let address_str = self.get_account_address();
177 self.address_from_string(address_str)
178 }
179
180 async fn sign_evm_message(&self, message: String) -> Result<Vec<u8>, CdpError> {
181 if !self.is_evm_address() {
182 return Err(CdpError::ConfigError(
183 "Account address is not an EVM address (must start with 0x)".to_string(),
184 ));
185 }
186 let address = self.get_account_address();
187
188 let message_body = types::SignEvmMessageBody::builder().message(message);
189
190 let response = self
191 .client
192 .sign_evm_message()
193 .address(address)
194 .x_wallet_auth("") .body(message_body)
196 .send()
197 .await
198 .map_err(|e| CdpError::SigningError(format!("Failed to sign message: {e}")))?;
199
200 let result = response.into_inner();
201
202 let signature_bytes = hex::decode(
204 result
205 .signature
206 .strip_prefix("0x")
207 .unwrap_or(&result.signature),
208 )
209 .map_err(|e| CdpError::SigningError(format!("Invalid signature hex: {e}")))?;
210
211 Ok(signature_bytes)
212 }
213
214 async fn sign_evm_transaction(&self, message: &[u8]) -> Result<Vec<u8>, CdpError> {
215 if !self.is_evm_address() {
216 return Err(CdpError::ConfigError(
217 "Account address is not an EVM address (must start with 0x)".to_string(),
218 ));
219 }
220 let address = self.get_account_address();
221
222 let hex_encoded = hex::encode(message);
224
225 let tx_body =
226 types::SignEvmTransactionBody::builder().transaction(format!("0x{hex_encoded}"));
227
228 let response = self
229 .client
230 .sign_evm_transaction()
231 .address(address)
232 .x_wallet_auth("")
233 .body(tx_body)
234 .send()
235 .await
236 .map_err(|e| CdpError::SigningError(format!("Failed to sign transaction: {e}")))?;
237
238 let result = response.into_inner();
239
240 let signed_tx_bytes = hex::decode(
242 result
243 .signed_transaction
244 .strip_prefix("0x")
245 .unwrap_or(&result.signed_transaction),
246 )
247 .map_err(|e| CdpError::SigningError(format!("Invalid signed transaction hex: {e}")))?;
248
249 Ok(signed_tx_bytes)
250 }
251
252 async fn sign_solana_message(&self, message: &[u8]) -> Result<Vec<u8>, CdpError> {
253 if !self.is_solana_address() {
254 return Err(CdpError::ConfigError(
255 "Account address is not a Solana address (must not start with 0x)".to_string(),
256 ));
257 }
258 let address = self.get_account_address();
259 let encoded_message = str::from_utf8(message)
260 .map_err(|e| CdpError::SerializationError(format!("Invalid UTF-8 message: {e}")))?
261 .to_string();
262
263 let message_body = types::SignSolanaMessageBody::builder().message(encoded_message);
264
265 let response = self
266 .client
267 .sign_solana_message()
268 .address(address)
269 .x_wallet_auth("") .body(message_body)
271 .send()
272 .await
273 .map_err(|e| CdpError::SigningError(format!("Failed to sign Solana message: {e}")))?;
274
275 let result = response.into_inner();
276
277 let signature_bytes = bs58::decode(result.signature)
279 .into_vec()
280 .map_err(|e| CdpError::SigningError(format!("Invalid Solana signature base58: {e}")))?;
281
282 Ok(signature_bytes)
283 }
284
285 async fn sign_solana_transaction(&self, transaction: String) -> Result<Vec<u8>, CdpError> {
286 if !self.is_solana_address() {
287 return Err(CdpError::ConfigError(
288 "Account address is not a Solana address (must not start with 0x)".to_string(),
289 ));
290 }
291 let address = self.get_account_address();
292
293 let message_body = types::SignSolanaTransactionBody::builder().transaction(transaction);
294
295 let response = self
296 .client
297 .sign_solana_transaction()
298 .address(address)
299 .x_wallet_auth("") .body(message_body)
301 .send()
302 .await
303 .map_err(|e| CdpError::SigningError(format!("Failed to sign Solana transaction: {e}")))?;
304
305 let result = response.into_inner();
306
307 let signature_bytes = general_purpose::STANDARD
309 .decode(result.signed_transaction)
310 .map_err(|e| {
311 CdpError::SigningError(format!("Invalid Solana signed transaction base64: {e}"))
312 })?;
313
314 Ok(signature_bytes)
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321 use crate::models::SecretString;
322 use mockito;
323 use serde_json::json;
324
325 fn create_test_config_evm() -> CdpSignerConfig {
326 CdpSignerConfig {
327 api_key_id: "test-api-key-id".to_string(),
328 api_key_secret: SecretString::new("test-api-key-secret"),
329 wallet_secret: SecretString::new("test-wallet-secret"),
330 account_address: "0x742d35Cc6634C0532925a3b844Bc454e4438f44f".to_string(),
331 }
332 }
333
334 fn create_test_config_solana() -> CdpSignerConfig {
335 CdpSignerConfig {
336 api_key_id: "test-api-key-id".to_string(),
337 api_key_secret: SecretString::new("test-api-key-secret"),
338 wallet_secret: SecretString::new("test-wallet-secret"),
339 account_address: "6s7RsvzcdXFJi1tXeDoGfSKZFzN3juVt9fTar6WEhEm2".to_string(),
340 }
341 }
342
343 fn create_test_client() -> reqwest_middleware::ClientWithMiddleware {
345 let inner = reqwest::ClientBuilder::new()
346 .redirect(reqwest::redirect::Policy::none())
347 .build()
348 .unwrap();
349 reqwest_middleware::ClientBuilder::new(inner).build()
350 }
351
352 async fn setup_mock_sign_evm_message(mock_server: &mut mockito::ServerGuard) -> mockito::Mock {
354 mock_server
355 .mock("POST", mockito::Matcher::Regex(r".*/v2/evm/accounts/.*/sign/message".to_string()))
356 .match_header("Content-Type", "application/json")
357 .with_status(200)
358 .with_header("content-type", "application/json")
359 .with_body(serde_json::to_string(&json!({
360 "signature": "0x3045022100abcdef1234567890abcdef1234567890abcdef1234567890abcdef123456789002201234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
361 })).unwrap())
362 .expect(1)
363 .create_async()
364 .await
365 }
366
367 async fn setup_mock_sign_evm_transaction(
369 mock_server: &mut mockito::ServerGuard,
370 ) -> mockito::Mock {
371 mock_server
372 .mock("POST", mockito::Matcher::Regex(r".*/v2/evm/accounts/.*/sign/transaction".to_string()))
373 .match_header("Content-Type", "application/json")
374 .with_status(200)
375 .with_header("content-type", "application/json")
376 .with_body(serde_json::to_string(&json!({
377 "signedTransaction": "0x02f87001020304050607080910111213141516171819202122232425262728293031"
378 })).unwrap())
379 .expect(1)
380 .create_async()
381 .await
382 }
383
384 async fn setup_mock_sign_solana_message(
386 mock_server: &mut mockito::ServerGuard,
387 ) -> mockito::Mock {
388 mock_server
389 .mock("POST", mockito::Matcher::Regex(r".*/v2/solana/accounts/.*/sign/message".to_string()))
390 .match_header("Content-Type", "application/json")
391 .with_status(200)
392 .with_header("content-type", "application/json")
393 .with_body(serde_json::to_string(&json!({
394 "signature": "5VERuXP42jC4Uxo1Rc3eLQgFaQGYdM9ZJvqK3JmZ6vxGz4s8FJ7KHkQpE3cN8RuQ2mW6tX9Y5K2P1VcZqL8TfABC3X"
395 })).unwrap())
396 .expect(1)
397 .create_async()
398 .await
399 }
400
401 async fn setup_mock_sign_solana_transaction(
403 mock_server: &mut mockito::ServerGuard,
404 ) -> mockito::Mock {
405 mock_server
406 .mock(
407 "POST",
408 mockito::Matcher::Regex(r".*/v2/solana/accounts/.*/sign/transaction".to_string()),
409 )
410 .match_header("Content-Type", "application/json")
411 .with_status(200)
412 .with_header("content-type", "application/json")
413 .with_body(
414 serde_json::to_string(&json!({
415 "signedTransaction": "SGVsbG8gV29ybGQh" }))
417 .unwrap(),
418 )
419 .expect(1)
420 .create_async()
421 .await
422 }
423
424 async fn setup_mock_error_400_malformed_transaction(
426 mock_server: &mut mockito::ServerGuard,
427 path_pattern: &str,
428 ) -> mockito::Mock {
429 mock_server
430 .mock("POST", mockito::Matcher::Regex(path_pattern.to_string()))
431 .match_header("Content-Type", "application/json")
432 .with_status(400)
433 .with_header("content-type", "application/json")
434 .with_body(
435 serde_json::to_string(&json!({
436 "errorType": "malformed_transaction",
437 "errorMessage": "Malformed unsigned transaction."
438 }))
439 .unwrap(),
440 )
441 .expect(1)
442 .create_async()
443 .await
444 }
445
446 async fn setup_mock_error_401_unauthorized(
448 mock_server: &mut mockito::ServerGuard,
449 path_pattern: &str,
450 ) -> mockito::Mock {
451 mock_server
452 .mock("POST", mockito::Matcher::Regex(path_pattern.to_string()))
453 .match_header("Content-Type", "application/json")
454 .with_status(401)
455 .with_header("content-type", "application/json")
456 .with_body(
457 serde_json::to_string(&json!({
458 "errorType": "unauthorized",
459 "errorMessage": "Invalid API credentials."
460 }))
461 .unwrap(),
462 )
463 .expect(1)
464 .create_async()
465 .await
466 }
467
468 async fn setup_mock_error_500_internal_error(
470 mock_server: &mut mockito::ServerGuard,
471 path_pattern: &str,
472 ) -> mockito::Mock {
473 mock_server
474 .mock("POST", mockito::Matcher::Regex(path_pattern.to_string()))
475 .match_header("Content-Type", "application/json")
476 .with_status(500)
477 .with_header("content-type", "application/json")
478 .with_body(
479 serde_json::to_string(&json!({
480 "errorType": "internal_error",
481 "errorMessage": "Internal server error occurred."
482 }))
483 .unwrap(),
484 )
485 .expect(1)
486 .create_async()
487 .await
488 }
489
490 async fn setup_mock_error_422_invalid_signature(
492 mock_server: &mut mockito::ServerGuard,
493 path_pattern: &str,
494 ) -> mockito::Mock {
495 mock_server
496 .mock("POST", mockito::Matcher::Regex(path_pattern.to_string()))
497 .match_header("Content-Type", "application/json")
498 .with_status(422)
499 .with_header("content-type", "application/json")
500 .with_body(
501 serde_json::to_string(&json!({
502 "errorType": "invalid_signature_request",
503 "errorMessage": "Unable to process signature request."
504 }))
505 .unwrap(),
506 )
507 .expect(1)
508 .create_async()
509 .await
510 }
511
512 #[test]
513 fn test_new_cdp_service_valid_config() {
514 let config = create_test_config_evm();
515 let result = CdpService::new(config);
516
517 assert!(result.is_ok());
519 }
520
521 #[test]
522 fn test_get_account_address() {
523 let config = create_test_config_evm();
524 let service = CdpService::new(config).unwrap();
525
526 let address = service.get_account_address();
527 assert_eq!(address, "0x742d35Cc6634C0532925a3b844Bc454e4438f44f");
528 }
529
530 #[test]
531 fn test_is_evm_address() {
532 let config = create_test_config_evm();
533 let service = CdpService::new(config).unwrap();
534 assert!(service.is_evm_address());
535 assert!(!service.is_solana_address());
536 }
537
538 #[test]
539 fn test_is_solana_address() {
540 let config = create_test_config_solana();
541 let service = CdpService::new(config).unwrap();
542 assert!(service.is_solana_address());
543 assert!(!service.is_evm_address());
544 }
545
546 #[tokio::test]
547 async fn test_address_evm_success() {
548 let config = create_test_config_evm();
549 let service = CdpService::new(config).unwrap();
550 let result = service.account_address().await;
551
552 assert!(result.is_ok());
553 match result.unwrap() {
554 Address::Evm(addr) => {
555 let expected = [
557 0x74, 0x2d, 0x35, 0xcc, 0x66, 0x34, 0xC0, 0x53, 0x29, 0x25, 0xa3, 0xb8, 0x44,
558 0xbc, 0x45, 0x4e, 0x44, 0x38, 0xf4, 0x4f,
559 ];
560 assert_eq!(addr, expected);
561 }
562 _ => panic!("Expected EVM address"),
563 }
564 }
565
566 #[tokio::test]
567 async fn test_address_solana_success() {
568 let config = create_test_config_solana();
569 let service = CdpService::new(config).unwrap();
570 let result = service.account_address().await;
571
572 assert!(result.is_ok());
573 match result.unwrap() {
574 Address::Solana(addr) => {
575 assert_eq!(addr, "6s7RsvzcdXFJi1tXeDoGfSKZFzN3juVt9fTar6WEhEm2");
576 }
577 _ => panic!("Expected Solana address"),
578 }
579 }
580
581 #[test]
582 fn test_address_from_string_valid_evm_address() {
583 let config = create_test_config_evm();
584 let service = CdpService::new(config).unwrap();
585
586 let test_address = "0x742d35Cc6634C0532925a3b844Bc454e4438f44f";
587 let result = service.address_from_string(test_address);
588
589 assert!(result.is_ok());
590 match result.unwrap() {
591 Address::Evm(addr) => {
592 let expected = [
593 0x74, 0x2d, 0x35, 0xcc, 0x66, 0x34, 0xC0, 0x53, 0x29, 0x25, 0xa3, 0xb8, 0x44,
594 0xbc, 0x45, 0x4e, 0x44, 0x38, 0xf4, 0x4f,
595 ];
596 assert_eq!(addr, expected);
597 }
598 _ => panic!("Expected EVM address"),
599 }
600 }
601
602 #[test]
603 fn test_address_from_string_valid_solana_address() {
604 let config = create_test_config_solana();
605 let service = CdpService::new(config).unwrap();
606
607 let test_address = "6s7RsvzcdXFJi1tXeDoGfSKZFzN3juVt9fTar6WEhEm2";
608 let result = service.address_from_string(test_address);
609
610 assert!(result.is_ok());
611 match result.unwrap() {
612 Address::Solana(addr) => {
613 assert_eq!(addr, "6s7RsvzcdXFJi1tXeDoGfSKZFzN3juVt9fTar6WEhEm2");
614 }
615 _ => panic!("Expected Solana address"),
616 }
617 }
618
619 #[test]
620 fn test_address_from_string_without_0x_prefix() {
621 let config = create_test_config_evm();
622 let service = CdpService::new(config).unwrap();
623
624 let test_address = "742d35Cc6634C0532925a3b844Bc454e4438f44f";
625 let result = service.address_from_string(test_address);
626
627 assert!(result.is_ok());
629 match result.unwrap() {
630 Address::Solana(addr) => {
631 assert_eq!(addr, "742d35Cc6634C0532925a3b844Bc454e4438f44f");
632 }
633 _ => panic!("Expected Solana address"),
634 }
635 }
636
637 #[test]
638 fn test_address_from_string_invalid_hex() {
639 let config = create_test_config_evm();
640 let service = CdpService::new(config).unwrap();
641
642 let test_address = "0xnot_valid_hex";
643 let result = service.address_from_string(test_address);
644
645 assert!(result.is_err());
646 match result {
647 Err(CdpError::ConfigError(msg)) => {
648 assert!(msg.contains("Invalid hex address"));
649 }
650 _ => panic!("Expected ConfigError for invalid hex"),
651 }
652 }
653
654 #[test]
655 fn test_address_from_string_wrong_length() {
656 let config = create_test_config_evm();
657 let service = CdpService::new(config).unwrap();
658
659 let test_address = "0x742d35Cc"; let result = service.address_from_string(test_address);
661
662 assert!(result.is_err());
663 match result {
664 Err(CdpError::ConfigError(msg)) => {
665 assert!(msg.contains("EVM address should be 20 bytes"));
666 }
667 _ => panic!("Expected ConfigError for wrong length"),
668 }
669 }
670
671 #[test]
672 fn test_cdp_error_display() {
673 let errors = [
674 CdpError::HttpError("HTTP error".to_string()),
675 CdpError::AuthenticationFailed("Auth failed".to_string()),
676 CdpError::ConfigError("Config error".to_string()),
677 CdpError::SigningError("Signing error".to_string()),
678 CdpError::SerializationError("Serialization error".to_string()),
679 CdpError::SignatureError("Signature error".to_string()),
680 CdpError::OtherError("Other error".to_string()),
681 ];
682
683 for error in errors {
684 let error_str = error.to_string();
685 assert!(!error_str.is_empty());
686 }
687 }
688
689 #[tokio::test]
690 async fn test_sign_evm_message_success() {
691 let mut mock_server = mockito::Server::new_async().await;
692 let _mock = setup_mock_sign_evm_message(&mut mock_server).await;
693
694 let config = create_test_config_evm();
695 let client = Client::new_with_client(&mock_server.url(), create_test_client());
696
697 let service = CdpService { config, client };
698
699 let message = "Hello World!".to_string();
700 let result = service.sign_evm_message(message).await;
701
702 match result {
703 Ok(signature) => {
704 assert!(!signature.is_empty());
705 }
706 Err(e) => {
707 panic!("Expected success but got error: {e:?}");
708 }
709 }
710 }
711
712 #[tokio::test]
713 async fn test_sign_evm_message_wrong_address_type() {
714 let config = create_test_config_solana(); let client = Client::new_with_client("http://test", create_test_client());
716 let service = CdpService { config, client };
717
718 let message = "Hello World!".to_string();
719 let result = service.sign_evm_message(message).await;
720
721 assert!(result.is_err());
722 match result {
723 Err(CdpError::ConfigError(msg)) => {
724 assert!(msg.contains("Account address is not an EVM address"));
725 }
726 _ => panic!("Expected ConfigError for wrong address type"),
727 }
728 }
729
730 #[tokio::test]
731 async fn test_sign_evm_transaction_success() {
732 let mut mock_server = mockito::Server::new_async().await;
733 let _mock = setup_mock_sign_evm_transaction(&mut mock_server).await;
734
735 let config = create_test_config_evm();
736 let client = Client::new_with_client(&mock_server.url(), create_test_client());
737
738 let service = CdpService { config, client };
739
740 let transaction_bytes = b"test transaction data";
741 let result = service.sign_evm_transaction(transaction_bytes).await;
742
743 match result {
744 Ok(signed_tx) => {
745 assert!(!signed_tx.is_empty());
746 }
747 Err(e) => {
748 panic!("Expected success but got error: {e:?}");
749 }
750 }
751 }
752
753 #[tokio::test]
754 async fn test_sign_evm_transaction_wrong_address_type() {
755 let config = create_test_config_solana(); let client = Client::new_with_client("http://test", create_test_client());
757 let service = CdpService { config, client };
758
759 let transaction_bytes = b"test transaction data";
760 let result = service.sign_evm_transaction(transaction_bytes).await;
761
762 assert!(result.is_err());
763 match result {
764 Err(CdpError::ConfigError(msg)) => {
765 assert!(msg.contains("Account address is not an EVM address"));
766 }
767 _ => panic!("Expected ConfigError for wrong address type"),
768 }
769 }
770
771 #[tokio::test]
772 async fn test_sign_solana_message_success() {
773 let mut mock_server = mockito::Server::new_async().await;
774 let _mock = setup_mock_sign_solana_message(&mut mock_server).await;
775
776 let config = create_test_config_solana();
777 let client = Client::new_with_client(&mock_server.url(), create_test_client());
778
779 let service = CdpService { config, client };
780
781 let message_bytes = b"Hello Solana!";
782 let result = service.sign_solana_message(message_bytes).await;
783
784 assert!(result.is_ok());
785 let signature = result.unwrap();
786 assert!(!signature.is_empty());
787 }
788
789 #[tokio::test]
790 async fn test_sign_solana_message_wrong_address_type() {
791 let config = create_test_config_evm(); let client = Client::new_with_client("http://test", create_test_client());
793 let service = CdpService { config, client };
794
795 let message_bytes = b"Hello Solana!";
796 let result = service.sign_solana_message(message_bytes).await;
797
798 assert!(result.is_err());
799 match result {
800 Err(CdpError::ConfigError(msg)) => {
801 assert!(msg.contains("Account address is not a Solana address"));
802 }
803 _ => panic!("Expected ConfigError for wrong address type"),
804 }
805 }
806
807 #[tokio::test]
808 async fn test_sign_solana_transaction_success() {
809 let mut mock_server = mockito::Server::new_async().await;
810 let _mock = setup_mock_sign_solana_transaction(&mut mock_server).await;
811
812 let config = create_test_config_solana();
813 let client = Client::new_with_client(&mock_server.url(), create_test_client());
814
815 let service = CdpService { config, client };
816
817 let transaction = "test-transaction-string".to_string();
818 let result = service.sign_solana_transaction(transaction).await;
819
820 match result {
821 Ok(signed_tx) => {
822 assert!(!signed_tx.is_empty());
823 }
824 Err(e) => {
825 panic!("Expected success but got error: {e:?}");
826 }
827 }
828 }
829
830 #[tokio::test]
831 async fn test_sign_solana_transaction_wrong_address_type() {
832 let config = create_test_config_evm(); let client = Client::new_with_client("http://test", create_test_client());
834 let service = CdpService { config, client };
835
836 let transaction = "test-transaction-string".to_string();
837 let result = service.sign_solana_transaction(transaction).await;
838
839 assert!(result.is_err());
840 match result {
841 Err(CdpError::ConfigError(msg)) => {
842 assert!(msg.contains("Account address is not a Solana address"));
843 }
844 _ => panic!("Expected ConfigError for wrong address type"),
845 }
846 }
847
848 #[tokio::test]
850 async fn test_sign_evm_message_error_400_malformed_transaction() {
851 let mut mock_server = mockito::Server::new_async().await;
852 let _mock = setup_mock_error_400_malformed_transaction(
853 &mut mock_server,
854 r".*/v2/evm/accounts/.*/sign/message",
855 )
856 .await;
857
858 let config = create_test_config_evm();
859 let client = Client::new_with_client(&mock_server.url(), create_test_client());
860 let service = CdpService { config, client };
861
862 let message = "Hello World!".to_string();
863 let result = service.sign_evm_message(message).await;
864
865 assert!(result.is_err());
866 match result {
867 Err(CdpError::SigningError(msg)) => {
868 assert!(msg.contains("Failed to sign message"));
869 }
870 _ => panic!("Expected SigningError for malformed transaction"),
871 }
872 }
873
874 #[tokio::test]
875 async fn test_sign_evm_message_error_401_unauthorized() {
876 let mut mock_server = mockito::Server::new_async().await;
877 let _mock = setup_mock_error_401_unauthorized(
878 &mut mock_server,
879 r".*/v2/evm/accounts/.*/sign/message",
880 )
881 .await;
882
883 let config = create_test_config_evm();
884 let client = Client::new_with_client(&mock_server.url(), create_test_client());
885 let service = CdpService { config, client };
886
887 let message = "Hello World!".to_string();
888 let result = service.sign_evm_message(message).await;
889
890 assert!(result.is_err());
891 match result {
892 Err(CdpError::SigningError(msg)) => {
893 assert!(msg.contains("Failed to sign message"));
894 }
895 _ => panic!("Expected SigningError for unauthorized"),
896 }
897 }
898
899 #[tokio::test]
900 async fn test_sign_evm_message_error_500_internal_error() {
901 let mut mock_server = mockito::Server::new_async().await;
902 let _mock = setup_mock_error_500_internal_error(
903 &mut mock_server,
904 r".*/v2/evm/accounts/.*/sign/message",
905 )
906 .await;
907
908 let config = create_test_config_evm();
909 let client = Client::new_with_client(&mock_server.url(), create_test_client());
910 let service = CdpService { config, client };
911
912 let message = "Hello World!".to_string();
913 let result = service.sign_evm_message(message).await;
914
915 assert!(result.is_err());
916 match result {
917 Err(CdpError::SigningError(msg)) => {
918 assert!(msg.contains("Failed to sign message"));
919 }
920 _ => panic!("Expected SigningError for internal error"),
921 }
922 }
923
924 #[tokio::test]
925 async fn test_sign_evm_transaction_error_400_malformed_transaction() {
926 let mut mock_server = mockito::Server::new_async().await;
927 let _mock = setup_mock_error_400_malformed_transaction(
928 &mut mock_server,
929 r".*/v2/evm/accounts/.*/sign/transaction",
930 )
931 .await;
932
933 let config = create_test_config_evm();
934 let client = Client::new_with_client(&mock_server.url(), create_test_client());
935 let service = CdpService { config, client };
936
937 let transaction_bytes = b"invalid transaction data";
938 let result = service.sign_evm_transaction(transaction_bytes).await;
939
940 assert!(result.is_err());
941 match result {
942 Err(CdpError::SigningError(msg)) => {
943 assert!(msg.contains("Failed to sign transaction"));
944 }
945 _ => panic!("Expected SigningError for malformed transaction"),
946 }
947 }
948
949 #[tokio::test]
950 async fn test_sign_evm_transaction_error_422_invalid_signature() {
951 let mut mock_server = mockito::Server::new_async().await;
952 let _mock = setup_mock_error_422_invalid_signature(
953 &mut mock_server,
954 r".*/v2/evm/accounts/.*/sign/transaction",
955 )
956 .await;
957
958 let config = create_test_config_evm();
959 let client = Client::new_with_client(&mock_server.url(), create_test_client());
960 let service = CdpService { config, client };
961
962 let transaction_bytes = b"test transaction data";
963 let result = service.sign_evm_transaction(transaction_bytes).await;
964
965 assert!(result.is_err());
966 match result {
967 Err(CdpError::SigningError(msg)) => {
968 assert!(msg.contains("Failed to sign transaction"));
969 }
970 _ => panic!("Expected SigningError for invalid signature request"),
971 }
972 }
973
974 #[tokio::test]
975 async fn test_sign_solana_message_error_400_malformed_transaction() {
976 let mut mock_server = mockito::Server::new_async().await;
977 let _mock = setup_mock_error_400_malformed_transaction(
978 &mut mock_server,
979 r".*/v2/solana/accounts/.*/sign/message",
980 )
981 .await;
982
983 let config = create_test_config_solana();
984 let client = Client::new_with_client(&mock_server.url(), create_test_client());
985 let service = CdpService { config, client };
986
987 let message_bytes = b"Hello Solana!";
988 let result = service.sign_solana_message(message_bytes).await;
989
990 assert!(result.is_err());
991 match result {
992 Err(CdpError::SigningError(msg)) => {
993 assert!(msg.contains("Failed to sign Solana message"));
994 }
995 _ => panic!("Expected SigningError for malformed transaction"),
996 }
997 }
998
999 #[tokio::test]
1000 async fn test_sign_solana_message_error_401_unauthorized() {
1001 let mut mock_server = mockito::Server::new_async().await;
1002 let _mock = setup_mock_error_401_unauthorized(
1003 &mut mock_server,
1004 r".*/v2/solana/accounts/.*/sign/message",
1005 )
1006 .await;
1007
1008 let config = create_test_config_solana();
1009 let client = Client::new_with_client(&mock_server.url(), create_test_client());
1010 let service = CdpService { config, client };
1011
1012 let message_bytes = b"Hello Solana!";
1013 let result = service.sign_solana_message(message_bytes).await;
1014
1015 assert!(result.is_err());
1016 match result {
1017 Err(CdpError::SigningError(msg)) => {
1018 assert!(msg.contains("Failed to sign Solana message"));
1019 }
1020 _ => panic!("Expected SigningError for unauthorized"),
1021 }
1022 }
1023
1024 #[tokio::test]
1025 async fn test_sign_solana_transaction_error_400_malformed_transaction() {
1026 let mut mock_server = mockito::Server::new_async().await;
1027 let _mock = setup_mock_error_400_malformed_transaction(
1028 &mut mock_server,
1029 r".*/v2/solana/accounts/.*/sign/transaction",
1030 )
1031 .await;
1032
1033 let config = create_test_config_solana();
1034 let client = Client::new_with_client(&mock_server.url(), create_test_client());
1035 let service = CdpService { config, client };
1036
1037 let transaction = "invalid-transaction-string".to_string();
1038 let result = service.sign_solana_transaction(transaction).await;
1039
1040 assert!(result.is_err());
1041 match result {
1042 Err(CdpError::SigningError(msg)) => {
1043 assert!(msg.contains("Failed to sign Solana transaction"));
1044 }
1045 _ => panic!("Expected SigningError for malformed transaction"),
1046 }
1047 }
1048
1049 #[tokio::test]
1050 async fn test_sign_solana_transaction_error_500_internal_error() {
1051 let mut mock_server = mockito::Server::new_async().await;
1052 let _mock = setup_mock_error_500_internal_error(
1053 &mut mock_server,
1054 r".*/v2/solana/accounts/.*/sign/transaction",
1055 )
1056 .await;
1057
1058 let config = create_test_config_solana();
1059 let client = Client::new_with_client(&mock_server.url(), create_test_client());
1060 let service = CdpService { config, client };
1061
1062 let transaction = "test-transaction-string".to_string();
1063 let result = service.sign_solana_transaction(transaction).await;
1064
1065 assert!(result.is_err());
1066 match result {
1067 Err(CdpError::SigningError(msg)) => {
1068 assert!(msg.contains("Failed to sign Solana transaction"));
1069 }
1070 _ => panic!("Expected SigningError for internal error"),
1071 }
1072 }
1073}