JitProxyClient for JIT trading strategies.
Understanding JIT Trading
JIT trading on Drift provides:- Price bounds: Set min/max position limits and bid/ask prices
- Atomic execution: Fill taker orders without on-chain order placement
- Maker protection: Orders only execute within your specified price range
- Swift order support: Fill Swift (off-chain signed) orders
Setting Up JitProxyClient
Initialize the Client
use drift_rs::{
jit_client::{JitProxyClient, ComputeBudgetParams},
DriftClient, Context, RpcClient, Wallet,
};
use solana_rpc_client_api::config::RpcSendTransactionConfig;
// Initialize DriftClient first
let drift = DriftClient::new(context, rpc_client, wallet.clone()).await?;
// Create JitProxyClient
let jit_client = JitProxyClient::new(
drift.clone(),
None, // Use default RPC config
None, // Use default compute budget
);
Configure Compute Budget
let cu_params = ComputeBudgetParams::new(
1_000, // microlamports per CU
200_000, // CU limit
);
let jit_client = JitProxyClient::new(
drift.clone(),
None,
Some(cu_params),
);
JIT Parameters
Define JIT Order Bounds
use drift_rs::jit_client::{JitIxParams, PriceType};
use drift_rs::types::PostOnlyParam;
let jit_params = JitIxParams::new(
10_000_000_000, // max_position: 10 SOL long
-10_000_000_000, // min_position: 10 SOL short
123_000_000, // bid: $123
125_000_000, // ask: $125
PriceType::Limit, // Use fixed limit prices
Some(PostOnlyParam::MustPostOnly),
);
Price Types
ThePriceType determines how prices are interpreted:
Limit Price Type
let jit_params = JitIxParams {
bid: 123_000_000, // Bid at exactly $123
ask: 125_000_000, // Ask at exactly $125
price_type: PriceType::Limit,
// ...
};
Oracle Price Type
Prices are offsets from the oracle price:let jit_params = JitIxParams {
bid: -500_000, // Bid at oracle - $0.50
ask: 500_000, // Ask at oracle + $0.50
price_type: PriceType::Oracle,
// ...
};
Filling Regular Orders
Build JIT Transaction
use drift_rs::jit_client::JitTakerParams;
use drift_rs::Wallet;
// Taker account information
let taker_pubkey = /* taker's user account pubkey */;
let taker_authority = /* taker's authority */;
let taker_account = drift.get_user_account(&taker_pubkey).await?;
let taker_stats_key = Wallet::derive_stats_account(&taker_authority);
let taker_params = JitTakerParams::new(
taker_pubkey,
taker_stats_key,
taker_account.clone(),
None, // referrer info
);
// Define JIT parameters
let jit_params = JitIxParams::new(
10_000_000_000, // max_position
-10_000_000_000, // min_position
123_000_000, // bid
125_000_000, // ask
PriceType::Limit,
Some(PostOnlyParam::MustPostOnly),
);
// Maker account
let maker_pubkey = wallet.default_sub_account();
let maker_account = drift.get_user_account(&maker_pubkey).await?;
// Build the JIT transaction
let taker_order_id = 42; // The taker's order ID
let jit_tx = jit_client.build_jit_tx(
taker_order_id,
&taker_params,
jit_params,
(&maker_pubkey, &maker_account),
)
.await?;
Execute JIT Fill
// Send the transaction
let sig = drift.sign_and_send(jit_tx).await?;
println!("JIT fill executed: {}", sig);
Using the Convenience Method
// All-in-one JIT execution
let sig = jit_client.jit(
taker_order_id,
&taker_params,
jit_params,
wallet.authority(), // maker authority
Some(0), // maker sub-account index
)
.await?;
println!("JIT fill: {}", sig);
Filling Swift Orders
Swift orders are off-chain signed orders that can be filled via JIT:Subscribe to Swift Orders
use drift_rs::MarketId;
let markets = vec![MarketId::perp(0)];
let mut swift_stream = drift.subscribe_swift_orders(
&markets,
Some(false), // accept_sanitized
Some(false), // accept_deposit_trades
None, // custom swift URL
)
.await?;
println!("Subscribed to Swift orders");
Process Swift Orders
use futures_util::StreamExt;
use drift_rs::swift_order_subscriber::SignedOrderInfo;
while let Some(signed_order_info) = swift_stream.next().await {
println!("Received Swift order: {:?}", signed_order_info.order_uuid());
// Extract order parameters
let order_params = signed_order_info.order_params();
println!(
"Market: {}, Direction: {:?}, Size: {}",
order_params.market_index,
order_params.direction,
order_params.base_asset_amount
);
// Determine if we want to fill this order
let should_fill = evaluate_swift_order(&signed_order_info, &drift).await;
if should_fill {
// Fill the Swift order
fill_swift_order(&jit_client, &drift, &signed_order_info, &wallet)
.await?;
}
}
Fill Swift Order with JIT
async fn fill_swift_order(
jit_client: &JitProxyClient,
drift: &DriftClient,
signed_order_info: &SignedOrderInfo,
wallet: &Wallet,
) -> Result<(), Box<dyn std::error::Error>> {
let order_params = signed_order_info.order_params();
// Get taker account info
let taker_authority = signed_order_info.authority();
let taker_pubkey = Wallet::derive_user_account(
&taker_authority,
order_params.sub_account_id,
);
let taker_account = drift.get_user_account(&taker_pubkey).await?;
let taker_stats = Wallet::derive_stats_account(&taker_authority);
let taker_params = JitTakerParams::new(
taker_pubkey,
taker_stats,
taker_account,
None,
);
// Get oracle price for dynamic pricing
let market_id = MarketId::new(
order_params.market_index,
order_params.market_type,
);
let oracle_price = drift.oracle_price(market_id).await? as i64;
// Calculate competitive bid/ask around oracle
let spread = 500_000; // $0.50
let jit_params = JitIxParams::new(
5_000_000_000, // 5 SOL max position
-5_000_000_000, // 5 SOL min position
-spread, // Bid: oracle - $0.50
spread, // Ask: oracle + $0.50
PriceType::Oracle,
Some(PostOnlyParam::None),
);
// Get maker account
let maker_pubkey = wallet.default_sub_account();
let maker_account = drift.get_user_account(&maker_pubkey).await?;
// Build and send Swift fill transaction
let sig = jit_client.try_swift_fill(
signed_order_info,
&taker_params,
&jit_params,
wallet.authority(),
Some(0), // maker sub-account
)
.await?;
println!("Swift order filled: {}", sig);
Ok(())
}
Build Swift Fill Transaction
For more control, build the transaction manually:let swift_tx = jit_client.build_swift_ix(
&signed_order_info,
&taker_params,
&jit_params,
&maker_pubkey,
&maker_account,
)
.await?;
let sig = drift.sign_and_send(swift_tx).await?;
Complete Example: JIT Market Maker
use drift_rs::{
DriftClient, Context, RpcClient, Wallet, MarketId,
jit_client::{JitProxyClient, JitIxParams, JitTakerParams, PriceType},
types::PostOnlyParam,
};
use futures_util::StreamExt;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
dotenv::dotenv().ok();
let wallet: Wallet = drift_rs::utils::load_keypair_multi_format(
&std::env::var("PRIVATE_KEY")?,
)?
.into();
let drift = DriftClient::new(
Context::MainNet,
RpcClient::new(std::env::var("RPC_URL")?),
wallet.clone(),
)
.await?;
let jit_client = JitProxyClient::new(drift.clone(), None, None);
// Subscribe to Swift orders
let markets = vec![MarketId::perp(0)];
let mut swift_stream = drift.subscribe_swift_orders(
&markets,
Some(false),
Some(false),
None,
)
.await?;
println!("JIT Market Maker running...");
while let Some(signed_order_info) = swift_stream.next().await {
let order_params = signed_order_info.order_params();
println!(
"Swift order: {} {} @ market {}",
if order_params.direction == drift_rs::types::PositionDirection::Long {
"BUY"
} else {
"SELL"
},
order_params.base_asset_amount,
order_params.market_index
);
// Get taker info
let taker_authority = signed_order_info.authority();
let taker_pubkey = Wallet::derive_user_account(
&taker_authority,
order_params.sub_account_id,
);
let taker_account = match drift.get_user_account(&taker_pubkey).await {
Ok(acc) => acc,
Err(e) => {
eprintln!("Failed to get taker account: {}", e);
continue;
}
};
let taker_stats = Wallet::derive_stats_account(&taker_authority);
let taker_params = JitTakerParams::new(
taker_pubkey,
taker_stats,
taker_account,
None,
);
// Get oracle price
let market_id = MarketId::new(
order_params.market_index,
order_params.market_type,
);
let oracle_price = drift.oracle_price(market_id).await?;
// Set competitive JIT params
let spread = 200_000; // $0.20
let jit_params = JitIxParams::new(
10_000_000_000, // 10 SOL max
-10_000_000_000, // 10 SOL min
-spread,
spread,
PriceType::Oracle,
Some(PostOnlyParam::None),
);
// Try to fill
match jit_client.try_swift_fill(
&signed_order_info,
&taker_params,
&jit_params,
wallet.authority(),
Some(0),
)
.await
{
Ok(sig) => println!("✅ Filled Swift order: {}", sig),
Err(e) => eprintln!("❌ Failed to fill: {}", e),
}
}
Ok(())
}
Evaluating Orders
Before filling, evaluate if an order is profitable:use drift_rs::swift_order_subscriber::SignedOrderInfo;
async fn evaluate_swift_order(
signed_order_info: &SignedOrderInfo,
drift: &DriftClient,
) -> bool {
let order_params = signed_order_info.order_params();
// Get oracle price
let market_id = MarketId::new(
order_params.market_index,
order_params.market_type,
);
let oracle_price = match drift.oracle_price(market_id).await {
Ok(price) => price as u64,
Err(_) => return false,
};
// Calculate expected fill price based on auction params
let auction_start = order_params.auction_start_price.unwrap_or(0) as i64;
let auction_end = order_params.auction_end_price.unwrap_or(0) as i64;
// For oracle orders, prices are relative to oracle
let estimated_fill_price = if order_params.order_type == drift_rs::types::OrderType::Oracle {
(oracle_price as i64 + auction_end) as u64
} else {
order_params.price
};
// Check if we can profitably fill
let min_profit_bps = 5; // 0.05%
let min_profit = (estimated_fill_price * min_profit_bps) / 10_000;
// Simple profitability check
let would_be_profitable = match order_params.direction {
drift_rs::types::PositionDirection::Long => {
// Taker buying, we sell
estimated_fill_price > oracle_price + min_profit
}
drift_rs::types::PositionDirection::Short => {
// Taker selling, we buy
estimated_fill_price + min_profit < oracle_price
}
};
if would_be_profitable {
println!("✅ Order looks profitable");
} else {
println!("❌ Order not profitable enough");
}
would_be_profitable
}
Handling Referrers
If the taker has a referrer, include it in the JIT params:use drift_rs::types::ReferrerInfo;
// Check if taker has a referrer
let referrer_info = if taker_account.has_referrer() {
Some(ReferrerInfo::new(
taker_account.referrer,
Wallet::derive_stats_account(&taker_account.referrer),
))
} else {
None
};
let taker_params = JitTakerParams::new(
taker_pubkey,
taker_stats_key,
taker_account,
referrer_info,
);
Best Practices
- Always check oracle prices before filling to ensure profitability
- Use Oracle price type for dynamic pricing around market price
- Set appropriate position limits to control risk exposure
- Evaluate order profitability before attempting fills
- Monitor failed fills and adjust parameters accordingly
- Use compute budget params to ensure transactions don’t fail due to CU limits
JIT fills are competitive. Multiple makers may attempt to fill the same order. Ensure your pricing is competitive while maintaining profitability.
Combine JIT trading with the DLOB to have complete market visibility and identify the best filling opportunities.