feat: add global MEV protection and refactor TradeConfig to builder pattern
- Add `mev_protection: bool` to `TradeConfig` and `InfrastructureConfig` (default: false) - Astralane QUIC: switches to port 9000 (MEV-protected endpoint) when enabled - BlockRazor HTTP: uses `mode=sandwichMitigation` query param when enabled - BlockRazor gRPC: uses `mode=sandwichMitigation` when enabled - Add `SWQOS_ENDPOINTS_ASTRALANE_QUIC_MEV` constants (port 9000) to `constants/swqos.rs` - Fix `astralane_quic.rs` IP candidates to use the actual port from the address (supports both 7000 and 9000) - Refactor `TradeConfig` to builder pattern via `TradeConfig::builder()` - Introduce `TradeConfigBuilder` with all optional fields and clear defaults - `TradeConfig::new()` kept as a shortcut (calls `builder().build()`) for backward compatibility - Remove old `with_wsol_ata_config` / `with_check_min_tip` / `with_swqos_cores_from_end` / `with_mev_protection` chain methods - Update all 16 examples to use `TradeConfig::builder()` with commented-out options so users can discover all available settings at a glance - Update README.md and README_CN.md code snippets to use builder pattern 🤖 Generated with [Qoder][https://qoder.com]
This commit is contained in:
+10
-10
@@ -52,32 +52,32 @@ impl AstralaneQuicClient {
|
||||
fn astralane_quic_ip_candidates(host: &str, port: u16) -> Vec<SocketAddr> {
|
||||
// Official recommended direct-IP list (faster/more stable than DNS-only for QUIC).
|
||||
// We intentionally avoid fr2/ams2 per prior guidance.
|
||||
// Both port 7000 (standard) and port 9000 (MEV-protected) use the same IPs.
|
||||
match host {
|
||||
"fr.gateway.astralane.io" => vec![
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(185, 191, 117, 97)), 7000),
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(45, 139, 132, 160)), 7000),
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(185, 191, 117, 97)), port),
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(45, 139, 132, 160)), port),
|
||||
],
|
||||
"ny.gateway.astralane.io" => {
|
||||
vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(64, 130, 45, 19)), 7000)]
|
||||
vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(64, 130, 45, 19)), port)]
|
||||
}
|
||||
"ams.gateway.astralane.io" => vec![
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(64, 130, 43, 43)), 7000),
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(84, 32, 186, 73)), 7000),
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(64, 130, 43, 43)), port),
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(84, 32, 186, 73)), port),
|
||||
],
|
||||
"la.gateway.astralane.io" => {
|
||||
vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(74, 118, 142, 151)), 7000)]
|
||||
vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(74, 118, 142, 151)), port)]
|
||||
}
|
||||
"lim.gateway.astralane.io" => {
|
||||
vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(162, 19, 222, 232)), 7000)]
|
||||
vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(162, 19, 222, 232)), port)]
|
||||
}
|
||||
"sg.gateway.astralane.io" => {
|
||||
vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(67, 209, 54, 176)), 7000)]
|
||||
vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(67, 209, 54, 176)), port)]
|
||||
}
|
||||
"lit.gateway.astralane.io" => {
|
||||
vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(84, 32, 97, 47)), 7000)]
|
||||
vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(84, 32, 97, 47)), port)]
|
||||
}
|
||||
_ => {
|
||||
let _ = port; // keep signature consistent; fall back to DNS below.
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
+24
-5
@@ -89,6 +89,8 @@ pub enum BlockRazorBackend {
|
||||
grpc_client: Arc<ArcSwap<BlockRazorGrpcClient>>,
|
||||
ping_handle: Arc<tokio::sync::Mutex<Option<JoinHandle<()>>>>,
|
||||
stop_ping: Arc<AtomicBool>,
|
||||
/// When true, gRPC send_transaction sets revert_protection=true for MEV protection.
|
||||
mev_protection: bool,
|
||||
},
|
||||
Http {
|
||||
endpoint: String,
|
||||
@@ -96,6 +98,8 @@ pub enum BlockRazorBackend {
|
||||
http_client: Client,
|
||||
ping_handle: Arc<tokio::sync::Mutex<Option<JoinHandle<()>>>>,
|
||||
stop_ping: Arc<AtomicBool>,
|
||||
/// When true, HTTP request adds revertProtection=true query param for MEV protection.
|
||||
mev_protection: bool,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -144,10 +148,10 @@ impl SwqosClientTrait for BlockRazorClient {
|
||||
impl BlockRazorClient {
|
||||
pub async fn new(rpc_url: String, endpoint: String, auth_token: String) -> Result<Self> {
|
||||
// 默认使用 HTTP 模式,避免 gRPC FRAME_SIZE_ERROR
|
||||
Ok(Self::new_http(rpc_url, endpoint, auth_token))
|
||||
Ok(Self::new_http(rpc_url, endpoint, auth_token, false))
|
||||
}
|
||||
|
||||
pub async fn new_grpc(rpc_url: String, endpoint: String, auth_token: String) -> 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,增加连接超时
|
||||
@@ -173,6 +177,7 @@ impl BlockRazorClient {
|
||||
grpc_client,
|
||||
ping_handle,
|
||||
stop_ping,
|
||||
mev_protection,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -184,7 +189,7 @@ impl BlockRazorClient {
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
pub fn new_http(rpc_url: String, endpoint: String, auth_token: String) -> 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));
|
||||
@@ -198,6 +203,7 @@ impl BlockRazorClient {
|
||||
http_client,
|
||||
ping_handle,
|
||||
stop_ping,
|
||||
mev_protection,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -217,6 +223,7 @@ impl BlockRazorClient {
|
||||
stop_ping,
|
||||
endpoint,
|
||||
auth_token,
|
||||
..
|
||||
} => {
|
||||
let grpc_client = grpc_client.clone();
|
||||
let ping_handle = ping_handle.clone();
|
||||
@@ -293,6 +300,7 @@ impl BlockRazorClient {
|
||||
http_client,
|
||||
ping_handle,
|
||||
stop_ping,
|
||||
..
|
||||
} => {
|
||||
let endpoint = endpoint.clone();
|
||||
let auth_token = auth_token.clone();
|
||||
@@ -374,6 +382,7 @@ impl BlockRazorClient {
|
||||
match &self.backend {
|
||||
BlockRazorBackend::Grpc {
|
||||
grpc_client,
|
||||
mev_protection,
|
||||
..
|
||||
} => {
|
||||
let (content, _signature) =
|
||||
@@ -383,7 +392,9 @@ impl BlockRazorClient {
|
||||
let client = grpc_client.load();
|
||||
let signature = client.send_transaction(
|
||||
content,
|
||||
"fast".to_string(),
|
||||
// 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;
|
||||
@@ -412,14 +423,22 @@ impl BlockRazorClient {
|
||||
endpoint,
|
||||
auth_token,
|
||||
http_client,
|
||||
mev_protection,
|
||||
..
|
||||
} => {
|
||||
let (content, _signature) =
|
||||
serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?;
|
||||
|
||||
let mut query_params: Vec<(&str, &str)> = vec![
|
||||
("auth", auth_token.as_str()),
|
||||
// mev_protection=true: sandwichMitigation mode skips blacklisted Leader slots (MEV protection).
|
||||
// revertProtection is unrelated to MEV; not set.
|
||||
("mode", if *mev_protection { "sandwichMitigation" } else { "fast" }),
|
||||
];
|
||||
|
||||
let response = http_client
|
||||
.post(endpoint)
|
||||
.query(&[("auth", auth_token.as_str())])
|
||||
.query(&query_params)
|
||||
.header("Content-Type", "text/plain")
|
||||
.body(content)
|
||||
.send()
|
||||
|
||||
+20
-7
@@ -29,7 +29,8 @@ use anyhow::Result;
|
||||
use crate::{
|
||||
common::SolanaRpcClient,
|
||||
constants::swqos::{
|
||||
SWQOS_ENDPOINTS_ASTRALANE, SWQOS_ENDPOINTS_ASTRALANE_QUIC, SWQOS_ENDPOINTS_BLOCKRAZOR,
|
||||
SWQOS_ENDPOINTS_ASTRALANE, 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,
|
||||
@@ -303,6 +304,7 @@ impl SwqosConfig {
|
||||
region: SwqosRegion,
|
||||
url: Option<String>,
|
||||
transport: Option<SwqosTransport>,
|
||||
mev_protection: bool,
|
||||
) -> String {
|
||||
if let Some(custom_url) = url {
|
||||
return custom_url;
|
||||
@@ -329,12 +331,17 @@ impl SwqosConfig {
|
||||
SwqosType::Astralane => {
|
||||
let use_quic = transport.map_or(false, |t| t == SwqosTransport::Quic);
|
||||
if use_quic {
|
||||
SWQOS_ENDPOINTS_ASTRALANE_QUIC[region as usize].to_string()
|
||||
// MEV protection: port 9000; standard: port 7000
|
||||
if mev_protection {
|
||||
SWQOS_ENDPOINTS_ASTRALANE_QUIC_MEV[region as usize].to_string()
|
||||
} else {
|
||||
SWQOS_ENDPOINTS_ASTRALANE_QUIC[region as usize].to_string()
|
||||
}
|
||||
} else {
|
||||
SWQOS_ENDPOINTS_ASTRALANE[region as usize].to_string()
|
||||
}
|
||||
}
|
||||
_ => Self::get_endpoint(swqos_type, region, url),
|
||||
_ => Self::get_endpoint(swqos_type, region, None),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,6 +349,7 @@ impl SwqosConfig {
|
||||
rpc_url: String,
|
||||
commitment: CommitmentConfig,
|
||||
swqos_config: SwqosConfig,
|
||||
mev_protection: bool,
|
||||
) -> Result<Arc<SwqosClient>> {
|
||||
match swqos_config {
|
||||
SwqosConfig::Jito(auth_token, region, url) => {
|
||||
@@ -398,15 +406,15 @@ 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);
|
||||
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);
|
||||
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).await?;
|
||||
BlockRazorClient::new_grpc(rpc_url.clone(), endpoint.to_string(), auth_token, mev_protection).await?;
|
||||
Ok(Arc::new(blockrazor_client))
|
||||
}
|
||||
}
|
||||
@@ -414,7 +422,12 @@ impl SwqosConfig {
|
||||
let use_quic = transport.map_or(false, |t| t == SwqosTransport::Quic);
|
||||
if use_quic {
|
||||
let quic_endpoint = url.unwrap_or_else(|| {
|
||||
SWQOS_ENDPOINTS_ASTRALANE_QUIC[region as usize].to_string()
|
||||
// MEV protection: port 9000; standard: port 7000
|
||||
if mev_protection {
|
||||
SWQOS_ENDPOINTS_ASTRALANE_QUIC_MEV[region as usize].to_string()
|
||||
} else {
|
||||
SWQOS_ENDPOINTS_ASTRALANE_QUIC[region as usize].to_string()
|
||||
}
|
||||
});
|
||||
let astralane_client =
|
||||
AstralaneClient::new_quic(rpc_url.clone(), &quic_endpoint, auth_token)
|
||||
|
||||
Reference in New Issue
Block a user