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(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 fn create_solana_signer(
244 signer_model: &SignerDomainModel,
245 ) -> Result<SolanaSigner, SignerFactoryError> {
246 let signer = match &signer_model.config {
247 SignerConfig::Local(_) => SolanaSigner::Local(LocalSigner::new(signer_model)?),
248 SignerConfig::Vault(config) => {
249 let vault_config = VaultConfig::new(
250 config.address.clone(),
251 config.role_id.clone(),
252 config.secret_id.clone(),
253 config.namespace.clone(),
254 config
255 .mount_point
256 .clone()
257 .unwrap_or_else(|| "secret".to_string()),
258 None,
259 );
260 let vault_service = VaultService::new(vault_config);
261
262 return Ok(SolanaSigner::Vault(VaultSigner::new(
263 signer_model.id.clone(),
264 config.clone(),
265 vault_service,
266 )));
267 }
268 SignerConfig::VaultTransit(vault_transit_signer_config) => {
269 let vault_service = VaultService::new(VaultConfig {
270 address: vault_transit_signer_config.address.clone(),
271 namespace: vault_transit_signer_config.namespace.clone(),
272 role_id: vault_transit_signer_config.role_id.clone(),
273 secret_id: vault_transit_signer_config.secret_id.clone(),
274 mount_path: "transit".to_string(),
275 token_ttl: None,
276 });
277
278 return Ok(SolanaSigner::VaultTransit(VaultTransitSigner::new(
279 signer_model,
280 vault_service,
281 )));
282 }
283 SignerConfig::AwsKms(config) => {
284 let aws_kms_service = futures::executor::block_on(AwsKmsService::new(
285 config.clone(),
286 ))
287 .map_err(|e| {
288 SignerFactoryError::InvalidConfig(format!(
289 "Failed to create AWS KMS service: {e}"
290 ))
291 })?;
292 return Ok(SolanaSigner::AwsKms(AwsKmsSigner::new(aws_kms_service)));
293 }
294 SignerConfig::Cdp(config) => {
295 let cdp_signer = CdpSigner::new(config.clone()).map_err(|e| {
296 SignerFactoryError::CreationFailed(format!("CDP service error: {e}"))
297 })?;
298 return Ok(SolanaSigner::Cdp(cdp_signer));
299 }
300 SignerConfig::Turnkey(turnkey_signer_config) => {
301 let turnkey_service =
302 TurnkeyService::new(turnkey_signer_config.clone()).map_err(|e| {
303 SignerFactoryError::InvalidConfig(format!(
304 "Failed to create Turnkey service: {e}"
305 ))
306 })?;
307
308 return Ok(SolanaSigner::Turnkey(TurnkeySigner::new(turnkey_service)));
309 }
310 SignerConfig::GoogleCloudKms(google_cloud_kms_signer_config) => {
311 let google_cloud_kms_service =
312 GoogleCloudKmsService::new(google_cloud_kms_signer_config).map_err(|e| {
313 SignerFactoryError::InvalidConfig(format!(
314 "Failed to create Google Cloud KMS service: {e}"
315 ))
316 })?;
317 return Ok(SolanaSigner::GoogleCloudKms(GoogleCloudKmsSigner::new(
318 google_cloud_kms_service,
319 )));
320 }
321 };
322
323 Ok(signer)
324 }
325}
326
327#[cfg(test)]
328mod solana_signer_factory_tests {
329 use super::*;
330 use crate::models::{
331 AwsKmsSignerConfig, CdpSignerConfig, GoogleCloudKmsSignerConfig,
332 GoogleCloudKmsSignerKeyConfig, GoogleCloudKmsSignerServiceAccountConfig, LocalSignerConfig,
333 SecretString, SignerConfig, SignerRepoModel, SolanaTransactionData, TurnkeySignerConfig,
334 VaultSignerConfig, VaultTransitSignerConfig,
335 };
336 use mockall::predicate::*;
337 use secrets::SecretVec;
338 use std::str::FromStr;
339 use std::sync::Arc;
340
341 fn test_key_bytes() -> SecretVec<u8> {
342 let key_bytes = vec![
343 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
344 25, 26, 27, 28, 29, 30, 31, 32,
345 ];
346 SecretVec::new(key_bytes.len(), |v| v.copy_from_slice(&key_bytes))
347 }
348
349 fn test_key_bytes_pubkey() -> Address {
350 Address::Solana("9C6hybhQ6Aycep9jaUnP6uL9ZYvDjUp1aSkFWPUFJtpj".to_string())
351 }
352
353 #[test]
354 fn test_create_solana_signer_local() {
355 let signer_model = SignerDomainModel {
356 id: "test".to_string(),
357 config: SignerConfig::Local(LocalSignerConfig {
358 raw_key: test_key_bytes(),
359 }),
360 };
361
362 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
363
364 match signer {
365 SolanaSigner::Local(_) => {}
366 _ => panic!("Expected Local signer"),
367 }
368 }
369
370 #[test]
371 fn test_create_solana_signer_vault() {
372 let signer_model = SignerDomainModel {
373 id: "test".to_string(),
374 config: SignerConfig::Vault(VaultSignerConfig {
375 address: "https://vault.test.com".to_string(),
376 namespace: Some("test-namespace".to_string()),
377 role_id: crate::models::SecretString::new("test-role-id"),
378 secret_id: crate::models::SecretString::new("test-secret-id"),
379 key_name: "test-key".to_string(),
380 mount_point: Some("secret".to_string()),
381 }),
382 };
383
384 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
385
386 match signer {
387 SolanaSigner::Vault(_) => {}
388 _ => panic!("Expected Vault signer"),
389 }
390 }
391
392 #[test]
393 fn test_create_solana_signer_vault_transit() {
394 let signer_model = SignerDomainModel {
395 id: "test".to_string(),
396 config: SignerConfig::VaultTransit(VaultTransitSignerConfig {
397 key_name: "test".to_string(),
398 address: "address".to_string(),
399 namespace: None,
400 role_id: SecretString::new("role_id"),
401 secret_id: SecretString::new("secret_id"),
402 pubkey: "pubkey".to_string(),
403 mount_point: None,
404 }),
405 };
406
407 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
408
409 match signer {
410 SolanaSigner::VaultTransit(_) => {}
411 _ => panic!("Expected Transit signer"),
412 }
413 }
414
415 #[test]
416 fn test_create_solana_signer_turnkey() {
417 let signer_model = SignerDomainModel {
418 id: "test".to_string(),
419 config: SignerConfig::Turnkey(TurnkeySignerConfig {
420 api_private_key: SecretString::new("api_private_key"),
421 api_public_key: "api_public_key".to_string(),
422 organization_id: "organization_id".to_string(),
423 private_key_id: "private_key_id".to_string(),
424 public_key: "public_key".to_string(),
425 }),
426 };
427
428 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
429
430 match signer {
431 SolanaSigner::Turnkey(_) => {}
432 _ => panic!("Expected Turnkey signer"),
433 }
434 }
435
436 #[test]
437 fn test_create_solana_signer_cdp() {
438 let signer_model = SignerDomainModel {
439 id: "test".to_string(),
440 config: SignerConfig::Cdp(CdpSignerConfig {
441 api_key_id: "test-api-key-id".to_string(),
442 api_key_secret: SecretString::new("test-api-key-secret"),
443 wallet_secret: SecretString::new("test-wallet-secret"),
444 account_address: "6s7RsvzcdXFJi1tXeDoGfSKZFzN3juVt9fTar6WEhEm2".to_string(),
445 }),
446 };
447
448 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
449
450 match signer {
451 SolanaSigner::Cdp(_) => {}
452 _ => panic!("Expected CDP signer"),
453 }
454 }
455
456 #[tokio::test]
457 async fn test_create_solana_signer_google_cloud_kms() {
458 let signer_model = SignerDomainModel {
459 id: "test".to_string(),
460 config: SignerConfig::GoogleCloudKms(GoogleCloudKmsSignerConfig {
461 service_account: GoogleCloudKmsSignerServiceAccountConfig {
462 project_id: "project_id".to_string(),
463 private_key_id: SecretString::new("private_key_id"),
464 private_key: SecretString::new("private_key"),
465 client_email: SecretString::new("client_email"),
466 client_id: "client_id".to_string(),
467 auth_uri: "auth_uri".to_string(),
468 token_uri: "token_uri".to_string(),
469 auth_provider_x509_cert_url: "auth_provider_x509_cert_url".to_string(),
470 client_x509_cert_url: "client_x509_cert_url".to_string(),
471 universe_domain: "universe_domain".to_string(),
472 },
473 key: GoogleCloudKmsSignerKeyConfig {
474 location: "global".to_string(),
475 key_id: "id".to_string(),
476 key_ring_id: "key_ring".to_string(),
477 key_version: 1,
478 },
479 }),
480 };
481
482 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
483
484 match signer {
485 SolanaSigner::GoogleCloudKms(_) => {}
486 _ => panic!("Expected Google Cloud KMS signer"),
487 }
488 }
489
490 #[tokio::test]
491 async fn test_address_solana_signer_local() {
492 let signer_model = SignerDomainModel {
493 id: "test".to_string(),
494 config: SignerConfig::Local(LocalSignerConfig {
495 raw_key: test_key_bytes(),
496 }),
497 };
498
499 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
500 let signer_address = signer.address().await.unwrap();
501 let signer_pubkey = signer.pubkey().await.unwrap();
502
503 assert_eq!(test_key_bytes_pubkey(), signer_address);
504 assert_eq!(test_key_bytes_pubkey(), signer_pubkey);
505 }
506
507 #[tokio::test]
508 async fn test_address_solana_signer_vault_transit() {
509 let signer_model = SignerDomainModel {
510 id: "test".to_string(),
511 config: SignerConfig::VaultTransit(VaultTransitSignerConfig {
512 key_name: "test".to_string(),
513 address: "address".to_string(),
514 namespace: None,
515 role_id: SecretString::new("role_id"),
516 secret_id: SecretString::new("secret_id"),
517 pubkey: "fV060x5X3Eo4uK/kTqQbSVL/qmMNaYKF2oaTa15hNfU=".to_string(),
518 mount_point: None,
519 }),
520 };
521 let expected_pubkey =
522 Address::Solana("9SNR5Sf993aphA7hzWSQsGv63x93trfuN8WjaToXcqKA".to_string());
523
524 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
525 let signer_address = signer.address().await.unwrap();
526 let signer_pubkey = signer.pubkey().await.unwrap();
527
528 assert_eq!(expected_pubkey, signer_address);
529 assert_eq!(expected_pubkey, signer_pubkey);
530 }
531
532 #[tokio::test]
533 async fn test_address_solana_signer_turnkey() {
534 let signer_model = SignerDomainModel {
535 id: "test".to_string(),
536 config: SignerConfig::Turnkey(TurnkeySignerConfig {
537 api_private_key: SecretString::new("api_private_key"),
538 api_public_key: "api_public_key".to_string(),
539 organization_id: "organization_id".to_string(),
540 private_key_id: "private_key_id".to_string(),
541 public_key: "5720be8aa9d2bb4be8e91f31d2c44c8629e42da16981c2cebabd55cafa0b76bd"
542 .to_string(),
543 }),
544 };
545 let expected_pubkey =
546 Address::Solana("6s7RsvzcdXFJi1tXeDoGfSKZFzN3juVt9fTar6WEhEm2".to_string());
547
548 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
549 let signer_address = signer.address().await.unwrap();
550 let signer_pubkey = signer.pubkey().await.unwrap();
551
552 assert_eq!(expected_pubkey, signer_address);
553 assert_eq!(expected_pubkey, signer_pubkey);
554 }
555
556 #[tokio::test]
557 async fn test_address_solana_signer_cdp() {
558 let signer_model = SignerDomainModel {
559 id: "test".to_string(),
560 config: SignerConfig::Cdp(CdpSignerConfig {
561 api_key_id: "test-api-key-id".to_string(),
562 api_key_secret: SecretString::new("test-api-key-secret"),
563 wallet_secret: SecretString::new("test-wallet-secret"),
564 account_address: "6s7RsvzcdXFJi1tXeDoGfSKZFzN3juVt9fTar6WEhEm2".to_string(),
565 }),
566 };
567 let expected_pubkey =
568 Address::Solana("6s7RsvzcdXFJi1tXeDoGfSKZFzN3juVt9fTar6WEhEm2".to_string());
569
570 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
571 let signer_address = signer.address().await.unwrap();
572 let signer_pubkey = signer.pubkey().await.unwrap();
573
574 assert_eq!(expected_pubkey, signer_address);
575 assert_eq!(expected_pubkey, signer_pubkey);
576 }
577
578 #[tokio::test]
579 async fn test_address_solana_signer_google_cloud_kms() {
580 let signer_model = SignerDomainModel {
581 id: "test".to_string(),
582 config: SignerConfig::GoogleCloudKms(GoogleCloudKmsSignerConfig {
583 service_account: GoogleCloudKmsSignerServiceAccountConfig {
584 project_id: "project_id".to_string(),
585 private_key_id: SecretString::new("private_key_id"),
586 private_key: SecretString::new("private_key"),
587 client_email: SecretString::new("client_email"),
588 client_id: "client_id".to_string(),
589 auth_uri: "auth_uri".to_string(),
590 token_uri: "token_uri".to_string(),
591 auth_provider_x509_cert_url: "auth_provider_x509_cert_url".to_string(),
592 client_x509_cert_url: "client_x509_cert_url".to_string(),
593 universe_domain: "universe_domain".to_string(),
594 },
595 key: GoogleCloudKmsSignerKeyConfig {
596 location: "global".to_string(),
597 key_id: "id".to_string(),
598 key_ring_id: "key_ring".to_string(),
599 key_version: 1,
600 },
601 }),
602 };
603
604 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
605 let signer_address = signer.address().await;
606 let signer_pubkey = signer.pubkey().await;
607
608 assert!(signer_address.is_err());
610 assert!(signer_pubkey.is_err());
611 }
612
613 #[tokio::test]
614 async fn test_sign_solana_signer_local() {
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_solana_signer_test() {
631 let signer_model = SignerDomainModel {
632 id: "test".to_string(),
633 config: SignerConfig::Local(LocalSignerConfig {
634 raw_key: test_key_bytes(),
635 }),
636 };
637
638 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
639 let message = b"test message";
640 let signature = signer.sign(message).await;
641
642 assert!(signature.is_ok());
643 }
644
645 #[tokio::test]
646 async fn test_sign_sdk_transaction_success() {
647 use solana_sdk::message::Message;
648 use solana_sdk::pubkey::Pubkey;
649 use solana_sdk::signature::Signature;
650 use solana_sdk::transaction::Transaction;
651
652 let signer_model = SignerDomainModel {
654 id: "test".to_string(),
655 config: SignerConfig::Local(LocalSignerConfig {
656 raw_key: test_key_bytes(),
657 }),
658 };
659 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
660
661 let signer_pubkey = Pubkey::from_str(&test_key_bytes_pubkey().to_string()).unwrap();
663 let recipient = Pubkey::new_unique();
664
665 let message = Message::new(
666 &[solana_system_interface::instruction::transfer(
667 &signer_pubkey,
668 &recipient,
669 1000,
670 )],
671 Some(&signer_pubkey),
672 );
673 let transaction = Transaction::new_unsigned(message);
674
675 let result = sign_sdk_transaction(&signer, transaction).await;
677 assert!(result.is_ok());
678
679 let (signed_tx, signature) = result.unwrap();
680 assert!(!signature.to_string().is_empty());
681 assert_eq!(signed_tx.signatures.len(), 1);
682 assert_eq!(signed_tx.signatures[0], signature);
683 }
684
685 #[tokio::test]
686 async fn test_sign_sdk_transaction_signer_not_in_accounts() {
687 use solana_sdk::message::Message;
688 use solana_sdk::pubkey::Pubkey;
689 use solana_sdk::transaction::Transaction;
690
691 let signer_model = SignerDomainModel {
693 id: "test".to_string(),
694 config: SignerConfig::Local(LocalSignerConfig {
695 raw_key: test_key_bytes(),
696 }),
697 };
698 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
699
700 let other_pubkey = Pubkey::new_unique();
702 let recipient = Pubkey::new_unique();
703
704 let message = Message::new(
705 &[solana_system_interface::instruction::transfer(
706 &other_pubkey,
707 &recipient,
708 1000,
709 )],
710 Some(&other_pubkey),
711 );
712 let transaction = Transaction::new_unsigned(message);
713
714 let result = sign_sdk_transaction(&signer, transaction).await;
716 assert!(result.is_err());
717 let error = result.unwrap_err();
718 match error {
719 SignerError::SigningError(msg) => {
720 assert!(msg.contains("Signer public key not found in transaction signers"));
721 }
722 _ => panic!("Expected SigningError, got {:?}", error),
723 }
724 }
725
726 #[tokio::test]
727 async fn test_sign_sdk_transaction_signer_not_required() {
728 use solana_sdk::message::Message;
729 use solana_sdk::pubkey::Pubkey;
730 use solana_sdk::transaction::Transaction;
731
732 let signer_model = SignerDomainModel {
734 id: "test".to_string(),
735 config: SignerConfig::Local(LocalSignerConfig {
736 raw_key: test_key_bytes(),
737 }),
738 };
739 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
740
741 let signer_pubkey = Pubkey::from_str(&test_key_bytes_pubkey().to_string()).unwrap();
743 let fee_payer = Pubkey::new_unique();
744 let recipient = Pubkey::new_unique();
745
746 let message = Message::new(
749 &[solana_system_interface::instruction::transfer(
750 &fee_payer, &recipient, 1000,
751 )],
752 Some(&fee_payer),
753 );
754 let transaction = Transaction::new_unsigned(message);
755
756 let mut modified_message = transaction.message.clone();
759 modified_message.account_keys.push(signer_pubkey); modified_message.header.num_readonly_unsigned_accounts += 1; let modified_transaction = Transaction::new_unsigned(modified_message);
763
764 let result = sign_sdk_transaction(&signer, modified_transaction).await;
766 assert!(result.is_err());
767 let error = result.unwrap_err();
768 match error {
769 SignerError::SigningError(msg) => {
770 assert!(msg.contains("Signer is not marked as a required signer"));
771 }
772 _ => panic!("Expected SigningError, got {:?}", error),
773 }
774 }
775
776 #[tokio::test]
777 async fn test_sign_transaction_with_domain_model() {
778 use crate::models::{NetworkTransactionData, SolanaTransactionData};
779 use solana_sdk::message::Message;
780 use solana_sdk::pubkey::Pubkey;
781
782 let signer_model = SignerDomainModel {
784 id: "test".to_string(),
785 config: SignerConfig::Local(LocalSignerConfig {
786 raw_key: test_key_bytes(),
787 }),
788 };
789 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
790
791 let signer_pubkey = Pubkey::from_str(&test_key_bytes_pubkey().to_string()).unwrap();
793 let recipient = Pubkey::new_unique();
794
795 let message = Message::new(
796 &[solana_system_interface::instruction::transfer(
797 &signer_pubkey,
798 &recipient,
799 1000,
800 )],
801 Some(&signer_pubkey),
802 );
803 let transaction = solana_sdk::transaction::Transaction::new_unsigned(message);
804 let encoded_tx =
805 crate::models::EncodedSerializedTransaction::try_from(&transaction).unwrap();
806
807 let solana_data = SolanaTransactionData {
808 transaction: Some(encoded_tx.into_inner()),
809 ..Default::default()
810 };
811
812 let network_data = NetworkTransactionData::Solana(solana_data);
813
814 let result = signer.sign_transaction(network_data).await;
816 assert!(result.is_ok());
817
818 let response = result.unwrap();
819 match response {
820 crate::domain::SignTransactionResponse::Solana(solana_response) => {
821 assert!(!solana_response.transaction.into_inner().is_empty());
822 assert!(!solana_response.signature.is_empty());
823 }
824 _ => panic!("Expected Solana response"),
825 }
826 }
827
828 #[test]
829 fn test_create_solana_signer_aws_kms_supported() {
830 let signer_model = SignerDomainModel {
831 id: "test".to_string(),
832 config: SignerConfig::AwsKms(AwsKmsSignerConfig {
833 region: Some("us-east-1".to_string()),
834 key_id: "test-key-id".to_string(),
835 }),
836 };
837
838 let result = SolanaSignerFactory::create_solana_signer(&signer_model);
839 assert!(result.is_ok(), "AWS KMS should be supported for Solana");
840 }
841
842 #[cfg(test)]
843 #[async_trait]
844 impl Signer for MockSolanaSignTrait {
845 async fn address(&self) -> Result<Address, SignerError> {
846 self.pubkey().await
847 }
848
849 async fn sign_transaction(
850 &self,
851 _transaction: NetworkTransactionData,
852 ) -> Result<SignTransactionResponse, SignerError> {
853 Ok(SignTransactionResponse::Solana(
855 crate::domain::SignTransactionResponseSolana {
856 transaction: crate::models::EncodedSerializedTransaction::new(
857 "signed_transaction_data".to_string(),
858 ),
859 signature: "signature_data".to_string(),
860 },
861 ))
862 }
863 }
864}