release: update Pump program metadata for v4.0.8
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "sol-trade-sdk"
|
||||
version = "4.0.7"
|
||||
version = "4.0.8"
|
||||
edition = "2021"
|
||||
authors = [
|
||||
"William <byteblock6@gmail.com>",
|
||||
|
||||
@@ -108,14 +108,14 @@ Add the dependency to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
# Add to your Cargo.toml
|
||||
sol-trade-sdk = { path = "./sol-trade-sdk", version = "4.0.7" }
|
||||
sol-trade-sdk = { path = "./sol-trade-sdk", version = "4.0.8" }
|
||||
```
|
||||
|
||||
### Use crates.io
|
||||
|
||||
```toml
|
||||
# Add to your Cargo.toml
|
||||
sol-trade-sdk = "4.0.7"
|
||||
sol-trade-sdk = "4.0.8"
|
||||
```
|
||||
|
||||
## 🛠️ Usage Examples
|
||||
|
||||
+2
-2
@@ -108,14 +108,14 @@ git clone https://github.com/0xfnzero/sol-trade-sdk
|
||||
|
||||
```toml
|
||||
# 添加到您的 Cargo.toml
|
||||
sol-trade-sdk = { path = "./sol-trade-sdk", version = "4.0.7" }
|
||||
sol-trade-sdk = { path = "./sol-trade-sdk", version = "4.0.8" }
|
||||
```
|
||||
|
||||
### 使用 crates.io
|
||||
|
||||
```toml
|
||||
# 添加到您的 Cargo.toml
|
||||
sol-trade-sdk = "4.0.7"
|
||||
sol-trade-sdk = "4.0.8"
|
||||
```
|
||||
|
||||
## 🛠️ 使用示例
|
||||
|
||||
@@ -176,7 +176,7 @@ async fn pumpfun_copy_trade_with_grpc(
|
||||
gas_fee_strategy,
|
||||
simulate: false,
|
||||
use_exact_sol_amount: None,
|
||||
use_pumpfun_v2: false,
|
||||
use_pumpfun_v2: false,
|
||||
grpc_recv_us: None,
|
||||
};
|
||||
client.buy(buy_params).await?;
|
||||
|
||||
@@ -179,7 +179,7 @@ async fn bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult<()>
|
||||
gas_fee_strategy: gas_fee_strategy.clone(),
|
||||
simulate: false,
|
||||
use_exact_sol_amount: None,
|
||||
use_pumpfun_v2: false,
|
||||
use_pumpfun_v2: false,
|
||||
grpc_recv_us: None,
|
||||
};
|
||||
client.buy(buy_params).await?;
|
||||
|
||||
@@ -147,7 +147,7 @@ async fn bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyResult<
|
||||
gas_fee_strategy: gas_fee_strategy.clone(),
|
||||
simulate: false,
|
||||
use_exact_sol_amount: None,
|
||||
use_pumpfun_v2: false,
|
||||
use_pumpfun_v2: false,
|
||||
grpc_recv_us: None,
|
||||
};
|
||||
client.buy(buy_params).await?;
|
||||
|
||||
@@ -636,7 +636,7 @@ async fn handle_buy_pumpfun(
|
||||
gas_fee_strategy: gas_fee_strategy,
|
||||
simulate: false,
|
||||
use_exact_sol_amount: None,
|
||||
use_pumpfun_v2: false,
|
||||
use_pumpfun_v2: false,
|
||||
grpc_recv_us: None,
|
||||
};
|
||||
match client.buy(buy_params).await {
|
||||
@@ -693,7 +693,7 @@ async fn handle_buy_pumpswap(
|
||||
gas_fee_strategy: gas_fee_strategy,
|
||||
simulate: false,
|
||||
use_exact_sol_amount: None,
|
||||
use_pumpfun_v2: false,
|
||||
use_pumpfun_v2: false,
|
||||
grpc_recv_us: None,
|
||||
};
|
||||
match client.buy(buy_params).await {
|
||||
@@ -750,7 +750,7 @@ async fn handle_buy_bonk(
|
||||
gas_fee_strategy: gas_fee_strategy,
|
||||
simulate: false,
|
||||
use_exact_sol_amount: None,
|
||||
use_pumpfun_v2: false,
|
||||
use_pumpfun_v2: false,
|
||||
grpc_recv_us: None,
|
||||
};
|
||||
match client.buy(buy_params).await {
|
||||
@@ -811,7 +811,7 @@ async fn handle_buy_raydium_v4(
|
||||
gas_fee_strategy: gas_fee_strategy,
|
||||
simulate: false,
|
||||
use_exact_sol_amount: None,
|
||||
use_pumpfun_v2: false,
|
||||
use_pumpfun_v2: false,
|
||||
grpc_recv_us: None,
|
||||
};
|
||||
match client.buy(buy_params).await {
|
||||
@@ -873,7 +873,7 @@ async fn handle_buy_raydium_cpmm(
|
||||
gas_fee_strategy: gas_fee_strategy,
|
||||
simulate: false,
|
||||
use_exact_sol_amount: None,
|
||||
use_pumpfun_v2: false,
|
||||
use_pumpfun_v2: false,
|
||||
grpc_recv_us: None,
|
||||
};
|
||||
match client.buy(buy_params).await {
|
||||
|
||||
@@ -51,7 +51,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
gas_fee_strategy: gas_fee_strategy.clone(),
|
||||
simulate: false,
|
||||
use_exact_sol_amount: None,
|
||||
use_pumpfun_v2: false,
|
||||
use_pumpfun_v2: false,
|
||||
grpc_recv_us: None,
|
||||
};
|
||||
client.buy(buy_params).await?;
|
||||
|
||||
@@ -112,7 +112,7 @@ async fn test_middleware() -> AnyResult<()> {
|
||||
gas_fee_strategy: gas_fee_strategy,
|
||||
simulate: false,
|
||||
use_exact_sol_amount: None,
|
||||
use_pumpfun_v2: false,
|
||||
use_pumpfun_v2: false,
|
||||
grpc_recv_us: None,
|
||||
};
|
||||
client.buy(buy_params).await?;
|
||||
|
||||
@@ -172,7 +172,7 @@ async fn pumpfun_copy_trade_with_grpc(
|
||||
gas_fee_strategy: gas_fee_strategy.clone(),
|
||||
simulate: false,
|
||||
use_exact_sol_amount: None,
|
||||
use_pumpfun_v2: false,
|
||||
use_pumpfun_v2: false,
|
||||
grpc_recv_us: None,
|
||||
};
|
||||
client.buy(buy_params).await?;
|
||||
|
||||
@@ -169,7 +169,7 @@ async fn pumpfun_copy_trade(e: sol_parser_sdk::core::events::PumpFunTradeEvent)
|
||||
gas_fee_strategy: gas_fee_strategy.clone(),
|
||||
simulate: false,
|
||||
use_exact_sol_amount: None,
|
||||
use_pumpfun_v2: false,
|
||||
use_pumpfun_v2: false,
|
||||
grpc_recv_us: None,
|
||||
};
|
||||
client.buy(buy_params).await?;
|
||||
|
||||
@@ -159,7 +159,7 @@ async fn pumpfun_sniper_trade(e: sol_parser_sdk::core::events::PumpFunTradeEvent
|
||||
gas_fee_strategy: gas_fee_strategy.clone(),
|
||||
simulate: false,
|
||||
use_exact_sol_amount: None,
|
||||
use_pumpfun_v2: false,
|
||||
use_pumpfun_v2: false,
|
||||
grpc_recv_us: None,
|
||||
};
|
||||
client.buy(buy_params).await?;
|
||||
|
||||
@@ -50,7 +50,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
gas_fee_strategy: gas_fee_strategy.clone(),
|
||||
simulate: false,
|
||||
use_exact_sol_amount: None,
|
||||
use_pumpfun_v2: false,
|
||||
use_pumpfun_v2: false,
|
||||
grpc_recv_us: None,
|
||||
};
|
||||
client.buy(buy_params).await?;
|
||||
|
||||
@@ -244,7 +244,7 @@ async fn pumpswap_trade_with_grpc(
|
||||
gas_fee_strategy: gas_fee_strategy.clone(),
|
||||
simulate: false,
|
||||
use_exact_sol_amount: None,
|
||||
use_pumpfun_v2: false,
|
||||
use_pumpfun_v2: false,
|
||||
grpc_recv_us: None,
|
||||
};
|
||||
client.buy(buy_params).await?;
|
||||
|
||||
@@ -181,7 +181,7 @@ async fn raydium_amm_v4_copy_trade_with_grpc(trade_info: RaydiumAmmV4SwapEvent)
|
||||
gas_fee_strategy: gas_fee_strategy.clone(),
|
||||
simulate: false,
|
||||
use_exact_sol_amount: None,
|
||||
use_pumpfun_v2: false,
|
||||
use_pumpfun_v2: false,
|
||||
grpc_recv_us: None,
|
||||
};
|
||||
client.buy(buy_params).await?;
|
||||
|
||||
@@ -169,7 +169,7 @@ async fn raydium_cpmm_copy_trade_with_grpc(trade_info: RaydiumCpmmSwapEvent) ->
|
||||
gas_fee_strategy: gas_fee_strategy.clone(),
|
||||
simulate: false,
|
||||
use_exact_sol_amount: None,
|
||||
use_pumpfun_v2: false,
|
||||
use_pumpfun_v2: false,
|
||||
grpc_recv_us: None,
|
||||
};
|
||||
client.buy(buy_params).await?;
|
||||
|
||||
@@ -50,7 +50,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
gas_fee_strategy: gas_fee_strategy.clone(),
|
||||
simulate: false,
|
||||
use_exact_sol_amount: None,
|
||||
use_pumpfun_v2: false,
|
||||
use_pumpfun_v2: false,
|
||||
grpc_recv_us: None,
|
||||
};
|
||||
client.buy(buy_params).await?;
|
||||
|
||||
+2323
-2896
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -4835,7 +4835,7 @@
|
||||
},
|
||||
{
|
||||
"code": 6052,
|
||||
"name": "TokensInVaultLessThanCashbackEarned"
|
||||
"name": "CashbackEarnedDoesNotMatchTokenInVault"
|
||||
}
|
||||
],
|
||||
"types": [
|
||||
|
||||
+1463
-242
File diff suppressed because it is too large
Load Diff
+27
-19
@@ -3,6 +3,7 @@
|
||||
use crate::common::nonce_cache::DurableNonceInfo;
|
||||
use crate::common::sdk_log;
|
||||
use crate::common::GasFeeStrategy;
|
||||
use crate::common::SolanaRpcClient;
|
||||
use crate::common::{InfrastructureConfig, TradeConfig};
|
||||
#[cfg(feature = "perf-trace")]
|
||||
use crate::constants::trade::trade::DEFAULT_SLIPPAGE;
|
||||
@@ -25,7 +26,6 @@ use crate::trading::factory::DexType;
|
||||
use crate::trading::MiddlewareManager;
|
||||
use crate::trading::SwapParams;
|
||||
use crate::trading::TradeFactory;
|
||||
use crate::common::SolanaRpcClient;
|
||||
use parking_lot::Mutex;
|
||||
use rustls::crypto::{ring::default_provider, CryptoProvider};
|
||||
use solana_sdk::hash::Hash;
|
||||
@@ -112,7 +112,9 @@ impl TradingInfrastructure {
|
||||
|
||||
// Initialize rent cache (with timeout so slow RPC doesn't block forever)
|
||||
const RENT_UPDATE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(15);
|
||||
match tokio::time::timeout(RENT_UPDATE_TIMEOUT, crate::common::seed::update_rents(&rpc)).await {
|
||||
match tokio::time::timeout(RENT_UPDATE_TIMEOUT, crate::common::seed::update_rents(&rpc))
|
||||
.await
|
||||
{
|
||||
Ok(Ok(())) => {}
|
||||
Ok(Err(e)) => {
|
||||
if sdk_log::sdk_log_enabled() {
|
||||
@@ -218,15 +220,9 @@ impl TradingInfrastructure {
|
||||
}
|
||||
|
||||
if !swqos_clients.is_empty() {
|
||||
let labels: Vec<&str> = swqos_clients
|
||||
.iter()
|
||||
.map(|c| c.get_swqos_type().as_str())
|
||||
.collect();
|
||||
eprintln!(
|
||||
"ℹ️ SWQOS 通道已就绪: {} 条 → [{}]",
|
||||
swqos_clients.len(),
|
||||
labels.join(", ")
|
||||
);
|
||||
let labels: Vec<&str> =
|
||||
swqos_clients.iter().map(|c| c.get_swqos_type().as_str()).collect();
|
||||
eprintln!("ℹ️ SWQOS 通道已就绪: {} 条 → [{}]", swqos_clients.len(), labels.join(", "));
|
||||
}
|
||||
|
||||
let swqos_count = swqos_clients.len();
|
||||
@@ -735,7 +731,8 @@ impl TradingClient {
|
||||
Some(v) => {
|
||||
self.use_dedicated_sender_threads = true;
|
||||
let cap = v.len().min(self.max_sender_concurrency);
|
||||
self.sender_thread_cores = Some(Arc::new(if cap < v.len() { v[..cap].to_vec() } else { v }));
|
||||
self.sender_thread_cores =
|
||||
Some(Arc::new(if cap < v.len() { v[..cap].to_vec() } else { v }));
|
||||
}
|
||||
}
|
||||
self
|
||||
@@ -798,7 +795,10 @@ impl TradingClient {
|
||||
pub async fn buy(
|
||||
&self,
|
||||
params: TradeBuyParams,
|
||||
) -> Result<(bool, Vec<Signature>, Option<TradeError>, Vec<(crate::swqos::SwqosType, i64)>), anyhow::Error> {
|
||||
) -> Result<
|
||||
(bool, Vec<Signature>, Option<TradeError>, Vec<(crate::swqos::SwqosType, i64)>),
|
||||
anyhow::Error,
|
||||
> {
|
||||
if params.recent_blockhash.is_none() && params.durable_nonce.is_none() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Must provide either recent_blockhash or durable_nonce for buy (required for transaction validity)"
|
||||
@@ -872,8 +872,9 @@ impl TradingClient {
|
||||
};
|
||||
|
||||
let swap_result = executor.swap(buy_params).await;
|
||||
let result =
|
||||
swap_result.map(|(success, sigs, err, timings)| (success, sigs, err.map(TradeError::from), timings));
|
||||
let result = swap_result.map(|(success, sigs, err, timings)| {
|
||||
(success, sigs, err.map(TradeError::from), timings)
|
||||
});
|
||||
result
|
||||
}
|
||||
|
||||
@@ -906,7 +907,10 @@ impl TradingClient {
|
||||
pub async fn sell(
|
||||
&self,
|
||||
params: TradeSellParams,
|
||||
) -> Result<(bool, Vec<Signature>, Option<TradeError>, Vec<(crate::swqos::SwqosType, i64)>), anyhow::Error> {
|
||||
) -> Result<
|
||||
(bool, Vec<Signature>, Option<TradeError>, Vec<(crate::swqos::SwqosType, i64)>),
|
||||
anyhow::Error,
|
||||
> {
|
||||
#[cfg(feature = "perf-trace")]
|
||||
if sdk_log::sdk_log_enabled() && params.slippage_basis_points.is_none() {
|
||||
debug!(
|
||||
@@ -980,8 +984,9 @@ impl TradingClient {
|
||||
};
|
||||
|
||||
let swap_result = executor.swap(sell_params).await;
|
||||
let result =
|
||||
swap_result.map(|(success, sigs, err, timings)| (success, sigs, err.map(TradeError::from), timings));
|
||||
let result = swap_result.map(|(success, sigs, err, timings)| {
|
||||
(success, sigs, err.map(TradeError::from), timings)
|
||||
});
|
||||
result
|
||||
}
|
||||
|
||||
@@ -1016,7 +1021,10 @@ impl TradingClient {
|
||||
mut params: TradeSellParams,
|
||||
amount_token: u64,
|
||||
percent: u64,
|
||||
) -> Result<(bool, Vec<Signature>, Option<TradeError>, Vec<(crate::swqos::SwqosType, i64)>), anyhow::Error> {
|
||||
) -> Result<
|
||||
(bool, Vec<Signature>, Option<TradeError>, Vec<(crate::swqos::SwqosType, i64)>),
|
||||
anyhow::Error,
|
||||
> {
|
||||
if percent == 0 || percent > 100 {
|
||||
return Err(anyhow::anyhow!("Percentage must be between 1 and 100"));
|
||||
}
|
||||
|
||||
@@ -8,16 +8,16 @@ pub async fn fetch_address_lookup_table_account(
|
||||
lookup_table_address: &Pubkey,
|
||||
) -> Result<AddressLookupTableAccount, anyhow::Error> {
|
||||
let account = rpc.get_account(lookup_table_address).await?;
|
||||
|
||||
|
||||
// Parse address lookup table manually
|
||||
// Layout: 4 bytes (type) + 4 bytes (deactivation_slot) + 4 bytes (last_extended_slot) + 1 byte (last_extended_slot_start_index) + 1 byte (authority) + padding
|
||||
// Then addresses start at offset 56, each address is 32 bytes
|
||||
// First 4 bytes indicate if initialized (should be 1 or 2)
|
||||
|
||||
|
||||
if account.data.len() < 56 {
|
||||
return Err(anyhow::anyhow!("Address lookup table account data too short"));
|
||||
}
|
||||
|
||||
|
||||
// Read number of addresses (stored at offset 20 as u32, but we need to scan the bitmap)
|
||||
// Actually simpler: addresses start at offset 56, count from bitmap at offset 8-20
|
||||
let mut addresses = Vec::new();
|
||||
@@ -30,10 +30,8 @@ pub async fn fetch_address_lookup_table_account(
|
||||
}
|
||||
offset += 32;
|
||||
}
|
||||
|
||||
let address_lookup_table_account = AddressLookupTableAccount {
|
||||
key: *lookup_table_address,
|
||||
addresses,
|
||||
};
|
||||
|
||||
let address_lookup_table_account =
|
||||
AddressLookupTableAccount { key: *lookup_table_address, addresses };
|
||||
Ok(address_lookup_table_account)
|
||||
}
|
||||
|
||||
@@ -34,10 +34,7 @@ fn extract_swqos_error_message(s: &str) -> String {
|
||||
// Try parse as JSON only when input looks like JSON (avoid parsing long non-JSON strings)
|
||||
if s.starts_with('{') {
|
||||
if let Ok(v) = serde_json::from_str::<serde_json::Value>(s) {
|
||||
let obj = v
|
||||
.get("error")
|
||||
.and_then(|e| e.as_object())
|
||||
.or_else(|| v.as_object());
|
||||
let obj = v.get("error").and_then(|e| e.as_object()).or_else(|| v.as_object());
|
||||
if let Some(o) = obj {
|
||||
if let Some(m) = o.get("message").and_then(|x| x.as_str()) {
|
||||
return m.to_string();
|
||||
@@ -69,11 +66,7 @@ pub fn set_sdk_log_enabled(enabled: bool) {
|
||||
|
||||
/// Aligned log: ` [Soyas ] Buy submitted: 13.936 µs`. Call only when sdk_log_enabled().
|
||||
#[inline]
|
||||
pub fn log_swqos_submitted(
|
||||
provider: &str,
|
||||
trade_type: impl fmt::Display,
|
||||
elapsed: Duration,
|
||||
) {
|
||||
pub fn log_swqos_submitted(provider: &str, trade_type: impl fmt::Display, elapsed: Duration) {
|
||||
println!(
|
||||
" [{:width$}] {} submitted: {}",
|
||||
provider,
|
||||
|
||||
+9
-2
@@ -94,8 +94,15 @@ pub fn create_associated_token_account_use_seed(
|
||||
let len = 165;
|
||||
// 🔧 修复:create_account_with_seed 的第3个参数必须是 payer(与第92行生成地址时使用的 base 一致)
|
||||
// 否则创建的账户地址与 ata_like 不匹配,导致 initializeAccount3 失败
|
||||
let create_acc =
|
||||
system_instruction::create_account_with_seed(payer, &ata_like, payer, seed, rent, len, token_program);
|
||||
let create_acc = system_instruction::create_account_with_seed(
|
||||
payer,
|
||||
&ata_like,
|
||||
payer,
|
||||
seed,
|
||||
rent,
|
||||
len,
|
||||
token_program,
|
||||
);
|
||||
|
||||
let init_acc = if is_2022_token {
|
||||
crate::common::spl_token_2022::initialize_account3(&token_program, &ata_like, mint, owner)?
|
||||
|
||||
@@ -20,10 +20,7 @@ pub fn close_account(
|
||||
let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
|
||||
accounts.push(AccountMeta::new(*account_pubkey, false));
|
||||
accounts.push(AccountMeta::new(*destination_pubkey, false));
|
||||
accounts.push(AccountMeta::new_readonly(
|
||||
*owner_pubkey,
|
||||
signer_pubkeys.is_empty(),
|
||||
));
|
||||
accounts.push(AccountMeta::new_readonly(*owner_pubkey, signer_pubkeys.is_empty()));
|
||||
for signer_pubkey in signer_pubkeys.iter() {
|
||||
accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
|
||||
}
|
||||
|
||||
+2
-3
@@ -95,9 +95,8 @@ pub struct TradeConfig {
|
||||
/// (Astralane QUIC `:9000` or Plain/Binary HTTP `mev-protect=true`, BlockRazor sandwichMitigation)
|
||||
/// use their MEV-protected endpoints/modes. Default false (no MEV protection, lower latency).
|
||||
pub mev_protection: bool,
|
||||
/// Use PumpFun V2 instructions (buy_v2 / sell_v2, 27-account metas, quote_mint support).
|
||||
/// Default: `false` — uses V1 instructions (18-account metas, legacy SOL-paired, smaller transaction).
|
||||
/// Set to `true` when PumpFun officially deploys V2 on mainnet.
|
||||
/// Use PumpFun V2 instructions (buy_v2 / sell_v2, 27/26-account metas, quote_mint support).
|
||||
/// Default: `false` keeps legacy SOL-paired instructions for smaller transactions; V2 is the official future-proof interface.
|
||||
pub use_pumpfun_v2: bool,
|
||||
}
|
||||
|
||||
|
||||
+8
-10
@@ -212,7 +212,7 @@ pub const SWQOS_ENDPOINTS_ZERO_SLOT: [&str; 10] = [
|
||||
"http://de2.0slot.trade", // Use de2 for TSW, and de1 for OVH
|
||||
"http://ams.0slot.trade",
|
||||
"http://ams.0slot.trade", // Dublin: 无 IE 专用;在已公布 EU 点中选距爱尔兰最近的 ams(相对 de2 等)
|
||||
"http://la.0slot.trade", // SLC: no UT PoP; nearest US-West published host
|
||||
"http://la.0slot.trade", // SLC: no UT PoP; nearest US-West published host
|
||||
"http://jp.0slot.trade",
|
||||
"http://jp.0slot.trade", // SG: 无本地 PoP;已公布 APAC 仅 jp,为表中离新加坡最近的大圆距离
|
||||
"http://ams.0slot.trade", // London: 无 UK 专用;已公布 EU 点中 ams 距伦敦最近之一
|
||||
@@ -252,11 +252,11 @@ pub const SWQOS_ENDPOINTS_NODE1: [&str; 10] = [
|
||||
"http://fra.node1.me",
|
||||
"http://ams.node1.me",
|
||||
"http://lon.node1.me", // Dublin: 已公布中英爱区域用 lon(地理上近爱尔兰)
|
||||
"http://ny.node1.me", // SLC: 已公布美国仅 ny;美西无 PoP,受可用区限制复用美东
|
||||
"http://ny.node1.me", // SLC: 已公布美国仅 ny;美西无 PoP,受可用区限制复用美东
|
||||
"http://tk.node1.me",
|
||||
"http://tk.node1.me", // SG: 已公布 APAC 仅 tk;地理上为表中离 SG 最近
|
||||
"http://lon.node1.me",
|
||||
"http://ny.node1.me", // LosAngeles: 同上,美国仅 ny 入口
|
||||
"http://ny.node1.me", // LosAngeles: 同上,美国仅 ny 入口
|
||||
"http://fra.node1.me", // Default: 非地理区域;与 QUIC 对齐为 EU 枢纽
|
||||
];
|
||||
|
||||
@@ -359,7 +359,7 @@ pub const SWQOS_ENDPOINTS_ASTRALANE_QUIC: [&str; 10] = [
|
||||
"fr.gateway.astralane.io:7000",
|
||||
"ams.gateway.astralane.io:7000",
|
||||
"ams.gateway.astralane.io:7000", // Dublin: 同 HTTP
|
||||
"la.gateway.astralane.io:7000", // SLC: 美西 la 为最近已公布美区入口
|
||||
"la.gateway.astralane.io:7000", // SLC: 美西 la 为最近已公布美区入口
|
||||
"jp.gateway.astralane.io:7000",
|
||||
"sg.gateway.astralane.io:7000",
|
||||
"ams.gateway.astralane.io:7000", // London: 同 HTTP
|
||||
@@ -503,12 +503,10 @@ mod tests {
|
||||
#[test]
|
||||
fn node1_http_host_matches_quic_without_port() {
|
||||
for i in 0..10 {
|
||||
let http_host = SWQOS_ENDPOINTS_NODE1[i]
|
||||
.strip_prefix("http://")
|
||||
.expect("NODE1 HTTP URL");
|
||||
let quic_host = SWQOS_ENDPOINTS_NODE1_QUIC[i]
|
||||
.strip_suffix(":16666")
|
||||
.expect("NODE1 QUIC endpoint");
|
||||
let http_host =
|
||||
SWQOS_ENDPOINTS_NODE1[i].strip_prefix("http://").expect("NODE1 HTTP URL");
|
||||
let quic_host =
|
||||
SWQOS_ENDPOINTS_NODE1_QUIC[i].strip_suffix(":16666").expect("NODE1 QUIC endpoint");
|
||||
assert_eq!(http_host, quic_host, "Node1 HTTP vs QUIC host mismatch at index {}", i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
pub mod bonk;
|
||||
pub mod meteora_damm_v2;
|
||||
pub(crate) mod pumpfun_ix_data;
|
||||
pub(crate) mod pumpswap_ix_data;
|
||||
pub mod pumpfun;
|
||||
pub(crate) mod pumpfun_ix_data;
|
||||
pub mod pumpswap;
|
||||
pub(crate) mod pumpswap_ix_data;
|
||||
pub mod raydium_amm_v4;
|
||||
pub mod raydium_cpmm;
|
||||
pub mod utils;
|
||||
|
||||
+119
-79
@@ -1,9 +1,8 @@
|
||||
//! Pump.fun bonding-curve swap ix assembly ([`SwapParams`](crate::trading::core::params::SwapParams)).
|
||||
//!
|
||||
//! Runtime flag: set `SwapParams.use_pumpfun_v2 = true` to use `buy_v2` / `sell_v2` /
|
||||
//! `buy_exact_quote_in_v2` (27-account metas, quote_mint support).
|
||||
//! Default (`false`) uses V1 instructions (18-account metas, legacy SOL-paired).
|
||||
//! PumpFun V2 is NOT live on mainnet yet — keep `false` until officially deployed.
|
||||
//! `buy_exact_quote_in_v2` (27/26-account unified metas, quote_mint support).
|
||||
//! Default (`false`) keeps the smaller legacy SOL-paired instruction layout for latency.
|
||||
|
||||
use crate::{
|
||||
common::spl_token::close_account,
|
||||
@@ -15,11 +14,9 @@ use crate::{
|
||||
};
|
||||
use crate::{
|
||||
instruction::utils::pumpfun::{
|
||||
accounts, get_bonding_curve_pda,
|
||||
get_user_volume_accumulator_pda,
|
||||
pump_fun_fee_recipient_meta,
|
||||
resolve_creator_vault_for_ix_with_fee_sharing,
|
||||
accounts, get_bonding_curve_pda, get_user_volume_accumulator_pda,
|
||||
global_constants::{self},
|
||||
pump_fun_fee_recipient_meta, resolve_creator_vault_for_ix_with_fee_sharing,
|
||||
},
|
||||
utils::calc::{
|
||||
common::{calculate_with_slippage_buy, calculate_with_slippage_sell},
|
||||
@@ -31,7 +28,10 @@ use solana_sdk::instruction::AccountMeta;
|
||||
use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signer::Signer};
|
||||
|
||||
#[inline]
|
||||
fn effective_pump_mint_token_program(protocol_params: &PumpFunParams) -> Pubkey {
|
||||
fn effective_pump_mint_token_program(mint: &Pubkey, protocol_params: &PumpFunParams) -> Pubkey {
|
||||
if mint.to_string().ends_with("pump") {
|
||||
return TOKEN_PROGRAM_2022;
|
||||
}
|
||||
let tp = protocol_params.token_program;
|
||||
if tp == Pubkey::default() {
|
||||
TOKEN_PROGRAM_2022
|
||||
@@ -106,12 +106,7 @@ fn build_buy_v1(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
¶ms.output_mint,
|
||||
protocol_params.fee_sharing_creator_vault_if_active,
|
||||
)
|
||||
.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"creator_vault PDA derivation failed (creator={})",
|
||||
creator
|
||||
)
|
||||
})?;
|
||||
.ok_or_else(|| anyhow!("creator_vault PDA derivation failed (creator={})", creator))?;
|
||||
|
||||
let buy_token_amount = match params.fixed_output_amount {
|
||||
Some(amount) => amount,
|
||||
@@ -131,7 +126,7 @@ fn build_buy_v1(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
})?;
|
||||
|
||||
let is_mayhem_mode = bonding_curve.is_mayhem_mode;
|
||||
let token_program = effective_pump_mint_token_program(protocol_params);
|
||||
let token_program = effective_pump_mint_token_program(¶ms.output_mint, protocol_params);
|
||||
let token_program_meta = if token_program == TOKEN_PROGRAM_2022 {
|
||||
crate::constants::TOKEN_PROGRAM_2022_META
|
||||
} else {
|
||||
@@ -174,11 +169,7 @@ fn build_buy_v1(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
let track_volume_val = if bonding_curve.is_cashback_coin { 1u8 } else { 0u8 };
|
||||
let buy_data = if params.use_exact_sol_amount.unwrap_or(true) {
|
||||
let min_tokens_out = calculate_with_slippage_sell(buy_token_amount, slippage_bp);
|
||||
encode_pumpfun_buy_exact_sol_in_ix_data(
|
||||
lamports_in,
|
||||
min_tokens_out,
|
||||
track_volume_val,
|
||||
)
|
||||
encode_pumpfun_buy_exact_sol_in_ix_data(lamports_in, min_tokens_out, track_volume_val)
|
||||
} else {
|
||||
encode_pumpfun_buy_ix_data(buy_token_amount, max_sol_cost, track_volume_val)
|
||||
};
|
||||
@@ -208,16 +199,9 @@ fn build_buy_v1(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
accounts::FEE_PROGRAM_META,
|
||||
];
|
||||
metas.push(AccountMeta::new_readonly(bonding_curve_v2, false));
|
||||
metas.push(AccountMeta::new(
|
||||
get_protocol_extra_fee_recipient_random(),
|
||||
false,
|
||||
));
|
||||
metas.push(AccountMeta::new(get_protocol_extra_fee_recipient_random(), false));
|
||||
|
||||
instructions.push(Instruction::new_with_bytes(
|
||||
accounts::PUMPFUN,
|
||||
&buy_data,
|
||||
metas,
|
||||
));
|
||||
instructions.push(Instruction::new_with_bytes(accounts::PUMPFUN, &buy_data, metas));
|
||||
|
||||
Ok(instructions)
|
||||
}
|
||||
@@ -262,10 +246,7 @@ fn build_sell_v1(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
protocol_params.fee_sharing_creator_vault_if_active,
|
||||
)
|
||||
.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"creator_vault PDA derivation failed (curve_creator={})",
|
||||
bonding_curve.creator
|
||||
)
|
||||
anyhow!("creator_vault PDA derivation failed (curve_creator={})", bonding_curve.creator)
|
||||
})?
|
||||
};
|
||||
|
||||
@@ -288,7 +269,7 @@ fn build_sell_v1(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
})?;
|
||||
|
||||
let is_mayhem_mode = bonding_curve.is_mayhem_mode;
|
||||
let token_program = effective_pump_mint_token_program(protocol_params);
|
||||
let token_program = effective_pump_mint_token_program(¶ms.input_mint, protocol_params);
|
||||
let token_program_meta = if token_program == TOKEN_PROGRAM_2022 {
|
||||
crate::constants::TOKEN_PROGRAM_2022_META
|
||||
} else {
|
||||
@@ -344,19 +325,11 @@ fn build_sell_v1(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
}
|
||||
|
||||
metas.push(AccountMeta::new_readonly(bonding_curve_v2, false));
|
||||
metas.push(AccountMeta::new(
|
||||
get_protocol_extra_fee_recipient_random(),
|
||||
false,
|
||||
));
|
||||
metas.push(AccountMeta::new(get_protocol_extra_fee_recipient_random(), false));
|
||||
|
||||
instructions.push(Instruction::new_with_bytes(
|
||||
accounts::PUMPFUN,
|
||||
&sell_data,
|
||||
metas,
|
||||
));
|
||||
instructions.push(Instruction::new_with_bytes(accounts::PUMPFUN, &sell_data, metas));
|
||||
|
||||
if protocol_params.close_token_account_when_sell.unwrap_or(false)
|
||||
|| params.close_input_mint_ata
|
||||
if protocol_params.close_token_account_when_sell.unwrap_or(false) || params.close_input_mint_ata
|
||||
{
|
||||
instructions.push(close_account(
|
||||
&token_program,
|
||||
@@ -403,12 +376,7 @@ fn build_buy_v2(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
¶ms.output_mint,
|
||||
protocol_params.fee_sharing_creator_vault_if_active,
|
||||
)
|
||||
.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"creator_vault PDA derivation failed (creator={})",
|
||||
creator
|
||||
)
|
||||
})?;
|
||||
.ok_or_else(|| anyhow!("creator_vault PDA derivation failed (creator={})", creator))?;
|
||||
|
||||
let buy_token_amount = match params.fixed_output_amount {
|
||||
Some(amount) => amount,
|
||||
@@ -428,7 +396,8 @@ fn build_buy_v2(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
})?;
|
||||
|
||||
let is_mayhem_mode = bonding_curve.is_mayhem_mode;
|
||||
let base_token_program = effective_pump_mint_token_program(protocol_params);
|
||||
let base_token_program =
|
||||
effective_pump_mint_token_program(¶ms.output_mint, protocol_params);
|
||||
let base_token_program_meta = if base_token_program == TOKEN_PROGRAM_2022 {
|
||||
crate::constants::TOKEN_PROGRAM_2022_META
|
||||
} else {
|
||||
@@ -490,10 +459,9 @@ fn build_buy_v2(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
"e_token_program,
|
||||
);
|
||||
|
||||
let sharing_config =
|
||||
get_fee_sharing_config_pda(¶ms.output_mint).ok_or_else(|| {
|
||||
anyhow!("sharing_config PDA derivation failed for mint {}", params.output_mint)
|
||||
})?;
|
||||
let sharing_config = get_fee_sharing_config_pda(¶ms.output_mint).ok_or_else(|| {
|
||||
anyhow!("sharing_config PDA derivation failed for mint {}", params.output_mint)
|
||||
})?;
|
||||
|
||||
let user_volume_accumulator = get_user_volume_accumulator_pda(¶ms.payer.pubkey())
|
||||
.ok_or_else(|| anyhow!("user_volume_accumulator PDA derivation failed"))?;
|
||||
@@ -567,11 +535,7 @@ fn build_buy_v2(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
accounts::PUMPFUN_META,
|
||||
];
|
||||
|
||||
instructions.push(Instruction::new_with_bytes(
|
||||
accounts::PUMPFUN,
|
||||
&buy_data,
|
||||
metas,
|
||||
));
|
||||
instructions.push(Instruction::new_with_bytes(accounts::PUMPFUN, &buy_data, metas));
|
||||
|
||||
Ok(instructions)
|
||||
}
|
||||
@@ -616,10 +580,7 @@ fn build_sell_v2(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
protocol_params.fee_sharing_creator_vault_if_active,
|
||||
)
|
||||
.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"creator_vault PDA derivation failed (curve_creator={})",
|
||||
bonding_curve.creator
|
||||
)
|
||||
anyhow!("creator_vault PDA derivation failed (curve_creator={})", bonding_curve.creator)
|
||||
})?
|
||||
};
|
||||
|
||||
@@ -642,7 +603,7 @@ fn build_sell_v2(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
})?;
|
||||
|
||||
let is_mayhem_mode = bonding_curve.is_mayhem_mode;
|
||||
let base_token_program = effective_pump_mint_token_program(protocol_params);
|
||||
let base_token_program = effective_pump_mint_token_program(¶ms.input_mint, protocol_params);
|
||||
let base_token_program_meta = if base_token_program == TOKEN_PROGRAM_2022 {
|
||||
crate::constants::TOKEN_PROGRAM_2022_META
|
||||
} else {
|
||||
@@ -762,14 +723,9 @@ fn build_sell_v2(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
accounts::PUMPFUN_META,
|
||||
];
|
||||
|
||||
instructions.push(Instruction::new_with_bytes(
|
||||
accounts::PUMPFUN,
|
||||
&sell_data,
|
||||
metas,
|
||||
));
|
||||
instructions.push(Instruction::new_with_bytes(accounts::PUMPFUN, &sell_data, metas));
|
||||
|
||||
if protocol_params.close_token_account_when_sell.unwrap_or(false)
|
||||
|| params.close_input_mint_ata
|
||||
if protocol_params.close_token_account_when_sell.unwrap_or(false) || params.close_input_mint_ata
|
||||
{
|
||||
instructions.push(close_account(
|
||||
&base_token_program,
|
||||
@@ -796,18 +752,90 @@ pub fn claim_cashback_pumpfun_instruction(payer: &Pubkey) -> Option<Instruction>
|
||||
AccountMeta::new(user_volume_accumulator, false),
|
||||
crate::constants::SYSTEM_PROGRAM_META,
|
||||
];
|
||||
let ix = Instruction::new_with_bytes(
|
||||
accounts::PUMPFUN,
|
||||
&CLAIM_CASHBACK_DISCRIMINATOR,
|
||||
ix_accounts,
|
||||
);
|
||||
let ix =
|
||||
Instruction::new_with_bytes(accounts::PUMPFUN, &CLAIM_CASHBACK_DISCRIMINATOR, ix_accounts);
|
||||
Some(ix)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{instruction::pumpfun_ix_data, trading::core::params::PumpFunParams};
|
||||
use crate::{
|
||||
common::{bonding_curve::BondingCurveAccount, GasFeeStrategy},
|
||||
constants::TOKEN_PROGRAM,
|
||||
trading::core::params::{DexParamEnum, PumpFunParams, SwapParams},
|
||||
};
|
||||
use solana_sdk::signature::Keypair;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn pump_mint() -> Pubkey {
|
||||
"E3JvmGcGFDzhu2Cnxyeq5BRvN7HH9JZUsfAUh2v8pump".parse().unwrap()
|
||||
}
|
||||
|
||||
fn swap_params_for_buy(mint: Pubkey, token_program: Pubkey) -> SwapParams {
|
||||
let bonding_curve =
|
||||
crate::instruction::utils::pumpfun::get_bonding_curve_pda(&mint).unwrap();
|
||||
let creator = Pubkey::new_unique();
|
||||
let creator_vault =
|
||||
crate::instruction::utils::pumpfun::get_creator_vault_pda(&creator).unwrap();
|
||||
let bc = BondingCurveAccount {
|
||||
account: bonding_curve,
|
||||
virtual_token_reserves: global_constants::INITIAL_VIRTUAL_TOKEN_RESERVES,
|
||||
virtual_sol_reserves: global_constants::INITIAL_VIRTUAL_SOL_RESERVES,
|
||||
real_token_reserves: global_constants::INITIAL_REAL_TOKEN_RESERVES,
|
||||
creator,
|
||||
..Default::default()
|
||||
};
|
||||
let params = PumpFunParams {
|
||||
bonding_curve: Arc::new(bc),
|
||||
associated_bonding_curve: Pubkey::default(),
|
||||
observed_trade_creator: Some(creator),
|
||||
creator_vault,
|
||||
fee_sharing_creator_vault_if_active: None,
|
||||
token_program,
|
||||
close_token_account_when_sell: None,
|
||||
fee_recipient: global_constants::FEE_RECIPIENT,
|
||||
quote_mint: Pubkey::default(),
|
||||
use_v2_ix: false,
|
||||
};
|
||||
|
||||
SwapParams {
|
||||
rpc: None,
|
||||
payer: Arc::new(Keypair::new()),
|
||||
trade_type: crate::swqos::TradeType::Buy,
|
||||
input_mint: crate::constants::SOL_TOKEN_ACCOUNT,
|
||||
input_token_program: None,
|
||||
output_mint: mint,
|
||||
output_token_program: None,
|
||||
input_amount: Some(10_000_000),
|
||||
slippage_basis_points: Some(300),
|
||||
address_lookup_table_account: None,
|
||||
recent_blockhash: None,
|
||||
wait_tx_confirmed: false,
|
||||
protocol_params: DexParamEnum::PumpFun(params),
|
||||
open_seed_optimize: true,
|
||||
swqos_clients: Arc::new(Vec::new()),
|
||||
middleware_manager: None,
|
||||
durable_nonce: None,
|
||||
with_tip: true,
|
||||
create_input_mint_ata: false,
|
||||
close_input_mint_ata: false,
|
||||
create_output_mint_ata: true,
|
||||
close_output_mint_ata: false,
|
||||
fixed_output_amount: None,
|
||||
gas_fee_strategy: GasFeeStrategy::new(),
|
||||
simulate: false,
|
||||
log_enabled: false,
|
||||
use_dedicated_sender_threads: false,
|
||||
sender_thread_cores: None,
|
||||
max_sender_concurrency: 0,
|
||||
effective_core_ids: Arc::new(Vec::new()),
|
||||
check_min_tip: false,
|
||||
grpc_recv_us: None,
|
||||
use_exact_sol_amount: Some(true),
|
||||
use_pumpfun_v2: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_claim_cashback_instruction() {
|
||||
@@ -816,4 +844,16 @@ mod tests {
|
||||
assert_eq!(ix.accounts.len(), 3);
|
||||
assert_eq!(ix.accounts[0].pubkey, payer);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pump_suffix_buy_forces_token_2022_even_with_legacy_cached_program() {
|
||||
crate::common::seed::set_default_rents();
|
||||
let params = swap_params_for_buy(pump_mint(), TOKEN_PROGRAM);
|
||||
let instructions = build_buy_v1(¶ms).unwrap();
|
||||
|
||||
assert_eq!(instructions.len(), 3);
|
||||
assert_eq!(instructions[1].program_id, TOKEN_PROGRAM_2022);
|
||||
assert_eq!(instructions[2].accounts[8].pubkey, TOKEN_PROGRAM_2022);
|
||||
assert_eq!(instructions[2].accounts.len(), 18);
|
||||
}
|
||||
}
|
||||
|
||||
+143
-174
@@ -12,173 +12,166 @@ use solana_sdk::{
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
|
||||
// --- seeds -------------------------------------------------------------
|
||||
|
||||
pub mod seeds {
|
||||
|
||||
/// Seed for bonding curve PDAs (`["bonding-curve", mint]`).
|
||||
pub const BONDING_CURVE_SEED: &[u8] = b"bonding-curve";
|
||||
/// Seed for bonding curve v2 PDA (`["bonding-curve-v2", mint]`).
|
||||
pub const BONDING_CURVE_V2_SEED: &[u8] = b"bonding-curve-v2";
|
||||
/// Creator vault PDA seeds prefix (`["creator-vault", authority]`).
|
||||
pub const CREATOR_VAULT_SEED: &[u8] = b"creator-vault";
|
||||
/// Metadata PDA seeds prefix.
|
||||
pub const METADATA_SEED: &[u8] = b"metadata";
|
||||
/// User volume accumulator for cashback / bonding-curve UX.
|
||||
pub const USER_VOLUME_ACCUMULATOR_SEED: &[u8] = b"user_volume_accumulator";
|
||||
/// Global volume accumulator.
|
||||
pub const GLOBAL_VOLUME_ACCUMULATOR_SEED: &[u8] = b"global_volume_accumulator";
|
||||
pub const FEE_CONFIG_SEED: &[u8] = b"fee_config";
|
||||
/// `feeSharingConfig` PDA under pump-fees (`feeSharingConfigPda`).
|
||||
pub const SHARING_CONFIG_SEED: &[u8] = b"sharing-config";
|
||||
/// Seed for bonding curve PDAs (`["bonding-curve", mint]`).
|
||||
pub const BONDING_CURVE_SEED: &[u8] = b"bonding-curve";
|
||||
/// Seed for bonding curve v2 PDA (`["bonding-curve-v2", mint]`).
|
||||
pub const BONDING_CURVE_V2_SEED: &[u8] = b"bonding-curve-v2";
|
||||
/// Creator vault PDA seeds prefix (`["creator-vault", authority]`).
|
||||
pub const CREATOR_VAULT_SEED: &[u8] = b"creator-vault";
|
||||
/// Metadata PDA seeds prefix.
|
||||
pub const METADATA_SEED: &[u8] = b"metadata";
|
||||
/// User volume accumulator for cashback / bonding-curve UX.
|
||||
pub const USER_VOLUME_ACCUMULATOR_SEED: &[u8] = b"user_volume_accumulator";
|
||||
/// Global volume accumulator.
|
||||
pub const GLOBAL_VOLUME_ACCUMULATOR_SEED: &[u8] = b"global_volume_accumulator";
|
||||
pub const FEE_CONFIG_SEED: &[u8] = b"fee_config";
|
||||
/// `feeSharingConfig` PDA under pump-fees (`feeSharingConfigPda`).
|
||||
pub const SHARING_CONFIG_SEED: &[u8] = b"sharing-config";
|
||||
}
|
||||
|
||||
|
||||
pub mod global_constants {
|
||||
|
||||
use solana_sdk::{pubkey, pubkey::Pubkey};
|
||||
|
||||
use solana_sdk::{pubkey, pubkey::Pubkey};
|
||||
pub const INITIAL_VIRTUAL_TOKEN_RESERVES: u64 = 1_073_000_000_000_000;
|
||||
pub const INITIAL_VIRTUAL_SOL_RESERVES: u64 = 30_000_000_000;
|
||||
pub const INITIAL_REAL_TOKEN_RESERVES: u64 = 793_100_000_000_000;
|
||||
pub const TOKEN_TOTAL_SUPPLY: u64 = 1_000_000_000_000_000;
|
||||
pub const FEE_BASIS_POINTS: u64 = 95;
|
||||
pub const ENABLE_MIGRATE: bool = false;
|
||||
pub const POOL_MIGRATION_FEE: u64 = 15_000_001;
|
||||
pub const CREATOR_FEE: u64 = 30;
|
||||
pub const SCALE: u64 = 1_000_000;
|
||||
pub const LAMPORTS_PER_SOL: u64 = 1_000_000_000;
|
||||
pub const COMPLETION_LAMPORTS: u64 = 85 * LAMPORTS_PER_SOL;
|
||||
|
||||
pub const INITIAL_VIRTUAL_TOKEN_RESERVES: u64 = 1_073_000_000_000_000;
|
||||
pub const INITIAL_VIRTUAL_SOL_RESERVES: u64 = 30_000_000_000;
|
||||
pub const INITIAL_REAL_TOKEN_RESERVES: u64 = 793_100_000_000_000;
|
||||
pub const TOKEN_TOTAL_SUPPLY: u64 = 1_000_000_000_000_000;
|
||||
pub const FEE_BASIS_POINTS: u64 = 95;
|
||||
pub const ENABLE_MIGRATE: bool = false;
|
||||
pub const POOL_MIGRATION_FEE: u64 = 15_000_001;
|
||||
pub const CREATOR_FEE: u64 = 30;
|
||||
pub const SCALE: u64 = 1_000_000;
|
||||
pub const LAMPORTS_PER_SOL: u64 = 1_000_000_000;
|
||||
pub const COMPLETION_LAMPORTS: u64 = 85 * LAMPORTS_PER_SOL;
|
||||
pub const FEE_RECIPIENT: Pubkey = pubkey!("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV");
|
||||
pub const FEE_RECIPIENT_META: solana_sdk::instruction::AccountMeta =
|
||||
solana_sdk::instruction::AccountMeta {
|
||||
pubkey: FEE_RECIPIENT,
|
||||
is_signer: false,
|
||||
is_writable: true,
|
||||
};
|
||||
|
||||
pub const FEE_RECIPIENT: Pubkey = pubkey!("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV");
|
||||
pub const FEE_RECIPIENT_META: solana_sdk::instruction::AccountMeta =
|
||||
solana_sdk::instruction::AccountMeta {
|
||||
pubkey: FEE_RECIPIENT,
|
||||
is_signer: false,
|
||||
is_writable: true,
|
||||
};
|
||||
pub const MAYHEM_FEE_RECIPIENTS: [Pubkey; 8] = [
|
||||
pubkey!("GesfTA3X2arioaHp8bbKdjG9vJtskViWACZoYvxp4twS"),
|
||||
pubkey!("4budycTjhs9fD6xw62VBducVTNgMgJJ5BgtKq7mAZwn6"),
|
||||
pubkey!("8SBKzEQU4nLSzcwF4a74F2iaUDQyTfjGndn6qUWBnrpR"),
|
||||
pubkey!("4UQeTP1T39KZ9Sfxzo3WR5skgsaP6NZa87BAkuazLEKH"),
|
||||
pubkey!("8sNeir4QsLsJdYpc9RZacohhK1Y5FLU3nC5LXgYB4aa6"),
|
||||
pubkey!("Fh9HmeLNUMVCvejxCtCL2DbYaRyBFVJ5xrWkLnMH6fdk"),
|
||||
pubkey!("463MEnMeGyJekNZFQSTUABBEbLnvMTALbT6ZmsxAbAdq"),
|
||||
pubkey!("6AUH3WEHucYZyC61hqpqYUWVto5qA5hjHuNQ32GNnNxA"),
|
||||
];
|
||||
pub const MAYHEM_FEE_RECIPIENT: Pubkey = MAYHEM_FEE_RECIPIENTS[0];
|
||||
pub const MAYHEM_FEE_RECIPIENT_META: solana_sdk::instruction::AccountMeta =
|
||||
solana_sdk::instruction::AccountMeta {
|
||||
pubkey: MAYHEM_FEE_RECIPIENT,
|
||||
is_signer: false,
|
||||
is_writable: true,
|
||||
};
|
||||
|
||||
pub const MAYHEM_FEE_RECIPIENTS: [Pubkey; 8] = [
|
||||
pubkey!("GesfTA3X2arioaHp8bbKdjG9vJtskViWACZoYvxp4twS"),
|
||||
pubkey!("4budycTjhs9fD6xw62VBducVTNgMgJJ5BgtKq7mAZwn6"),
|
||||
pubkey!("8SBKzEQU4nLSzcwF4a74F2iaUDQyTfjGndn6qUWBnrpR"),
|
||||
pubkey!("4UQeTP1T39KZ9Sfxzo3WR5skgsaP6NZa87BAkuazLEKH"),
|
||||
pubkey!("8sNeir4QsLsJdYpc9RZacohhK1Y5FLU3nC5LXgYB4aa6"),
|
||||
pubkey!("Fh9HmeLNUMVCvejxCtCL2DbYaRyBFVJ5xrWkLnMH6fdk"),
|
||||
pubkey!("463MEnMeGyJekNZFQSTUABBEbLnvMTALbT6ZmsxAbAdq"),
|
||||
pubkey!("6AUH3WEHucYZyC61hqpqYUWVto5qA5hjHuNQ32GNnNxA"),
|
||||
];
|
||||
pub const MAYHEM_FEE_RECIPIENT: Pubkey = MAYHEM_FEE_RECIPIENTS[0];
|
||||
pub const MAYHEM_FEE_RECIPIENT_META: solana_sdk::instruction::AccountMeta =
|
||||
solana_sdk::instruction::AccountMeta {
|
||||
pubkey: MAYHEM_FEE_RECIPIENT,
|
||||
is_signer: false,
|
||||
is_writable: true,
|
||||
};
|
||||
pub const GLOBAL_ACCOUNT: Pubkey = pubkey!("4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf");
|
||||
pub const GLOBAL_ACCOUNT_META: solana_sdk::instruction::AccountMeta =
|
||||
solana_sdk::instruction::AccountMeta {
|
||||
pubkey: GLOBAL_ACCOUNT,
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
};
|
||||
|
||||
pub const GLOBAL_ACCOUNT: Pubkey = pubkey!("4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf");
|
||||
pub const GLOBAL_ACCOUNT_META: solana_sdk::instruction::AccountMeta =
|
||||
solana_sdk::instruction::AccountMeta {
|
||||
pubkey: GLOBAL_ACCOUNT,
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
};
|
||||
pub const AUTHORITY: Pubkey = pubkey!("FFWtrEQ4B4PKQoVuHYzZq8FabGkVatYzDpEVHsK5rrhF");
|
||||
pub const WITHDRAW_AUTHORITY: Pubkey = pubkey!("39azUYFWPz3VHgKCf3VChUwbpURdCHRxjWVowf5jUJjg");
|
||||
|
||||
pub const AUTHORITY: Pubkey = pubkey!("FFWtrEQ4B4PKQoVuHYzZq8FabGkVatYzDpEVHsK5rrhF");
|
||||
pub const WITHDRAW_AUTHORITY: Pubkey = pubkey!("39azUYFWPz3VHgKCf3VChUwbpURdCHRxjWVowf5jUJjg");
|
||||
pub const PUMPFUN_AMM_FEE_1: Pubkey = pubkey!("7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ");
|
||||
pub const PUMPFUN_AMM_FEE_2: Pubkey = pubkey!("7hTckgnGnLQR6sdH7YkqFTAA7VwTfYFaZ6EhEsU3saCX");
|
||||
pub const PUMPFUN_AMM_FEE_3: Pubkey = pubkey!("9rPYyANsfQZw3DnDmKE3YCQF5E8oD89UXoHn9JFEhJUz");
|
||||
pub const PUMPFUN_AMM_FEE_4: Pubkey = pubkey!("AVmoTthdrX6tKt4nDjco2D775W2YK3sDhxPcMmzUAmTY");
|
||||
pub const PUMPFUN_AMM_FEE_5: Pubkey = pubkey!("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM");
|
||||
pub const PUMPFUN_AMM_FEE_6: Pubkey = pubkey!("FWsW1xNtWscwNmKv6wVsU1iTzRN6wmmk3MjxRP5tT7hz");
|
||||
pub const PUMPFUN_AMM_FEE_7: Pubkey = pubkey!("G5UZAVbAf46s7cKWoyKu8kYTip9DGTpbLZ2qa9Aq69dP");
|
||||
|
||||
pub const PUMPFUN_AMM_FEE_1: Pubkey = pubkey!("7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ");
|
||||
pub const PUMPFUN_AMM_FEE_2: Pubkey = pubkey!("7hTckgnGnLQR6sdH7YkqFTAA7VwTfYFaZ6EhEsU3saCX");
|
||||
pub const PUMPFUN_AMM_FEE_3: Pubkey = pubkey!("9rPYyANsfQZw3DnDmKE3YCQF5E8oD89UXoHn9JFEhJUz");
|
||||
pub const PUMPFUN_AMM_FEE_4: Pubkey = pubkey!("AVmoTthdrX6tKt4nDjco2D775W2YK3sDhxPcMmzUAmTY");
|
||||
pub const PUMPFUN_AMM_FEE_5: Pubkey = pubkey!("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM");
|
||||
pub const PUMPFUN_AMM_FEE_6: Pubkey = pubkey!("FWsW1xNtWscwNmKv6wVsU1iTzRN6wmmk3MjxRP5tT7hz");
|
||||
pub const PUMPFUN_AMM_FEE_7: Pubkey = pubkey!("G5UZAVbAf46s7cKWoyKu8kYTip9DGTpbLZ2qa9Aq69dP");
|
||||
pub const PROTOCOL_EXTRA_FEE_RECIPIENTS: [Pubkey; 8] = [
|
||||
pubkey!("5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD"),
|
||||
pubkey!("9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7"),
|
||||
pubkey!("GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL"),
|
||||
pubkey!("3BpXnfJaUTiwXnJNe7Ej1rcbzqTTQUvLShZaWazebsVR"),
|
||||
pubkey!("5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6"),
|
||||
pubkey!("EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL"),
|
||||
pubkey!("5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD"),
|
||||
pubkey!("A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"),
|
||||
];
|
||||
|
||||
pub const PROTOCOL_EXTRA_FEE_RECIPIENTS: [Pubkey; 8] = [
|
||||
pubkey!("5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD"),
|
||||
pubkey!("9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7"),
|
||||
pubkey!("GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL"),
|
||||
pubkey!("3BpXnfJaUTiwXnJNe7Ej1rcbzqTTQUvLShZaWazebsVR"),
|
||||
pubkey!("5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6"),
|
||||
pubkey!("EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL"),
|
||||
pubkey!("5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD"),
|
||||
pubkey!("A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"),
|
||||
];
|
||||
|
||||
/// Buyback fee recipients (v2 account #9 in buy_v2/sell_v2).
|
||||
/// 对应官方 FEE_RECIPIENTS.md "Buyback (Applies to All)" 池,与主 fee_recipient 池互斥。
|
||||
pub const BUYBACK_FEE_RECIPIENTS: [Pubkey; 8] = [
|
||||
pubkey!("5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD"),
|
||||
pubkey!("9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7"),
|
||||
pubkey!("GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL"),
|
||||
pubkey!("3BpXnfJaUTiwXnJNe7Ej1rcbzqTTQUvLShZaWazebsVR"),
|
||||
pubkey!("5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6"),
|
||||
pubkey!("EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL"),
|
||||
pubkey!("5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD"),
|
||||
pubkey!("A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"),
|
||||
];
|
||||
/// Buyback fee recipients (v2 account #9 in buy_v2/sell_v2).
|
||||
/// 对应官方 FEE_RECIPIENTS.md "Buyback (Applies to All)" 池,与主 fee_recipient 池互斥。
|
||||
pub const BUYBACK_FEE_RECIPIENTS: [Pubkey; 8] = [
|
||||
pubkey!("5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD"),
|
||||
pubkey!("9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7"),
|
||||
pubkey!("GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL"),
|
||||
pubkey!("3BpXnfJaUTiwXnJNe7Ej1rcbzqTTQUvLShZaWazebsVR"),
|
||||
pubkey!("5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6"),
|
||||
pubkey!("EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL"),
|
||||
pubkey!("5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD"),
|
||||
pubkey!("A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
pub mod accounts {
|
||||
|
||||
use solana_sdk::{pubkey, pubkey::Pubkey};
|
||||
|
||||
use solana_sdk::{pubkey, pubkey::Pubkey};
|
||||
pub const PUMPFUN: Pubkey = pubkey!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P");
|
||||
pub const MPL_TOKEN_METADATA: Pubkey = pubkey!("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s");
|
||||
pub const EVENT_AUTHORITY: Pubkey = pubkey!("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1");
|
||||
pub const ASSOCIATED_TOKEN_PROGRAM: Pubkey =
|
||||
pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
|
||||
pub const AMM_PROGRAM: Pubkey = pubkey!("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8");
|
||||
pub const FEE_PROGRAM: Pubkey = pubkey!("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ");
|
||||
pub const GLOBAL_VOLUME_ACCUMULATOR: Pubkey =
|
||||
pubkey!("Hq2wp8uJ9jCPsYgNHex8RtqdvMPfVGoYwjvF1ATiwn2Y");
|
||||
pub const FEE_CONFIG: Pubkey = pubkey!("8Wf5TiAheLUqBrKXeYg2JtAFFMWtKdG2BSFgqUcPVwTt");
|
||||
|
||||
pub const PUMPFUN: Pubkey = pubkey!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P");
|
||||
pub const MPL_TOKEN_METADATA: Pubkey = pubkey!("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s");
|
||||
pub const EVENT_AUTHORITY: Pubkey = pubkey!("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1");
|
||||
pub const ASSOCIATED_TOKEN_PROGRAM: Pubkey =
|
||||
pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
|
||||
pub const AMM_PROGRAM: Pubkey = pubkey!("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8");
|
||||
pub const FEE_PROGRAM: Pubkey = pubkey!("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ");
|
||||
pub const GLOBAL_VOLUME_ACCUMULATOR: Pubkey =
|
||||
pubkey!("Hq2wp8uJ9jCPsYgNHex8RtqdvMPfVGoYwjvF1ATiwn2Y");
|
||||
pub const FEE_CONFIG: Pubkey = pubkey!("8Wf5TiAheLUqBrKXeYg2JtAFFMWtKdG2BSFgqUcPVwTt");
|
||||
pub const PUMPFUN_META: solana_sdk::instruction::AccountMeta =
|
||||
solana_sdk::instruction::AccountMeta {
|
||||
pubkey: PUMPFUN,
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
};
|
||||
|
||||
pub const PUMPFUN_META: solana_sdk::instruction::AccountMeta =
|
||||
solana_sdk::instruction::AccountMeta {
|
||||
pubkey: PUMPFUN,
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
};
|
||||
pub const EVENT_AUTHORITY_META: solana_sdk::instruction::AccountMeta =
|
||||
solana_sdk::instruction::AccountMeta {
|
||||
pubkey: EVENT_AUTHORITY,
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
};
|
||||
|
||||
pub const EVENT_AUTHORITY_META: solana_sdk::instruction::AccountMeta =
|
||||
solana_sdk::instruction::AccountMeta {
|
||||
pubkey: EVENT_AUTHORITY,
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
};
|
||||
pub const FEE_PROGRAM_META: solana_sdk::instruction::AccountMeta =
|
||||
solana_sdk::instruction::AccountMeta {
|
||||
pubkey: FEE_PROGRAM,
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
};
|
||||
|
||||
pub const FEE_PROGRAM_META: solana_sdk::instruction::AccountMeta =
|
||||
solana_sdk::instruction::AccountMeta {
|
||||
pubkey: FEE_PROGRAM,
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
};
|
||||
pub const GLOBAL_VOLUME_ACCUMULATOR_META: solana_sdk::instruction::AccountMeta =
|
||||
solana_sdk::instruction::AccountMeta {
|
||||
pubkey: GLOBAL_VOLUME_ACCUMULATOR,
|
||||
is_signer: false,
|
||||
is_writable: true,
|
||||
};
|
||||
|
||||
pub const GLOBAL_VOLUME_ACCUMULATOR_META: solana_sdk::instruction::AccountMeta =
|
||||
solana_sdk::instruction::AccountMeta {
|
||||
pubkey: GLOBAL_VOLUME_ACCUMULATOR,
|
||||
is_signer: false,
|
||||
is_writable: true,
|
||||
};
|
||||
|
||||
pub const FEE_CONFIG_META: solana_sdk::instruction::AccountMeta =
|
||||
solana_sdk::instruction::AccountMeta {
|
||||
pubkey: FEE_CONFIG,
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
};
|
||||
pub const FEE_CONFIG_META: solana_sdk::instruction::AccountMeta =
|
||||
solana_sdk::instruction::AccountMeta {
|
||||
pubkey: FEE_CONFIG,
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// --- Anchor / layout constants ---------------------------------------
|
||||
|
||||
|
||||
/// Minimum bonding curve account data length (`sdk.ts` `BONDING_CURVE_NEW_SIZE`).
|
||||
pub const PUMP_BONDING_CURVE_MIN_DATA_LEN: usize = 151;
|
||||
|
||||
@@ -199,7 +192,6 @@ pub const SHARING_CONFIG_ACCOUNT_DISCRIMINATOR: [u8; 8] = [216, 74, 9, 0, 56, 14
|
||||
|
||||
pub(crate) const SHARING_CONFIG_STATUS_ACTIVE: u8 = 1;
|
||||
|
||||
|
||||
// --- Fee recipient pools -----------------------------------------------
|
||||
|
||||
#[inline]
|
||||
@@ -263,11 +255,7 @@ pub fn get_mayhem_fee_recipient_meta_random() -> AccountMeta {
|
||||
let recipient = *global_constants::MAYHEM_FEE_RECIPIENTS
|
||||
.choose(&mut rand::rng())
|
||||
.unwrap_or(&global_constants::MAYHEM_FEE_RECIPIENTS[0]);
|
||||
AccountMeta {
|
||||
pubkey: recipient,
|
||||
is_signer: false,
|
||||
is_writable: true,
|
||||
}
|
||||
AccountMeta { pubkey: recipient, is_signer: false, is_writable: true }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -282,14 +270,8 @@ pub fn get_standard_fee_recipient_meta_random() -> AccountMeta {
|
||||
global_constants::PUMPFUN_AMM_FEE_6,
|
||||
global_constants::PUMPFUN_AMM_FEE_7,
|
||||
];
|
||||
let recipient = *POOL
|
||||
.choose(&mut rand::rng())
|
||||
.unwrap_or(&global_constants::FEE_RECIPIENT);
|
||||
AccountMeta {
|
||||
pubkey: recipient,
|
||||
is_signer: false,
|
||||
is_writable: true,
|
||||
}
|
||||
let recipient = *POOL.choose(&mut rand::rng()).unwrap_or(&global_constants::FEE_RECIPIENT);
|
||||
AccountMeta { pubkey: recipient, is_signer: false, is_writable: true }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -308,15 +290,14 @@ pub fn get_buyback_fee_recipient_random() -> Pubkey {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pump_fun_fee_recipient_meta(observed_fee_recipient: Pubkey, is_mayhem_mode: bool) -> AccountMeta {
|
||||
pub fn pump_fun_fee_recipient_meta(
|
||||
observed_fee_recipient: Pubkey,
|
||||
is_mayhem_mode: bool,
|
||||
) -> AccountMeta {
|
||||
let trust_observation = observed_fee_recipient != Pubkey::default()
|
||||
&& fee_recipient_ok_for_bonding_curve_mode(&observed_fee_recipient, is_mayhem_mode);
|
||||
if trust_observation {
|
||||
AccountMeta {
|
||||
pubkey: observed_fee_recipient,
|
||||
is_signer: false,
|
||||
is_writable: true,
|
||||
}
|
||||
AccountMeta { pubkey: observed_fee_recipient, is_signer: false, is_writable: true }
|
||||
} else if is_mayhem_mode {
|
||||
get_mayhem_fee_recipient_meta_random()
|
||||
} else {
|
||||
@@ -344,7 +325,6 @@ pub fn extend_bonding_curve_account_instruction(
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// --- Cached PDAs + creator_vault resolve ------------------------------
|
||||
|
||||
#[inline]
|
||||
@@ -496,7 +476,6 @@ pub fn get_buy_price(
|
||||
s_u64.min(real_token_reserves)
|
||||
}
|
||||
|
||||
|
||||
// --- RPC -------------------------------------------------------------
|
||||
|
||||
#[inline]
|
||||
@@ -522,9 +501,7 @@ pub async fn fetch_fee_sharing_creator_vault_if_active(
|
||||
return Ok(None);
|
||||
}
|
||||
let mint_on_chain = Pubkey::new_from_array(
|
||||
d[11..43]
|
||||
.try_into()
|
||||
.map_err(|_| anyhow!("SharingConfig mint slice"))?,
|
||||
d[11..43].try_into().map_err(|_| anyhow!("SharingConfig mint slice"))?,
|
||||
);
|
||||
if mint_on_chain != *mint {
|
||||
return Ok(None);
|
||||
@@ -556,13 +533,10 @@ pub async fn fetch_bonding_curve_account(
|
||||
Ok((Arc::new(bonding_curve), bonding_curve_pda))
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
global_constants,
|
||||
phantom_default_creator_vault, pump_fun_fee_recipient_meta,
|
||||
global_constants, phantom_default_creator_vault, pump_fun_fee_recipient_meta,
|
||||
reconcile_mayhem_mode_for_trade, resolve_creator_vault_for_ix,
|
||||
resolve_creator_vault_for_ix_with_fee_sharing, *,
|
||||
};
|
||||
@@ -650,12 +624,8 @@ mod tests {
|
||||
let sharing_pk = get_fee_sharing_config_pda(&mint).unwrap();
|
||||
let vs = get_creator_vault_pda(&sharing_pk).unwrap();
|
||||
assert_ne!(v_derived, vs);
|
||||
let resolved = resolve_creator_vault_for_ix_with_fee_sharing(
|
||||
&creator,
|
||||
v_derived,
|
||||
&mint,
|
||||
Some(vs),
|
||||
);
|
||||
let resolved =
|
||||
resolve_creator_vault_for_ix_with_fee_sharing(&creator, v_derived, &mint, Some(vs));
|
||||
assert_eq!(
|
||||
resolved,
|
||||
Some(v_derived),
|
||||
@@ -671,8 +641,7 @@ mod tests {
|
||||
let vs = get_creator_vault_pda(&sharing_pk).unwrap();
|
||||
let creator_vault = get_creator_vault_pda(&creator).unwrap();
|
||||
assert_ne!(vs, creator_vault);
|
||||
let resolved =
|
||||
resolve_creator_vault_for_ix_with_fee_sharing(&creator, vs, &mint, Some(vs));
|
||||
let resolved = resolve_creator_vault_for_ix_with_fee_sharing(&creator, vs, &mint, Some(vs));
|
||||
assert_eq!(
|
||||
resolved,
|
||||
Some(vs),
|
||||
|
||||
+36
-6
@@ -202,11 +202,20 @@ impl AstralaneClient {
|
||||
let _ = response.bytes().await;
|
||||
if status.is_success() {
|
||||
if crate::common::sdk_log::sdk_log_enabled() {
|
||||
crate::common::sdk_log::log_swqos_submitted("Astralane", trade_type, start_time.elapsed());
|
||||
crate::common::sdk_log::log_swqos_submitted(
|
||||
"Astralane",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if crate::common::sdk_log::sdk_log_enabled() {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("Astralane", trade_type, start_time.elapsed(), format!("status {}", status));
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"Astralane",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
format!("status {}", status),
|
||||
);
|
||||
}
|
||||
return Err(anyhow::anyhow!("Astralane sendTransaction failed: {}", status));
|
||||
}
|
||||
@@ -214,12 +223,21 @@ impl AstralaneClient {
|
||||
AstralaneBackend::Quic(quic) => {
|
||||
if let Err(e) = quic.send_transaction(&body_bytes).await {
|
||||
if crate::common::sdk_log::sdk_log_enabled() {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("Astralane", trade_type, start_time.elapsed(), &e);
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"Astralane",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
&e,
|
||||
);
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
if crate::common::sdk_log::sdk_log_enabled() {
|
||||
crate::common::sdk_log::log_swqos_submitted("Astralane", trade_type, start_time.elapsed());
|
||||
crate::common::sdk_log::log_swqos_submitted(
|
||||
"Astralane",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,14 +248,26 @@ impl AstralaneClient {
|
||||
Err(e) => {
|
||||
if crate::common::sdk_log::sdk_log_enabled() {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" [{:width$}] {} confirmation failed: {:?}", "Astralane", trade_type, start_time.elapsed(), width = crate::common::sdk_log::SWQOS_LABEL_WIDTH);
|
||||
println!(
|
||||
" [{:width$}] {} confirmation failed: {:?}",
|
||||
"Astralane",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
width = crate::common::sdk_log::SWQOS_LABEL_WIDTH
|
||||
);
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
if wait_confirmation && crate::common::sdk_log::sdk_log_enabled() {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" [{:width$}] {} confirmed: {:?}", "Astralane", trade_type, start_time.elapsed(), width = crate::common::sdk_log::SWQOS_LABEL_WIDTH);
|
||||
println!(
|
||||
" [{:width$}] {} confirmed: {:?}",
|
||||
"Astralane",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
width = crate::common::sdk_log::SWQOS_LABEL_WIDTH
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -77,9 +77,7 @@ impl AstralaneQuicClient {
|
||||
"lit.gateway.astralane.io" => {
|
||||
vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(84, 32, 97, 47)), port)]
|
||||
}
|
||||
_ => {
|
||||
Vec::new()
|
||||
}
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,8 +130,8 @@ impl AstralaneQuicClient {
|
||||
/// Generates a self-signed TLS certificate with the API key as the Common Name (CN).
|
||||
pub async fn connect(server_addr: &str, api_key: &str) -> Result<Self> {
|
||||
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||
let candidates = Self::resolve_server_candidates(server_addr)
|
||||
.context("Invalid server address")?;
|
||||
let candidates =
|
||||
Self::resolve_server_candidates(server_addr).context("Invalid server address")?;
|
||||
let addr = candidates[0];
|
||||
|
||||
info!("[astralane-quic] Building TLS config (CN = api_key)");
|
||||
@@ -167,7 +165,8 @@ impl AstralaneQuicClient {
|
||||
}
|
||||
}
|
||||
let connection = connection_opt.ok_or_else(|| {
|
||||
last_err.unwrap_or_else(|| anyhow::anyhow!("Failed to connect to Astralane QUIC server"))
|
||||
last_err
|
||||
.unwrap_or_else(|| anyhow::anyhow!("Failed to connect to Astralane QUIC server"))
|
||||
})?;
|
||||
|
||||
info!("[astralane-quic] Connected at {}", selected_addr);
|
||||
@@ -203,7 +202,8 @@ impl AstralaneQuicClient {
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(last_err.unwrap_or_else(|| anyhow::anyhow!("Failed to reconnect to Astralane QUIC server")))
|
||||
Err(last_err
|
||||
.unwrap_or_else(|| anyhow::anyhow!("Failed to reconnect to Astralane QUIC server")))
|
||||
}
|
||||
|
||||
/// Send a single bincode-serialized `VersionedTransaction`.
|
||||
|
||||
+92
-45
@@ -5,9 +5,9 @@ use rand::seq::IndexedRandom;
|
||||
use reqwest::Client;
|
||||
use std::{sync::Arc, time::Instant};
|
||||
|
||||
use arc_swap::ArcSwap;
|
||||
use solana_transaction_status::UiTransactionEncoding;
|
||||
use std::time::Duration;
|
||||
use arc_swap::ArcSwap;
|
||||
|
||||
use crate::swqos::SwqosClientTrait;
|
||||
use crate::swqos::{SwqosType, TradeType};
|
||||
@@ -18,8 +18,8 @@ use crate::{common::SolanaRpcClient, constants::swqos::BLOCKRAZOR_TIP_ACCOUNTS};
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use tokio::task::JoinHandle;
|
||||
use tonic::transport::Channel;
|
||||
use tonic::metadata::AsciiMetadataValue;
|
||||
use tonic::transport::Channel;
|
||||
|
||||
// Include pre-generated gRPC code
|
||||
pub mod serverpb {
|
||||
@@ -46,7 +46,9 @@ impl BlockRazorGrpcClient {
|
||||
let mut request = tonic::Request::new(serverpb::HealthRequest {});
|
||||
request.metadata_mut().insert("apikey", apikey);
|
||||
|
||||
let response = client.get_health(request).await
|
||||
let response = client
|
||||
.get_health(request)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("gRPC health check failed: {}", e))?;
|
||||
Ok(response.into_inner().status)
|
||||
}
|
||||
@@ -75,7 +77,9 @@ impl BlockRazorGrpcClient {
|
||||
});
|
||||
request.metadata_mut().insert("apikey", apikey);
|
||||
|
||||
let response = client.send_transaction(request).await
|
||||
let response = client
|
||||
.send_transaction(request)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("gRPC send transaction failed: {}", e))?;
|
||||
Ok(response.into_inner().signature)
|
||||
}
|
||||
@@ -151,7 +155,12 @@ impl BlockRazorClient {
|
||||
Ok(Self::new_http(rpc_url, endpoint, auth_token, false))
|
||||
}
|
||||
|
||||
pub async fn new_grpc(rpc_url: String, endpoint: String, auth_token: String, mev_protection: bool) -> Result<Self> {
|
||||
pub async fn new_grpc(
|
||||
rpc_url: String,
|
||||
endpoint: String,
|
||||
auth_token: String,
|
||||
mev_protection: bool,
|
||||
) -> Result<Self> {
|
||||
let rpc_client = SolanaRpcClient::new(rpc_url);
|
||||
|
||||
// 配置 Channel,增加连接超时
|
||||
@@ -162,10 +171,8 @@ impl BlockRazorClient {
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("Failed to connect to gRPC endpoint: {}", e))?;
|
||||
|
||||
let grpc_client = Arc::new(ArcSwap::from_pointee(BlockRazorGrpcClient::new(
|
||||
channel,
|
||||
auth_token.clone(),
|
||||
)));
|
||||
let grpc_client =
|
||||
Arc::new(ArcSwap::from_pointee(BlockRazorGrpcClient::new(channel, auth_token.clone())));
|
||||
let ping_handle = Arc::new(tokio::sync::Mutex::new(None));
|
||||
let stop_ping = Arc::new(AtomicBool::new(false));
|
||||
|
||||
@@ -189,7 +196,12 @@ impl BlockRazorClient {
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
pub fn new_http(rpc_url: String, endpoint: String, auth_token: String, mev_protection: bool) -> Self {
|
||||
pub fn new_http(
|
||||
rpc_url: String,
|
||||
endpoint: String,
|
||||
auth_token: String,
|
||||
mev_protection: bool,
|
||||
) -> Self {
|
||||
let rpc_client = SolanaRpcClient::new(rpc_url);
|
||||
let http_client = default_http_client_builder().user_agent("").build().unwrap();
|
||||
let ping_handle = Arc::new(tokio::sync::Mutex::new(None));
|
||||
@@ -279,7 +291,10 @@ impl BlockRazorClient {
|
||||
}
|
||||
Err(reconnect_err) => {
|
||||
if crate::common::sdk_log::sdk_log_enabled() {
|
||||
eprintln!("BlockRazor gRPC reconnect failed: {}", reconnect_err);
|
||||
eprintln!(
|
||||
"BlockRazor gRPC reconnect failed: {}",
|
||||
reconnect_err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -309,7 +324,8 @@ impl BlockRazorClient {
|
||||
let stop_ping = stop_ping.clone();
|
||||
|
||||
let handle = tokio::spawn(async move {
|
||||
if let Err(e) = Self::send_http_ping(&http_client, &endpoint, &auth_token).await {
|
||||
if let Err(e) = Self::send_http_ping(&http_client, &endpoint, &auth_token).await
|
||||
{
|
||||
if crate::common::sdk_log::sdk_log_enabled() {
|
||||
eprintln!("BlockRazor HTTP ping request failed: {}", e);
|
||||
}
|
||||
@@ -320,7 +336,9 @@ impl BlockRazorClient {
|
||||
if stop_ping.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
if let Err(e) = Self::send_http_ping(&http_client, &endpoint, &auth_token).await {
|
||||
if let Err(e) =
|
||||
Self::send_http_ping(&http_client, &endpoint, &auth_token).await
|
||||
{
|
||||
if crate::common::sdk_log::sdk_log_enabled() {
|
||||
eprintln!("BlockRazor HTTP ping request failed: {}", e);
|
||||
}
|
||||
@@ -337,11 +355,7 @@ impl BlockRazorClient {
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_http_ping(
|
||||
http_client: &Client,
|
||||
endpoint: &str,
|
||||
auth_token: &str,
|
||||
) -> Result<()> {
|
||||
async fn send_http_ping(http_client: &Client, endpoint: &str, auth_token: &str) -> Result<()> {
|
||||
let ping_url = endpoint.replace("/v2/sendTransaction", "/v2/health");
|
||||
let response = http_client
|
||||
.post(&ping_url)
|
||||
@@ -380,51 +394,68 @@ impl BlockRazorClient {
|
||||
let start_time = Instant::now();
|
||||
|
||||
match &self.backend {
|
||||
BlockRazorBackend::Grpc {
|
||||
grpc_client,
|
||||
mev_protection,
|
||||
..
|
||||
} => {
|
||||
BlockRazorBackend::Grpc { grpc_client, mev_protection, .. } => {
|
||||
let (content, _signature) =
|
||||
serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?;
|
||||
|
||||
// 使用 load() 无锁获取客户端引用
|
||||
let client = grpc_client.load();
|
||||
let signature = client.send_transaction(
|
||||
content,
|
||||
// mev_protection=true: sandwichMitigation mode skips blacklisted Leader slots (MEV protection).
|
||||
// revert_protection is unrelated to MEV; keep false.
|
||||
if *mev_protection { "sandwichMitigation".to_string() } else { "fast".to_string() },
|
||||
None,
|
||||
false,
|
||||
).await;
|
||||
let signature = client
|
||||
.send_transaction(
|
||||
content,
|
||||
// mev_protection=true: sandwichMitigation mode skips blacklisted Leader slots (MEV protection).
|
||||
// revert_protection is unrelated to MEV; keep false.
|
||||
if *mev_protection {
|
||||
"sandwichMitigation".to_string()
|
||||
} else {
|
||||
"fast".to_string()
|
||||
},
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
match signature {
|
||||
Ok(sig) => {
|
||||
if !sig.is_empty() {
|
||||
if crate::common::sdk_log::sdk_log_enabled() {
|
||||
crate::common::sdk_log::log_swqos_submitted("BlockRazor", trade_type, start_time.elapsed());
|
||||
crate::common::sdk_log::log_swqos_submitted(
|
||||
"BlockRazor",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if crate::common::sdk_log::sdk_log_enabled() {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("BlockRazor", trade_type, start_time.elapsed(), "empty signature".to_string());
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"BlockRazor",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
"empty signature".to_string(),
|
||||
);
|
||||
}
|
||||
return Err(anyhow::anyhow!("BlockRazor gRPC returned empty signature"));
|
||||
return Err(anyhow::anyhow!(
|
||||
"BlockRazor gRPC returned empty signature"
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if crate::common::sdk_log::sdk_log_enabled() {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("BlockRazor", trade_type, start_time.elapsed(), format!("gRPC error: {}", e));
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"BlockRazor",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
format!("gRPC error: {}", e),
|
||||
);
|
||||
}
|
||||
return Err(anyhow::anyhow!("BlockRazor gRPC sendTransaction failed: {}", e));
|
||||
return Err(anyhow::anyhow!(
|
||||
"BlockRazor gRPC sendTransaction failed: {}",
|
||||
e
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
BlockRazorBackend::Http {
|
||||
endpoint,
|
||||
auth_token,
|
||||
http_client,
|
||||
mev_protection,
|
||||
..
|
||||
endpoint, auth_token, http_client, mev_protection, ..
|
||||
} => {
|
||||
let (content, _signature) =
|
||||
serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?;
|
||||
@@ -448,12 +479,21 @@ impl BlockRazorClient {
|
||||
if status.is_success() {
|
||||
let _ = response.bytes().await;
|
||||
if crate::common::sdk_log::sdk_log_enabled() {
|
||||
crate::common::sdk_log::log_swqos_submitted("blockrazor", trade_type, start_time.elapsed());
|
||||
crate::common::sdk_log::log_swqos_submitted(
|
||||
"blockrazor",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let body = response.text().await.unwrap_or_default();
|
||||
if crate::common::sdk_log::sdk_log_enabled() {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("blockrazor", trade_type, start_time.elapsed(), format!("status {} body: {}", status, body));
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"blockrazor",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
format!("status {} body: {}", status, body),
|
||||
);
|
||||
}
|
||||
return Err(anyhow::anyhow!(
|
||||
"BlockRazor HTTP sendTransaction failed: status {} body: {}",
|
||||
@@ -485,7 +525,13 @@ impl BlockRazorClient {
|
||||
}
|
||||
if wait_confirmation && crate::common::sdk_log::sdk_log_enabled() {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" [{:width$}] {} confirmed: {:?}", "blockrazor", trade_type, start_time.elapsed(), width = crate::common::sdk_log::SWQOS_LABEL_WIDTH);
|
||||
println!(
|
||||
" [{:width$}] {} confirmed: {:?}",
|
||||
"blockrazor",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
width = crate::common::sdk_log::SWQOS_LABEL_WIDTH
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -495,7 +541,8 @@ impl BlockRazorClient {
|
||||
impl Drop for BlockRazorClient {
|
||||
fn drop(&mut self) {
|
||||
match &self.backend {
|
||||
BlockRazorBackend::Grpc { stop_ping, ping_handle, .. } | BlockRazorBackend::Http { stop_ping, ping_handle, .. } => {
|
||||
BlockRazorBackend::Grpc { stop_ping, ping_handle, .. }
|
||||
| BlockRazorBackend::Http { stop_ping, ping_handle, .. } => {
|
||||
stop_ping.store(true, Ordering::Relaxed);
|
||||
|
||||
let ping_handle = ping_handle.clone();
|
||||
|
||||
+30
-5
@@ -100,13 +100,27 @@ impl BloxrouteClient {
|
||||
if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
|
||||
if crate::common::sdk_log::sdk_log_enabled() {
|
||||
if response_json.get("result").is_some() {
|
||||
crate::common::sdk_log::log_swqos_submitted("bloxroute", trade_type, start_time.elapsed());
|
||||
crate::common::sdk_log::log_swqos_submitted(
|
||||
"bloxroute",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
);
|
||||
} else if let Some(_error) = response_json.get("error") {
|
||||
eprintln!(" [bloxroute] {} submission failed after {:?}: {:?}", trade_type, start_time.elapsed(), _error);
|
||||
eprintln!(
|
||||
" [bloxroute] {} submission failed after {:?}: {:?}",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
_error
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if crate::common::sdk_log::sdk_log_enabled() {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("bloxroute", trade_type, start_time.elapsed(), response_text);
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"bloxroute",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
response_text,
|
||||
);
|
||||
}
|
||||
|
||||
let start_time: Instant = Instant::now();
|
||||
@@ -128,7 +142,13 @@ impl BloxrouteClient {
|
||||
}
|
||||
if wait_confirmation && crate::common::sdk_log::sdk_log_enabled() {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" [{:width$}] {} confirmed: {:?}", "bloxroute", trade_type, start_time.elapsed(), width = crate::common::sdk_log::SWQOS_LABEL_WIDTH);
|
||||
println!(
|
||||
" [{:width$}] {} confirmed: {:?}",
|
||||
"bloxroute",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
width = crate::common::sdk_log::SWQOS_LABEL_WIDTH
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -170,7 +190,12 @@ impl BloxrouteClient {
|
||||
if response_json.get("result").is_some() {
|
||||
println!(" bloxroute {} submitted: {:?}", trade_type, start_time.elapsed());
|
||||
} else if let Some(_error) = response_json.get("error") {
|
||||
eprintln!(" bloxroute {} submission failed after {:?}: {:?}", trade_type, start_time.elapsed(), _error);
|
||||
eprintln!(
|
||||
" bloxroute {} submission failed after {:?}: {:?}",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
_error
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+24
-4
@@ -97,12 +97,26 @@ impl FlashBlockClient {
|
||||
// Parse response
|
||||
if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
|
||||
if response_json.get("success").is_some() || response_json.get("result").is_some() {
|
||||
crate::common::sdk_log::log_swqos_submitted("FlashBlock", trade_type, start_time.elapsed());
|
||||
crate::common::sdk_log::log_swqos_submitted(
|
||||
"FlashBlock",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
);
|
||||
} else if let Some(_error) = response_json.get("error") {
|
||||
eprintln!(" [FlashBlock] {} submission failed after {:?}: {:?}", trade_type, start_time.elapsed(), _error);
|
||||
eprintln!(
|
||||
" [FlashBlock] {} submission failed after {:?}: {:?}",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
_error
|
||||
);
|
||||
}
|
||||
} else {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("FlashBlock", trade_type, start_time.elapsed(), response_text);
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"FlashBlock",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
response_text,
|
||||
);
|
||||
}
|
||||
|
||||
let start_time: Instant = Instant::now();
|
||||
@@ -122,7 +136,13 @@ impl FlashBlockClient {
|
||||
}
|
||||
if wait_confirmation {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" [{:width$}] {} confirmed: {:?}", "FlashBlock", trade_type, start_time.elapsed(), width = crate::common::sdk_log::SWQOS_LABEL_WIDTH);
|
||||
println!(
|
||||
" [{:width$}] {} confirmed: {:?}",
|
||||
"FlashBlock",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
width = crate::common::sdk_log::SWQOS_LABEL_WIDTH
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
+28
-5
@@ -107,7 +107,10 @@ impl HeliusClient {
|
||||
if crate::common::sdk_log::sdk_log_enabled() {
|
||||
eprintln!(
|
||||
" [helius] {} submission failed after {:?} status={} body={}",
|
||||
trade_type, start_time.elapsed(), status, response_text
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
status,
|
||||
response_text
|
||||
);
|
||||
}
|
||||
return Err(anyhow::anyhow!(
|
||||
@@ -124,15 +127,29 @@ impl HeliusClient {
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("unknown");
|
||||
if crate::common::sdk_log::sdk_log_enabled() {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("helius", trade_type, start_time.elapsed(), err_msg);
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"helius",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
err_msg,
|
||||
);
|
||||
}
|
||||
return Err(anyhow::anyhow!("Helius Sender error: {}", err_msg));
|
||||
}
|
||||
if response_json.get("result").is_some() && crate::common::sdk_log::sdk_log_enabled() {
|
||||
crate::common::sdk_log::log_swqos_submitted("helius", trade_type, start_time.elapsed());
|
||||
crate::common::sdk_log::log_swqos_submitted(
|
||||
"helius",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
);
|
||||
}
|
||||
} else if crate::common::sdk_log::sdk_log_enabled() {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("helius", trade_type, start_time.elapsed(), response_text);
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"helius",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
response_text,
|
||||
);
|
||||
}
|
||||
|
||||
match poll_transaction_confirmation(&self.rpc_client, signature, wait_confirmation).await {
|
||||
@@ -152,7 +169,13 @@ impl HeliusClient {
|
||||
}
|
||||
if wait_confirmation && crate::common::sdk_log::sdk_log_enabled() {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" [{:width$}] {} confirmed: {:?}", "helius", trade_type, start_time.elapsed(), width = crate::common::sdk_log::SWQOS_LABEL_WIDTH);
|
||||
println!(
|
||||
" [{:width$}] {} confirmed: {:?}",
|
||||
"helius",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
width = crate::common::sdk_log::SWQOS_LABEL_WIDTH
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+37
-6
@@ -105,12 +105,26 @@ impl JitoClient {
|
||||
|
||||
if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
|
||||
if response_json.get("result").is_some() {
|
||||
crate::common::sdk_log::log_swqos_submitted("jito", trade_type, start_time.elapsed());
|
||||
crate::common::sdk_log::log_swqos_submitted(
|
||||
"jito",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
);
|
||||
} else if let Some(_error) = response_json.get("error") {
|
||||
eprintln!(" [jito] {} submission failed after {:?}: {:?}", trade_type, start_time.elapsed(), _error);
|
||||
eprintln!(
|
||||
" [jito] {} submission failed after {:?}: {:?}",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
_error
|
||||
);
|
||||
}
|
||||
} else {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("jito", trade_type, start_time.elapsed(), response_text);
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"jito",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
response_text,
|
||||
);
|
||||
}
|
||||
|
||||
let start_time: Instant = Instant::now();
|
||||
@@ -118,13 +132,25 @@ impl JitoClient {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" [{:width$}] {} confirmation failed: {:?}", "jito", trade_type, start_time.elapsed(), width = crate::common::sdk_log::SWQOS_LABEL_WIDTH);
|
||||
println!(
|
||||
" [{:width$}] {} confirmation failed: {:?}",
|
||||
"jito",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
width = crate::common::sdk_log::SWQOS_LABEL_WIDTH
|
||||
);
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
if wait_confirmation {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" [{:width$}] {} confirmed: {:?}", "jito", trade_type, start_time.elapsed(), width = crate::common::sdk_log::SWQOS_LABEL_WIDTH);
|
||||
println!(
|
||||
" [{:width$}] {} confirmed: {:?}",
|
||||
"jito",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
width = crate::common::sdk_log::SWQOS_LABEL_WIDTH
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -171,7 +197,12 @@ impl JitoClient {
|
||||
if response_json.get("result").is_some() {
|
||||
println!(" jito {} submitted: {:?}", trade_type, start_time.elapsed());
|
||||
} else if let Some(_error) = response_json.get("error") {
|
||||
eprintln!(" jito {} submission failed after {:?}: {:?}", trade_type, start_time.elapsed(), _error);
|
||||
eprintln!(
|
||||
" jito {} submission failed after {:?}: {:?}",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
_error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+24
-4
@@ -103,12 +103,26 @@ impl LightspeedClient {
|
||||
|
||||
if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
|
||||
if response_json.get("result").is_some() {
|
||||
crate::common::sdk_log::log_swqos_submitted("lightspeed", trade_type, start_time.elapsed());
|
||||
crate::common::sdk_log::log_swqos_submitted(
|
||||
"lightspeed",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
);
|
||||
} else if let Some(_error) = response_json.get("error") {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("lightspeed", trade_type, start_time.elapsed(), _error);
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"lightspeed",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
_error,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("lightspeed", trade_type, start_time.elapsed(), response_text);
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"lightspeed",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
response_text,
|
||||
);
|
||||
}
|
||||
|
||||
let start_time: Instant = Instant::now();
|
||||
@@ -128,7 +142,13 @@ impl LightspeedClient {
|
||||
}
|
||||
if wait_confirmation {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" [{:width$}] {} confirmed: {:?}", "lightspeed", trade_type, start_time.elapsed(), width = crate::common::sdk_log::SWQOS_LABEL_WIDTH);
|
||||
println!(
|
||||
" [{:width$}] {} confirmed: {:?}",
|
||||
"lightspeed",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
width = crate::common::sdk_log::SWQOS_LABEL_WIDTH
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
+30
-16
@@ -30,17 +30,16 @@ use crate::{
|
||||
constants::swqos::{
|
||||
SWQOS_ENDPOINTS_ASTRALANE_BINARY, SWQOS_ENDPOINTS_ASTRALANE_PLAIN,
|
||||
SWQOS_ENDPOINTS_ASTRALANE_QUIC, SWQOS_ENDPOINTS_ASTRALANE_QUIC_MEV,
|
||||
SWQOS_ENDPOINTS_BLOCKRAZOR,
|
||||
SWQOS_ENDPOINTS_BLOCKRAZOR_GRPC, SWQOS_ENDPOINTS_BLOX, SWQOS_ENDPOINTS_FLASHBLOCK,
|
||||
SWQOS_ENDPOINTS_HELIUS, SWQOS_ENDPOINTS_JITO, SWQOS_ENDPOINTS_NEXTBLOCK,
|
||||
SWQOS_ENDPOINTS_NODE1, SWQOS_ENDPOINTS_NODE1_QUIC, SWQOS_ENDPOINTS_SOYAS,
|
||||
SWQOS_ENDPOINTS_SPEEDLANDING, SWQOS_ENDPOINTS_STELLIUM, SWQOS_ENDPOINTS_TEMPORAL,
|
||||
SWQOS_ENDPOINTS_ZERO_SLOT, SWQOS_MIN_TIP_ASTRALANE, SWQOS_MIN_TIP_BLOCKRAZOR,
|
||||
SWQOS_MIN_TIP_BLOXROUTE, SWQOS_MIN_TIP_DEFAULT, SWQOS_MIN_TIP_FLASHBLOCK,
|
||||
SWQOS_MIN_TIP_HELIUS, SWQOS_MIN_TIP_JITO, SWQOS_MIN_TIP_LIGHTSPEED,
|
||||
SWQOS_MIN_TIP_NEXTBLOCK, SWQOS_MIN_TIP_NODE1, SWQOS_MIN_TIP_SOYAS,
|
||||
SWQOS_MIN_TIP_SPEEDLANDING, SWQOS_MIN_TIP_STELLIUM, SWQOS_MIN_TIP_TEMPORAL,
|
||||
SWQOS_MIN_TIP_ZERO_SLOT,
|
||||
SWQOS_ENDPOINTS_BLOCKRAZOR, SWQOS_ENDPOINTS_BLOCKRAZOR_GRPC, SWQOS_ENDPOINTS_BLOX,
|
||||
SWQOS_ENDPOINTS_FLASHBLOCK, SWQOS_ENDPOINTS_HELIUS, SWQOS_ENDPOINTS_JITO,
|
||||
SWQOS_ENDPOINTS_NEXTBLOCK, SWQOS_ENDPOINTS_NODE1, SWQOS_ENDPOINTS_NODE1_QUIC,
|
||||
SWQOS_ENDPOINTS_SOYAS, SWQOS_ENDPOINTS_SPEEDLANDING, SWQOS_ENDPOINTS_STELLIUM,
|
||||
SWQOS_ENDPOINTS_TEMPORAL, SWQOS_ENDPOINTS_ZERO_SLOT, SWQOS_MIN_TIP_ASTRALANE,
|
||||
SWQOS_MIN_TIP_BLOCKRAZOR, SWQOS_MIN_TIP_BLOXROUTE, SWQOS_MIN_TIP_DEFAULT,
|
||||
SWQOS_MIN_TIP_FLASHBLOCK, SWQOS_MIN_TIP_HELIUS, SWQOS_MIN_TIP_JITO,
|
||||
SWQOS_MIN_TIP_LIGHTSPEED, SWQOS_MIN_TIP_NEXTBLOCK, SWQOS_MIN_TIP_NODE1,
|
||||
SWQOS_MIN_TIP_SOYAS, SWQOS_MIN_TIP_SPEEDLANDING, SWQOS_MIN_TIP_STELLIUM,
|
||||
SWQOS_MIN_TIP_TEMPORAL, SWQOS_MIN_TIP_ZERO_SLOT,
|
||||
},
|
||||
swqos::{
|
||||
astralane::AstralaneClient, blockrazor::BlockRazorClient, bloxroute::BloxrouteClient,
|
||||
@@ -411,15 +410,30 @@ impl SwqosConfig {
|
||||
SwqosConfig::BlockRazor(auth_token, region, url, transport) => {
|
||||
// BlockRazor: transport=None 或 transport=Grpc 时使用 gRPC,transport=Http 时使用 HTTP
|
||||
let use_http = transport.map_or(false, |t| t == SwqosTransport::Http);
|
||||
let endpoint = SwqosConfig::get_endpoint_with_transport(SwqosType::BlockRazor, region, url, transport, mev_protection);
|
||||
let endpoint = SwqosConfig::get_endpoint_with_transport(
|
||||
SwqosType::BlockRazor,
|
||||
region,
|
||||
url,
|
||||
transport,
|
||||
mev_protection,
|
||||
);
|
||||
if use_http {
|
||||
let blockrazor_client =
|
||||
BlockRazorClient::new_http(rpc_url.clone(), endpoint.to_string(), auth_token, mev_protection);
|
||||
let blockrazor_client = BlockRazorClient::new_http(
|
||||
rpc_url.clone(),
|
||||
endpoint.to_string(),
|
||||
auth_token,
|
||||
mev_protection,
|
||||
);
|
||||
Ok(Arc::new(blockrazor_client))
|
||||
} else {
|
||||
// 使用 gRPC 模式(默认或用户明确指定了 gRPC)
|
||||
let blockrazor_client =
|
||||
BlockRazorClient::new_grpc(rpc_url.clone(), endpoint.to_string(), auth_token, mev_protection).await?;
|
||||
let blockrazor_client = BlockRazorClient::new_grpc(
|
||||
rpc_url.clone(),
|
||||
endpoint.to_string(),
|
||||
auth_token,
|
||||
mev_protection,
|
||||
)
|
||||
.await?;
|
||||
Ok(Arc::new(blockrazor_client))
|
||||
}
|
||||
}
|
||||
|
||||
+24
-4
@@ -99,12 +99,26 @@ impl NextBlockClient {
|
||||
|
||||
if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
|
||||
if response_json.get("result").is_some() {
|
||||
crate::common::sdk_log::log_swqos_submitted("nextblock", trade_type, start_time.elapsed());
|
||||
crate::common::sdk_log::log_swqos_submitted(
|
||||
"nextblock",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
);
|
||||
} else if let Some(_error) = response_json.get("error") {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("nextblock", trade_type, start_time.elapsed(), _error);
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"nextblock",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
_error,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("nextblock", trade_type, start_time.elapsed(), response_text);
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"nextblock",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
response_text,
|
||||
);
|
||||
}
|
||||
|
||||
let start_time: Instant = Instant::now();
|
||||
@@ -124,7 +138,13 @@ impl NextBlockClient {
|
||||
}
|
||||
if wait_confirmation {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" [{:width$}] {} confirmed: {:?}", "nextblock", trade_type, start_time.elapsed(), width = crate::common::sdk_log::SWQOS_LABEL_WIDTH);
|
||||
println!(
|
||||
" [{:width$}] {} confirmed: {:?}",
|
||||
"nextblock",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
width = crate::common::sdk_log::SWQOS_LABEL_WIDTH
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
+24
-4
@@ -184,13 +184,27 @@ impl Node1Client {
|
||||
if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
|
||||
if crate::common::sdk_log::sdk_log_enabled() {
|
||||
if response_json.get("result").is_some() {
|
||||
crate::common::sdk_log::log_swqos_submitted("node1", trade_type, start_time.elapsed());
|
||||
crate::common::sdk_log::log_swqos_submitted(
|
||||
"node1",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
);
|
||||
} else if let Some(_error) = response_json.get("error") {
|
||||
eprintln!(" [node1] {} submission failed after {:?}: {:?}", trade_type, start_time.elapsed(), _error);
|
||||
eprintln!(
|
||||
" [node1] {} submission failed after {:?}: {:?}",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
_error
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if crate::common::sdk_log::sdk_log_enabled() {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("node1", trade_type, start_time.elapsed(), response_text);
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"node1",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
response_text,
|
||||
);
|
||||
}
|
||||
|
||||
let start_time: Instant = Instant::now();
|
||||
@@ -212,7 +226,13 @@ impl Node1Client {
|
||||
}
|
||||
if wait_confirmation && crate::common::sdk_log::sdk_log_enabled() {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" [{:width$}] {} confirmed: {:?}", "node1", trade_type, start_time.elapsed(), width = crate::common::sdk_log::SWQOS_LABEL_WIDTH);
|
||||
println!(
|
||||
" [{:width$}] {} confirmed: {:?}",
|
||||
"node1",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
width = crate::common::sdk_log::SWQOS_LABEL_WIDTH
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
+39
-15
@@ -73,13 +73,14 @@ impl rustls::client::danger::ServerCertVerifier for SkipServerVerification {
|
||||
}
|
||||
|
||||
/// TLS 客户端证书:ECDSA P-256 + CN=钱包公钥(与 Speedlanding / Astralane QUIC 策略一致)。
|
||||
fn generate_client_tls_credentials(keypair: &Keypair) -> Result<(CertificateDer<'static>, PrivateKeyDer<'static>)> {
|
||||
fn generate_client_tls_credentials(
|
||||
keypair: &Keypair,
|
||||
) -> Result<(CertificateDer<'static>, PrivateKeyDer<'static>)> {
|
||||
let tls_key = RcgenKeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256)?;
|
||||
let mut cert_params = CertificateParams::new(vec![])?;
|
||||
cert_params.distinguished_name.push(
|
||||
rcgen::DnType::CommonName,
|
||||
rcgen::DnValue::Utf8String(keypair.pubkey().to_string()),
|
||||
);
|
||||
cert_params
|
||||
.distinguished_name
|
||||
.push(rcgen::DnType::CommonName, rcgen::DnValue::Utf8String(keypair.pubkey().to_string()));
|
||||
let cert = cert_params.self_signed(&tls_key)?;
|
||||
let cert_der = CertificateDer::from(cert.der().to_vec());
|
||||
let key_der = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(tls_key.serialize_der()));
|
||||
@@ -103,11 +104,12 @@ pub struct SoyasClient {
|
||||
impl SoyasClient {
|
||||
pub async fn new(rpc_url: String, endpoint_string: String, api_key: String) -> Result<Self> {
|
||||
let rpc_client = SolanaRpcClient::new(rpc_url);
|
||||
let keypair_bytes = bs58::decode(api_key.trim())
|
||||
.into_vec()
|
||||
.map_err(|e| anyhow::anyhow!("Soyas api_token base58 解码失败(QUIC mTLS 用): {}", e))?;
|
||||
let keypair = Keypair::try_from(keypair_bytes.as_slice())
|
||||
.map_err(|e| anyhow::anyhow!("Soyas api_token 无法解析为 Solana keypair(QUIC mTLS 用): {}", e))?;
|
||||
let keypair_bytes = bs58::decode(api_key.trim()).into_vec().map_err(|e| {
|
||||
anyhow::anyhow!("Soyas api_token base58 解码失败(QUIC mTLS 用): {}", e)
|
||||
})?;
|
||||
let keypair = Keypair::try_from(keypair_bytes.as_slice()).map_err(|e| {
|
||||
anyhow::anyhow!("Soyas api_token 无法解析为 Solana keypair(QUIC mTLS 用): {}", e)
|
||||
})?;
|
||||
let (cert, key) = generate_client_tls_credentials(&keypair)?;
|
||||
let mut crypto = rustls::ClientConfig::builder()
|
||||
.dangerous()
|
||||
@@ -175,13 +177,23 @@ impl SwqosClientTrait for SoyasClient {
|
||||
let connection = self.connection.load_full();
|
||||
if Self::try_send_bytes(&connection, &serialized_tx).await.is_err() {
|
||||
if crate::common::sdk_log::sdk_log_enabled() {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("Soyas", trade_type, start_time.elapsed(), "reconnecting");
|
||||
}
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"Soyas",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
"reconnecting",
|
||||
);
|
||||
}
|
||||
self.reconnect().await?;
|
||||
let connection = self.connection.load_full();
|
||||
if let Err(e) = Self::try_send_bytes(&connection, &serialized_tx).await {
|
||||
if crate::common::sdk_log::sdk_log_enabled() {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("Soyas", trade_type, start_time.elapsed(), &e);
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"Soyas",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
&e,
|
||||
);
|
||||
}
|
||||
return Err(e.into());
|
||||
}
|
||||
@@ -195,14 +207,26 @@ impl SwqosClientTrait for SoyasClient {
|
||||
Err(e) => {
|
||||
if crate::common::sdk_log::sdk_log_enabled() {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" [{:width$}] {} confirmation failed: {:?}", "Soyas", trade_type, start_time.elapsed(), width = crate::common::sdk_log::SWQOS_LABEL_WIDTH);
|
||||
println!(
|
||||
" [{:width$}] {} confirmation failed: {:?}",
|
||||
"Soyas",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
width = crate::common::sdk_log::SWQOS_LABEL_WIDTH
|
||||
);
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
if wait_confirmation && crate::common::sdk_log::sdk_log_enabled() {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" [{:width$}] {} confirmed: {:?}", "Soyas", trade_type, start_time.elapsed(), width = crate::common::sdk_log::SWQOS_LABEL_WIDTH);
|
||||
println!(
|
||||
" [{:width$}] {} confirmed: {:?}",
|
||||
"Soyas",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
width = crate::common::sdk_log::SWQOS_LABEL_WIDTH
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+13
-11
@@ -48,11 +48,12 @@ impl SpeedlandingClient {
|
||||
pub async fn new(rpc_url: String, endpoint_string: String, api_key: String) -> Result<Self> {
|
||||
let rpc_client = SolanaRpcClient::new(rpc_url);
|
||||
// Speedlanding QUIC:与官方一致使用 `solana_tls_utils::new_dummy_x509_certificate`(Ed25519 dummy cert)+ SNI `speed-landing`。
|
||||
let keypair_bytes = bs58::decode(api_key.trim())
|
||||
.into_vec()
|
||||
.map_err(|e| anyhow::anyhow!("Speedlanding api_token base58 解码失败(mTLS 用): {}", e))?;
|
||||
let keypair = Keypair::try_from(keypair_bytes.as_slice())
|
||||
.map_err(|e| anyhow::anyhow!("Speedlanding api_token 无法解析为 Solana keypair(mTLS 用): {}", e))?;
|
||||
let keypair_bytes = bs58::decode(api_key.trim()).into_vec().map_err(|e| {
|
||||
anyhow::anyhow!("Speedlanding api_token base58 解码失败(mTLS 用): {}", e)
|
||||
})?;
|
||||
let keypair = Keypair::try_from(keypair_bytes.as_slice()).map_err(|e| {
|
||||
anyhow::anyhow!("Speedlanding api_token 无法解析为 Solana keypair(mTLS 用): {}", e)
|
||||
})?;
|
||||
let (cert, key) = new_dummy_x509_certificate(&keypair);
|
||||
let mut crypto = rustls::ClientConfig::builder()
|
||||
.dangerous()
|
||||
@@ -111,11 +112,8 @@ impl SpeedlandingClient {
|
||||
let _guard = self.reconnect.lock().await;
|
||||
let current = self.connection.load_full();
|
||||
if current.close_reason().is_some() {
|
||||
let connecting = self.endpoint.connect_with(
|
||||
self.client_config.clone(),
|
||||
self.addr,
|
||||
SPEED_SERVER,
|
||||
)?;
|
||||
let connecting =
|
||||
self.endpoint.connect_with(self.client_config.clone(), self.addr, SPEED_SERVER)?;
|
||||
let connection = timeout(CONNECT_TIMEOUT, connecting)
|
||||
.await
|
||||
.context("Speedlanding QUIC reconnect timeout")?
|
||||
@@ -169,7 +167,11 @@ impl SwqosClientTrait for SpeedlandingClient {
|
||||
match send_result.context("Speedlanding QUIC send timeout") {
|
||||
Ok(Ok(())) => {
|
||||
// 提交结果与「详细耗时/SDK 开关」无关,便于确认当前通道确实在执行
|
||||
crate::common::sdk_log::log_swqos_submitted("Speedlanding", trade_type, start_time.elapsed());
|
||||
crate::common::sdk_log::log_swqos_submitted(
|
||||
"Speedlanding",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
);
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
|
||||
+24
-4
@@ -168,13 +168,27 @@ impl StelliumClient {
|
||||
if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
|
||||
if crate::common::sdk_log::sdk_log_enabled() {
|
||||
if response_json.get("result").is_some() {
|
||||
crate::common::sdk_log::log_swqos_submitted("Stellium", trade_type, start_time.elapsed());
|
||||
crate::common::sdk_log::log_swqos_submitted(
|
||||
"Stellium",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
);
|
||||
} else if let Some(_error) = response_json.get("error") {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("Stellium", trade_type, start_time.elapsed(), _error);
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"Stellium",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
_error,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if crate::common::sdk_log::sdk_log_enabled() {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("Stellium", trade_type, start_time.elapsed(), response_text);
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"Stellium",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
response_text,
|
||||
);
|
||||
}
|
||||
|
||||
let start_time: Instant = Instant::now();
|
||||
@@ -196,7 +210,13 @@ impl StelliumClient {
|
||||
}
|
||||
if wait_confirmation && crate::common::sdk_log::sdk_log_enabled() {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" [{:width$}] {} confirmed: {:?}", "Stellium", trade_type, start_time.elapsed(), width = crate::common::sdk_log::SWQOS_LABEL_WIDTH);
|
||||
println!(
|
||||
" [{:width$}] {} confirmed: {:?}",
|
||||
"Stellium",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
width = crate::common::sdk_log::SWQOS_LABEL_WIDTH
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
+24
-4
@@ -209,12 +209,26 @@ impl TemporalClient {
|
||||
|
||||
if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
|
||||
if response_json.get("result").is_some() {
|
||||
crate::common::sdk_log::log_swqos_submitted("nozomi", trade_type, start_time.elapsed());
|
||||
crate::common::sdk_log::log_swqos_submitted(
|
||||
"nozomi",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
);
|
||||
} else if let Some(_error) = response_json.get("error") {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("nozomi", trade_type, start_time.elapsed(), _error);
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"nozomi",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
_error,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("nozomi", trade_type, start_time.elapsed(), response_text);
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"nozomi",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
response_text,
|
||||
);
|
||||
}
|
||||
|
||||
let start_time: Instant = Instant::now();
|
||||
@@ -232,7 +246,13 @@ impl TemporalClient {
|
||||
}
|
||||
if wait_confirmation {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" [{:width$}] {} confirmed: {:?}", "nozomi", trade_type, start_time.elapsed(), width = crate::common::sdk_log::SWQOS_LABEL_WIDTH);
|
||||
println!(
|
||||
" [{:width$}] {} confirmed: {:?}",
|
||||
"nozomi",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
width = crate::common::sdk_log::SWQOS_LABEL_WIDTH
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
+79
-20
@@ -1,12 +1,10 @@
|
||||
use crate::swqos::common::{
|
||||
default_http_client_builder, poll_transaction_confirmation,
|
||||
};
|
||||
use crate::swqos::common::{default_http_client_builder, poll_transaction_confirmation};
|
||||
use bincode;
|
||||
use rand::seq::IndexedRandom;
|
||||
use reqwest::Client;
|
||||
use std::{sync::Arc, time::Instant, time::Duration};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::{sync::Arc, time::Duration, time::Instant};
|
||||
use tokio::task::JoinHandle;
|
||||
use bincode;
|
||||
|
||||
use crate::swqos::SwqosClientTrait;
|
||||
use crate::swqos::{SwqosType, TradeType};
|
||||
@@ -101,7 +99,8 @@ impl ZeroSlotClient {
|
||||
if stop_ping.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await {
|
||||
if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await
|
||||
{
|
||||
if crate::common::sdk_log::sdk_log_enabled() {
|
||||
eprintln!("0slot ping request failed: {}", e);
|
||||
}
|
||||
@@ -176,41 +175,89 @@ impl ZeroSlotClient {
|
||||
200 => {
|
||||
if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(&response_text) {
|
||||
if json_value.get("result").is_some() {
|
||||
crate::common::sdk_log::log_swqos_submitted("0slot", trade_type, start_time.elapsed());
|
||||
crate::common::sdk_log::log_swqos_submitted(
|
||||
"0slot",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
);
|
||||
} else if let Some(error) = json_value.get("error") {
|
||||
let code = error.get("code")
|
||||
let code = error
|
||||
.get("code")
|
||||
.and_then(|c| c.as_i64())
|
||||
.map(|c| c.to_string())
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
let message = error.get("message")
|
||||
let message = error
|
||||
.get("message")
|
||||
.and_then(|m| m.as_str())
|
||||
.unwrap_or("unknown error");
|
||||
crate::common::sdk_log::log_swqos_submission_failed("0slot", trade_type, start_time.elapsed(), format!("code {}: {}", code, message));
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"0slot",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
format!("code {}: {}", code, message),
|
||||
);
|
||||
return Err(anyhow::anyhow!("0slot Binary-Tx error: {}", message));
|
||||
} else {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("0slot", trade_type, start_time.elapsed(), format!("unexpected JSON: {}", response_text));
|
||||
return Err(anyhow::anyhow!("0slot Binary-Tx unexpected JSON: {}", response_text));
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"0slot",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
format!("unexpected JSON: {}", response_text),
|
||||
);
|
||||
return Err(anyhow::anyhow!(
|
||||
"0slot Binary-Tx unexpected JSON: {}",
|
||||
response_text
|
||||
));
|
||||
}
|
||||
} else {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("0slot", trade_type, start_time.elapsed(), format!("invalid JSON: {}", response_text));
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"0slot",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
format!("invalid JSON: {}", response_text),
|
||||
);
|
||||
return Err(anyhow::anyhow!("0slot Binary-Tx invalid JSON: {}", response_text));
|
||||
}
|
||||
}
|
||||
403 => {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("0slot", trade_type, start_time.elapsed(), response_text.clone());
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"0slot",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
response_text.clone(),
|
||||
);
|
||||
return Err(anyhow::anyhow!("0slot API key error: {}", response_text));
|
||||
}
|
||||
419 => {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("0slot", trade_type, start_time.elapsed(), response_text.clone());
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"0slot",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
response_text.clone(),
|
||||
);
|
||||
return Err(anyhow::anyhow!("0slot rate limit exceeded"));
|
||||
}
|
||||
500 => {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("0slot", trade_type, start_time.elapsed(), "submission failed".to_string());
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"0slot",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
"submission failed".to_string(),
|
||||
);
|
||||
return Err(anyhow::anyhow!("0slot transaction submission failed"));
|
||||
}
|
||||
_ => {
|
||||
crate::common::sdk_log::log_swqos_submission_failed("0slot", trade_type, start_time.elapsed(), format!("status {} body: {}", status, response_text));
|
||||
return Err(anyhow::anyhow!("0slot Binary-Tx failed with status {}: {}", status, response_text));
|
||||
crate::common::sdk_log::log_swqos_submission_failed(
|
||||
"0slot",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
format!("status {} body: {}", status, response_text),
|
||||
);
|
||||
return Err(anyhow::anyhow!(
|
||||
"0slot Binary-Tx failed with status {}: {}",
|
||||
status,
|
||||
response_text
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,13 +269,25 @@ impl ZeroSlotClient {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" [{:width$}] {} confirmation failed: {:?}", "0slot", trade_type, start_time.elapsed(), width = crate::common::sdk_log::SWQOS_LABEL_WIDTH);
|
||||
println!(
|
||||
" [{:width$}] {} confirmation failed: {:?}",
|
||||
"0slot",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
width = crate::common::sdk_log::SWQOS_LABEL_WIDTH
|
||||
);
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
if wait_confirmation {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" [{:width$}] {} confirmed: {:?}", "0slot", trade_type, start_time.elapsed(), width = crate::common::sdk_log::SWQOS_LABEL_WIDTH);
|
||||
println!(
|
||||
" [{:width$}] {} confirmed: {:?}",
|
||||
"0slot",
|
||||
trade_type,
|
||||
start_time.elapsed(),
|
||||
width = crate::common::sdk_log::SWQOS_LABEL_WIDTH
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use solana_hash::Hash;
|
||||
use solana_sdk::{
|
||||
instruction::Instruction, pubkey::Pubkey,
|
||||
signature::Keypair, signer::Signer, transaction::VersionedTransaction,
|
||||
};
|
||||
use solana_message::AddressLookupTableAccount;
|
||||
use solana_sdk::{
|
||||
instruction::Instruction, pubkey::Pubkey, signature::Keypair, signer::Signer,
|
||||
transaction::VersionedTransaction,
|
||||
};
|
||||
use solana_system_interface::instruction as system_instruction;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
||||
@@ -88,7 +88,8 @@ pub async fn transfer_sol(
|
||||
return Err(anyhow!("Insufficient balance"));
|
||||
}
|
||||
|
||||
let transfer_instruction = system_instruction::transfer(&payer.pubkey(), receive_wallet, amount);
|
||||
let transfer_instruction =
|
||||
system_instruction::transfer(&payer.pubkey(), receive_wallet, amount);
|
||||
|
||||
let recent_blockhash = rpc.get_latest_blockhash().await?;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::common::{
|
||||
spl_token::close_account,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use solana_sdk::{instruction::Instruction, instruction::AccountMeta, pubkey::Pubkey};
|
||||
use solana_sdk::{instruction::AccountMeta, instruction::Instruction, pubkey::Pubkey};
|
||||
use solana_system_interface::instruction as system_instruction;
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use crossbeam_queue::ArrayQueue;
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use solana_hash::Hash;
|
||||
use solana_message::AddressLookupTableAccount;
|
||||
use solana_sdk::{
|
||||
@@ -21,7 +22,6 @@ use solana_sdk::{
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::hash::BuildHasherDefault;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::{
|
||||
str::FromStr,
|
||||
@@ -187,11 +187,7 @@ fn ensure_dedicated_pool(
|
||||
.map(|all_ids| {
|
||||
sender_thread_cores
|
||||
.map(|indices| {
|
||||
indices
|
||||
.iter()
|
||||
.take(n)
|
||||
.filter_map(|&i| all_ids.get(i).cloned())
|
||||
.collect()
|
||||
indices.iter().take(n).filter_map(|&i| all_ids.get(i).cloned()).collect()
|
||||
})
|
||||
.unwrap_or_else(|| all_ids.into_iter().take(n).collect())
|
||||
})
|
||||
@@ -416,8 +412,7 @@ impl ResultCollector {
|
||||
}
|
||||
// 「不等待链上确认」仍会等各 SWQOS 的 HTTP 回包;主循环在收齐或触达 `timeout_secs` 后结束。
|
||||
// 若主窗口到时仍有未回包通道,晚到的 TaskResult 若立刻 drain 会丢签名——仅在该路径上拉长 grace。
|
||||
let all_submitted =
|
||||
self.completed_count.load(Ordering::Acquire) >= self.total_tasks;
|
||||
let all_submitted = self.completed_count.load(Ordering::Acquire) >= self.total_tasks;
|
||||
if all_submitted {
|
||||
// 全数已登记:仅留极短 settle,避免极端情况下最后一笔与计数可见性竞态。
|
||||
tokio::time::sleep(Duration::from_millis(35)).await;
|
||||
@@ -472,11 +467,8 @@ pub async fn execute_parallel(
|
||||
let instructions = Arc::new(instructions);
|
||||
|
||||
// One get_strategies call per batch (avoid N calls in loop).
|
||||
let gas_fee_configs = gas_fee_strategy.get_strategies(if is_buy {
|
||||
TradeType::Buy
|
||||
} else {
|
||||
TradeType::Sell
|
||||
});
|
||||
let gas_fee_configs =
|
||||
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) {
|
||||
@@ -589,18 +581,12 @@ pub async fn execute_parallel(
|
||||
if !wait_transaction_confirmed {
|
||||
// submit_timeout_secs 为「等齐各 SWQOS HTTP 应答」的上限;收齐后会立刻进入短 settle,不会睡满整段秒数。
|
||||
// `wait_for_all_submitted` 仅在未收齐时追加长 grace,避免过早 drain 丢晚到签名。
|
||||
let ret = collector
|
||||
.wait_for_all_submitted(submit_timeout_secs)
|
||||
.await
|
||||
.unwrap_or((
|
||||
false,
|
||||
vec![],
|
||||
Some(anyhow!(
|
||||
"No SWQOS result within grace window (primary {}s)",
|
||||
submit_timeout_secs
|
||||
)),
|
||||
vec![],
|
||||
));
|
||||
let ret = collector.wait_for_all_submitted(submit_timeout_secs).await.unwrap_or((
|
||||
false,
|
||||
vec![],
|
||||
Some(anyhow!("No SWQOS result within grace window (primary {}s)", submit_timeout_secs)),
|
||||
vec![],
|
||||
));
|
||||
let (success, signatures, last_error, submit_timings) = ret;
|
||||
return Ok((success, signatures, last_error, submit_timings));
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use anyhow::Result;
|
||||
use solana_hash::Hash;
|
||||
use solana_sdk::{
|
||||
instruction::Instruction, pubkey::Pubkey,
|
||||
signature::Keypair, signature::Signature,
|
||||
};
|
||||
use solana_message::AddressLookupTableAccount;
|
||||
use solana_sdk::{
|
||||
instruction::Instruction, pubkey::Pubkey, signature::Keypair, signature::Signature,
|
||||
};
|
||||
use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
@@ -13,6 +12,7 @@ use std::{
|
||||
use tracing::{info, trace, warn};
|
||||
|
||||
use super::{params::SwapParams, traits::InstructionBuilder};
|
||||
use crate::swqos::SwqosType;
|
||||
use crate::swqos::TradeType;
|
||||
use crate::{
|
||||
common::{nonce_cache::DurableNonceInfo, GasFeeStrategy, SolanaRpcClient},
|
||||
@@ -26,7 +26,6 @@ use crate::{
|
||||
trading::MiddlewareManager,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use crate::swqos::{ SwqosType};
|
||||
|
||||
/// Global syscall bypass manager (reserved for future time/IO optimizations).
|
||||
/// 全局系统调用绕过管理器(预留,后续可接入时间/IO 等优化)。
|
||||
@@ -234,10 +233,7 @@ impl TradeExecutor for GenericTradeExecutor {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Ok((ok, signatures, err, submit_timings))
|
||||
|
||||
};
|
||||
|
||||
result
|
||||
@@ -368,8 +364,20 @@ mod tests {
|
||||
let before_submit_ms = 15.67;
|
||||
let w = 12usize; // same as crate::common::sdk_log::SWQOS_LABEL_WIDTH
|
||||
println!("\n--- 1. 构建指令耗时 / 提交前耗时(各打印一次,统一 ms,保留 4 位小数)---\n");
|
||||
println!(" [SDK][{:width$}] {} build_instructions: {:.4} ms", "-", dir, build_ms, width = w);
|
||||
println!(" [SDK][{:width$}] {} before_submit: {:.4} ms", "-", dir, before_submit_ms, width = w);
|
||||
println!(
|
||||
" [SDK][{:width$}] {} build_instructions: {:.4} ms",
|
||||
"-",
|
||||
dir,
|
||||
build_ms,
|
||||
width = w
|
||||
);
|
||||
println!(
|
||||
" [SDK][{:width$}] {} before_submit: {:.4} ms",
|
||||
"-",
|
||||
dir,
|
||||
before_submit_ms,
|
||||
width = w
|
||||
);
|
||||
|
||||
println!("\n--- 2. 每个 SWQOS 独立耗时:submit_done=起点→该通道提交完成, confirmed=该通道提交→链上确认, total=起点→链上确认 ---\n");
|
||||
for (swqos_type, submit_ms, confirmed_ms, total_ms) in [
|
||||
@@ -388,7 +396,9 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
println!("\n--- 3. 不等待链上确认时:每行 total = 该通道 submit_done(提交完成总耗时)---\n");
|
||||
println!(
|
||||
"\n--- 3. 不等待链上确认时:每行 total = 该通道 submit_done(提交完成总耗时)---\n"
|
||||
);
|
||||
for (swqos_type, submit_ms, total_ms) in
|
||||
[(SwqosType::Jito, 44.20, 44.20), (SwqosType::Helius, 51.80, 51.80)]
|
||||
{
|
||||
@@ -403,8 +413,20 @@ mod tests {
|
||||
}
|
||||
|
||||
println!("\n--- 4. Simulate 模式(build/before_submit 仍从 grpc_recv_us 起算)---\n");
|
||||
println!(" [SDK][{:width$}] {} build_instructions: {:.4} ms", "-", dir, build_ms, width = w);
|
||||
println!(" [SDK][{:width$}] {} before_submit: {:.4} ms", "-", dir, before_submit_ms, width = w);
|
||||
println!(
|
||||
" [SDK][{:width$}] {} build_instructions: {:.4} ms",
|
||||
"-",
|
||||
dir,
|
||||
build_ms,
|
||||
width = w
|
||||
);
|
||||
println!(
|
||||
" [SDK][{:width$}] {} before_submit: {:.4} ms",
|
||||
"-",
|
||||
dir,
|
||||
before_submit_ms,
|
||||
width = w
|
||||
);
|
||||
println!(" [SDK][{:width$}] {} simulate (dry-run): {:.4} ms", "-", dir, 8.50, width = w);
|
||||
println!(" [SDK][{:width$}] {} total: {:.4} ms", "-", dir, 36.51, width = w);
|
||||
println!();
|
||||
|
||||
@@ -97,9 +97,8 @@ pub struct SwapParams {
|
||||
/// When Some(false), uses regular buy instruction where slippage is applied to SOL/quote input.
|
||||
/// This option only applies to PumpFun and PumpSwap DEXes; it is ignored for other DEXes.
|
||||
pub use_exact_sol_amount: Option<bool>,
|
||||
/// Use PumpFun V2 instructions (buy_v2 / sell_v2 / buy_exact_quote_in_v2, 27-account metas, quote_mint support).
|
||||
/// Default: `false` — uses V1 instructions (18-account metas, legacy SOL-paired).
|
||||
/// Set to `true` when PumpFun officially deploys V2 on mainnet. Until then, keep `false` for smaller transaction size.
|
||||
/// Use PumpFun V2 instructions (buy_v2 / sell_v2 / buy_exact_quote_in_v2, 27/26-account metas, quote_mint support).
|
||||
/// Default: `false` keeps legacy SOL-paired instructions for smaller transactions; V2 is the official future-proof interface.
|
||||
pub use_pumpfun_v2: bool,
|
||||
}
|
||||
|
||||
|
||||
@@ -41,9 +41,8 @@ impl MeteoraDammV2Params {
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let pool_data =
|
||||
crate::instruction::utils::meteora_damm_v2::fetch_pool(rpc, pool_address).await?;
|
||||
let mint_accounts = rpc
|
||||
.get_multiple_accounts(&[pool_data.token_a_mint, pool_data.token_b_mint])
|
||||
.await?;
|
||||
let mint_accounts =
|
||||
rpc.get_multiple_accounts(&[pool_data.token_a_mint, pool_data.token_b_mint]).await?;
|
||||
let token_a_program = mint_accounts
|
||||
.get(0)
|
||||
.and_then(|a| a.as_ref())
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::instruction::utils::pumpfun::reconcile_mayhem_mode_for_trade;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
||||
/// PumpFun protocol specific parameters
|
||||
/// Configuration parameters specific to PumpFun trading protocol.
|
||||
///
|
||||
@@ -96,16 +95,19 @@ impl PumpFunParams {
|
||||
is_mayhem_mode,
|
||||
is_cashback_coin,
|
||||
);
|
||||
let creator_vault_resolved = crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix_with_fee_sharing(
|
||||
&bonding_curve_account.creator,
|
||||
creator_vault,
|
||||
&mint,
|
||||
None,
|
||||
)
|
||||
.or_else(|| {
|
||||
crate::instruction::utils::pumpfun::get_creator_vault_pda(&bonding_curve_account.creator)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let creator_vault_resolved =
|
||||
crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix_with_fee_sharing(
|
||||
&bonding_curve_account.creator,
|
||||
creator_vault,
|
||||
&mint,
|
||||
None,
|
||||
)
|
||||
.or_else(|| {
|
||||
crate::instruction::utils::pumpfun::get_creator_vault_pda(
|
||||
&bonding_curve_account.creator,
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
Self {
|
||||
bonding_curve: Arc::new(bonding_curve_account),
|
||||
associated_bonding_curve: associated_bonding_curve,
|
||||
@@ -154,16 +156,17 @@ impl PumpFunParams {
|
||||
is_mayhem_mode,
|
||||
is_cashback_coin,
|
||||
);
|
||||
let creator_vault_resolved = crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix_with_fee_sharing(
|
||||
&bonding_curve.creator,
|
||||
creator_vault,
|
||||
&mint,
|
||||
None,
|
||||
)
|
||||
.or_else(|| {
|
||||
crate::instruction::utils::pumpfun::get_creator_vault_pda(&bonding_curve.creator)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let creator_vault_resolved =
|
||||
crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix_with_fee_sharing(
|
||||
&bonding_curve.creator,
|
||||
creator_vault,
|
||||
&mint,
|
||||
None,
|
||||
)
|
||||
.or_else(|| {
|
||||
crate::instruction::utils::pumpfun::get_creator_vault_pda(&bonding_curve.creator)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
Self {
|
||||
bonding_curve: Arc::new(bonding_curve),
|
||||
associated_bonding_curve: associated_bonding_curve,
|
||||
@@ -205,16 +208,21 @@ impl PumpFunParams {
|
||||
&mint_account.owner,
|
||||
);
|
||||
let fee_sharing_creator_vault_if_active =
|
||||
crate::instruction::utils::pumpfun::fetch_fee_sharing_creator_vault_if_active(rpc, mint)
|
||||
.await?;
|
||||
let creator_vault = crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix_with_fee_sharing(
|
||||
&bonding_curve.creator,
|
||||
Pubkey::default(),
|
||||
mint,
|
||||
fee_sharing_creator_vault_if_active,
|
||||
)
|
||||
.or_else(|| crate::instruction::utils::pumpfun::get_creator_vault_pda(&bonding_curve.creator))
|
||||
.unwrap_or_default();
|
||||
crate::instruction::utils::pumpfun::fetch_fee_sharing_creator_vault_if_active(
|
||||
rpc, mint,
|
||||
)
|
||||
.await?;
|
||||
let creator_vault =
|
||||
crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix_with_fee_sharing(
|
||||
&bonding_curve.creator,
|
||||
Pubkey::default(),
|
||||
mint,
|
||||
fee_sharing_creator_vault_if_active,
|
||||
)
|
||||
.or_else(|| {
|
||||
crate::instruction::utils::pumpfun::get_creator_vault_pda(&bonding_curve.creator)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
Ok(Self {
|
||||
bonding_curve: Arc::new(bonding_curve),
|
||||
associated_bonding_curve: associated_bonding_curve,
|
||||
@@ -246,8 +254,10 @@ impl PumpFunParams {
|
||||
mint: &Pubkey,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
self.fee_sharing_creator_vault_if_active =
|
||||
crate::instruction::utils::pumpfun::fetch_fee_sharing_creator_vault_if_active(rpc, mint)
|
||||
.await?;
|
||||
crate::instruction::utils::pumpfun::fetch_fee_sharing_creator_vault_if_active(
|
||||
rpc, mint,
|
||||
)
|
||||
.await?;
|
||||
let c = self.effective_creator_for_trade();
|
||||
if let Some(v) =
|
||||
crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix_with_fee_sharing(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::swqos::SwqosType;
|
||||
use crate::trading::SwapParams;
|
||||
use anyhow::Result;
|
||||
use solana_sdk::{instruction::Instruction, signature::Signature};
|
||||
use crate::swqos::{SwqosType};
|
||||
/// 交易执行器trait - 定义了所有交易协议都需要实现的核心方法
|
||||
#[async_trait::async_trait]
|
||||
pub trait TradeExecutor: Send + Sync {
|
||||
@@ -9,7 +9,10 @@ pub trait TradeExecutor: Send + Sync {
|
||||
/// - bool: 是否至少有一个交易成功
|
||||
/// - Vec<Signature>: 所有提交的交易签名(按SWQOS顺序)
|
||||
/// - Option<anyhow::Error>: 最后一个错误(如果全部失败)
|
||||
async fn swap(&self, params: SwapParams) -> Result<(bool, Vec<Signature>, Option<anyhow::Error>, Vec<(SwqosType, i64)>)>;
|
||||
async fn swap(
|
||||
&self,
|
||||
params: SwapParams,
|
||||
) -> Result<(bool, Vec<Signature>, Option<anyhow::Error>, Vec<(SwqosType, i64)>)>;
|
||||
/// 获取协议名称
|
||||
fn protocol_name(&self) -> &'static str;
|
||||
}
|
||||
|
||||
@@ -19,13 +19,13 @@ const TX_BUILDER_POOL_PREFILL: usize = 64;
|
||||
|
||||
use crossbeam_queue::ArrayQueue;
|
||||
use once_cell::sync::Lazy;
|
||||
use solana_message::AddressLookupTableAccount;
|
||||
use solana_sdk::{
|
||||
hash::Hash,
|
||||
instruction::Instruction,
|
||||
message::{v0, Message, VersionedMessage},
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
use solana_message::AddressLookupTableAccount;
|
||||
use std::sync::Arc;
|
||||
/// 预分配的交易构建器
|
||||
pub struct PreallocatedTxBuilder {
|
||||
|
||||
@@ -13,11 +13,8 @@ pub(crate) fn creator_side_fee_basis_points(
|
||||
coin_creator: &Pubkey,
|
||||
cashback_fee_basis_points: u64,
|
||||
) -> u64 {
|
||||
let creator_bps = if *coin_creator == Pubkey::default() {
|
||||
0
|
||||
} else {
|
||||
COIN_CREATOR_FEE_BASIS_POINTS
|
||||
};
|
||||
let creator_bps =
|
||||
if *coin_creator == Pubkey::default() { 0 } else { COIN_CREATOR_FEE_BASIS_POINTS };
|
||||
creator_bps.saturating_add(cashback_fee_basis_points)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user