Skip to content

Commit a7c640c

Browse files
committed
cap the number of concurrent Jupiter calls to 2 to avoid 429 errors
1 parent ae57f55 commit a7c640c

4 files changed

Lines changed: 43 additions & 12 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ serde = { version = "1.0.228", features = ["derive"] }
5454
serde_json = "1.0.149"
5555
signal-hook = "0.4.3"
5656
tiny_http = "0.12.0"
57-
tokio = { version = "1.50.0", features = ["rt", "macros"] }
57+
tokio = { version = "1.50.0", features = ["rt", "macros", "sync"] }
5858
warp = "0.4.2"
5959

6060
yellowstone-grpc-client = "10.2.0"

src/execution/inventory.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ use log::{debug, info};
2222
use solana_dex_superagg::{buy_shortfall, DexSuperAggClient};
2323
use solana_program::pubkey::Pubkey;
2424
use solana_sdk::transaction::VersionedTransaction;
25-
use tokio::runtime::{Builder, Runtime};
25+
use tokio::{
26+
runtime::{Builder, Runtime},
27+
sync::Semaphore,
28+
};
2629

2730
use crate::clock_manager;
2831
use crate::wrappers::{
@@ -41,6 +44,7 @@ const INPUT_BUFFER: f64 = 1.02;
4144
pub struct InventoryStrategy {
4245
liquidator_account: Arc<LiquidatorAccount>,
4346
dex_client: Arc<DexSuperAggClient>,
47+
dex_gate: Arc<Semaphore>,
4448
tokio_rt: Runtime,
4549
swap_mint: Pubkey,
4650
slippage_bps: u16,
@@ -51,6 +55,7 @@ impl InventoryStrategy {
5155
liquidator_account: Arc<LiquidatorAccount>,
5256
swap_mint: Pubkey,
5357
dex_client: Arc<DexSuperAggClient>,
58+
dex_gate: Arc<Semaphore>,
5459
slippage_bps: u16,
5560
) -> Result<Self> {
5661
// Multi-threaded so concurrent liquidations can `block_on` DEX quotes at the same time —
@@ -64,6 +69,7 @@ impl InventoryStrategy {
6469

6570
Ok(Self {
6671
liquidator_account,
72+
dex_gate,
6773
dex_client,
6874
tokio_rt,
6975
swap_mint,
@@ -114,16 +120,20 @@ impl InventoryStrategy {
114120
route_config.slippage_bps = Some(self.slippage_bps);
115121
route_config.wrap_and_unwrap_sol = false;
116122

117-
let prepared = self.tokio_rt.block_on(
123+
// One permit for the whole sequence: the min-out flow quotes, re-quotes and then builds,
124+
// which is a handful of aggregator requests per liquidation.
125+
let prepared = self.tokio_rt.block_on(async {
126+
let _permit = self.dex_gate.acquire().await?;
118127
self.dex_client
119128
.build_swap_transaction_for_min_out_with_route_config(
120129
&self.swap_mint.to_string(),
121130
&output_mint.to_string(),
122131
input_amount,
123132
min_out,
124133
route_config,
125-
),
126-
)?;
134+
)
135+
.await
136+
})?;
127137
debug!(
128138
"InventoryStrategy: ExactIn spends {} {} for {} {} (need {})",
129139
prepared.in_amount, self.swap_mint, prepared.out_amount, output_mint, min_out

src/liquidator.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ impl Liquidator {
9393

9494
let rebalancer = Rebalancer::new(config.clone(), cache.clone())?;
9595
let dex_client = rebalancer.dex_client();
96+
let dex_gate = rebalancer.dex_gate();
9697

9798
let jito = JitoClient::new(
9899
config.jito_block_engine_url.clone(),
@@ -116,6 +117,7 @@ impl Liquidator {
116117
liquidator_account.clone(),
117118
config.swap_mint,
118119
dex_client,
120+
dex_gate,
119121
config.slippage_bps,
120122
)?;
121123

src/rebalancer.rs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,15 @@ use solana_dex_superagg::{
99
};
1010
use solana_program::pubkey::Pubkey;
1111
use std::{collections::HashSet, sync::Arc};
12-
use tokio::runtime::{Builder, Runtime};
12+
use tokio::{
13+
runtime::{Builder, Runtime},
14+
sync::Semaphore,
15+
};
16+
17+
/// Concurrent DEX operations allowed across the liquidation workers and the rebalancer. Each one
18+
/// costs several aggregator round-trips, so an unbounded burst trips the Jupiter rate limit (429)
19+
/// and the failing targets then sit in the executor's backoff.
20+
const MAX_CONCURRENT_DEX_CALLS: usize = 2;
1321

1422
/// Don't bother selling a position worth less than this (USD); the swap fee/dust isn't worth it.
1523
const MIN_REBALANCE_VALUE: I80F48 = I80F48!(1.0);
@@ -23,6 +31,7 @@ pub struct Rebalancer {
2331
tokio_rt: Runtime,
2432
cache: Arc<Cache>,
2533
dex_client: Arc<DexSuperAggClient>,
34+
dex_gate: Arc<Semaphore>,
2635
empty_stake_banks: HashSet<Pubkey>,
2736
unpriceable_mints: HashSet<Pubkey>,
2837
}
@@ -74,6 +83,7 @@ impl Rebalancer {
7483
tokio_rt,
7584
cache,
7685
dex_client,
86+
dex_gate: Arc::new(Semaphore::new(MAX_CONCURRENT_DEX_CALLS)),
7787
empty_stake_banks: HashSet::new(),
7888
unpriceable_mints: HashSet::new(),
7989
})
@@ -83,6 +93,10 @@ impl Rebalancer {
8393
Arc::clone(&self.dex_client)
8494
}
8595

96+
pub fn dex_gate(&self) -> Arc<Semaphore> {
97+
Arc::clone(&self.dex_gate)
98+
}
99+
86100
/// Sell every non-swap-mint token the wallet holds (above the dust floor) back to the swap mint.
87101
pub fn run(&mut self) -> anyhow::Result<()> {
88102
info!("Running the Rebalancing process...");
@@ -191,12 +205,17 @@ impl Rebalancer {
191205
const WSOL: Pubkey = Pubkey::from_str_const("So11111111111111111111111111111111111111112");
192206
let wrap_and_unwrap_sol = input_mint == WSOL || output_mint == WSOL;
193207

194-
let result = self.tokio_rt.block_on(self.dex_client.swap(
195-
&input_mint.to_string(),
196-
&output_mint.to_string(),
197-
amount,
198-
wrap_and_unwrap_sol,
199-
))?;
208+
let result = self.tokio_rt.block_on(async {
209+
let _permit = self.dex_gate.acquire().await?;
210+
self.dex_client
211+
.swap(
212+
&input_mint.to_string(),
213+
&output_mint.to_string(),
214+
amount,
215+
wrap_and_unwrap_sol,
216+
)
217+
.await
218+
})?;
200219

201220
info!(
202221
"Swap successful! Transaction: {}, Output: {} tokens of mint {}",

0 commit comments

Comments
 (0)