Skip to content

Commit bc8dcb5

Browse files
committed
Tune MM tx polling and gas bidding
1 parent aa4860b commit bc8dcb5

3 files changed

Lines changed: 84 additions & 20 deletions

File tree

‎sdk/rust/src/client.rs‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use alloy::primitives::Address;
2323
use alloy::providers::{DynProvider, Provider, ProviderBuilder};
2424
use alloy::signers::local::PrivateKeySigner;
2525
use std::sync::Arc;
26+
use std::time::Duration;
2627
use tokio::sync::Mutex;
2728

2829
use crate::chain::markets::MarketsClient;
@@ -107,6 +108,10 @@ impl StrikeClientBuilder {
107108
(DynProvider::new(p), None)
108109
};
109110

111+
provider.client().set_poll_interval(Duration::from_millis(
112+
self.config.tx.receipt_poll_interval_ms,
113+
));
114+
110115
Ok(StrikeClient {
111116
provider,
112117
config: self.config,
@@ -172,7 +177,7 @@ impl StrikeClient {
172177
/// The nonce manager is shared across clones of this client via `Arc`.
173178
pub async fn init_nonce_sender(&mut self) -> Result<()> {
174179
let signer = self.signer_addr.ok_or(StrikeError::NoWallet)?;
175-
let ns = NonceSender::new(self.provider.clone(), signer)
180+
let ns = NonceSender::new(self.provider.clone(), signer, self.config.tx.clone())
176181
.await
177182
.map_err(StrikeError::from)?;
178183
self.nonce_sender = Some(Arc::new(Mutex::new(ns)));

‎sdk/rust/src/config.rs‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,27 @@
22
33
use alloy::primitives::Address;
44

5+
/// Transaction send / confirmation tuning.
6+
#[derive(Debug, Clone)]
7+
pub struct TxConfig {
8+
/// HTTP receipt polling interval for remote RPC transports.
9+
pub receipt_poll_interval_ms: u64,
10+
/// Legacy gas price multiplier in basis points (10000 = 1.00x).
11+
pub gas_price_multiplier_bps: u64,
12+
/// Optional hard cap for legacy gas price bids.
13+
pub max_gas_price_wei: Option<u128>,
14+
}
15+
16+
impl Default for TxConfig {
17+
fn default() -> Self {
18+
Self {
19+
receipt_poll_interval_ms: 500,
20+
gas_price_multiplier_bps: 11_000,
21+
max_gas_price_wei: None,
22+
}
23+
}
24+
}
25+
526
/// Contract addresses for a Strike deployment.
627
#[derive(Debug, Clone)]
728
pub struct ContractAddresses {
@@ -29,6 +50,8 @@ pub struct StrikeConfig {
2950
pub wss_url: String,
3051
/// Indexer base URL.
3152
pub indexer_url: String,
53+
/// Transaction send / confirmation tuning.
54+
pub tx: TxConfig,
3255
}
3356

3457
impl StrikeConfig {
@@ -69,6 +92,7 @@ impl StrikeConfig {
6992
wss_url: "wss://bsc-testnet.core.chainstack.com/e602061228197d446d43e62320004d74"
7093
.to_string(),
7194
indexer_url: "https://testnet.strike.pm/api".to_string(),
95+
tx: TxConfig::default(),
7296
}
7397
}
7498

@@ -108,6 +132,7 @@ impl StrikeConfig {
108132
rpc_url: "https://bsc-dataseed1.binance.org".to_string(),
109133
wss_url: "wss://bsc-ws-node.nariox.org:443".to_string(),
110134
indexer_url: "https://app.strike.pm/api".to_string(),
135+
tx: TxConfig::default(),
111136
}
112137
}
113138

@@ -119,6 +144,7 @@ impl StrikeConfig {
119144
rpc_url: String::new(),
120145
wss_url: String::new(),
121146
indexer_url: String::new(),
147+
tx: TxConfig::default(),
122148
}
123149
}
124150
}

‎sdk/rust/src/nonce.rs‎

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ use alloy::rpc::types::TransactionRequest;
1111
use eyre::{Result, WrapErr};
1212
use tracing::{info, warn};
1313

14+
use crate::config::TxConfig;
15+
1416
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1517
enum SendErrorKind {
1618
NonceDrift,
@@ -51,20 +53,28 @@ pub struct NonceSender {
5153
provider: DynProvider,
5254
signer_addr: Address,
5355
nonce: u64,
56+
tx_config: TxConfig,
5457
}
5558

5659
impl NonceSender {
5760
/// Create a new NonceSender, fetching the current nonce from chain.
58-
pub async fn new(provider: DynProvider, signer_addr: Address) -> Result<Self> {
61+
pub async fn new(provider: DynProvider, signer_addr: Address, tx_config: TxConfig) -> Result<Self> {
5962
let nonce = provider
6063
.get_transaction_count(signer_addr)
6164
.await
6265
.wrap_err("failed to get initial nonce")?;
63-
info!(nonce, "NonceSender initialized");
66+
info!(
67+
nonce,
68+
receipt_poll_interval_ms = tx_config.receipt_poll_interval_ms,
69+
gas_price_multiplier_bps = tx_config.gas_price_multiplier_bps,
70+
max_gas_price_wei = ?tx_config.max_gas_price_wei,
71+
"NonceSender initialized"
72+
);
6473
Ok(Self {
6574
provider,
6675
signer_addr,
6776
nonce,
77+
tx_config,
6878
})
6979
}
7080

@@ -89,25 +99,48 @@ impl NonceSender {
8999
self.nonce
90100
}
91101

92-
/// Enforce BSC minimum gas price on a transaction request.
93-
///
94-
/// BSC uses legacy (non-EIP-1559) transactions. When no gas fields are set,
95-
/// alloy auto-fills at the provider level — which can result in values below
96-
/// the RPC's minimum. We force legacy `gas_price` to at least 0.05 gwei and
97-
/// clear EIP-1559 fields to prevent alloy from choosing type-2 transactions.
98-
fn apply_gas_floor(tx: TransactionRequest) -> TransactionRequest {
102+
/// Choose a competitive legacy gas price while preserving a hard floor.
103+
async fn resolve_gas_price(&self, explicit_gas_price: Option<u128>) -> Result<u128> {
104+
let configured_multiplier_bps = self.tx_config.gas_price_multiplier_bps.max(10_000);
105+
let live_gas_price = self
106+
.provider
107+
.get_gas_price()
108+
.await
109+
.wrap_err("failed to fetch live gas price")?;
110+
111+
let bumped_live_gas_price = live_gas_price
112+
.saturating_mul(configured_multiplier_bps as u128)
113+
.saturating_add(9_999)
114+
/ 10_000;
115+
116+
let mut chosen_gas_price = explicit_gas_price
117+
.unwrap_or(bumped_live_gas_price)
118+
.max(BSC_MIN_GAS_PRICE);
119+
120+
if let Some(max_gas_price_wei) = self.tx_config.max_gas_price_wei {
121+
if chosen_gas_price > max_gas_price_wei {
122+
warn!(
123+
live_gas_price,
124+
bumped_live_gas_price,
125+
chosen_gas_price,
126+
max_gas_price_wei,
127+
"capping legacy gas price at configured maximum"
128+
);
129+
chosen_gas_price = max_gas_price_wei.max(BSC_MIN_GAS_PRICE);
130+
}
131+
}
132+
133+
Ok(chosen_gas_price)
134+
}
135+
136+
/// Enforce BSC legacy gas pricing on a transaction request.
137+
async fn prepare_transaction(&self, tx: TransactionRequest) -> Result<TransactionRequest> {
99138
let mut tx = tx;
100-
// Force legacy gas price — BSC doesn't use EIP-1559
101-
let gp = tx.gas_price.unwrap_or(BSC_MIN_GAS_PRICE);
102-
tx.gas_price = Some(if gp < BSC_MIN_GAS_PRICE {
103-
BSC_MIN_GAS_PRICE
104-
} else {
105-
gp
106-
});
107-
// Clear EIP-1559 fields so alloy sends a type-0 (legacy) tx
139+
let gas_price = self.resolve_gas_price(tx.gas_price).await?;
140+
tx.gas_price = Some(gas_price);
108141
tx.max_fee_per_gas = None;
109142
tx.max_priority_fee_per_gas = None;
110-
tx
143+
Ok(tx)
111144
}
112145

113146
/// Send a transaction, stamping it with the next nonce.
@@ -120,7 +153,7 @@ impl NonceSender {
120153
/// The returned [`PendingTx`] can be `.await`ed for the receipt
121154
/// **after** releasing the Mutex lock.
122155
pub async fn send(&mut self, tx: TransactionRequest) -> Result<PendingTx> {
123-
let tx = Self::apply_gas_floor(tx);
156+
let tx = self.prepare_transaction(tx).await?;
124157
let attempt = tx.clone().nonce(self.nonce);
125158
match self.provider.send_transaction(attempt).await {
126159
Ok(pending) => {

0 commit comments

Comments
 (0)