Skip to main content
This example demonstrates how to place an order and immediately match it with the best available makers in a single atomic transaction. This is useful for executing trades with minimal slippage.

What It Does

The place-and-take example:
  • Queries the DLOB server for top makers
  • Fetches and decodes maker account data
  • Builds an atomic transaction
  • Places order and matches with makers simultaneously
  • Ensures all-or-nothing execution

Complete Source Code

use anchor_lang::AccountDeserialize;
use drift_rs::{
    Context, DriftClient, RpcClient, TransactionBuilder, Wallet,
    types::{
        MarketType, OrderParams, OrderType, 
        PositionDirection, PostOnlyParam, accounts::User
    },
};
use serde::Deserialize;
use solana_pubkey::Pubkey;
use std::str::FromStr;

#[derive(Debug, Deserialize)]
struct TopMakerResponse {
    #[serde(rename = "userAccountPubKey")]
    user_account_pubkey: String,
    #[serde(rename = "accountBase64")]
    account_base64: String,
}

async fn get_top_makers(
    context: Context,
    market_index: u16,
    market_type: MarketType,
    side: &str,
    limit: Option<usize>,
) -> Result<Vec<(Pubkey, User)>, Box<dyn std::error::Error>> {
    let dlob_server_url = if context == Context::MainNet {
        "https://dlob.drift.trade"
    } else {
        "https://master.dlob.drift.trade"
    };
    
    // NOTE: This parameter controls the number of top makers.
    // It is suggested not to use more than 4, as the transaction 
    // size may exceed current limits with more makers.
    let limit = limit.unwrap_or(4);

    let query_params = format!(
        "marketIndex={}&marketType={}&side={}&limit={}&includeAccounts=true",
        market_index,
        market_type.as_str(),
        side,
        limit
    );

    let url = format!("{dlob_server_url}/topMakers?{query_params}");
    println!("{url}");

    let response = reqwest::get(&url).await?;

    if !response.status().is_success() {
        return Err(
            format!("Failed to fetch top makers: HTTP {}", response.status())
            .into()
        );
    }

    let makers: Vec<TopMakerResponse> = response.json().await?;

    if makers.is_empty() {
        return Ok(Vec::new());
    }

    let mut maker_infos = Vec::new();

    for maker in makers {
        // Decode the user account from base64
        let account_bytes = base64::Engine::decode(
            &base64::engine::general_purpose::STANDARD,
            &maker.account_base64,
        )?;
        let user_account = User::try_deserialize(&mut account_bytes.as_slice())
            .expect("User deserializes");
        let maker_pubkey = Pubkey::from_str(&maker.user_account_pubkey)?;

        maker_infos.push((maker_pubkey, user_account));
    }

    Ok(maker_infos)
}

