feat(swqos): add Stellium support and update minimum tip requirements
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
# 更新日志
|
||||
|
||||
## [3.3.6] - 2025-01-30
|
||||
|
||||
### 新增
|
||||
- **Stellium SWQOS 支持**:全新 Stellium 客户端实现
|
||||
- 使用标准 Solana `sendTransaction` RPC 格式
|
||||
- 自动连接保活,60 秒 ping 间隔
|
||||
- 5 个小费账户用于负载分配
|
||||
- 支持 8 个区域端点(纽约、法兰克福、阿姆斯特丹、东京、伦敦等)
|
||||
- 最低小费要求:0.001 SOL
|
||||
|
||||
### 变更
|
||||
- **更新最低小费要求**以提高交易成功率:
|
||||
- NextBlock: 0.00001 → 0.001 SOL
|
||||
- ZeroSlot: 0.00001 → 0.001 SOL
|
||||
- Temporal: 0.00001 → 0.001 SOL
|
||||
- BloxRoute: 0.00001 → 0.001 SOL
|
||||
- FlashBlock: 0.00001 → 0.001 SOL
|
||||
- BlockRazor: 0.00001 → 0.001 SOL
|
||||
- 增强异步执行器,添加小费验证警告
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "sol-trade-sdk"
|
||||
version = "3.3.5"
|
||||
version = "3.3.6"
|
||||
edition = "2021"
|
||||
authors = [
|
||||
"William <byteblock6@gmail.com>",
|
||||
|
||||
@@ -87,14 +87,14 @@ Add the dependency to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
# Add to your Cargo.toml
|
||||
sol-trade-sdk = { path = "./sol-trade-sdk", version = "3.3.5" }
|
||||
sol-trade-sdk = { path = "./sol-trade-sdk", version = "3.3.6" }
|
||||
```
|
||||
|
||||
### Use crates.io
|
||||
|
||||
```toml
|
||||
# Add to your Cargo.toml
|
||||
sol-trade-sdk = "3.3.5"
|
||||
sol-trade-sdk = "3.3.6"
|
||||
```
|
||||
|
||||
## 🛠️ Usage Examples
|
||||
|
||||
+2
-2
@@ -87,14 +87,14 @@ git clone https://github.com/0xfnzero/sol-trade-sdk
|
||||
|
||||
```toml
|
||||
# 添加到您的 Cargo.toml
|
||||
sol-trade-sdk = { path = "./sol-trade-sdk", version = "3.3.5" }
|
||||
sol-trade-sdk = { path = "./sol-trade-sdk", version = "3.3.6" }
|
||||
```
|
||||
|
||||
### 使用 crates.io
|
||||
|
||||
```toml
|
||||
# 添加到您的 Cargo.toml
|
||||
sol-trade-sdk = "3.3.5"
|
||||
sol-trade-sdk = "3.3.6"
|
||||
```
|
||||
|
||||
## 🛠️ 使用示例
|
||||
|
||||
@@ -3,8 +3,10 @@ use std::sync::{
|
||||
Arc,
|
||||
};
|
||||
|
||||
use sol_trade_sdk::common::{spl_associated_token_account::get_associated_token_address, GasFeeStrategy};
|
||||
use sol_trade_sdk::common::TradeConfig;
|
||||
use sol_trade_sdk::common::{
|
||||
fast_fn::get_associated_token_address_with_program_id_fast_use_seed, GasFeeStrategy,
|
||||
};
|
||||
use sol_trade_sdk::{
|
||||
common::AnyResult,
|
||||
swqos::SwqosConfig,
|
||||
@@ -125,7 +127,16 @@ async fn bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult<()>
|
||||
let recent_blockhash = client.rpc.get_latest_blockhash().await?;
|
||||
|
||||
let gas_fee_strategy = GasFeeStrategy::new();
|
||||
gas_fee_strategy.set_global_fee_strategy(150000,150000, 500000,500000, 0.001, 0.001, 256 * 1024, 0);
|
||||
gas_fee_strategy.set_global_fee_strategy(
|
||||
150000,
|
||||
150000,
|
||||
500000,
|
||||
500000,
|
||||
0.001,
|
||||
0.001,
|
||||
256 * 1024,
|
||||
0,
|
||||
);
|
||||
|
||||
// Buy tokens
|
||||
println!("Buying tokens from Bonk...");
|
||||
@@ -174,7 +185,12 @@ async fn bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult<()>
|
||||
|
||||
let rpc = client.rpc.clone();
|
||||
let payer = client.payer.pubkey();
|
||||
let account = get_associated_token_address(&payer, &mint_pubkey);
|
||||
let account = get_associated_token_address_with_program_id_fast_use_seed(
|
||||
&payer,
|
||||
&mint_pubkey,
|
||||
&trade_info.base_token_program,
|
||||
client.use_seed_optimize,
|
||||
);
|
||||
let balance = rpc.get_token_account_balance(&account).await?;
|
||||
println!("Balance: {:?}", balance);
|
||||
let amount_token = balance.amount.parse::<u64>().unwrap();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use sol_trade_sdk::common::spl_associated_token_account::get_associated_token_address;
|
||||
use sol_trade_sdk::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed;
|
||||
use sol_trade_sdk::common::TradeConfig;
|
||||
use sol_trade_sdk::{
|
||||
common::AnyResult,
|
||||
@@ -95,7 +95,16 @@ async fn bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyResult<
|
||||
let recent_blockhash = client.rpc.get_latest_blockhash().await?;
|
||||
|
||||
let gas_fee_strategy = sol_trade_sdk::common::GasFeeStrategy::new();
|
||||
gas_fee_strategy.set_global_fee_strategy(150000,150000, 500000,500000, 0.001, 0.001, 256 * 1024, 0);
|
||||
gas_fee_strategy.set_global_fee_strategy(
|
||||
150000,
|
||||
150000,
|
||||
500000,
|
||||
500000,
|
||||
0.001,
|
||||
0.001,
|
||||
256 * 1024,
|
||||
0,
|
||||
);
|
||||
|
||||
let token_type = if trade_info.quote_token_mint == sol_trade_sdk::constants::USD1_TOKEN_ACCOUNT
|
||||
{
|
||||
@@ -144,7 +153,12 @@ async fn bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyResult<
|
||||
|
||||
let rpc = client.rpc.clone();
|
||||
let payer = client.payer.pubkey();
|
||||
let account = get_associated_token_address(&payer, &mint_pubkey);
|
||||
let account = get_associated_token_address_with_program_id_fast_use_seed(
|
||||
&payer,
|
||||
&mint_pubkey,
|
||||
&trade_info.base_token_program,
|
||||
client.use_seed_optimize,
|
||||
);
|
||||
let balance = rpc.get_token_account_balance(&account).await?;
|
||||
println!("Balance: {:?}", balance);
|
||||
let amount_token = balance.amount.parse::<u64>().unwrap();
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::sync::{
|
||||
};
|
||||
|
||||
use sol_trade_sdk::common::{
|
||||
spl_associated_token_account::get_associated_token_address, TradeConfig,
|
||||
fast_fn::get_associated_token_address_with_program_id_fast_use_seed, TradeConfig,
|
||||
};
|
||||
use sol_trade_sdk::TradeTokenType;
|
||||
use sol_trade_sdk::{
|
||||
@@ -121,7 +121,16 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul
|
||||
let recent_blockhash = client.rpc.get_latest_blockhash().await?;
|
||||
|
||||
let gas_fee_strategy = sol_trade_sdk::common::GasFeeStrategy::new();
|
||||
gas_fee_strategy.set_global_fee_strategy(150000,150000, 500000,500000, 0.001, 0.001, 256 * 1024, 0);
|
||||
gas_fee_strategy.set_global_fee_strategy(
|
||||
150000,
|
||||
150000,
|
||||
500000,
|
||||
500000,
|
||||
0.001,
|
||||
0.001,
|
||||
256 * 1024,
|
||||
0,
|
||||
);
|
||||
|
||||
// Buy tokens
|
||||
println!("Buying tokens from PumpFun...");
|
||||
@@ -164,7 +173,12 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul
|
||||
|
||||
let rpc = client.rpc.clone();
|
||||
let payer = client.payer.pubkey();
|
||||
let account = get_associated_token_address(&payer, &mint_pubkey);
|
||||
let account = get_associated_token_address_with_program_id_fast_use_seed(
|
||||
&payer,
|
||||
&mint_pubkey,
|
||||
&trade_info.token_program,
|
||||
client.use_seed_optimize,
|
||||
);
|
||||
let balance = rpc.get_token_account_balance(&account).await?;
|
||||
println!("Balance: {:?}", balance);
|
||||
let amount_token = balance.amount.parse::<u64>().unwrap();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use sol_trade_sdk::common::spl_associated_token_account::get_associated_token_address;
|
||||
use sol_trade_sdk::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed;
|
||||
use sol_trade_sdk::common::TradeConfig;
|
||||
use sol_trade_sdk::TradeTokenType;
|
||||
use sol_trade_sdk::{
|
||||
@@ -89,7 +89,16 @@ async fn pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyR
|
||||
let recent_blockhash = client.rpc.get_latest_blockhash().await?;
|
||||
|
||||
let gas_fee_strategy = sol_trade_sdk::common::GasFeeStrategy::new();
|
||||
gas_fee_strategy.set_global_fee_strategy(150000,150000, 500000,500000, 0.001, 0.001, 256 * 1024, 0);
|
||||
gas_fee_strategy.set_global_fee_strategy(
|
||||
150000,
|
||||
150000,
|
||||
500000,
|
||||
500000,
|
||||
0.001,
|
||||
0.001,
|
||||
256 * 1024,
|
||||
0,
|
||||
);
|
||||
|
||||
// Buy tokens
|
||||
println!("Buying tokens from PumpFun...");
|
||||
@@ -130,7 +139,12 @@ async fn pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyR
|
||||
|
||||
let rpc = client.rpc.clone();
|
||||
let payer = client.payer.pubkey();
|
||||
let account = get_associated_token_address(&payer, &mint_pubkey);
|
||||
let account = get_associated_token_address_with_program_id_fast_use_seed(
|
||||
&payer,
|
||||
&mint_pubkey,
|
||||
&trade_info.token_program,
|
||||
client.use_seed_optimize,
|
||||
);
|
||||
let balance = rpc.get_token_account_balance(&account).await?;
|
||||
println!("Balance: {:?}", balance);
|
||||
let amount_token = balance.amount.parse::<u64>().unwrap();
|
||||
@@ -144,7 +158,11 @@ async fn pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyR
|
||||
slippage_basis_points: slippage_basis_points,
|
||||
recent_blockhash: Some(recent_blockhash),
|
||||
with_tip: false,
|
||||
extension_params: Box::new(PumpFunParams::immediate_sell(trade_info.creator_vault, trade_info.token_program, true)),
|
||||
extension_params: Box::new(PumpFunParams::immediate_sell(
|
||||
trade_info.creator_vault,
|
||||
trade_info.token_program,
|
||||
true,
|
||||
)),
|
||||
address_lookup_table_account: None,
|
||||
wait_transaction_confirmed: true,
|
||||
create_output_token_ata: true,
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use sol_trade_sdk::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed;
|
||||
use sol_trade_sdk::{
|
||||
common::TradeConfig, instruction::utils::raydium_amm_v4::fetch_amm_info,
|
||||
trading::common::get_multi_token_balances, TradeTokenType,
|
||||
};
|
||||
use sol_trade_sdk::{
|
||||
common::{spl_associated_token_account::get_associated_token_address, AnyResult},
|
||||
common::AnyResult,
|
||||
swqos::SwqosConfig,
|
||||
trading::{core::params::RaydiumAmmV4Params, factory::DexType},
|
||||
SolanaTrade,
|
||||
};
|
||||
use sol_trade_sdk::{
|
||||
common::TradeConfig, instruction::utils::raydium_amm_v4::fetch_amm_info,
|
||||
trading::common::get_multi_token_balances, TradeTokenType,
|
||||
};
|
||||
use solana_commitment_config::CommitmentConfig;
|
||||
use solana_sdk::signature::Keypair;
|
||||
use solana_sdk::signer::Signer;
|
||||
@@ -123,8 +124,9 @@ async fn raydium_amm_v4_copy_trade_with_grpc(trade_info: RaydiumAmmV4SwapEvent)
|
||||
let amm_info = fetch_amm_info(&client.rpc, trade_info.amm).await?;
|
||||
let (coin_reserve, pc_reserve) =
|
||||
get_multi_token_balances(&client.rpc, &amm_info.token_coin, &amm_info.token_pc).await?;
|
||||
let mint_pubkey = if amm_info.pc_mint == sol_trade_sdk::constants::WSOL_TOKEN_ACCOUNT
|
||||
|| amm_info.pc_mint == sol_trade_sdk::constants::USDC_TOKEN_ACCOUNT {
|
||||
let mint_pubkey = if amm_info.pc_mint == sol_trade_sdk::constants::WSOL_TOKEN_ACCOUNT
|
||||
|| amm_info.pc_mint == sol_trade_sdk::constants::USDC_TOKEN_ACCOUNT
|
||||
{
|
||||
amm_info.coin_mint
|
||||
} else {
|
||||
amm_info.pc_mint
|
||||
@@ -140,7 +142,16 @@ async fn raydium_amm_v4_copy_trade_with_grpc(trade_info: RaydiumAmmV4SwapEvent)
|
||||
);
|
||||
|
||||
let gas_fee_strategy = sol_trade_sdk::common::GasFeeStrategy::new();
|
||||
gas_fee_strategy.set_global_fee_strategy(150000,150000, 500000,500000, 0.001, 0.001, 256 * 1024, 0);
|
||||
gas_fee_strategy.set_global_fee_strategy(
|
||||
150000,
|
||||
150000,
|
||||
500000,
|
||||
500000,
|
||||
0.001,
|
||||
0.001,
|
||||
256 * 1024,
|
||||
0,
|
||||
);
|
||||
|
||||
// Buy tokens
|
||||
println!("Buying tokens from Raydium_amm_v4...");
|
||||
@@ -172,7 +183,12 @@ async fn raydium_amm_v4_copy_trade_with_grpc(trade_info: RaydiumAmmV4SwapEvent)
|
||||
|
||||
let rpc = client.rpc.clone();
|
||||
let payer = client.payer.pubkey();
|
||||
let account = get_associated_token_address(&payer, &mint_pubkey);
|
||||
let account = get_associated_token_address_with_program_id_fast_use_seed(
|
||||
&payer,
|
||||
&mint_pubkey,
|
||||
&trade_info.token_program,
|
||||
client.use_seed_optimize,
|
||||
);
|
||||
let balance = rpc.get_token_account_balance(&account).await?;
|
||||
println!("Balance: {:?}", balance);
|
||||
let amount_token = balance.amount.parse::<u64>().unwrap();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use sol_trade_sdk::common::spl_associated_token_account::get_associated_token_address;
|
||||
use sol_trade_sdk::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed;
|
||||
use sol_trade_sdk::common::TradeConfig;
|
||||
use sol_trade_sdk::constants::{WSOL_TOKEN_ACCOUNT, USDC_TOKEN_ACCOUNT};
|
||||
use sol_trade_sdk::constants::{USDC_TOKEN_ACCOUNT, WSOL_TOKEN_ACCOUNT};
|
||||
use sol_trade_sdk::trading::core::params::RaydiumCpmmParams;
|
||||
use sol_trade_sdk::trading::factory::DexType;
|
||||
use sol_trade_sdk::TradeTokenType;
|
||||
@@ -130,11 +130,20 @@ async fn raydium_cpmm_copy_trade_with_grpc(trade_info: RaydiumCpmmSwapEvent) ->
|
||||
let recent_blockhash = client.rpc.get_latest_blockhash().await?;
|
||||
|
||||
let gas_fee_strategy = sol_trade_sdk::common::GasFeeStrategy::new();
|
||||
gas_fee_strategy.set_global_fee_strategy(150000,150000, 500000,500000, 0.001, 0.001, 256 * 1024, 0);
|
||||
gas_fee_strategy.set_global_fee_strategy(
|
||||
150000,
|
||||
150000,
|
||||
500000,
|
||||
500000,
|
||||
0.001,
|
||||
0.001,
|
||||
256 * 1024,
|
||||
0,
|
||||
);
|
||||
|
||||
let buy_params =
|
||||
RaydiumCpmmParams::from_pool_address_by_rpc(&client.rpc, &trade_info.pool_state).await?;
|
||||
|
||||
|
||||
let is_wsol = trade_info.input_token_mint == sol_trade_sdk::constants::WSOL_TOKEN_ACCOUNT
|
||||
|| trade_info.output_token_mint == sol_trade_sdk::constants::WSOL_TOKEN_ACCOUNT;
|
||||
|
||||
@@ -166,7 +175,12 @@ async fn raydium_cpmm_copy_trade_with_grpc(trade_info: RaydiumCpmmSwapEvent) ->
|
||||
|
||||
let rpc = client.rpc.clone();
|
||||
let payer = client.payer.pubkey();
|
||||
let account = get_associated_token_address(&payer, &mint_pubkey);
|
||||
let account = get_associated_token_address_with_program_id_fast_use_seed(
|
||||
&payer,
|
||||
&mint_pubkey,
|
||||
&trade_info.output_token_program,
|
||||
client.use_seed_optimize,
|
||||
);
|
||||
let balance = rpc.get_token_account_balance(&account).await?;
|
||||
println!("Balance: {:?}", balance);
|
||||
let amount_token = balance.amount.parse::<u64>().unwrap();
|
||||
|
||||
@@ -5,6 +5,14 @@ use solana_sdk::{
|
||||
|
||||
use crate::constants::{ASSOCIATED_TOKEN_PROGRAM_ID, SYSTEM_PROGRAM};
|
||||
|
||||
/// Get the associated token address with a specified token program ID.
|
||||
///
|
||||
/// # Warning
|
||||
/// **This SDK enables seed optimization by default.** When seed is enabled, you should use
|
||||
/// [`get_associated_token_address_with_program_id_fast_use_seed`] instead for better performance.
|
||||
/// This function performs PDA derivation which is slower than the optimized version.
|
||||
///
|
||||
/// [`get_associated_token_address_with_program_id_fast_use_seed`]: crate::common::token_account::get_associated_token_address_with_program_id_fast_use_seed
|
||||
pub fn get_associated_token_address_with_program_id(
|
||||
wallet_address: &Pubkey,
|
||||
token_mint_address: &Pubkey,
|
||||
@@ -17,6 +25,14 @@ pub fn get_associated_token_address_with_program_id(
|
||||
.0
|
||||
}
|
||||
|
||||
/// Get the associated token address for the default token program.
|
||||
///
|
||||
/// # Warning
|
||||
/// **This SDK enables seed optimization by default.** When seed is enabled, you should use
|
||||
/// [`get_associated_token_address_with_program_id_fast_use_seed`] instead for better performance.
|
||||
/// This function performs PDA derivation which is slower than the optimized version.
|
||||
///
|
||||
/// [`get_associated_token_address_with_program_id_fast_use_seed`]: crate::common::token_account::get_associated_token_address_with_program_id_fast_use_seed
|
||||
pub fn get_associated_token_address(
|
||||
wallet_address: &Pubkey,
|
||||
token_mint_address: &Pubkey,
|
||||
|
||||
+26
-6
@@ -109,6 +109,14 @@ pub const ASTRALANE_TIP_ACCOUNTS: &[Pubkey] = &[
|
||||
pubkey!("astrawVNP4xDBKT7rAdxrLYiTSTdqtUr63fSMduivXK"),
|
||||
];
|
||||
|
||||
pub const STELLIUM_TIP_ACCOUNTS: &[Pubkey] = &[
|
||||
pubkey!("ste11JV3MLMM7x7EJUM2sXcJC1H7F4jBLnP9a9PG8PH"),
|
||||
pubkey!("ste11MWPjXCRfQryCshzi86SGhuXjF4Lv6xMXD2AoSt"),
|
||||
pubkey!("ste11p5x8tJ53H1NbNQsRBg1YNRd4GcVpxtDw8PBpmb"),
|
||||
pubkey!("ste11p7e2KLYou5bwtt35H7BM6uMdo4pvioGjJXKFcN"),
|
||||
pubkey!("ste11TMV68LMi1BguM4RQujtbNCZvf1sjsASpqgAvSX"),
|
||||
];
|
||||
|
||||
// NewYork,
|
||||
// Frankfurt,
|
||||
// Amsterdam,
|
||||
@@ -217,13 +225,25 @@ pub const SWQOS_ENDPOINTS_ASTRALANE: [&str; 8] = [
|
||||
"http://lim.gateway.astralane.io/iris",
|
||||
];
|
||||
|
||||
pub const SWQOS_ENDPOINTS_STELLIUM: [&str; 8] = [
|
||||
"http://ewr1.flashrpc.com",
|
||||
"http://fra1.flashrpc.com",
|
||||
"http://ams1.flashrpc.com",
|
||||
"http://ewr1.flashrpc.com",
|
||||
"http://tyo1.flashrpc.com",
|
||||
"http://lhr1.flashrpc.com",
|
||||
"http://ewr1.flashrpc.com",
|
||||
"http://fra1.flashrpc.com",
|
||||
];
|
||||
|
||||
pub const SWQOS_MIN_TIP_DEFAULT: f64 = 0.00001; // 其它SWQOS默认最低小费
|
||||
pub const SWQOS_MIN_TIP_JITO: f64 = SWQOS_MIN_TIP_DEFAULT;
|
||||
pub const SWQOS_MIN_TIP_NEXTBLOCK: f64 = SWQOS_MIN_TIP_DEFAULT;
|
||||
pub const SWQOS_MIN_TIP_ZERO_SLOT: f64 = SWQOS_MIN_TIP_DEFAULT;
|
||||
pub const SWQOS_MIN_TIP_TEMPORAL: f64 = SWQOS_MIN_TIP_DEFAULT;
|
||||
pub const SWQOS_MIN_TIP_BLOXROUTE: f64 = SWQOS_MIN_TIP_DEFAULT;
|
||||
pub const SWQOS_MIN_TIP_NEXTBLOCK: f64 = 0.001;
|
||||
pub const SWQOS_MIN_TIP_ZERO_SLOT: f64 = 0.001;
|
||||
pub const SWQOS_MIN_TIP_TEMPORAL: f64 = 0.001;
|
||||
pub const SWQOS_MIN_TIP_BLOXROUTE: f64 = 0.001;
|
||||
pub const SWQOS_MIN_TIP_NODE1: f64 = 0.002; // 如需更高阈值可调整
|
||||
pub const SWQOS_MIN_TIP_FLASHBLOCK: f64 = SWQOS_MIN_TIP_DEFAULT;
|
||||
pub const SWQOS_MIN_TIP_BLOCKRAZOR: f64 = SWQOS_MIN_TIP_DEFAULT;
|
||||
pub const SWQOS_MIN_TIP_FLASHBLOCK: f64 = 0.001;
|
||||
pub const SWQOS_MIN_TIP_BLOCKRAZOR: f64 = 0.001;
|
||||
pub const SWQOS_MIN_TIP_ASTRALANE: f64 = SWQOS_MIN_TIP_DEFAULT;
|
||||
pub const SWQOS_MIN_TIP_STELLIUM: f64 = 0.001; // Stellium requires minimum 0.001 SOL tip
|
||||
|
||||
+36
-19
@@ -10,6 +10,7 @@ pub mod node1;
|
||||
pub mod flashblock;
|
||||
pub mod blockrazor;
|
||||
pub mod astralane;
|
||||
pub mod stellium;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -20,29 +21,31 @@ use tokio::sync::RwLock;
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{
|
||||
common::SolanaRpcClient,
|
||||
common::SolanaRpcClient,
|
||||
constants::swqos::{
|
||||
SWQOS_ENDPOINTS_BLOX,
|
||||
SWQOS_ENDPOINTS_JITO,
|
||||
SWQOS_ENDPOINTS_NEXTBLOCK,
|
||||
SWQOS_ENDPOINTS_TEMPORAL,
|
||||
SWQOS_ENDPOINTS_ZERO_SLOT,
|
||||
SWQOS_ENDPOINTS_NODE1,
|
||||
SWQOS_ENDPOINTS_BLOX,
|
||||
SWQOS_ENDPOINTS_JITO,
|
||||
SWQOS_ENDPOINTS_NEXTBLOCK,
|
||||
SWQOS_ENDPOINTS_TEMPORAL,
|
||||
SWQOS_ENDPOINTS_ZERO_SLOT,
|
||||
SWQOS_ENDPOINTS_NODE1,
|
||||
SWQOS_ENDPOINTS_FLASHBLOCK,
|
||||
SWQOS_ENDPOINTS_BLOCKRAZOR,
|
||||
SWQOS_ENDPOINTS_ASTRALANE
|
||||
},
|
||||
SWQOS_ENDPOINTS_ASTRALANE,
|
||||
SWQOS_ENDPOINTS_STELLIUM
|
||||
},
|
||||
swqos::{
|
||||
bloxroute::BloxrouteClient,
|
||||
jito::JitoClient,
|
||||
nextblock::NextBlockClient,
|
||||
solana_rpc::SolRpcClient,
|
||||
temporal::TemporalClient,
|
||||
zeroslot::ZeroSlotClient,
|
||||
node1::Node1Client,
|
||||
bloxroute::BloxrouteClient,
|
||||
jito::JitoClient,
|
||||
nextblock::NextBlockClient,
|
||||
solana_rpc::SolRpcClient,
|
||||
temporal::TemporalClient,
|
||||
zeroslot::ZeroSlotClient,
|
||||
node1::Node1Client,
|
||||
flashblock::FlashBlockClient,
|
||||
blockrazor::BlockRazorClient,
|
||||
astralane::AstralaneClient
|
||||
astralane::AstralaneClient,
|
||||
stellium::StelliumClient
|
||||
}
|
||||
};
|
||||
|
||||
@@ -81,6 +84,7 @@ pub enum SwqosType {
|
||||
FlashBlock,
|
||||
BlockRazor,
|
||||
Astralane,
|
||||
Stellium,
|
||||
Default,
|
||||
}
|
||||
|
||||
@@ -96,6 +100,7 @@ impl SwqosType {
|
||||
Self::FlashBlock,
|
||||
Self::BlockRazor,
|
||||
Self::Astralane,
|
||||
Self::Stellium,
|
||||
Self::Default,
|
||||
]
|
||||
}
|
||||
@@ -144,6 +149,8 @@ pub enum SwqosConfig {
|
||||
BlockRazor(String, SwqosRegion, Option<String>),
|
||||
/// Astralane(api_token, region, custom_url)
|
||||
Astralane(String, SwqosRegion, Option<String>),
|
||||
/// Stellium(api_token, region, custom_url)
|
||||
Stellium(String, SwqosRegion, Option<String>),
|
||||
}
|
||||
|
||||
impl SwqosConfig {
|
||||
@@ -151,7 +158,7 @@ impl SwqosConfig {
|
||||
if let Some(custom_url) = url {
|
||||
return custom_url;
|
||||
}
|
||||
|
||||
|
||||
match swqos_type {
|
||||
SwqosType::Jito => SWQOS_ENDPOINTS_JITO[region as usize].to_string(),
|
||||
SwqosType::NextBlock => SWQOS_ENDPOINTS_NEXTBLOCK[region as usize].to_string(),
|
||||
@@ -162,6 +169,7 @@ impl SwqosConfig {
|
||||
SwqosType::FlashBlock => SWQOS_ENDPOINTS_FLASHBLOCK[region as usize].to_string(),
|
||||
SwqosType::BlockRazor => SWQOS_ENDPOINTS_BLOCKRAZOR[region as usize].to_string(),
|
||||
SwqosType::Astralane => SWQOS_ENDPOINTS_ASTRALANE[region as usize].to_string(),
|
||||
SwqosType::Stellium => SWQOS_ENDPOINTS_STELLIUM[region as usize].to_string(),
|
||||
SwqosType::Default => "".to_string(),
|
||||
}
|
||||
}
|
||||
@@ -249,11 +257,20 @@ impl SwqosConfig {
|
||||
);
|
||||
Arc::new(astralane_client)
|
||||
},
|
||||
SwqosConfig::Stellium(auth_token, region, url) => {
|
||||
let endpoint = SwqosConfig::get_endpoint(SwqosType::Stellium, region, url);
|
||||
let stellium_client = StelliumClient::new(
|
||||
rpc_url.clone(),
|
||||
endpoint.to_string(),
|
||||
auth_token
|
||||
);
|
||||
Arc::new(stellium_client)
|
||||
},
|
||||
SwqosConfig::Default(endpoint) => {
|
||||
let rpc = SolanaRpcClient::new_with_commitment(
|
||||
endpoint,
|
||||
commitment
|
||||
);
|
||||
);
|
||||
let rpc_client = SolRpcClient::new(Arc::new(rpc));
|
||||
Arc::new(rpc_client)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
use crate::swqos::common::{poll_transaction_confirmation, serialize_transaction_and_encode};
|
||||
use rand::seq::IndexedRandom;
|
||||
use reqwest::Client;
|
||||
use serde_json::json;
|
||||
use std::{sync::Arc, time::Instant};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use std::time::Duration;
|
||||
use solana_transaction_status::UiTransactionEncoding;
|
||||
|
||||
use anyhow::Result;
|
||||
use solana_sdk::transaction::VersionedTransaction;
|
||||
use crate::swqos::{SwqosType, TradeType};
|
||||
use crate::swqos::SwqosClientTrait;
|
||||
|
||||
use crate::{common::SolanaRpcClient, constants::swqos::STELLIUM_TIP_ACCOUNTS};
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct StelliumClient {
|
||||
pub endpoint: String,
|
||||
pub auth_token: String,
|
||||
pub rpc_client: Arc<SolanaRpcClient>,
|
||||
pub http_client: Client,
|
||||
keep_alive_running: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SwqosClientTrait for StelliumClient {
|
||||
async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction) -> Result<()> {
|
||||
self.send_transaction(trade_type, transaction).await
|
||||
}
|
||||
|
||||
async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec<VersionedTransaction>) -> Result<()> {
|
||||
self.send_transactions(trade_type, transactions).await
|
||||
}
|
||||
|
||||
fn get_tip_account(&self) -> Result<String> {
|
||||
let tip_account = *STELLIUM_TIP_ACCOUNTS.choose(&mut rand::rng()).or_else(|| STELLIUM_TIP_ACCOUNTS.first()).unwrap();
|
||||
Ok(tip_account.to_string())
|
||||
}
|
||||
|
||||
fn get_swqos_type(&self) -> SwqosType {
|
||||
SwqosType::Stellium
|
||||
}
|
||||
}
|
||||
|
||||
impl StelliumClient {
|
||||
pub fn new(rpc_url: String, endpoint: String, auth_token: String) -> Self {
|
||||
let rpc_client = SolanaRpcClient::new(rpc_url);
|
||||
let http_client = Client::builder()
|
||||
// Optimized connection pool settings for high performance
|
||||
.pool_idle_timeout(Duration::from_secs(120))
|
||||
.pool_max_idle_per_host(256) // Increased from 64 to 256
|
||||
.tcp_keepalive(Some(Duration::from_secs(60))) // Reduced from 1200 to 60
|
||||
.tcp_nodelay(true) // Disable Nagle's algorithm for lower latency
|
||||
.http2_keep_alive_interval(Duration::from_secs(10))
|
||||
.http2_keep_alive_timeout(Duration::from_secs(5))
|
||||
.http2_adaptive_window(true) // Enable adaptive flow control
|
||||
.timeout(Duration::from_millis(3000)) // Reduced from 10s to 3s
|
||||
.connect_timeout(Duration::from_millis(2000)) // Reduced from 5s to 2s
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let keep_alive_running = Arc::new(AtomicBool::new(true));
|
||||
|
||||
let client = Self {
|
||||
rpc_client: Arc::new(rpc_client),
|
||||
endpoint: endpoint.clone(),
|
||||
auth_token: auth_token.clone(),
|
||||
http_client: http_client.clone(),
|
||||
keep_alive_running: keep_alive_running.clone(),
|
||||
};
|
||||
|
||||
// Start ping task
|
||||
let client_clone = client.clone();
|
||||
tokio::spawn(async move {
|
||||
client_clone.start_ping_task().await;
|
||||
});
|
||||
|
||||
client
|
||||
}
|
||||
|
||||
/// Start periodic ping task to keep connections active
|
||||
async fn start_ping_task(&self) {
|
||||
let endpoint = self.endpoint.clone();
|
||||
let auth_token = self.auth_token.clone();
|
||||
let http_client = self.http_client.clone();
|
||||
let stop_ping = self.keep_alive_running.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut interval = tokio::time::interval(Duration::from_secs(60)); // Ping every 60 seconds
|
||||
|
||||
loop {
|
||||
interval.tick().await;
|
||||
|
||||
if stop_ping.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Send ping request
|
||||
let url = format!("{}/{}", endpoint, auth_token);
|
||||
match http_client.get(&url).send().await {
|
||||
Ok(response) => {
|
||||
if !response.status().is_success() {
|
||||
eprintln!(" [Stellium] Ping failed with status: {}", response.status());
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!(" [Stellium] Ping request error: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction) -> Result<()> {
|
||||
let start_time = Instant::now();
|
||||
let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64).await?;
|
||||
|
||||
// Stellium uses standard Solana sendTransaction format
|
||||
let request_body = serde_json::to_string(&json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "sendTransaction",
|
||||
"params": [
|
||||
content,
|
||||
{ "encoding": "base64" }
|
||||
]
|
||||
}))?;
|
||||
|
||||
// Build the URL with the API key
|
||||
let url = format!("{}/{}", self.endpoint, self.auth_token);
|
||||
|
||||
// Send request to Stellium
|
||||
let response_text = self.http_client.post(&url)
|
||||
.body(request_body)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Connection", "keep-alive")
|
||||
.header("Keep-Alive", "timeout=30, max=1000")
|
||||
.send()
|
||||
.await?
|
||||
.text()
|
||||
.await?;
|
||||
|
||||
// Parse response
|
||||
if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
|
||||
if response_json.get("result").is_some() {
|
||||
println!(" [Stellium] {} submitted: {:?}", trade_type, start_time.elapsed());
|
||||
} else if let Some(_error) = response_json.get("error") {
|
||||
eprintln!(" [Stellium] {} submission failed: {:?}", trade_type, _error);
|
||||
}
|
||||
} else {
|
||||
eprintln!(" [Stellium] {} submission failed: {:?}", trade_type, response_text);
|
||||
}
|
||||
|
||||
let start_time: Instant = Instant::now();
|
||||
match poll_transaction_confirmation(&self.rpc_client, signature).await {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" [Stellium] {} confirmation failed: {:?}", trade_type, start_time.elapsed());
|
||||
return Err(e);
|
||||
},
|
||||
}
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" [Stellium] {} confirmed: {:?}", trade_type, start_time.elapsed());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec<VersionedTransaction>) -> Result<()> {
|
||||
for transaction in transactions {
|
||||
self.send_transaction(trade_type, transaction).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Stop the ping task
|
||||
pub fn stop_ping_task(&self) {
|
||||
self.keep_alive_running.store(false, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for StelliumClient {
|
||||
fn drop(&mut self) {
|
||||
// Stop ping task when client is dropped
|
||||
self.keep_alive_running.store(false, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ use crate::{
|
||||
SWQOS_MIN_TIP_FLASHBLOCK,
|
||||
SWQOS_MIN_TIP_BLOCKRAZOR,
|
||||
SWQOS_MIN_TIP_ASTRALANE,
|
||||
SWQOS_MIN_TIP_STELLIUM,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -167,8 +168,15 @@ pub async fn execute_parallel(
|
||||
SwqosType::FlashBlock => SWQOS_MIN_TIP_FLASHBLOCK,
|
||||
SwqosType::BlockRazor => SWQOS_MIN_TIP_BLOCKRAZOR,
|
||||
SwqosType::Astralane => SWQOS_MIN_TIP_ASTRALANE,
|
||||
SwqosType::Stellium => SWQOS_MIN_TIP_STELLIUM,
|
||||
SwqosType::Default => SWQOS_MIN_TIP_DEFAULT,
|
||||
};
|
||||
if config.2.tip < min_tip {
|
||||
println!(
|
||||
"⚠️ Config filtered: {:?} tip {} is below minimum required tip {}",
|
||||
config.0, config.2.tip, min_tip
|
||||
);
|
||||
}
|
||||
config.2.tip >= min_tip
|
||||
} else {
|
||||
true
|
||||
|
||||
Reference in New Issue
Block a user