Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
shuimuliang committed Jan 8, 2025
1 parent 08ab913 commit c88bb0c
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 36 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,18 +132,21 @@ Our SDK is designed to provide a seamless developer experience when building on
- [`remove_addresses_from_webhook`](https://github.com/helius-labs/helius-rust-sdk/blob/bf24259e3333ae93126bb65b342c2c63e80e07a6/src/webhook.rs#L75-L105) - Removes a list of addresses from an existing webhook by its ID

### Smart Transactions
- [`create_smart_transaction`](https://github.com/helius-labs/helius-rust-sdk/blob/bd9e0b10c81ab9ea56dfcd286336b086f6737b64/src/optimized_transaction.rs#L131-L331) - Creates an optimized transaction based on the provided configuration
- [`create_smart_transaction`](https://github.com/helius-labs/helius-rust-sdk/blob/bd9e0b10c81ab9ea56dfcd286336b086f6737b64/src/optimized_transaction.rs#L131-L331) - Creates an optimized transaction based on the provided configuration
- [`create_smart_transaction_with_seeds`](https://github.com/helius-labs/helius-rust-sdk/blob/8102d87c6551c7645389a813e60a832a2eaf98c7/src/optimized_transaction.rs#L478-L633) - Creates a thread-safe, optimized transaction using seed bytes
- [`get_compute_units`](https://github.com/helius-labs/helius-rust-sdk/blob/bd9e0b10c81ab9ea56dfcd286336b086f6737b64/src/optimized_transaction.rs#L34-L87) - Simulates a transaction to get the total compute units consumed
- [`get_compute_units_thread_safe`](https://github.com/helius-labs/helius-rust-sdk/blob/8102d87c6551c7645389a813e60a832a2eaf98c7/src/optimized_transaction.rs#L421-L476) - A thread-safe version of `get_compute_units`
- [`poll_transaction_confirmation`](https://github.com/helius-labs/helius-rust-sdk/blob/bd9e0b10c81ab9ea56dfcd286336b086f6737b64/src/optimized_transaction.rs#L89-L129) - Polls a transaction to check whether it has been confirmed in 5 second intervals with a 15 second timeout
- [`send_smart_transaction`](https://github.com/helius-labs/helius-rust-sdk/blob/bd9e0b10c81ab9ea56dfcd286336b086f6737b64/src/optimized_transaction.rs#L333-L364) - Builds and sends an optimized transaction, and handles its confirmation status
- [`send_and_confirm_transaction`](https://github.com/helius-labs/helius-rust-sdk/blob/bd9e0b10c81ab9ea56dfcd286336b086f6737b64/src/optimized_transaction.rs#L366-L412) - Sends a transaction and handles its confirmation status with retry logic
- [`send_smart_transaction_with_seeds`](https://github.com/helius-labs/helius-rust-sdk/blob/bd9e0b10c81ab9ea56dfcd286336b086f6737b64/src/optimized_transaction.rs#L414-L487) - Sends a smart transaction using seed bytes
- [`send_smart_transaction_with_seeds`](https://github.com/helius-labs/helius-rust-sdk/blob/bd9e0b10c81ab9ea56dfcd286336b086f6737b64/src/optimized_transaction.rs#L414-L487) - Sends a smart transaction using seed bytes. This function is thread-safe

### Jito Smart Transactions and Helper Methods
- [`add_tip_instruction`](https://github.com/helius-labs/helius-rust-sdk/blob/02b351a5ee3fe16a36078b40f92dc72d0ad077ed/src/jito.rs#L66-L83) - Adds a tip instruction to the instructions provided
- [`create_smart_transaction_with_tip`](https://github.com/helius-labs/helius-rust-sdk/blob/bd9e0b10c81ab9ea56dfcd286336b086f6737b64/src/jito.rs#L85-L125) - Creates a smart transaction with a Jito tip
- [`get_bundle_statuses`](https://github.com/helius-labs/helius-rust-sdk/blob/bd9e0b10c81ab9ea56dfcd286336b086f6737b64/src/jito.rs#L170-L203) - Get the status of Jito bundles
- [`send_jito_bundle`](https://github.com/helius-labs/helius-rust-sdk/blob/bd9e0b10c81ab9ea56dfcd286336b086f6737b64/src/jito.rs#L127-L168) - Sends a bundle of transactions to the Jito Block Engine
- [`send_smart_transaction_with_seeds_and_tip`](https://github.com/helius-labs/helius-rust-sdk/blob/8102d87c6551c7645389a813e60a832a2eaf98c7/src/jito.rs#L272-L353) - Sends a smart transaction as a Jito bundle with a tip using seed bytes to create the transaction
- [`send_smart_transaction_with_tip`](https://github.com/helius-labs/helius-rust-sdk/blob/bd9e0b10c81ab9ea56dfcd286336b086f6737b64/src/jito.rs#L205-L270) - Sends a smart transaction as a Jito bundle with a tip

### Helper Methods
Expand Down
89 changes: 86 additions & 3 deletions src/jito.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
/// the status of sent bundles
use crate::error::{HeliusError, Result};
use crate::types::{
BasicRequest, CreateSmartTransactionConfig, GetPriorityFeeEstimateOptions, GetPriorityFeeEstimateRequest,
GetPriorityFeeEstimateResponse, SmartTransaction, SmartTransactionConfig,
BasicRequest, CreateSmartTransactionConfig, CreateSmartTransactionSeedConfig, GetPriorityFeeEstimateOptions,
GetPriorityFeeEstimateRequest, GetPriorityFeeEstimateResponse, SmartTransaction, SmartTransactionConfig, Timeout,
};
use crate::Helius;

Expand All @@ -21,6 +21,7 @@ use serde::Serialize;
use serde_json::Value;
use solana_client::rpc_config::{RpcSendTransactionConfig, RpcSimulateTransactionConfig};
use solana_client::rpc_response::{Response, RpcSimulateTransactionResult};
use solana_sdk::signature::keypair_from_seed;
use solana_sdk::system_instruction;
use solana_sdk::{
address_lookup_table::AddressLookupTableAccount,
Expand Down Expand Up @@ -210,7 +211,7 @@ impl Helius {
/// * `region` - The Jito Block Engine region. Defaults to `"Default"`
///
/// # Returns
/// A `Result` containing the bundle IDc
/// A `Result` containing the bundle ID
pub async fn send_smart_transaction_with_tip(
&self,
config: SmartTransactionConfig,
Expand Down Expand Up @@ -267,4 +268,86 @@ impl Helius {
text: "Bundle failed to confirm within the timeout period".to_string(),
})
}

/// Sends a smart transaction as a Jito bundle with a tip using seed bytes to create the transaction
///
/// This method provides a thread-safe way to send transactions with Jito tips by using seed bytes instead of `Signers`. It
/// combines the functionality of `send_smart_transaction_with_seeds` with Jito's bundles, therefore, allowing for them
/// in async contexts
///
/// # Arguments
/// * `create_config`: The configuration for creating the transaction
/// * `tip_amount`: Optional amount of lamports to tip Jito validators. Defaults to 1000
/// * `region`: Optional Jito region for the block engine. Defaults to "Default"
/// * `timeout`: Optional duration for polling the transaction confirmation in seconds. Defaults to 60s
///
/// # Returns a `Result<String>` containing the bundle ID if successful
pub async fn send_smart_transaction_with_seeds_and_tip(
&self,
mut create_config: CreateSmartTransactionSeedConfig,
tip_amount: Option<u64>,
region: Option<JitoRegion>,
timeout: Option<Duration>,
) -> Result<String> {
// Add Jito tip instruction
let fee_payer_pubkey = if let Some(fee_payer_seed) = &create_config.fee_payer_seed {
keypair_from_seed(fee_payer_seed)
.expect("Failed to create fee payer keypair from seed")
.pubkey()
} else {
keypair_from_seed(&create_config.signer_seeds[0])
.expect("Failed to create keypair from first seed")
.pubkey()
};

// Add Jito tip
let tip: u64 = tip_amount.unwrap_or(1000);
let random_tip_account = *JITO_TIP_ACCOUNTS.choose(&mut rand::thread_rng()).unwrap();
create_config.instructions.push(system_instruction::transfer(
&fee_payer_pubkey,
&Pubkey::from_str(random_tip_account).unwrap(),
tip,
));

// Create transaction and convert to base58
let (transaction, _) = self.create_smart_transaction_with_seeds(&create_config).await?;
let serialized_tx = match &transaction {
SmartTransaction::Legacy(tx) => serialize(tx).map_err(|e| HeliusError::InvalidInput(e.to_string()))?,
SmartTransaction::Versioned(tx) => serialize(tx).map_err(|e| HeliusError::InvalidInput(e.to_string()))?,
};
let tx_base58: String = encode(&serialized_tx).into_string();

// Send via Jito
let jito_region: &str = *JITO_API_URLS
.get(region.unwrap_or("Default"))
.ok_or_else(|| HeliusError::InvalidInput("Invalid Jito region".to_string()))?;
let jito_api_url = format!("{}/api/v1/bundles", jito_region);

let bundle_id: String = self.send_jito_bundle(vec![tx_base58], &jito_api_url).await?;

// Poll for the bundle confirmation
let timeout: Duration = timeout.unwrap_or(Duration::from_secs(60));
let start: tokio::time::Instant = tokio::time::Instant::now();

while start.elapsed() < timeout {
let bundle_statuses = self.get_bundle_statuses(vec![bundle_id.clone()], &jito_api_url).await?;

if let Some(values) = bundle_statuses["result"]["value"].as_array() {
if !values.is_empty() {
if let Some(status) = values[0]["confirmation_status"].as_str() {
if status == "confirmed" {
return Ok(values[0]["transactions"][0].as_str().unwrap().to_string());
}
}
}
}

sleep(Duration::from_secs(5)).await;
}

Err(HeliusError::Timeout {
code: StatusCode::REQUEST_TIMEOUT,
text: "Bundle failed to confirm within the timeout period".to_string(),
})
}
}
100 changes: 69 additions & 31 deletions src/optimized_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,40 +475,34 @@ impl Helius {
Ok(result.value.units_consumed)
}

/// Sends a smart transaction using seed bytes
/// Creates a smart transaction using seed bytes for thread-safe transaction creation
///
/// This method allows for sending smart transactions in asynchronous contexts
/// where the Signer trait's lack of Send + Sync would otherwise cause issues.
/// It creates Keypairs from the provided seed bytes and uses them to sign the transaction.
/// Creates an optimized transaction using seed bytes instead of `Signers`` for thread-safe operations.
///
/// # Arguments
///
/// * `create_config` - A `CreateSmartTransactionSeedConfig` containing:
/// - `instructions`: The instructions to be executed in the transaction.
/// - `signer_seeds`: Seed bytes for generating signer keypairs.
/// - `fee_payer_seed`: Optional seed bytes for generating the fee payer keypair.
/// - `lookup_tables`: Optional address lookup tables for the transaction.
/// * `send_options` - Optional `RpcSendTransactionConfig` for sending the transaction.
/// * `timeout` - Optional `Timeout` wait time for polling transaction confirmation.
/// * `create_config` - Transaction configuration containing:
/// - `instructions`: Instructions to execute
/// - `signer_seeds`: Seed bytes for generating keypairs
/// - `fee_payer_seed`: Optional fee payer seed (defaults to first signer)
/// - `lookup_tables`: Optional address lookup tables for versioned transactions
/// - `priority_fee_cap`: Optional maximum priority fee
///
/// # Returns
///
/// A `Result<Signature>` containing the transaction signature if successful, or an error if not.
/// A tuple containing:
/// - `SmartTransaction`: The created transaction (legacy or versioned)
/// - `u64`: Last valid block height for the transaction
///
/// # Errors
///
/// This function will return an error if keypair creation from seeds fails, the transaction sending fails,
/// or no signer seeds are provided
///
/// # Notes
///
/// If no `fee_payer_seed` is provided, the first signer (i.e., derived from the first seed in `signer_seeds`) will be used as the fee payer
pub async fn send_smart_transaction_with_seeds(
/// Returns `HeliusError` if:
/// - No signer seeds provided
/// - Failed to create keypairs from seeds
/// - Failed to get compute units
/// - Failed to estimate priority fees
/// - Transaction creation fails
pub async fn create_smart_transaction_with_seeds(
&self,
create_config: CreateSmartTransactionSeedConfig,
send_options: Option<RpcSendTransactionConfig>,
timeout: Option<Timeout>,
) -> Result<Signature> {
create_config: &CreateSmartTransactionSeedConfig,
) -> Result<(SmartTransaction, u64)> {
if create_config.signer_seeds.is_empty() {
return Err(HeliusError::InvalidInput(
"At least one signer seed must be provided".to_string(),
Expand All @@ -517,8 +511,8 @@ impl Helius {

let keypairs: Vec<Keypair> = create_config
.signer_seeds
.into_iter()
.map(|seed| keypair_from_seed(&seed).expect("Failed to create keypair from seed"))
.iter()
.map(|seed| keypair_from_seed(seed).expect("Failed to create keypair from seed"))
.collect();

// Create the fee payer keypair if provided. Otherwise, we default to the first signer
Expand Down Expand Up @@ -596,10 +590,10 @@ impl Helius {
};

final_instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(customers_cu));
final_instructions.extend(create_config.instructions);
final_instructions.extend(create_config.instructions.clone());

// Create the final transaction
let transaction: SmartTransaction = if let Some(lookup_tables) = create_config.lookup_tables {
let transaction: SmartTransaction = if let Some(lookup_tables) = &create_config.lookup_tables {
let message: v0::Message = v0::Message::try_compile(
&fee_payer.pubkey(),
&final_instructions,
Expand Down Expand Up @@ -635,7 +629,51 @@ impl Helius {
SmartTransaction::Legacy(tx)
};

// Send and confirm the transaction
Ok((transaction, last_valid_block_hash))
}

/// Sends a smart transaction using seed bytes
///
/// This method allows for sending smart transactions in asynchronous contexts
/// where the Signer trait's lack of Send + Sync would otherwise cause issues.
/// It creates Keypairs from the provided seed bytes and uses them to sign the transaction.
///
/// # Arguments
///
/// * `create_config` - A `CreateSmartTransactionSeedConfig` containing:
/// - `instructions`: The instructions to be executed in the transaction.
/// - `signer_seeds`: Seed bytes for generating signer keypairs.
/// - `fee_payer_seed`: Optional seed bytes for generating the fee payer keypair.
/// - `lookup_tables`: Optional address lookup tables for the transaction.
/// * `send_options` - Optional `RpcSendTransactionConfig` for sending the transaction.
/// * `timeout` - Optional `Timeout` wait time for polling transaction confirmation.
///
/// # Returns
///
/// A `Result<Signature>` containing the transaction signature if successful, or an error if not.
///
/// # Errors
///
/// This function will return an error if keypair creation from seeds fails, the transaction sending fails,
/// or no signer seeds are provided
///
/// # Notes
///
/// If no `fee_payer_seed` is provided, the first signer (i.e., derived from the first seed in `signer_seeds`) will be used as the fee payer
pub async fn send_smart_transaction_with_seeds(
&self,
create_config: CreateSmartTransactionSeedConfig,
send_options: Option<RpcSendTransactionConfig>,
timeout: Option<Timeout>,
) -> Result<Signature> {
if create_config.signer_seeds.is_empty() {
return Err(HeliusError::InvalidInput(
"At least one signer seed required".to_string(),
));
}

let (transaction, last_valid_block_hash) = self.create_smart_transaction_with_seeds(&create_config).await?;

match transaction {
SmartTransaction::Legacy(tx) => {
self.send_and_confirm_transaction(
Expand Down

0 comments on commit c88bb0c

Please sign in to comment.