feat: major gas fee strategy and documentation overhaul

- feat(gas): add comprehensive gas fee strategy management system
  * Implement GasFeeStrategyType with Default/LowTipHighCuPrice/HighTipLowCuPrice strategies
  * Add dynamic gas fee configuration per SWQOS service and trade type
  * Integrate arc-swap for thread-safe strategy updates

- docs: restructure documentation with dedicated guides
  * Add ADDRESS_LOOKUP_TABLE.md/CN.md for lookup table configuration
  * Add NONCE_CACHE.md/CN.md for nonce management documentation
  * Add TRADING_PARAMETERS.md/CN.md for comprehensive parameter guides
  * Simplify main README.md with focused overview

- refactor: update all examples with new gas fee strategy integration
  * Modernize 14 trading examples with consistent parameter handling
  * Improve error handling and configuration management
  * Enhance parallel trading implementation
This commit is contained in:
ysq
2025-09-19 00:49:45 +08:00
parent fad343ffbf
commit 006686e8a3
45 changed files with 2133 additions and 1681 deletions
+316
View File
@@ -0,0 +1,316 @@
use crate::swqos::{SwqosType, TradeType};
use arc_swap::ArcSwap;
use std::collections::HashMap;
use std::sync::{Arc, OnceLock};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum GasFeeStrategyType {
Default,
LowTipHighCuPrice,
HighTipLowCuPrice,
}
impl GasFeeStrategyType {
pub fn values() -> Vec<Self> {
vec![Self::Default, Self::LowTipHighCuPrice, Self::HighTipLowCuPrice]
}
}
#[derive(Debug, Clone, Copy)]
pub struct GasFeeStrategyValue {
pub cu_limit: u32,
pub cu_price: u64,
pub tip: f64,
}
#[derive(Debug)]
pub struct GasFeeStrategy {
strategies: ArcSwap<HashMap<(SwqosType, TradeType, GasFeeStrategyType), GasFeeStrategyValue>>,
enabled_types: ArcSwap<HashMap<TradeType, Vec<GasFeeStrategyType>>>,
swqos_disabled_types: ArcSwap<HashMap<(SwqosType, TradeType), Vec<GasFeeStrategyType>>>,
}
static INSTANCE: OnceLock<Arc<GasFeeStrategy>> = OnceLock::new();
impl GasFeeStrategy {
pub fn instance() -> Arc<GasFeeStrategy> {
INSTANCE.get_or_init(|| Arc::new(GasFeeStrategy::new())).clone()
}
fn new() -> Self {
Self {
strategies: ArcSwap::new(Arc::new(HashMap::new())),
enabled_types: ArcSwap::new(Arc::new(HashMap::new())),
swqos_disabled_types: ArcSwap::new(Arc::new(HashMap::new())),
}
}
pub fn add_high_low_fee_strategies(
&self,
swqos_types: &[SwqosType],
trade_type: TradeType,
cu_limit: u32,
low_cu_price: u64,
high_cu_price: u64,
low_tip: f64,
high_tip: f64,
) -> &Self {
self.strategies.rcu(|current_map| {
let mut new_map = (**current_map).clone();
for swqos_type in swqos_types {
if swqos_type.eq(&SwqosType::Default) {
continue;
}
new_map.insert(
(*swqos_type, trade_type, GasFeeStrategyType::LowTipHighCuPrice),
GasFeeStrategyValue { cu_limit, cu_price: high_cu_price, tip: low_tip },
);
new_map.insert(
(*swqos_type, trade_type, GasFeeStrategyType::HighTipLowCuPrice),
GasFeeStrategyValue { cu_limit, cu_price: low_cu_price, tip: high_tip },
);
}
Arc::new(new_map)
});
self
}
pub fn add_high_low_fee_strategy(
&self,
swqos_type: SwqosType,
trade_type: TradeType,
cu_limit: u32,
low_cu_price: u64,
high_cu_price: u64,
low_tip: f64,
high_tip: f64,
) -> &Self {
if swqos_type.eq(&SwqosType::Default) {
return self;
}
self.strategies.rcu(|current_map| {
let mut new_map = (**current_map).clone();
new_map.insert(
(swqos_type, trade_type, GasFeeStrategyType::LowTipHighCuPrice),
GasFeeStrategyValue { cu_limit, cu_price: high_cu_price, tip: low_tip },
);
new_map.insert(
(swqos_type, trade_type, GasFeeStrategyType::HighTipLowCuPrice),
GasFeeStrategyValue { cu_limit, cu_price: low_cu_price, tip: high_tip },
);
Arc::new(new_map)
});
self
}
pub fn add_default_fee_strategies(
&self,
swqos_types: &[SwqosType],
trade_type: TradeType,
cu_price: u64,
tip: f64,
cu_limit: u32,
) -> &Self {
self.strategies.rcu(|current_map| {
let mut new_map = (**current_map).clone();
for swqos_type in swqos_types {
new_map.insert(
(*swqos_type, trade_type, GasFeeStrategyType::Default),
GasFeeStrategyValue { cu_limit, cu_price, tip },
);
}
Arc::new(new_map)
});
self
}
pub fn add_default_fee_strategy(
&self,
swqos_type: SwqosType,
trade_type: TradeType,
cu_price: u64,
tip: f64,
cu_limit: u32,
) -> &Self {
self.strategies.rcu(|current_map| {
let mut new_map = (**current_map).clone();
new_map.insert(
(swqos_type, trade_type, GasFeeStrategyType::Default),
GasFeeStrategyValue { cu_limit, cu_price, tip },
);
Arc::new(new_map)
});
self
}
pub fn remove_default_fee_strategy(
&self,
swqos_type: SwqosType,
trade_type: TradeType,
) -> &Self {
self.strategies.rcu(|current_map| {
let mut new_map = (**current_map).clone();
new_map.remove(&(swqos_type, trade_type, GasFeeStrategyType::Default));
Arc::new(new_map)
});
self
}
pub fn remove_high_low_fee_strategy(
&self,
swqos_type: SwqosType,
trade_type: TradeType,
) -> &Self {
self.strategies.rcu(|current_map| {
let mut new_map = (**current_map).clone();
new_map.remove(&(swqos_type, trade_type, GasFeeStrategyType::LowTipHighCuPrice));
new_map.remove(&(swqos_type, trade_type, GasFeeStrategyType::HighTipLowCuPrice));
Arc::new(new_map)
});
self
}
pub fn get_all_strategies(
&self,
) -> HashMap<(SwqosType, TradeType, GasFeeStrategyType), GasFeeStrategyValue> {
(**self.strategies.load()).clone()
}
pub fn get_strategies(
&self,
trade_type: TradeType,
) -> Vec<(SwqosType, GasFeeStrategyType, GasFeeStrategyValue)> {
let strategies = self.strategies.load();
let mut result = Vec::new();
let mut swqos_types = std::collections::HashSet::new();
for (swqos_type, t_type, _) in strategies.keys() {
if *t_type == trade_type {
swqos_types.insert(*swqos_type);
}
}
for swqos_type in swqos_types {
for strategy_type in GasFeeStrategyType::values() {
if let Some(strategy_value) =
strategies.get(&(swqos_type, trade_type, strategy_type))
{
result.push((swqos_type, strategy_type, *strategy_value));
}
}
}
result
}
pub fn get_available_strategies(
&self,
trade_type: TradeType,
) -> Vec<(SwqosType, GasFeeStrategyType, GasFeeStrategyValue)> {
let strategies = self.strategies.load();
let enabled_types = self.get_enabled_strategy_types(trade_type);
let mut result = Vec::new();
let mut swqos_types = std::collections::HashSet::new();
for (swqos_type, t_type, _) in strategies.keys() {
if *t_type == trade_type {
swqos_types.insert(*swqos_type);
}
}
for swqos_type in swqos_types {
let disabled_types = self.get_swqos_disabled_strategy_types(swqos_type, trade_type);
for strategy_type in &enabled_types {
if disabled_types.contains(strategy_type) {
continue;
}
if let Some(strategy_value) =
strategies.get(&(swqos_type, trade_type, *strategy_type))
{
result.push((swqos_type, *strategy_type, *strategy_value));
}
}
}
result
}
pub fn set_enabled_strategy_types(
&self,
trade_type: TradeType,
types: &[GasFeeStrategyType],
) -> &Self {
self.enabled_types.rcu(|current_map| {
let mut new_map = (**current_map).clone();
new_map.insert(trade_type, types.to_vec());
Arc::new(new_map)
});
self
}
pub fn get_enabled_strategy_types(&self, trade_type: TradeType) -> Vec<GasFeeStrategyType> {
let strategies = self.enabled_types.load();
(**strategies).get(&trade_type).cloned().unwrap_or_default()
}
pub fn set_swqos_disabled_strategy_types(
&self,
swqos_type: SwqosType,
trade_type: TradeType,
types: &[GasFeeStrategyType],
) -> &Self {
self.swqos_disabled_types.rcu(|current_map| {
let mut new_map = (**current_map).clone();
new_map.insert((swqos_type, trade_type), types.to_vec());
Arc::new(new_map)
});
self
}
pub fn get_swqos_disabled_strategy_types(
&self,
swqos_type: SwqosType,
trade_type: TradeType,
) -> Vec<GasFeeStrategyType> {
let disabled_strategies = self.swqos_disabled_types.load();
(**disabled_strategies).get(&(swqos_type, trade_type)).cloned().unwrap_or_default()
}
pub fn clear(&self) -> &Self {
self.strategies.store(Arc::new(HashMap::new()));
self.enabled_types.store(Arc::new(HashMap::new()));
self.swqos_disabled_types.store(Arc::new(HashMap::new()));
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn demo() {
GasFeeStrategy::instance()
// 给SwqosType::Default在 Buy 时添加默认策略
.add_default_fee_strategy(SwqosType::Default, TradeType::Buy, 100, 0.0001, 10)
// 给SwqosType::Jito在 Buy 时添加高低价策略
.add_high_low_fee_strategy(SwqosType::Jito, TradeType::Buy, 10, 100, 10000, 0.001, 0.1)
// 给SwqosType::Jito在 Buy 时添加默认策略
.add_default_fee_strategy(SwqosType::Jito, TradeType::Buy, 100, 0.0001, 10)
// 设置在 Buy 时启用策略 - 启用两个高低价策略、默认策略
.set_enabled_strategy_types(
TradeType::Buy,
&[
GasFeeStrategyType::HighTipLowCuPrice,
GasFeeStrategyType::LowTipHighCuPrice,
GasFeeStrategyType::Default,
],
)
// 设置SwqosType::Jito在 Buy 时禁用策略 - 禁用 (默认策略)
.set_swqos_disabled_strategy_types(
SwqosType::Jito,
TradeType::Buy,
&[GasFeeStrategyType::Default],
);
// 获取在 Buy 时可用的策略
let strategies = GasFeeStrategy::instance().get_available_strategies(TradeType::Buy);
println!("strategies: {:?}", strategies);
// 获取所有 Buy 策略(包括禁用的)
let all_strategies = GasFeeStrategy::instance().get_strategies(TradeType::Buy);
println!("all_strategies: {:?}", all_strategies);
}
}
+2
View File
@@ -6,5 +6,7 @@ pub mod nonce_cache;
pub mod seed;
pub mod subscription_handle;
pub mod types;
pub mod gas_fee_strategy;
pub use types::*;
pub use gas_fee_strategy::*;
+3 -17
View File
@@ -15,8 +15,6 @@ pub struct NonceInfo {
pub nonce_account: Option<Pubkey>,
/// Current nonce value
pub current_nonce: Hash,
/// Next available time (Unix timestamp in seconds)
pub next_buy_time: i64,
/// Whether it has been used
pub used: bool,
}
@@ -39,7 +37,6 @@ impl NonceCache {
nonce_info: Mutex::new(NonceInfo {
nonce_account: None,
current_nonce: Hash::default(),
next_buy_time: 0,
used: false,
}),
})
@@ -50,7 +47,7 @@ impl NonceCache {
/// Initialize nonce information
pub fn init(&self, nonce_account_str: Option<String>) {
let nonce_account = nonce_account_str.and_then(|s| Pubkey::from_str(&s).ok());
self.update_nonce_info_partial(nonce_account, None, None, Some(false));
self.update_nonce_info_partial(nonce_account, None, Some(false));
}
/// Get a copy of NonceInfo
@@ -59,7 +56,6 @@ impl NonceCache {
NonceInfo {
nonce_account: nonce_info.nonce_account,
current_nonce: nonce_info.current_nonce,
next_buy_time: nonce_info.next_buy_time,
used: nonce_info.used,
}
}
@@ -69,7 +65,6 @@ impl NonceCache {
&self,
nonce_account: Option<Pubkey>,
current_nonce: Option<Hash>,
next_buy_time: Option<i64>,
used: Option<bool>,
) {
let mut current = self.nonce_info.lock();
@@ -83,10 +78,6 @@ impl NonceCache {
current.current_nonce = nonce;
}
if let Some(time) = next_buy_time {
current.next_buy_time = time;
}
if let Some(u) = used {
current.used = u;
}
@@ -94,7 +85,7 @@ impl NonceCache {
/// Mark nonce as used
pub fn mark_used(&self) {
self.update_nonce_info_partial(None, None, None, Some(true));
self.update_nonce_info_partial(None, None, Some(true));
}
/// Fetch nonce information using RPC
@@ -109,12 +100,7 @@ impl NonceCache {
let blockhash = data.durable_nonce.as_hash();
let old_nonce_info = self.get_nonce_info();
if old_nonce_info.current_nonce != *blockhash {
self.update_nonce_info_partial(
None,
Some(*blockhash),
None,
Some(false),
);
self.update_nonce_info_partial(None, Some(*blockhash), Some(false));
}
}
}
+3 -61
View File
@@ -1,21 +1,10 @@
use std::sync::Arc;
use crate::{
constants::trade::trade::{
DEFAULT_BUY_TIP_FEE, DEFAULT_RPC_UNIT_LIMIT, DEFAULT_RPC_UNIT_PRICE, DEFAULT_SELL_TIP_FEE,
DEFAULT_TIP_UNIT_LIMIT, DEFAULT_TIP_UNIT_PRICE,
},
swqos::{SwqosClient, SwqosConfig},
};
use serde::Deserialize;
use solana_client::rpc_client::RpcClient;
use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair};
use crate::swqos::SwqosConfig;
use solana_sdk::commitment_config::CommitmentConfig;
#[derive(Debug, Clone)]
pub struct TradeConfig {
pub rpc_url: String,
pub swqos_configs: Vec<SwqosConfig>,
pub priority_fee: PriorityFee,
pub commitment: CommitmentConfig,
}
@@ -23,58 +12,11 @@ impl TradeConfig {
pub fn new(
rpc_url: String,
swqos_configs: Vec<SwqosConfig>,
priority_fee: PriorityFee,
commitment: CommitmentConfig,
) -> Self {
Self { rpc_url, swqos_configs, priority_fee, commitment }
}
}
#[derive(Debug, Deserialize, Clone, PartialEq)]
pub struct PriorityFee {
pub tip_unit_limit: u32,
pub tip_unit_price: u64,
pub rpc_unit_limit: u32,
pub rpc_unit_price: u64,
// Matches the order of swqos
pub buy_tip_fees: Vec<f64>,
// Matches the order of swqos
pub sell_tip_fees: Vec<f64>,
}
impl Default for PriorityFee {
fn default() -> Self {
Self {
tip_unit_limit: DEFAULT_TIP_UNIT_LIMIT,
tip_unit_price: DEFAULT_TIP_UNIT_PRICE,
rpc_unit_limit: DEFAULT_RPC_UNIT_LIMIT,
rpc_unit_price: DEFAULT_RPC_UNIT_PRICE,
// Matches the order of swqos
buy_tip_fees: vec![DEFAULT_BUY_TIP_FEE],
// Matches the order of swqos
sell_tip_fees: vec![DEFAULT_SELL_TIP_FEE],
}
Self { rpc_url, swqos_configs, commitment }
}
}
pub type SolanaRpcClient = solana_client::nonblocking::rpc_client::RpcClient;
pub struct MethodArgs {
pub payer: Arc<Keypair>,
pub rpc: Arc<RpcClient>,
pub nonblocking_rpc: Arc<SolanaRpcClient>,
pub jito_client: Arc<SwqosClient>,
}
impl MethodArgs {
pub fn new(
payer: Arc<Keypair>,
rpc: Arc<RpcClient>,
nonblocking_rpc: Arc<SolanaRpcClient>,
jito_client: Arc<SwqosClient>,
) -> Self {
Self { payer, rpc, nonblocking_rpc, jito_client }
}
}
pub type AnyResult<T> = anyhow::Result<T>;