solana_trader_client_rust/common/
signing.rs

1use anyhow::Result;
2use base64::{engine::general_purpose::STANDARD, Engine};
3use serde::Serialize;
4use anyhow::{anyhow};
5use solana_hash::Hash;
6use solana_sdk::{
7    instruction::Instruction,
8    pubkey::Pubkey,
9    signature::{Keypair, Signature},
10    signer::Signer,
11    transaction::{Transaction, VersionedTransaction},
12};
13use solana_trader_proto::api;
14
15use crate::provider::utils::IntoTransactionMessage;
16
17#[derive(Debug, Clone, Serialize)]
18pub struct SubmitParams {
19    pub skip_pre_flight: bool,
20    pub front_running_protection: bool,
21    pub use_staked_rpcs: bool,
22    pub fast_best_effort: bool,
23    pub submit_strategy: api::SubmitStrategy,
24    pub allow_back_run: Option<bool>,
25    pub revenue_address: Option<String>,
26    pub allow_revert: Option<bool>,
27}
28
29impl Default for SubmitParams {
30    fn default() -> Self {
31        Self {
32            skip_pre_flight: true,
33            front_running_protection: false,
34            use_staked_rpcs: false,
35            fast_best_effort: false,
36            submit_strategy: api::SubmitStrategy::PSubmitAll,
37            allow_back_run: None,
38            revenue_address: None,
39            allow_revert: None,
40        }
41    }
42}
43
44#[derive(Debug, Serialize)]
45pub struct SignedTransaction {
46    pub content: String,
47    pub is_cleanup: bool,
48}
49
50pub async fn sign_transaction<T>(
51    tx: &T,
52    keypair: &Keypair,
53) -> Result<SignedTransaction>
54where
55    T: IntoTransactionMessage + Clone,
56{
57    let tx_message = tx.clone().into_transaction_message();
58
59    let signed_b64 = sign_existing_transaction(&tx_message.content, keypair)?;
60    Ok(SignedTransaction {
61        content: signed_b64,
62        is_cleanup: tx_message.is_cleanup,
63    })
64}
65
66pub fn create_signed_transaction(
67    instruction: Vec<Instruction>,
68    payer: &Pubkey,
69    keypair: &Keypair,
70    block_hash: Hash,
71) -> anyhow::Result<Transaction> {
72    let mut transaction =
73        Transaction::new_signed_with_payer(&instruction, Some(payer), &[keypair], block_hash);
74
75    let message_data = transaction.message.serialize();
76    transaction.signatures = vec![Signature::default()];
77    transaction.signatures[0] = keypair.sign_message(&message_data);
78
79    Ok(transaction)
80}
81
82fn sign_existing_transaction(base64_tx: &str, keypair: &Keypair) -> Result<String> {
83    let tx_bytes = STANDARD.decode(base64_tx)?;
84
85    // Versioned transactions should be a super set of versioned and legacy transactions
86    if let Ok(mut tx) = bincode::deserialize::<VersionedTransaction>(&tx_bytes) {
87        let sig_index = tx.signatures.iter().position(|sig| *sig == Signature::default())
88            .ok_or_else(|| anyhow!("No empty signature slot found"))?;
89
90        let msg_bytes = tx.message.serialize();
91        tx.signatures[sig_index] = keypair.sign_message(&msg_bytes);
92
93        let signed_bytes = bincode::serialize(&tx)?;
94        return Ok(STANDARD.encode(signed_bytes));
95    }
96
97    // Fallback to legacy
98    let mut legacy_tx: Transaction = bincode::deserialize(&tx_bytes)?;
99    legacy_tx.try_partial_sign(&[keypair], legacy_tx.message.recent_blockhash)?;
100    let signed_bytes = bincode::serialize(&legacy_tx)?;
101    Ok(STANDARD.encode(signed_bytes))
102}
103
104