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:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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::*;
|
||||
@@ -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
@@ -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>;
|
||||
|
||||
Reference in New Issue
Block a user