#[tokio::main]
async fn main() {
    let _ = env_logger::init();
    let _ = dotenv::dotenv();
    
    let wallet: Wallet = (drift_rs::utils::load_keypair_multi_format(
        &std::env::var("PRIVATE_KEY").expect("base58 PRIVATE_KEY set"),
    )
    .unwrap())
    .into();

    let context = if std::env::var("MAINNET").is_ok() {
        Context::MainNet
    } else {
        Context::DevNet
    };
    
    let rpc_url = std::env::var("RPC_URL")
        .unwrap_or_else(|_| "https://api.mainnet-beta.solana.com".to_string());
    let drift = DriftClient::new(context, RpcClient::new(rpc_url), wallet.clone())
        .await
        .expect("initialized client");

    // Choose a sub-account for order placement
    let taker_subaccount = wallet.default_sub_account();

    let taker_subaccount_data = drift
        .get_account_value(&taker_subaccount)
        .await
        .expect("drift account exists");

    // Example order parameters - modify as needed
    let market_index = 0; // SOL-PERP market
    let market_type = MarketType::Perp;
    let order_direction = PositionDirection::Long;
    let side = if order_direction == PositionDirection::Long {
        "ask"  // We're buying, so we match with asks (sellers)
    } else {
        "bid"  // We're selling, so we match with bids (buyers)
    };

    // Create a sample order
    let order = OrderParams {
        market_index,
        market_type,
        base_asset_amount: 10_000_000,  // 0.01 SOL
        order_type: OrderType::Market,
        direction: PositionDirection::Long,
        post_only: PostOnlyParam::None,
        ..Default::default()
    };

    // Get top makers
    let makers = match get_top_makers(
        context, 
        market_index, 
        market_type, 
        side, 
        Some(4)
    ).await {
        Ok(makers) if !makers.is_empty() => {
            println!(
                "Found {} makers for market {} on {} side",
                makers.len(),
                market_index,
                side
            );
            makers
        }
        Ok(_) => {
            eprintln!(
                "No makers found for market {} on {} side",
                market_index, side
            );
            return;
        }
        Err(e) => {
            eprintln!("Error fetching makers: {}", e);
            return;
        }
    };

    // Display information about all makers
    for (i, (maker, account)) in makers.iter().enumerate() {
        println!(
            "Maker {}: {} (authority: {})",
            i + 1,
            maker,
            account.authority
        );
    }

    let referrer = None; // Optional referrer

    let tx = TransactionBuilder::new(
        drift.program_data(),
        taker_subaccount,
        std::borrow::Cow::Borrowed(&taker_subaccount_data),
        false,
    )
    .with_priority_fee(1_000, Some(200_000))
    .place_and_take(order, &makers, referrer, None, None)
    .build();

    match drift.sign_and_send(tx).await {
        Ok(sig) => {
            println!("sent tx: {sig:?}");
        }
        Err(err) => {
            println!("send tx err: {err:?}");
        }
    }
}

Key Concepts

Top Makers API

The DLOB server provides a /topMakers endpoint:
https://dlob.drift.trade/topMakers?
  marketIndex=0
  &marketType=perp
  &side=ask
  &limit=4
  &includeAccounts=true
Parameters:
  • marketIndex: Which market (0 = SOL-PERP)
  • marketType: “perp” or “spot”
  • side: “bid” or “ask” (opposite of your order direction)
  • limit: Number of makers (max 4 recommended)
  • includeAccounts: Include base64-encoded account data

Side Selection

Important: Use the opposite side of your order:
let side = if order_direction == PositionDirection::Long {
    "ask"  // You're buying, match with sellers
} else {
    "bid"  // You're selling, match with buyers
};

Atomic Execution

The place_and_take method creates a single transaction:
TransactionBuilder::new(...)
    .place_and_take(
        order,          // Your order parameters
        &makers,        // List of (Pubkey, User) tuples
        referrer,       // Optional referrer
        None,           // Optional maker info
        None,           // Optional maker stats
    )
    .build()
This transaction:
  1. Places your order on-chain
  2. Matches with available maker orders
  3. Settles the trade
  4. All atomically (succeeds or fails together)

Transaction Size Limits

Important: Solana transactions have a size limit (1232 bytes). Including too many makers can exceed this limit. Recommendation: Use maximum 4 makers
let limit = limit.unwrap_or(4);  // Safe default
If you need more liquidity:
  • Split into multiple transactions
  • Use larger order sizes with fewer makers
  • Implement multi-transaction routing

Decoding Maker Accounts

The API returns base64-encoded account data:
let account_bytes = base64::Engine::decode(
    &base64::engine::general_purpose::STANDARD,
    &maker.account_base64,
)?;

let user_account = User::try_deserialize(
    &mut account_bytes.as_slice()
)?;
This avoids additional RPC calls to fetch maker accounts.

Order Parameters

Market Order (Immediate Execution)

let order = OrderParams {
    market_index: 0,
    market_type: MarketType::Perp,
    order_type: OrderType::Market,
    base_asset_amount: 10_000_000,
    direction: PositionDirection::Long,
    ..Default::default()
};

Limit Order (Price Protection)

