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