release: update DEX protocol support for v4.0.9
Sync supported DEX IDLs from bitquery/solana-idl-lib, upgrade protocol builders, and keep trade submission hot paths free of RPC query calls. Include exact-output coverage, token-account setup fixes for WSOL and stable pairs, and low-latency transaction build improvements.
This commit is contained in:
@@ -9,7 +9,7 @@ use std::sync::Arc;
|
||||
|
||||
use super::nonce_manager::{add_nonce_instruction, get_transaction_blockhash};
|
||||
use crate::{
|
||||
common::{nonce_cache::DurableNonceInfo, SolanaRpcClient},
|
||||
common::nonce_cache::DurableNonceInfo,
|
||||
trading::{
|
||||
core::transaction_pool::{acquire_builder, release_builder},
|
||||
MiddlewareManager,
|
||||
@@ -26,11 +26,10 @@ fn sol_f64_to_lamports(sol: f64) -> u64 {
|
||||
(lamports.min(u64::MAX as f64)).round() as u64
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub async fn build_transaction(
|
||||
/// 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(
|
||||
payer: &Arc<Keypair>,
|
||||
_rpc: Option<&Arc<SolanaRpcClient>>,
|
||||
unit_limit: u32,
|
||||
unit_price: u64,
|
||||
business_instructions: &[Instruction],
|
||||
@@ -74,10 +73,9 @@ pub async fn build_transaction(
|
||||
protocol_name,
|
||||
is_buy,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn build_versioned_transaction(
|
||||
fn build_versioned_transaction(
|
||||
payer: &Arc<Keypair>,
|
||||
instructions: Vec<Instruction>,
|
||||
address_lookup_table_account: Option<&AddressLookupTableAccount>,
|
||||
|
||||
@@ -36,7 +36,7 @@ type FnvHashMap<K, V> = HashMap<K, V, BuildHasherDefault<FnvHasher>>;
|
||||
|
||||
use crate::{
|
||||
common::nonce_cache::DurableNonceInfo,
|
||||
common::{GasFeeStrategy, SolanaRpcClient},
|
||||
common::GasFeeStrategy,
|
||||
swqos::{SwqosClient, SwqosType, TradeType},
|
||||
trading::core::params::SenderConcurrencyConfig,
|
||||
trading::{common::build_transaction, MiddlewareManager},
|
||||
@@ -51,7 +51,6 @@ const SWQOS_DEDICATED_DEFAULT_THREADS: usize = 18;
|
||||
struct SwqosSharedContext {
|
||||
payer: Arc<Keypair>,
|
||||
instructions: Arc<Vec<Instruction>>,
|
||||
rpc: Option<Arc<SolanaRpcClient>>,
|
||||
address_lookup_table_account: Option<AddressLookupTableAccount>,
|
||||
recent_blockhash: Option<Hash>,
|
||||
durable_nonce: Option<DurableNonceInfo>,
|
||||
@@ -88,7 +87,6 @@ async fn run_one_swqos_job(job: SwqosJob) {
|
||||
|
||||
let transaction = match build_transaction(
|
||||
&s.payer,
|
||||
s.rpc.as_ref(),
|
||||
job.unit_limit,
|
||||
job.unit_price,
|
||||
s.instructions.as_ref(),
|
||||
@@ -101,9 +99,7 @@ async fn run_one_swqos_job(job: SwqosJob) {
|
||||
&job.tip_account,
|
||||
tip_amount,
|
||||
s.durable_nonce.as_ref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
) {
|
||||
Ok(tx) => tx,
|
||||
Err(e) => {
|
||||
s.collector.submit(TaskResult {
|
||||
@@ -436,7 +432,6 @@ impl ResultCollector {
|
||||
pub async fn execute_parallel(
|
||||
swqos_clients: &[Arc<SwqosClient>],
|
||||
payer: Arc<Keypair>,
|
||||
rpc: Option<&Arc<SolanaRpcClient>>,
|
||||
instructions: Vec<Instruction>,
|
||||
address_lookup_table_account: Option<AddressLookupTableAccount>,
|
||||
recent_blockhash: Option<Hash>,
|
||||
@@ -471,10 +466,10 @@ pub async fn execute_parallel(
|
||||
gas_fee_strategy.get_strategies(if is_buy { TradeType::Buy } else { TradeType::Sell });
|
||||
let mut task_configs = Vec::with_capacity(swqos_clients.len() * 3);
|
||||
for (i, swqos_client) in swqos_clients.iter().enumerate() {
|
||||
if !with_tip && !matches!(swqos_client.get_swqos_type(), SwqosType::Default) {
|
||||
let swqos_type = swqos_client.get_swqos_type();
|
||||
if !with_tip && !matches!(swqos_type, SwqosType::Default) {
|
||||
continue;
|
||||
}
|
||||
let swqos_type = swqos_client.get_swqos_type();
|
||||
let check_tip = with_tip && !matches!(swqos_type, SwqosType::Default) && check_min_tip;
|
||||
let min_tip = if check_tip { swqos_client.min_tip_sol() } else { 0.0 };
|
||||
for config in &gas_fee_configs {
|
||||
@@ -513,7 +508,6 @@ pub async fn execute_parallel(
|
||||
let shared = Arc::new(SwqosSharedContext {
|
||||
payer,
|
||||
instructions,
|
||||
rpc: rpc.cloned(),
|
||||
address_lookup_table_account,
|
||||
recent_blockhash,
|
||||
durable_nonce,
|
||||
|
||||
@@ -162,7 +162,6 @@ impl TradeExecutor for GenericTradeExecutor {
|
||||
let result = execute_parallel(
|
||||
params.swqos_clients.as_slice(),
|
||||
params.payer,
|
||||
params.rpc.as_ref(),
|
||||
final_instructions,
|
||||
params.address_lookup_table_account,
|
||||
params.recent_blockhash,
|
||||
@@ -282,7 +281,6 @@ async fn simulate_transaction(
|
||||
|
||||
let transaction = build_transaction(
|
||||
&payer,
|
||||
Some(&rpc),
|
||||
unit_limit,
|
||||
unit_price,
|
||||
&instructions,
|
||||
@@ -295,8 +293,7 @@ async fn simulate_transaction(
|
||||
&Pubkey::default(),
|
||||
tip,
|
||||
durable_nonce.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
)?;
|
||||
|
||||
// Simulate the transaction
|
||||
use solana_commitment_config::CommitmentConfig;
|
||||
|
||||
@@ -75,6 +75,8 @@ pub struct SwapParams {
|
||||
pub close_input_mint_ata: bool,
|
||||
pub create_output_mint_ata: bool,
|
||||
pub close_output_mint_ata: bool,
|
||||
/// Fixed output amount. For protocols with exact-out instructions this selects exact-out
|
||||
/// semantics and treats `input_amount` as the maximum input budget.
|
||||
pub fixed_output_amount: Option<u64>,
|
||||
pub gas_fee_strategy: GasFeeStrategy,
|
||||
pub simulate: bool,
|
||||
|
||||
@@ -12,6 +12,11 @@ pub struct MeteoraDammV2Params {
|
||||
pub token_b_mint: Pubkey,
|
||||
pub token_a_program: Pubkey,
|
||||
pub token_b_program: Pubkey,
|
||||
pub referral_token_account: Option<Pubkey>,
|
||||
/// `swap2` mode: 0 exact-in, 1 partial-fill (recommended default), 2 exact-out.
|
||||
pub swap_mode: u8,
|
||||
/// Include the instructions sysvar remaining account when the pool's rate limiter applies.
|
||||
pub include_rate_limiter_sysvar: bool,
|
||||
}
|
||||
|
||||
impl MeteoraDammV2Params {
|
||||
@@ -32,9 +37,27 @@ impl MeteoraDammV2Params {
|
||||
token_b_mint,
|
||||
token_a_program,
|
||||
token_b_program,
|
||||
referral_token_account: None,
|
||||
swap_mode: crate::instruction::utils::meteora_damm_v2::SWAP_MODE_PARTIAL_FILL,
|
||||
include_rate_limiter_sysvar: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_referral_token_account(mut self, referral_token_account: Pubkey) -> Self {
|
||||
self.referral_token_account = Some(referral_token_account);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_swap_mode(mut self, swap_mode: u8) -> Self {
|
||||
self.swap_mode = swap_mode;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_rate_limiter_sysvar(mut self, include: bool) -> Self {
|
||||
self.include_rate_limiter_sysvar = include;
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn from_pool_address_by_rpc(
|
||||
rpc: &SolanaRpcClient,
|
||||
pool_address: &Pubkey,
|
||||
@@ -61,6 +84,9 @@ impl MeteoraDammV2Params {
|
||||
token_b_mint: pool_data.token_b_mint,
|
||||
token_a_program,
|
||||
token_b_program,
|
||||
referral_token_account: None,
|
||||
swap_mode: crate::instruction::utils::meteora_damm_v2::SWAP_MODE_PARTIAL_FILL,
|
||||
include_rate_limiter_sysvar: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,26 @@ pub struct RaydiumAmmV4Params {
|
||||
pub token_coin: Pubkey,
|
||||
/// Pool's pc token account address
|
||||
pub token_pc: Pubkey,
|
||||
/// AMM open orders account
|
||||
pub amm_open_orders: Pubkey,
|
||||
/// AMM target orders account
|
||||
pub amm_target_orders: Pubkey,
|
||||
/// Serum/OpenBook program used by the AMM market
|
||||
pub serum_program: Pubkey,
|
||||
/// Serum/OpenBook market account
|
||||
pub serum_market: Pubkey,
|
||||
/// Serum/OpenBook bids account
|
||||
pub serum_bids: Pubkey,
|
||||
/// Serum/OpenBook asks account
|
||||
pub serum_asks: Pubkey,
|
||||
/// Serum/OpenBook event queue account
|
||||
pub serum_event_queue: Pubkey,
|
||||
/// Serum/OpenBook coin vault account
|
||||
pub serum_coin_vault_account: Pubkey,
|
||||
/// Serum/OpenBook pc vault account
|
||||
pub serum_pc_vault_account: Pubkey,
|
||||
/// Serum/OpenBook vault signer PDA
|
||||
pub serum_vault_signer: Pubkey,
|
||||
/// Current coin reserve amount in the pool
|
||||
pub coin_reserve: u64,
|
||||
/// Current pc reserve amount in the pool
|
||||
@@ -32,13 +52,68 @@ impl RaydiumAmmV4Params {
|
||||
coin_reserve: u64,
|
||||
pc_reserve: u64,
|
||||
) -> Self {
|
||||
Self { amm, coin_mint, pc_mint, token_coin, token_pc, coin_reserve, pc_reserve }
|
||||
Self {
|
||||
amm,
|
||||
coin_mint,
|
||||
pc_mint,
|
||||
token_coin,
|
||||
token_pc,
|
||||
amm_open_orders: Pubkey::default(),
|
||||
amm_target_orders: Pubkey::default(),
|
||||
serum_program: Pubkey::default(),
|
||||
serum_market: Pubkey::default(),
|
||||
serum_bids: Pubkey::default(),
|
||||
serum_asks: Pubkey::default(),
|
||||
serum_event_queue: Pubkey::default(),
|
||||
serum_coin_vault_account: Pubkey::default(),
|
||||
serum_pc_vault_account: Pubkey::default(),
|
||||
serum_vault_signer: Pubkey::default(),
|
||||
coin_reserve,
|
||||
pc_reserve,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn with_market_accounts(
|
||||
mut self,
|
||||
amm_open_orders: Pubkey,
|
||||
amm_target_orders: Pubkey,
|
||||
serum_program: Pubkey,
|
||||
serum_market: Pubkey,
|
||||
serum_bids: Pubkey,
|
||||
serum_asks: Pubkey,
|
||||
serum_event_queue: Pubkey,
|
||||
serum_coin_vault_account: Pubkey,
|
||||
serum_pc_vault_account: Pubkey,
|
||||
serum_vault_signer: Pubkey,
|
||||
) -> Self {
|
||||
self.amm_open_orders = amm_open_orders;
|
||||
self.amm_target_orders = amm_target_orders;
|
||||
self.serum_program = serum_program;
|
||||
self.serum_market = serum_market;
|
||||
self.serum_bids = serum_bids;
|
||||
self.serum_asks = serum_asks;
|
||||
self.serum_event_queue = serum_event_queue;
|
||||
self.serum_coin_vault_account = serum_coin_vault_account;
|
||||
self.serum_pc_vault_account = serum_pc_vault_account;
|
||||
self.serum_vault_signer = serum_vault_signer;
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn from_amm_address_by_rpc(
|
||||
rpc: &SolanaRpcClient,
|
||||
amm: Pubkey,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let amm_info = crate::instruction::utils::raydium_amm_v4::fetch_amm_info(rpc, amm).await?;
|
||||
let market_state =
|
||||
crate::instruction::utils::raydium_amm_v4::fetch_market_state(rpc, amm_info.market)
|
||||
.await?;
|
||||
let serum_vault_signer =
|
||||
crate::instruction::utils::raydium_amm_v4::derive_serum_vault_signer(
|
||||
&amm_info.serum_dex,
|
||||
&amm_info.market,
|
||||
market_state.vault_signer_nonce,
|
||||
)?;
|
||||
let (coin_reserve, pc_reserve) =
|
||||
get_multi_token_balances(rpc, &amm_info.token_coin, &amm_info.token_pc).await?;
|
||||
Ok(Self {
|
||||
@@ -47,6 +122,16 @@ impl RaydiumAmmV4Params {
|
||||
pc_mint: amm_info.pc_mint,
|
||||
token_coin: amm_info.token_coin,
|
||||
token_pc: amm_info.token_pc,
|
||||
amm_open_orders: amm_info.open_orders,
|
||||
amm_target_orders: amm_info.target_orders,
|
||||
serum_program: amm_info.serum_dex,
|
||||
serum_market: amm_info.market,
|
||||
serum_bids: market_state.serum_bids,
|
||||
serum_asks: market_state.serum_asks,
|
||||
serum_event_queue: market_state.serum_event_queue,
|
||||
serum_coin_vault_account: market_state.serum_coin_vault_account,
|
||||
serum_pc_vault_account: market_state.serum_pc_vault_account,
|
||||
serum_vault_signer,
|
||||
coin_reserve,
|
||||
pc_reserve,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user