let order = OrderParams {
    order_type: OrderType::Limit,
    price: 100_000_000,  // Max price willing to pay
    base_asset_amount: 10_000_000,
    direction: PositionDirection::Long,
    ..Default::default()
};

Running the Example

# Set environment variables
export PRIVATE_KEY="your-base58-key"
export RPC_URL="https://api.mainnet-beta.solana.com"
export MAINNET=1

# Run
cargo run --example place-and-take

Example Output

https://dlob.drift.trade/topMakers?marketIndex=0&marketType=perp&side=ask&limit=4&includeAccounts=true
Found 4 makers for market 0 on ask side
Maker 1: 7Xr2... (authority: 9Yx3...)
Maker 2: 5Kp1... (authority: 3Zw8...)
Maker 3: 2Np9... (authority: 8Qm2...)
Maker 4: 6Jt4... (authority: 1Rk7...)
sent tx: 4Xy9Zb3...kLm8Pq1

Error Handling

No Makers Available

Ok(makers) if makers.is_empty() => {
    eprintln!("No makers found for market {} on {} side", market_index, side);
    // Consider:
    // - Waiting and retrying
    // - Placing a regular limit order instead
    // - Trying a different market
    return;
}

Transaction Too Large

Err(err) if err.to_string().contains("too large") => {
    // Reduce number of makers
    let makers = get_top_makers(context, market_index, market_type, side, Some(2)).await?;
    // Try again with fewer makers
}

Insufficient Liquidity

Err(err) if err.to_string().contains("insufficient liquidity") => {
    // Order size exceeds available liquidity
    // Consider:
    // - Reducing order size
    // - Splitting into multiple orders
    // - Using a time-weighted average execution strategy
}

Use Cases

One-Click Trading

Execute trades immediately with best available prices:
place_and_take(market_order, &top_makers, None, None, None)

Arbitrage

Quickly capture price differences:
// Buy on Drift
place_and_take(buy_order, &makers, None, None, None)?;
// Sell on another venue
sell_on_other_venue()?;

Liquidations

Quickly close positions during liquidations:
let close_order = OrderParams {
    reduce_only: true,  // Only close existing position
    ..existing_order
};
place_and_take(close_order, &makers, None, None, None)?;

Advanced Features

Referrer Fees

Include a referrer to share fees:
let referrer = Some(referrer_pubkey);
place_and_take(order, &makers, referrer, None, None)

Priority Fees

Increase priority for faster execution:
TransactionBuilder::new(...)
    .with_priority_fee(
        1_000,      // Base fee in lamports
        Some(200_000) // Compute units
    )
    .place_and_take(order, &makers, None, None, None)

Custom Maker Selection

Filter makers before including:
let filtered_makers: Vec<_> = makers
    .into_iter()
    .filter(|(pubkey, account)| {
        // Only use makers with good reputation
        is_trusted_maker(pubkey)
    })
    .take(4)
    .collect();

place_and_take(order, &filtered_makers, None, None, None)

Best Practices

  1. Limit Makers: Use max 4 makers to avoid transaction size issues
  2. Check Liquidity: Verify makers have sufficient liquidity before executing
  3. Set Price Limits: Use limit orders to protect against slippage
  4. Handle Errors: Implement retry logic for transient failures
  5. Monitor Execution: Track fill prices and compare to expected prices
  6. Use Priority Fees: For time-sensitive trades, use higher priority fees

Performance Considerations

Fast Path:
  • Query top makers (HTTP request)
  • Accounts included in response (no additional RPC calls)
  • Single transaction (atomic execution)
Optimization:
// Cache maker data if executing frequently
let makers = if cache_valid {
    cached_makers
} else {
    let fresh_makers = get_top_makers(...).await?;
    update_cache(fresh_makers.clone());
    fresh_makers
};

Next Steps

  • Implement TWAP (Time-Weighted Average Price) execution
  • Add slippage protection and monitoring
  • Build a smart order router
  • Compare execution quality across different methods

Build docs developers (and LLMs) love