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