2025-06-17 23:32:20 +08:00
|
|
|
use solana_hash::Hash;
|
|
|
|
|
use solana_sdk::{
|
2026-04-11 18:43:18 +08:00
|
|
|
instruction::Instruction, pubkey::Pubkey,
|
2026-03-08 01:04:11 +08:00
|
|
|
signature::Keypair, signer::Signer, transaction::VersionedTransaction,
|
2025-06-17 23:32:20 +08:00
|
|
|
};
|
2026-04-11 18:43:18 +08:00
|
|
|
use solana_message::AddressLookupTableAccount;
|
|
|
|
|
use solana_system_interface::instruction as system_instruction;
|
2025-06-17 23:32:20 +08:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
2026-03-08 01:04:11 +08:00
|
|
|
use super::nonce_manager::{add_nonce_instruction, get_transaction_blockhash};
|
2025-10-06 23:40:27 +08:00
|
|
|
use crate::{
|
|
|
|
|
common::{nonce_cache::DurableNonceInfo, SolanaRpcClient},
|
2026-03-14 18:16:55 +02:00
|
|
|
trading::{
|
|
|
|
|
core::transaction_pool::{acquire_builder, release_builder},
|
|
|
|
|
MiddlewareManager,
|
|
|
|
|
},
|
2025-10-06 23:40:27 +08:00
|
|
|
};
|
2025-06-17 23:32:20 +08:00
|
|
|
|
2026-03-08 01:04:11 +08:00
|
|
|
/// Convert SOL amount (f64) to lamports without string allocation (hot path).
|
|
|
|
|
#[inline(always)]
|
|
|
|
|
fn sol_f64_to_lamports(sol: f64) -> u64 {
|
|
|
|
|
if sol <= 0.0 {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
let lamports = sol * 1_000_000_000.0;
|
|
|
|
|
(lamports.min(u64::MAX as f64)).round() as u64
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-17 04:52:27 +08:00
|
|
|
/// Build standard RPC transaction (worker hot path).
|
|
|
|
|
/// Takes Arc/refs only; one Vec allocation (with_capacity), extend_from_slice for business_instructions, no extra clone of payer/rpc/middleware.
|
2025-09-07 17:22:58 +08:00
|
|
|
pub async fn build_transaction(
|
2026-03-08 01:04:11 +08:00
|
|
|
payer: &Arc<Keypair>,
|
|
|
|
|
_rpc: Option<&Arc<SolanaRpcClient>>,
|
2025-09-19 00:49:45 +08:00
|
|
|
unit_limit: u32,
|
|
|
|
|
unit_price: u64,
|
2026-02-25 01:25:42 +08:00
|
|
|
business_instructions: &[Instruction],
|
2026-03-08 01:04:11 +08:00
|
|
|
address_lookup_table_account: Option<&AddressLookupTableAccount>,
|
2025-09-20 14:00:45 +08:00
|
|
|
recent_blockhash: Option<Hash>,
|
2026-03-08 01:04:11 +08:00
|
|
|
middleware_manager: Option<&Arc<MiddlewareManager>>,
|
2025-09-07 18:07:45 +08:00
|
|
|
protocol_name: &str,
|
2025-08-19 18:07:21 +08:00
|
|
|
is_buy: bool,
|
2025-09-07 17:22:58 +08:00
|
|
|
with_tip: bool,
|
2025-06-17 23:32:20 +08:00
|
|
|
tip_account: &Pubkey,
|
|
|
|
|
tip_amount: f64,
|
2026-03-08 01:04:11 +08:00
|
|
|
durable_nonce: Option<&DurableNonceInfo>,
|
2025-06-17 23:32:20 +08:00
|
|
|
) -> Result<VersionedTransaction, anyhow::Error> {
|
2025-09-07 18:07:45 +08:00
|
|
|
let mut instructions = Vec::with_capacity(business_instructions.len() + 5);
|
2025-06-17 23:32:20 +08:00
|
|
|
|
2026-03-08 01:04:11 +08:00
|
|
|
if let Err(e) = add_nonce_instruction(&mut instructions, payer.as_ref(), durable_nonce) {
|
2025-09-19 00:49:45 +08:00
|
|
|
return Err(e);
|
2025-06-17 23:32:20 +08:00
|
|
|
}
|
|
|
|
|
|
2025-09-14 23:28:45 +08:00
|
|
|
if with_tip && tip_amount > 0.0 {
|
2026-03-08 01:04:11 +08:00
|
|
|
let tip_lamports = sol_f64_to_lamports(tip_amount);
|
2026-04-11 18:43:18 +08:00
|
|
|
instructions.push(system_instruction::transfer(&payer.pubkey(), tip_account, tip_lamports));
|
2025-09-14 23:28:45 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-08 01:04:11 +08:00
|
|
|
super::compute_budget_manager::extend_compute_budget_instructions(
|
|
|
|
|
&mut instructions,
|
2025-09-19 00:49:45 +08:00
|
|
|
unit_price,
|
|
|
|
|
unit_limit,
|
2026-03-08 01:04:11 +08:00
|
|
|
);
|
2025-06-17 23:32:20 +08:00
|
|
|
|
2026-02-25 01:25:42 +08:00
|
|
|
instructions.extend_from_slice(business_instructions);
|
2025-06-17 23:32:20 +08:00
|
|
|
|
2026-03-08 01:04:11 +08:00
|
|
|
let blockhash = get_transaction_blockhash(recent_blockhash, durable_nonce)?;
|
2025-06-17 23:32:20 +08:00
|
|
|
|
2025-08-19 18:07:21 +08:00
|
|
|
build_versioned_transaction(
|
|
|
|
|
payer,
|
|
|
|
|
instructions,
|
2025-10-07 20:56:02 +08:00
|
|
|
address_lookup_table_account,
|
2025-08-19 18:07:21 +08:00
|
|
|
blockhash,
|
|
|
|
|
middleware_manager,
|
|
|
|
|
protocol_name,
|
|
|
|
|
is_buy,
|
|
|
|
|
)
|
|
|
|
|
.await
|
2025-06-17 23:32:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn build_versioned_transaction(
|
2026-03-08 01:04:11 +08:00
|
|
|
payer: &Arc<Keypair>,
|
2025-06-17 23:32:20 +08:00
|
|
|
instructions: Vec<Instruction>,
|
2026-03-08 01:04:11 +08:00
|
|
|
address_lookup_table_account: Option<&AddressLookupTableAccount>,
|
2025-06-17 23:32:20 +08:00
|
|
|
blockhash: Hash,
|
2026-03-08 01:04:11 +08:00
|
|
|
middleware_manager: Option<&Arc<MiddlewareManager>>,
|
2025-09-07 18:07:45 +08:00
|
|
|
protocol_name: &str,
|
2025-08-19 18:07:21 +08:00
|
|
|
is_buy: bool,
|
2025-06-17 23:32:20 +08:00
|
|
|
) -> Result<VersionedTransaction, anyhow::Error> {
|
2025-08-19 18:07:21 +08:00
|
|
|
let full_instructions = match middleware_manager {
|
|
|
|
|
Some(middleware_manager) => middleware_manager
|
2026-03-08 01:04:11 +08:00
|
|
|
.apply_middlewares_process_full_instructions(instructions, protocol_name, is_buy)?,
|
2025-08-19 18:07:21 +08:00
|
|
|
None => instructions,
|
|
|
|
|
};
|
2025-10-06 23:40:27 +08:00
|
|
|
|
|
|
|
|
// 使用预分配的交易构建器以降低延迟
|
|
|
|
|
let mut builder = acquire_builder();
|
|
|
|
|
|
|
|
|
|
let versioned_msg = builder.build_zero_alloc(
|
2025-06-17 23:32:20 +08:00
|
|
|
&payer.pubkey(),
|
2025-08-19 18:07:21 +08:00
|
|
|
&full_instructions,
|
2025-10-07 20:56:02 +08:00
|
|
|
address_lookup_table_account,
|
2025-06-17 23:32:20 +08:00
|
|
|
blockhash,
|
2025-10-06 23:40:27 +08:00
|
|
|
);
|
|
|
|
|
|
2025-09-08 17:12:29 +08:00
|
|
|
let msg_bytes = versioned_msg.serialize();
|
2026-03-08 01:04:11 +08:00
|
|
|
let signature = payer.as_ref().try_sign_message(&msg_bytes).expect("sign failed");
|
2025-10-06 23:40:27 +08:00
|
|
|
let tx = VersionedTransaction { signatures: vec![signature], message: versioned_msg };
|
|
|
|
|
|
|
|
|
|
// 归还构建器到池
|
|
|
|
|
release_builder(builder);
|
|
|
|
|
|
|
|
|
|
Ok(tx)
|
2025-06-17 23:32:20 +08:00
|
|
|
}
|