1use async_trait::async_trait;
18mod local_signer;
19use local_signer::*;
20
21mod vault_signer;
22use vault_signer::*;
23
24mod vault_transit_signer;
25use vault_transit_signer::*;
26
27mod turnkey_signer;
28use turnkey_signer::*;
29
30mod cdp_signer;
31use cdp_signer::*;
32
33mod google_cloud_kms_signer;
34use google_cloud_kms_signer::*;
35
36use solana_program::message::compiled_instruction::CompiledInstruction;
37use solana_sdk::pubkey::Pubkey;
38use solana_sdk::signature::Signature;
39use solana_sdk::transaction::Transaction as SolanaTransaction;
40use std::str::FromStr;
41
42use solana_system_interface::instruction as system_instruction;
43
44use crate::{
45 domain::{
46 SignDataRequest, SignDataResponse, SignDataResponseEvm, SignTransactionResponse,
47 SignTransactionResponseSolana, SignTypedDataRequest,
48 },
49 models::{
50 Address, EncodedSerializedTransaction, NetworkTransactionData, Signer as SignerDomainModel,
51 SignerConfig, SignerRepoModel, SignerType, TransactionRepoModel, VaultSignerConfig,
52 },
53 services::{CdpService, GoogleCloudKmsService, TurnkeyService, VaultConfig, VaultService},
54};
55use eyre::Result;
56
57use super::{Signer, SignerError, SignerFactoryError};
58#[cfg(test)]
59use mockall::automock;
60
61#[derive(Debug)]
62pub enum SolanaSigner {
63 Local(LocalSigner),
64 Vault(VaultSigner<VaultService>),
65 VaultTransit(VaultTransitSigner),
66 Turnkey(TurnkeySigner),
67 Cdp(CdpSigner),
68 GoogleCloudKms(GoogleCloudKmsSigner),
69}
70
71#[async_trait]
72impl Signer for SolanaSigner {
73 async fn address(&self) -> Result<Address, SignerError> {
74 self.pubkey().await
76 }
77
78 async fn sign_transaction(
79 &self,
80 transaction: NetworkTransactionData,
81 ) -> Result<SignTransactionResponse, SignerError> {
82 let solana_data = transaction.get_solana_transaction_data().map_err(|e| {
84 SignerError::SigningError(format!("Invalid transaction type for Solana signer: {e}"))
85 })?;
86
87 let transaction_str = solana_data.transaction.ok_or_else(|| {
89 SignerError::SigningError(
90 "Transaction not yet built - only available after preparation".to_string(),
91 )
92 })?;
93
94 let encoded_tx = EncodedSerializedTransaction::new(transaction_str);
96 let sdk_transaction = SolanaTransaction::try_from(encoded_tx)
97 .map_err(|e| SignerError::SigningError(format!("Failed to decode transaction: {e}")))?;
98
99 let (signed_tx, signature) = sign_sdk_transaction(self, sdk_transaction).await?;
101
102 let encoded_signed_tx =
104 EncodedSerializedTransaction::try_from(&signed_tx).map_err(|e| {
105 SignerError::SigningError(format!("Failed to encode signed transaction: {e}"))
106 })?;
107
108 Ok(SignTransactionResponse::Solana(
110 SignTransactionResponseSolana {
111 transaction: encoded_signed_tx,
112 signature: signature.to_string(),
113 },
114 ))
115 }
116}
117
118#[async_trait]
119#[cfg_attr(test, automock)]
120pub trait SolanaSignTrait: Sync + Send {
125 async fn pubkey(&self) -> Result<Address, SignerError>;
127
128 async fn sign(&self, message: &[u8]) -> Result<Signature, SignerError>;
138}
139
140pub async fn sign_sdk_transaction<T: SolanaSignTrait + ?Sized>(
163 signer: &T,
164 mut transaction: solana_sdk::transaction::Transaction,
165) -> Result<(solana_sdk::transaction::Transaction, Signature), SignerError> {
166 let signer_address = signer.pubkey().await?;
168 let signer_pubkey = Pubkey::from_str(&signer_address.to_string())
169 .map_err(|e| SignerError::KeyError(format!("Invalid signer address: {e}")))?;
170
171 let signer_index = transaction
173 .message
174 .account_keys
175 .iter()
176 .position(|key| *key == signer_pubkey)
177 .ok_or_else(|| {
178 SignerError::SigningError(
179 "Signer public key not found in transaction signers".to_string(),
180 )
181 })?;
182
183 if signer_index >= transaction.message.header.num_required_signatures as usize {
185 return Err(SignerError::SigningError(format!(
186 "Signer is not marked as a required signer in the transaction (position {} >= {})",
187 signer_index, transaction.message.header.num_required_signatures
188 )));
189 }
190
191 let signature = signer.sign(&transaction.message_data()).await?;
193
194 let num_required = transaction.message.header.num_required_signatures as usize;
197 transaction
198 .signatures
199 .resize(num_required, Signature::default());
200
201 transaction.signatures[signer_index] = signature;
203
204 Ok((transaction, signature))
205}
206
207#[async_trait]
208impl SolanaSignTrait for SolanaSigner {
209 async fn pubkey(&self) -> Result<Address, SignerError> {
210 match self {
211 Self::Local(signer) => signer.pubkey().await,
212 Self::Vault(signer) => signer.pubkey().await,
213 Self::VaultTransit(signer) => signer.pubkey().await,
214 Self::Turnkey(signer) => signer.pubkey().await,
215 Self::Cdp(signer) => signer.pubkey().await,
216 Self::GoogleCloudKms(signer) => signer.pubkey().await,
217 }
218 }
219
220 async fn sign(&self, message: &[u8]) -> Result<Signature, SignerError> {
221 match self {
222 Self::Local(signer) => Ok(signer.sign(message).await?),
223 Self::Vault(signer) => Ok(signer.sign(message).await?),
224 Self::VaultTransit(signer) => Ok(signer.sign(message).await?),
225 Self::Turnkey(signer) => Ok(signer.sign(message).await?),
226 Self::Cdp(signer) => Ok(signer.sign(message).await?),
227 Self::GoogleCloudKms(signer) => Ok(signer.sign(message).await?),
228 }
229 }
230}
231
232pub struct SolanaSignerFactory;
233
234impl SolanaSignerFactory {
235 pub fn create_solana_signer(
236 signer_model: &SignerDomainModel,
237 ) -> Result<SolanaSigner, SignerFactoryError> {
238 let signer = match &signer_model.config {
239 SignerConfig::Local(_) => SolanaSigner::Local(LocalSigner::new(signer_model)?),
240 SignerConfig::Vault(config) => {
241 let vault_config = VaultConfig::new(
242 config.address.clone(),
243 config.role_id.clone(),
244 config.secret_id.clone(),
245 config.namespace.clone(),
246 config
247 .mount_point
248 .clone()
249 .unwrap_or_else(|| "secret".to_string()),
250 None,
251 );
252 let vault_service = VaultService::new(vault_config);
253
254 return Ok(SolanaSigner::Vault(VaultSigner::new(
255 signer_model.id.clone(),
256 config.clone(),
257 vault_service,
258 )));
259 }
260 SignerConfig::VaultTransit(vault_transit_signer_config) => {
261 let vault_service = VaultService::new(VaultConfig {
262 address: vault_transit_signer_config.address.clone(),
263 namespace: vault_transit_signer_config.namespace.clone(),
264 role_id: vault_transit_signer_config.role_id.clone(),
265 secret_id: vault_transit_signer_config.secret_id.clone(),
266 mount_path: "transit".to_string(),
267 token_ttl: None,
268 });
269
270 return Ok(SolanaSigner::VaultTransit(VaultTransitSigner::new(
271 signer_model,
272 vault_service,
273 )));
274 }
275 SignerConfig::AwsKms(_) => {
276 return Err(SignerFactoryError::UnsupportedType("AWS KMS".into()));
277 }
278 SignerConfig::Cdp(config) => {
279 let cdp_signer = CdpSigner::new(config.clone()).map_err(|e| {
280 SignerFactoryError::CreationFailed(format!("CDP service error: {e}"))
281 })?;
282 return Ok(SolanaSigner::Cdp(cdp_signer));
283 }
284 SignerConfig::Turnkey(turnkey_signer_config) => {
285 let turnkey_service =
286 TurnkeyService::new(turnkey_signer_config.clone()).map_err(|e| {
287 SignerFactoryError::InvalidConfig(format!(
288 "Failed to create Turnkey service: {e}"
289 ))
290 })?;
291
292 return Ok(SolanaSigner::Turnkey(TurnkeySigner::new(turnkey_service)));
293 }
294 SignerConfig::GoogleCloudKms(google_cloud_kms_signer_config) => {
295 let google_cloud_kms_service =
296 GoogleCloudKmsService::new(google_cloud_kms_signer_config).map_err(|e| {
297 SignerFactoryError::InvalidConfig(format!(
298 "Failed to create Google Cloud KMS service: {e}"
299 ))
300 })?;
301 return Ok(SolanaSigner::GoogleCloudKms(GoogleCloudKmsSigner::new(
302 google_cloud_kms_service,
303 )));
304 }
305 };
306
307 Ok(signer)
308 }
309}
310
311#[cfg(test)]
312mod solana_signer_factory_tests {
313 use super::*;
314 use crate::models::{
315 AwsKmsSignerConfig, CdpSignerConfig, GoogleCloudKmsSignerConfig,
316 GoogleCloudKmsSignerKeyConfig, GoogleCloudKmsSignerServiceAccountConfig, LocalSignerConfig,
317 SecretString, SignerConfig, SignerRepoModel, SolanaTransactionData, TurnkeySignerConfig,
318 VaultSignerConfig, VaultTransitSignerConfig,
319 };
320 use mockall::predicate::*;
321 use secrets::SecretVec;
322 use std::str::FromStr;
323 use std::sync::Arc;
324
325 fn test_key_bytes() -> SecretVec<u8> {
326 let key_bytes = vec![
327 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
328 25, 26, 27, 28, 29, 30, 31, 32,
329 ];
330 SecretVec::new(key_bytes.len(), |v| v.copy_from_slice(&key_bytes))
331 }
332
333 fn test_key_bytes_pubkey() -> Address {
334 Address::Solana("9C6hybhQ6Aycep9jaUnP6uL9ZYvDjUp1aSkFWPUFJtpj".to_string())
335 }
336
337 #[test]
338 fn test_create_solana_signer_local() {
339 let signer_model = SignerDomainModel {
340 id: "test".to_string(),
341 config: SignerConfig::Local(LocalSignerConfig {
342 raw_key: test_key_bytes(),
343 }),
344 };
345
346 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
347
348 match signer {
349 SolanaSigner::Local(_) => {}
350 _ => panic!("Expected Local signer"),
351 }
352 }
353
354 #[test]
355 fn test_create_solana_signer_vault() {
356 let signer_model = SignerDomainModel {
357 id: "test".to_string(),
358 config: SignerConfig::Vault(VaultSignerConfig {
359 address: "https://vault.test.com".to_string(),
360 namespace: Some("test-namespace".to_string()),
361 role_id: crate::models::SecretString::new("test-role-id"),
362 secret_id: crate::models::SecretString::new("test-secret-id"),
363 key_name: "test-key".to_string(),
364 mount_point: Some("secret".to_string()),
365 }),
366 };
367
368 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
369
370 match signer {
371 SolanaSigner::Vault(_) => {}
372 _ => panic!("Expected Vault signer"),
373 }
374 }
375
376 #[test]
377 fn test_create_solana_signer_vault_transit() {
378 let signer_model = SignerDomainModel {
379 id: "test".to_string(),
380 config: SignerConfig::VaultTransit(VaultTransitSignerConfig {
381 key_name: "test".to_string(),
382 address: "address".to_string(),
383 namespace: None,
384 role_id: SecretString::new("role_id"),
385 secret_id: SecretString::new("secret_id"),
386 pubkey: "pubkey".to_string(),
387 mount_point: None,
388 }),
389 };
390
391 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
392
393 match signer {
394 SolanaSigner::VaultTransit(_) => {}
395 _ => panic!("Expected Transit signer"),
396 }
397 }
398
399 #[test]
400 fn test_create_solana_signer_turnkey() {
401 let signer_model = SignerDomainModel {
402 id: "test".to_string(),
403 config: SignerConfig::Turnkey(TurnkeySignerConfig {
404 api_private_key: SecretString::new("api_private_key"),
405 api_public_key: "api_public_key".to_string(),
406 organization_id: "organization_id".to_string(),
407 private_key_id: "private_key_id".to_string(),
408 public_key: "public_key".to_string(),
409 }),
410 };
411
412 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
413
414 match signer {
415 SolanaSigner::Turnkey(_) => {}
416 _ => panic!("Expected Turnkey signer"),
417 }
418 }
419
420 #[test]
421 fn test_create_solana_signer_cdp() {
422 let signer_model = SignerDomainModel {
423 id: "test".to_string(),
424 config: SignerConfig::Cdp(CdpSignerConfig {
425 api_key_id: "test-api-key-id".to_string(),
426 api_key_secret: SecretString::new("test-api-key-secret"),
427 wallet_secret: SecretString::new("test-wallet-secret"),
428 account_address: "6s7RsvzcdXFJi1tXeDoGfSKZFzN3juVt9fTar6WEhEm2".to_string(),
429 }),
430 };
431
432 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
433
434 match signer {
435 SolanaSigner::Cdp(_) => {}
436 _ => panic!("Expected CDP signer"),
437 }
438 }
439
440 #[tokio::test]
441 async fn test_create_solana_signer_google_cloud_kms() {
442 let signer_model = SignerDomainModel {
443 id: "test".to_string(),
444 config: SignerConfig::GoogleCloudKms(GoogleCloudKmsSignerConfig {
445 service_account: GoogleCloudKmsSignerServiceAccountConfig {
446 project_id: "project_id".to_string(),
447 private_key_id: SecretString::new("private_key_id"),
448 private_key: SecretString::new("private_key"),
449 client_email: SecretString::new("client_email"),
450 client_id: "client_id".to_string(),
451 auth_uri: "auth_uri".to_string(),
452 token_uri: "token_uri".to_string(),
453 auth_provider_x509_cert_url: "auth_provider_x509_cert_url".to_string(),
454 client_x509_cert_url: "client_x509_cert_url".to_string(),
455 universe_domain: "universe_domain".to_string(),
456 },
457 key: GoogleCloudKmsSignerKeyConfig {
458 location: "global".to_string(),
459 key_id: "id".to_string(),
460 key_ring_id: "key_ring".to_string(),
461 key_version: 1,
462 },
463 }),
464 };
465
466 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
467
468 match signer {
469 SolanaSigner::GoogleCloudKms(_) => {}
470 _ => panic!("Expected Google Cloud KMS signer"),
471 }
472 }
473
474 #[tokio::test]
475 async fn test_address_solana_signer_local() {
476 let signer_model = SignerDomainModel {
477 id: "test".to_string(),
478 config: SignerConfig::Local(LocalSignerConfig {
479 raw_key: test_key_bytes(),
480 }),
481 };
482
483 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
484 let signer_address = signer.address().await.unwrap();
485 let signer_pubkey = signer.pubkey().await.unwrap();
486
487 assert_eq!(test_key_bytes_pubkey(), signer_address);
488 assert_eq!(test_key_bytes_pubkey(), signer_pubkey);
489 }
490
491 #[tokio::test]
492 async fn test_address_solana_signer_vault_transit() {
493 let signer_model = SignerDomainModel {
494 id: "test".to_string(),
495 config: SignerConfig::VaultTransit(VaultTransitSignerConfig {
496 key_name: "test".to_string(),
497 address: "address".to_string(),
498 namespace: None,
499 role_id: SecretString::new("role_id"),
500 secret_id: SecretString::new("secret_id"),
501 pubkey: "fV060x5X3Eo4uK/kTqQbSVL/qmMNaYKF2oaTa15hNfU=".to_string(),
502 mount_point: None,
503 }),
504 };
505 let expected_pubkey =
506 Address::Solana("9SNR5Sf993aphA7hzWSQsGv63x93trfuN8WjaToXcqKA".to_string());
507
508 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
509 let signer_address = signer.address().await.unwrap();
510 let signer_pubkey = signer.pubkey().await.unwrap();
511
512 assert_eq!(expected_pubkey, signer_address);
513 assert_eq!(expected_pubkey, signer_pubkey);
514 }
515
516 #[tokio::test]
517 async fn test_address_solana_signer_turnkey() {
518 let signer_model = SignerDomainModel {
519 id: "test".to_string(),
520 config: SignerConfig::Turnkey(TurnkeySignerConfig {
521 api_private_key: SecretString::new("api_private_key"),
522 api_public_key: "api_public_key".to_string(),
523 organization_id: "organization_id".to_string(),
524 private_key_id: "private_key_id".to_string(),
525 public_key: "5720be8aa9d2bb4be8e91f31d2c44c8629e42da16981c2cebabd55cafa0b76bd"
526 .to_string(),
527 }),
528 };
529 let expected_pubkey =
530 Address::Solana("6s7RsvzcdXFJi1tXeDoGfSKZFzN3juVt9fTar6WEhEm2".to_string());
531
532 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
533 let signer_address = signer.address().await.unwrap();
534 let signer_pubkey = signer.pubkey().await.unwrap();
535
536 assert_eq!(expected_pubkey, signer_address);
537 assert_eq!(expected_pubkey, signer_pubkey);
538 }
539
540 #[tokio::test]
541 async fn test_address_solana_signer_cdp() {
542 let signer_model = SignerDomainModel {
543 id: "test".to_string(),
544 config: SignerConfig::Cdp(CdpSignerConfig {
545 api_key_id: "test-api-key-id".to_string(),
546 api_key_secret: SecretString::new("test-api-key-secret"),
547 wallet_secret: SecretString::new("test-wallet-secret"),
548 account_address: "6s7RsvzcdXFJi1tXeDoGfSKZFzN3juVt9fTar6WEhEm2".to_string(),
549 }),
550 };
551 let expected_pubkey =
552 Address::Solana("6s7RsvzcdXFJi1tXeDoGfSKZFzN3juVt9fTar6WEhEm2".to_string());
553
554 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
555 let signer_address = signer.address().await.unwrap();
556 let signer_pubkey = signer.pubkey().await.unwrap();
557
558 assert_eq!(expected_pubkey, signer_address);
559 assert_eq!(expected_pubkey, signer_pubkey);
560 }
561
562 #[tokio::test]
563 async fn test_address_solana_signer_google_cloud_kms() {
564 let signer_model = SignerDomainModel {
565 id: "test".to_string(),
566 config: SignerConfig::GoogleCloudKms(GoogleCloudKmsSignerConfig {
567 service_account: GoogleCloudKmsSignerServiceAccountConfig {
568 project_id: "project_id".to_string(),
569 private_key_id: SecretString::new("private_key_id"),
570 private_key: SecretString::new("private_key"),
571 client_email: SecretString::new("client_email"),
572 client_id: "client_id".to_string(),
573 auth_uri: "auth_uri".to_string(),
574 token_uri: "token_uri".to_string(),
575 auth_provider_x509_cert_url: "auth_provider_x509_cert_url".to_string(),
576 client_x509_cert_url: "client_x509_cert_url".to_string(),
577 universe_domain: "universe_domain".to_string(),
578 },
579 key: GoogleCloudKmsSignerKeyConfig {
580 location: "global".to_string(),
581 key_id: "id".to_string(),
582 key_ring_id: "key_ring".to_string(),
583 key_version: 1,
584 },
585 }),
586 };
587
588 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
589 let signer_address = signer.address().await;
590 let signer_pubkey = signer.pubkey().await;
591
592 assert!(signer_address.is_err());
594 assert!(signer_pubkey.is_err());
595 }
596
597 #[tokio::test]
598 async fn test_sign_solana_signer_local() {
599 let signer_model = SignerDomainModel {
600 id: "test".to_string(),
601 config: SignerConfig::Local(LocalSignerConfig {
602 raw_key: test_key_bytes(),
603 }),
604 };
605
606 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
607 let message = b"test message";
608 let signature = signer.sign(message).await;
609
610 assert!(signature.is_ok());
611 }
612
613 #[tokio::test]
614 async fn test_sign_solana_signer_test() {
615 let signer_model = SignerDomainModel {
616 id: "test".to_string(),
617 config: SignerConfig::Local(LocalSignerConfig {
618 raw_key: test_key_bytes(),
619 }),
620 };
621
622 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
623 let message = b"test message";
624 let signature = signer.sign(message).await;
625
626 assert!(signature.is_ok());
627 }
628
629 #[tokio::test]
630 async fn test_sign_sdk_transaction_success() {
631 use solana_sdk::message::Message;
632 use solana_sdk::pubkey::Pubkey;
633 use solana_sdk::signature::Signature;
634 use solana_sdk::transaction::Transaction;
635
636 let signer_model = SignerDomainModel {
638 id: "test".to_string(),
639 config: SignerConfig::Local(LocalSignerConfig {
640 raw_key: test_key_bytes(),
641 }),
642 };
643 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
644
645 let signer_pubkey = Pubkey::from_str(&test_key_bytes_pubkey().to_string()).unwrap();
647 let recipient = Pubkey::new_unique();
648
649 let message = Message::new(
650 &[solana_system_interface::instruction::transfer(
651 &signer_pubkey,
652 &recipient,
653 1000,
654 )],
655 Some(&signer_pubkey),
656 );
657 let transaction = Transaction::new_unsigned(message);
658
659 let result = sign_sdk_transaction(&signer, transaction).await;
661 assert!(result.is_ok());
662
663 let (signed_tx, signature) = result.unwrap();
664 assert!(!signature.to_string().is_empty());
665 assert_eq!(signed_tx.signatures.len(), 1);
666 assert_eq!(signed_tx.signatures[0], signature);
667 }
668
669 #[tokio::test]
670 async fn test_sign_sdk_transaction_signer_not_in_accounts() {
671 use solana_sdk::message::Message;
672 use solana_sdk::pubkey::Pubkey;
673 use solana_sdk::transaction::Transaction;
674
675 let signer_model = SignerDomainModel {
677 id: "test".to_string(),
678 config: SignerConfig::Local(LocalSignerConfig {
679 raw_key: test_key_bytes(),
680 }),
681 };
682 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
683
684 let other_pubkey = Pubkey::new_unique();
686 let recipient = Pubkey::new_unique();
687
688 let message = Message::new(
689 &[solana_system_interface::instruction::transfer(
690 &other_pubkey,
691 &recipient,
692 1000,
693 )],
694 Some(&other_pubkey),
695 );
696 let transaction = Transaction::new_unsigned(message);
697
698 let result = sign_sdk_transaction(&signer, transaction).await;
700 assert!(result.is_err());
701 let error = result.unwrap_err();
702 match error {
703 SignerError::SigningError(msg) => {
704 assert!(msg.contains("Signer public key not found in transaction signers"));
705 }
706 _ => panic!("Expected SigningError, got {:?}", error),
707 }
708 }
709
710 #[tokio::test]
711 async fn test_sign_sdk_transaction_signer_not_required() {
712 use solana_sdk::message::Message;
713 use solana_sdk::pubkey::Pubkey;
714 use solana_sdk::transaction::Transaction;
715
716 let signer_model = SignerDomainModel {
718 id: "test".to_string(),
719 config: SignerConfig::Local(LocalSignerConfig {
720 raw_key: test_key_bytes(),
721 }),
722 };
723 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
724
725 let signer_pubkey = Pubkey::from_str(&test_key_bytes_pubkey().to_string()).unwrap();
727 let fee_payer = Pubkey::new_unique();
728 let recipient = Pubkey::new_unique();
729
730 let message = Message::new(
733 &[solana_system_interface::instruction::transfer(
734 &fee_payer, &recipient, 1000,
735 )],
736 Some(&fee_payer),
737 );
738 let transaction = Transaction::new_unsigned(message);
739
740 let mut modified_message = transaction.message.clone();
743 modified_message.account_keys.push(signer_pubkey); modified_message.header.num_readonly_unsigned_accounts += 1; let modified_transaction = Transaction::new_unsigned(modified_message);
747
748 let result = sign_sdk_transaction(&signer, modified_transaction).await;
750 assert!(result.is_err());
751 let error = result.unwrap_err();
752 match error {
753 SignerError::SigningError(msg) => {
754 assert!(msg.contains("Signer is not marked as a required signer"));
755 }
756 _ => panic!("Expected SigningError, got {:?}", error),
757 }
758 }
759
760 #[tokio::test]
761 async fn test_sign_transaction_with_domain_model() {
762 use crate::models::{NetworkTransactionData, SolanaTransactionData};
763 use solana_sdk::message::Message;
764 use solana_sdk::pubkey::Pubkey;
765
766 let signer_model = SignerDomainModel {
768 id: "test".to_string(),
769 config: SignerConfig::Local(LocalSignerConfig {
770 raw_key: test_key_bytes(),
771 }),
772 };
773 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
774
775 let signer_pubkey = Pubkey::from_str(&test_key_bytes_pubkey().to_string()).unwrap();
777 let recipient = Pubkey::new_unique();
778
779 let message = Message::new(
780 &[solana_system_interface::instruction::transfer(
781 &signer_pubkey,
782 &recipient,
783 1000,
784 )],
785 Some(&signer_pubkey),
786 );
787 let transaction = solana_sdk::transaction::Transaction::new_unsigned(message);
788 let encoded_tx =
789 crate::models::EncodedSerializedTransaction::try_from(&transaction).unwrap();
790
791 let solana_data = SolanaTransactionData {
792 transaction: Some(encoded_tx.into_inner()),
793 ..Default::default()
794 };
795
796 let network_data = NetworkTransactionData::Solana(solana_data);
797
798 let result = signer.sign_transaction(network_data).await;
800 assert!(result.is_ok());
801
802 let response = result.unwrap();
803 match response {
804 crate::domain::SignTransactionResponse::Solana(solana_response) => {
805 assert!(!solana_response.transaction.into_inner().is_empty());
806 assert!(!solana_response.signature.is_empty());
807 }
808 _ => panic!("Expected Solana response"),
809 }
810 }
811
812 #[test]
813 fn test_create_solana_signer_aws_kms_unsupported() {
814 let signer_model = SignerDomainModel {
815 id: "test".to_string(),
816 config: SignerConfig::AwsKms(AwsKmsSignerConfig {
817 region: Some("us-east-1".to_string()),
818 key_id: "test-key-id".to_string(),
819 }),
820 };
821
822 let result = SolanaSignerFactory::create_solana_signer(&signer_model);
823 assert!(result.is_err());
824 let error = result.unwrap_err();
825 match error {
826 SignerFactoryError::UnsupportedType(msg) => {
827 assert_eq!(msg, "AWS KMS");
828 }
829 _ => panic!("Expected UnsupportedType error, got {:?}", error),
830 }
831 }
832
833 #[cfg(test)]
834 #[async_trait]
835 impl Signer for MockSolanaSignTrait {
836 async fn address(&self) -> Result<Address, SignerError> {
837 self.pubkey().await
838 }
839
840 async fn sign_transaction(
841 &self,
842 _transaction: NetworkTransactionData,
843 ) -> Result<SignTransactionResponse, SignerError> {
844 Ok(SignTransactionResponse::Solana(
846 crate::domain::SignTransactionResponseSolana {
847 transaction: crate::models::EncodedSerializedTransaction::new(
848 "signed_transaction_data".to_string(),
849 ),
850 signature: "signature_data".to_string(),
851 },
852 ))
853 }
854 }
855}