2026-05-21 17:50:06 +08:00
use anyhow ::anyhow ;
2025-06-17 23:32:20 +08:00
use solana_hash ::Hash ;
2026-04-11 18:43:18 +08:00
use solana_message ::AddressLookupTableAccount ;
2026-05-15 01:08:33 +08:00
use solana_sdk ::{
instruction ::Instruction , pubkey ::Pubkey , signature ::Keypair , signer ::Signer ,
transaction ::VersionedTransaction ,
};
2026-04-11 18:43:18 +08:00
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 ::{
2026-05-16 06:42:50 +08:00
common ::nonce_cache ::DurableNonceInfo ,
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-06-07 22:22:26 +08:00
const PACKET_DATA_SIZE : usize = 1232 ;
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-05-16 06:42:50 +08:00
/// Build signed transaction (worker hot path, no RPC).
/// Takes Arc/refs only; one Vec allocation (with_capacity), extend_from_slice for business_instructions, no extra clone of payer/middleware.
pub fn build_transaction (
2026-03-08 01:04:11 +08:00
payer : & Arc < Keypair > ,
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 > ,
2026-06-07 22:22:26 +08:00
) -> Result < VersionedTransaction , anyhow ::Error > {
2026-06-08 03:13:04 +08:00
let transaction = build_transaction_inner (
2026-06-07 22:22:26 +08:00
payer ,
unit_limit ,
unit_price ,
business_instructions ,
address_lookup_table_account ,
recent_blockhash ,
middleware_manager ,
protocol_name ,
is_buy ,
with_tip ,
tip_account ,
tip_amount ,
durable_nonce ,
) ? ;
let serialized_len = bincode ::serialized_size ( & transaction ) ? as usize ;
2026-06-08 03:13:04 +08:00
if crate ::common ::sdk_log ::sdk_log_enabled () {
println! (
" [SDK][tx-size ] {} {} serialized= {} bytes, business_ix= {} , nonce= {} , tip= {} , cu_limit= {} , cu_price= {} , alt= {} " ,
protocol_name ,
if is_buy { "buy" } else { "sell" },
serialized_len ,
business_instructions . len (),
durable_nonce . is_some (),
with_tip && tip_amount > 0.0 ,
unit_limit ,
unit_price ,
address_lookup_table_account . is_some ()
);
}
if serialized_len <= PACKET_DATA_SIZE {
2026-06-07 22:22:26 +08:00
return Ok ( transaction );
}
Err ( anyhow! (
2026-06-08 03:13:04 +08:00
"transaction too large: {} > {}; SDK did not remove compute budget or relay tip because that changes transaction priority semantics. Use an address lookup table or pre-create token ATAs before submitting" ,
2026-06-07 22:22:26 +08:00
serialized_len ,
2026-06-08 03:13:04 +08:00
PACKET_DATA_SIZE
2026-06-07 22:22:26 +08:00
))
}
2026-06-08 03:13:04 +08:00
fn build_transaction_inner (
2026-06-07 22:22:26 +08:00
payer : & Arc < Keypair > ,
unit_limit : u32 ,
unit_price : u64 ,
business_instructions : & [ Instruction ],
address_lookup_table_account : Option <& AddressLookupTableAccount > ,
recent_blockhash : Option < Hash > ,
middleware_manager : Option <& Arc < MiddlewareManager >> ,
protocol_name : & str ,
is_buy : bool ,
with_tip : bool ,
tip_account : & Pubkey ,
tip_amount : f64 ,
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 ,
)
2025-06-17 23:32:20 +08:00
}
2026-05-16 06:42:50 +08:00
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 ();
2026-05-21 17:50:06 +08:00
let build_result = 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
);
2026-05-21 17:50:06 +08:00
release_builder ( builder );
let versioned_msg = build_result ? ;
2025-10-06 23:40:27 +08:00
2025-09-08 17:12:29 +08:00
let msg_bytes = versioned_msg . serialize ();
2026-05-21 17:50:06 +08:00
let signature =
payer . as_ref (). try_sign_message ( & msg_bytes ). map_err ( | e | anyhow! ( "sign failed: {e}" )) ? ;
2025-10-06 23:40:27 +08:00
let tx = VersionedTransaction { signatures : vec ! [ signature ], message : versioned_msg };
Ok ( tx )
2025-06-17 23:32:20 +08:00
}
2026-06-08 03:13:04 +08:00
#[cfg(test)]
mod tests {
use super ::* ;
use solana_sdk ::instruction ::AccountMeta ;
fn oversized_instruction ( account_count : usize , data_len : usize ) -> Instruction {
let accounts =
( 0 .. account_count ). map ( | _ | AccountMeta ::new ( Pubkey ::new_unique (), false )). collect ();
Instruction { program_id : Pubkey ::new_unique (), accounts , data : vec ! [ 7 ; data_len ] }
}
#[test]
fn oversized_transaction_returns_error_without_dropping_priority_semantics () {
let payer = Arc ::new ( Keypair ::new ());
let business_instructions = vec! [ oversized_instruction ( 36 , 700 )];
let err = build_transaction (
& payer ,
80_000 ,
100_000 ,
& business_instructions ,
None ,
Some ( Hash ::new_unique ()),
None ,
"test" ,
true ,
true ,
& Pubkey ::new_unique (),
0.001 ,
None ,
)
. unwrap_err ()
. to_string ();
assert! ( err . contains ( "transaction too large" ), "{err}" );
assert! ( err . contains ( "did not remove compute budget or relay tip" ), "{err}" );
}
}