kora_lib/rpc_server/method/
sign_transaction.rs1use crate::{
2 rpc_server::middleware_utils::default_sig_verify,
3 state::{get_config, get_request_signer_with_signer_key},
4 transaction::{TransactionUtil, VersionedTransactionOps, VersionedTransactionResolved},
5 usage_limit::UsageTracker,
6 KoraError,
7};
8use serde::{Deserialize, Serialize};
9use solana_client::nonblocking::rpc_client::RpcClient;
10use solana_keychain::SolanaSigner;
11use std::sync::Arc;
12use utoipa::ToSchema;
13
14#[derive(Debug, Deserialize, ToSchema)]
15pub struct SignTransactionRequest {
16 pub transaction: String,
17 #[serde(default, skip_serializing_if = "Option::is_none")]
19 pub signer_key: Option<String>,
20 #[serde(default = "default_sig_verify")]
22 pub sig_verify: bool,
23}
24
25#[derive(Debug, Serialize, ToSchema)]
26pub struct SignTransactionResponse {
27 pub signed_transaction: String,
28 pub signer_pubkey: String,
30}
31
32pub async fn sign_transaction(
33 rpc_client: &Arc<RpcClient>,
34 request: SignTransactionRequest,
35) -> Result<SignTransactionResponse, KoraError> {
36 let transaction = TransactionUtil::decode_b64_transaction(&request.transaction)?;
37
38 let config = get_config()?;
39
40 UsageTracker::check_transaction_usage_limit(config, &transaction).await?;
42
43 let signer = get_request_signer_with_signer_key(request.signer_key.as_deref())?;
44
45 let mut resolved_transaction = VersionedTransactionResolved::from_transaction(
46 &transaction,
47 config,
48 rpc_client,
49 request.sig_verify,
50 )
51 .await?;
52
53 let (signed_transaction, _) =
54 resolved_transaction.sign_transaction(config, &signer, rpc_client).await?;
55
56 let encoded = TransactionUtil::encode_versioned_transaction(&signed_transaction)?;
57
58 Ok(SignTransactionResponse {
59 signed_transaction: encoded,
60 signer_pubkey: signer.pubkey().to_string(),
61 })
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use crate::tests::{
68 common::{setup_or_get_test_signer, setup_or_get_test_usage_limiter, RpcMockBuilder},
69 config_mock::ConfigMockBuilder,
70 transaction_mock::create_mock_encoded_transaction,
71 };
72
73 #[tokio::test]
74 async fn test_sign_transaction_decode_error() {
75 let _m = ConfigMockBuilder::new().build_and_setup();
76 let _ = setup_or_get_test_signer();
77
78 let _ = setup_or_get_test_usage_limiter().await;
79
80 let rpc_client = Arc::new(RpcMockBuilder::new().build());
81
82 let request = SignTransactionRequest {
83 transaction: "invalid_base64!@#$".to_string(),
84 signer_key: None,
85 sig_verify: true,
86 };
87
88 let result = sign_transaction(&rpc_client, request).await;
89
90 assert!(result.is_err(), "Should fail with decode error");
91 }
92
93 #[tokio::test]
94 async fn test_sign_transaction_invalid_signer_key() {
95 let _m = ConfigMockBuilder::new().build_and_setup();
96 let _ = setup_or_get_test_signer();
97
98 let _ = setup_or_get_test_usage_limiter().await;
99
100 let rpc_client = Arc::new(RpcMockBuilder::new().build());
101
102 let request = SignTransactionRequest {
103 transaction: create_mock_encoded_transaction(),
104 signer_key: Some("invalid_pubkey".to_string()),
105 sig_verify: true,
106 };
107
108 let result = sign_transaction(&rpc_client, request).await;
109
110 assert!(result.is_err(), "Should fail with invalid signer key");
111 let error = result.unwrap_err();
112 assert!(matches!(error, KoraError::ValidationError(_)), "Should return ValidationError");
113 }
114}