Skip to main content
JIT (Just-In-Time) trading allows makers to fill taker orders with price protection through the JIT proxy program. This guide covers how to use the 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

The PriceType 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

  1. Always check oracle prices before filling to ensure profitability
  2. Use Oracle price type for dynamic pricing around market price
  3. Set appropriate position limits to control risk exposure
  4. Evaluate order profitability before attempting fills
  5. Monitor failed fills and adjust parameters accordingly
  6. 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.

Next Steps

Build docs developers (and LLMs) love