use rialo_s_sdk::{
signature::Signature,
transaction::{Transaction, VersionedTransaction},
};
use crate::{
rpc::{traits::RpcClient, types::SendTransactionOptions},
RialoError,
};
pub struct TransactionSender<'a> {
rpc_client: &'a dyn RpcClient,
options: Option<SendTransactionOptions>,
transaction: &'a Transaction,
}
impl<'a> TransactionSender<'a> {
pub fn new(transaction: &'a Transaction, rpc_client: &'a dyn RpcClient) -> Self {
Self {
rpc_client,
options: None,
transaction,
}
}
pub fn with_options(mut self, options: SendTransactionOptions) -> Self {
self.options = Some(options);
self
}
pub async fn send(self) -> Result<Signature, RialoError> {
let serialized_tx = bincode::serialize(&self.transaction).map_err(|err| {
RialoError::SerializationError(format!("Failed to serialize transaction: {err}"))
})?;
self.rpc_client
.send_transaction(&serialized_tx, self.options)
.await
}
}
pub async fn send_versioned_transaction(
rpc_client: &impl RpcClient,
transaction: &VersionedTransaction,
) -> Result<Signature, RialoError> {
let serialized_tx = bincode::serialize(transaction).map_err(|err| {
RialoError::SerializationError(format!("Failed to serialize transaction: {err}"))
})?;
rpc_client.send_transaction(&serialized_tx, None).await
}
pub async fn send_versioned_transaction_with_options(
rpc_client: &impl RpcClient,
transaction: &VersionedTransaction,
options: SendTransactionOptions,
) -> Result<Signature, RialoError> {
let serialized_tx = bincode::serialize(transaction).map_err(|err| {
RialoError::SerializationError(format!("Failed to serialize transaction: {err}"))
})?;
rpc_client
.send_transaction(&serialized_tx, Some(options))
.await
}
pub async fn get_transaction_details(
rpc_client: &impl RpcClient,
signature: &str,
) -> Result<crate::rpc::types::TransactionResponse, RialoError> {
use std::str::FromStr;
let sig = Signature::from_str(signature)
.map_err(|e| RialoError::SerializationError(format!("Invalid signature: {e}")))?;
rpc_client.get_transaction(&sig).await
}
pub async fn get_transaction_status(
rpc_client: &impl RpcClient,
signature: &str,
) -> Result<Vec<crate::rpc::types::SignatureStatus>, RialoError> {
use std::str::FromStr;
let sig = Signature::from_str(signature)
.map_err(|e| RialoError::SerializationError(format!("Invalid signature: {e}")))?;
rpc_client.get_signature_statuses(&[sig]).await
}
#[cfg(test)]
mod tests {
use rialo_s_sdk::{
pubkey::Pubkey,
signature::{Keypair, Signer},
system_instruction,
};
use super::*;
use crate::rpc::mock_client::{MockRpcClient, ERROR_MESSAGE, MOCK_SIGNATURE};
#[tokio::test]
async fn test_send_versioned_transaction_success() {
use std::str::FromStr;
let mock_rpc =
MockRpcClient::new().with_signature(Signature::from_str(MOCK_SIGNATURE).unwrap());
let keypair = Keypair::new();
let recipient = Pubkey::new_unique();
let instruction = system_instruction::transfer(&keypair.pubkey(), &recipient, 1000);
let transaction = rialo_s_sdk::v0_message_utils::create_v0_transaction(
&[&keypair],
&[instruction],
&keypair.pubkey(),
0,
)
.expect("Failed to create transaction");
let result = send_versioned_transaction(&mock_rpc, &transaction).await;
assert!(result.is_ok());
let signature = result.unwrap();
assert_eq!(signature.to_string(), MOCK_SIGNATURE);
}
#[tokio::test]
async fn test_send_versioned_transaction_error() {
let mock_rpc = MockRpcClient::new()
.with_error("Transaction simulation failed: Error processing Instruction 0: custom program error: 0x1".to_string());
let keypair = Keypair::new();
let recipient = Pubkey::new_unique();
let instruction = system_instruction::transfer(&keypair.pubkey(), &recipient, 1000);
let transaction = rialo_s_sdk::v0_message_utils::create_v0_transaction(
&[&keypair],
&[instruction],
&keypair.pubkey(),
0,
)
.expect("Failed to create transaction");
let result = send_versioned_transaction(&mock_rpc, &transaction).await;
assert!(result.is_err());
match result {
Err(RialoError::Rpc(msg)) => {
assert!(msg.message.contains("Transaction simulation failed"));
}
_ => panic!("Expected RpcError but got different error type"),
}
}
#[tokio::test]
async fn test_get_transaction_details_success() {
use crate::rpc::types::*;
let test_transaction = crate::rpc::types::Transaction {
signatures: vec![MOCK_SIGNATURE.to_string()],
message: crate::rpc::types::TransactionMessage {
header: crate::rpc::types::MessageHeader {
num_required_signatures: 1,
num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: 1,
},
account_keys: vec![
Pubkey::new_unique().to_string(),
rialo_s_sdk::system_program::id().to_string(),
],
instructions: vec![],
},
valid_from: 0,
};
let mock_rpc = MockRpcClient::new().with_transaction(test_transaction);
let result = get_transaction_details(&mock_rpc, MOCK_SIGNATURE).await;
assert!(result.is_ok());
let transaction = result.unwrap();
assert_eq!(transaction.transaction.signatures.len(), 1);
assert_eq!(
transaction
.transaction
.message
.header
.num_required_signatures,
1
);
}
#[tokio::test]
async fn test_get_transaction_details_error() {
let mock_rpc = MockRpcClient::new().with_error(ERROR_MESSAGE.to_string());
let result = get_transaction_details(&mock_rpc, MOCK_SIGNATURE).await;
assert!(result.is_err());
match result {
Err(RialoError::Rpc(msg)) => {
assert!(msg.message.contains(ERROR_MESSAGE));
}
_ => panic!("Expected RpcError but got different error type"),
}
}
#[tokio::test]
async fn test_get_transaction_status_success() {
let mock_rpc = MockRpcClient::new();
let result = get_transaction_status(&mock_rpc, MOCK_SIGNATURE).await;
assert!(result.is_ok());
let statuses = result.unwrap();
assert_eq!(statuses.len(), 1);
let status = &statuses[0];
assert!(status.executed); }
#[tokio::test]
async fn test_get_transaction_status_error() {
let mock_rpc = MockRpcClient::new().with_error(ERROR_MESSAGE.to_string());
let result = get_transaction_status(&mock_rpc, MOCK_SIGNATURE).await;
assert!(result.is_err());
match result {
Err(RialoError::Rpc(msg)) => {
assert!(msg.message.contains(ERROR_MESSAGE));
}
_ => panic!("Expected RpcError but got different error type"),
}
}
}