Skip to main content
The TransactionBuilder provides a fluent API for constructing Drift transactions. This guide covers how to place, modify, and cancel orders.

Prerequisites

Before placing orders, ensure you have:
  1. Initialized a DriftClient connected to your desired network
  2. A funded wallet with USDC deposits in your Drift account
  3. Subscribed to blockhashes for faster transaction building (recommended)
use drift_rs::{Context, DriftClient, RpcClient, Wallet};

let drift = DriftClient::new(context, RpcClient::new(rpc_url), wallet.clone())
    .await?;

// Subscribe to blockhashes for fast tx building
drift.subscribe_blockhashes().await?;

Understanding Order Parameters

The OrderParams struct defines order specifications:
use drift_rs::types::{
    OrderParams, OrderType, PositionDirection, 
    MarketType, PostOnlyParam
};

let order = OrderParams {
    order_type: OrderType::Limit,
    market_type: MarketType::Perp,
    market_index: 0,  // SOL-PERP
    direction: PositionDirection::Long,
    base_asset_amount: 1_000_000_000,  // 1 SOL (9 decimals)
    price: 123_000_000,  // $123 (6 decimals)
    post_only: PostOnlyParam::MustPostOnly,
    user_order_id: 1,  // Self-assigned ID (0-255)
    ..Default::default()
};

Creating a TransactionBuilder

Initialize a TransactionBuilder with your sub-account:
use drift_rs::TransactionBuilder;
use std::borrow::Cow;

// Get sub-account address and data
let subaccount = wallet.default_sub_account();
let subaccount_data = drift.get_user_account(&subaccount).await?;

// Create transaction builder
let builder = TransactionBuilder::new(
    drift.program_data(),
    subaccount,
    Cow::Borrowed(&subaccount_data),
    false,  // delegated = false
);

Placing Orders

Place a Single Limit Order

use drift_rs::math::constants::{BASE_PRECISION_U64, PRICE_PRECISION_U64};

let order = OrderParams {
    order_type: OrderType::Limit,
    market_type: MarketType::Perp,
    market_index: 0,  // SOL-PERP
    direction: PositionDirection::Long,
    base_asset_amount: 5 * BASE_PRECISION_U64,  // 5 SOL
    price: 123 * PRICE_PRECISION_U64,  // $123.00
    post_only: PostOnlyParam::MustPostOnly,
    user_order_id: 1,
    ..Default::default()
};

let tx = builder
    .place_orders(vec![order])
    .build();

let sig = drift.sign_and_send(tx).await?;
println!("Order placed: {}", sig);

Place Multiple Orders

let orders = vec![
    // Long limit order
    OrderParams {
        order_type: OrderType::Limit,
        market_type: MarketType::Perp,
        market_index: 0,
        direction: PositionDirection::Long,
        base_asset_amount: 5 * BASE_PRECISION_U64,
        price: 122 * PRICE_PRECISION_U64,
        post_only: PostOnlyParam::MustPostOnly,
        user_order_id: 1,
        ..Default::default()
    },
    // Short limit order
    OrderParams {
        order_type: OrderType::Limit,
        market_type: MarketType::Perp,
        market_index: 0,
        direction: PositionDirection::Short,
        base_asset_amount: 5 * BASE_PRECISION_U64,
        price: 125 * PRICE_PRECISION_U64,
        post_only: PostOnlyParam::MustPostOnly,
        user_order_id: 2,
        ..Default::default()
    },
];

let tx = builder
    .place_orders(orders)
    .build();

let sig = drift.sign_and_send(tx).await?;

Place a Market Order

let market_order = OrderParams {
    order_type: OrderType::Market,
    market_type: MarketType::Perp,
    market_index: 0,
    direction: PositionDirection::Long,
    base_asset_amount: 1 * BASE_PRECISION_U64,
    ..Default::default()
};

let tx = builder
    .place_orders(vec![market_order])
    .build();

let sig = drift.sign_and_send(tx).await?;

Place an Oracle Order

Oracle orders are priced relative to the oracle price:
let oracle_order = OrderParams {
    order_type: OrderType::Oracle,
    market_type: MarketType::Perp,
    market_index: 0,
    direction: PositionDirection::Long,
    base_asset_amount: 2 * BASE_PRECISION_U64,
    // Price is oracle_price + offset
    auction_start_price: Some(-500_000),  // -$0.50
    auction_end_price: Some(500_000),     // +$0.50
    auction_duration: Some(10),  // 10 slots
    ..Default::default()
};

let tx = builder
    .place_orders(vec![oracle_order])
    .build();

let sig = drift.sign_and_send(tx).await?;

Place a Floating Limit Order

Floating limit orders maintain a fixed offset from the oracle price:
let floating_order = OrderParams {
    order_type: OrderType::Limit,
    market_type: MarketType::Perp,
    market_index: 0,
    direction: PositionDirection::Short,
    base_asset_amount: 5 * BASE_PRECISION_U64,
    oracle_price_offset: Some((1 * PRICE_PRECISION_U64) as i32),  // $1 above oracle
    post_only: PostOnlyParam::MustPostOnly,
    ..Default::default()
};

let tx = builder
    .place_orders(vec![floating_order])
    .build();

let sig = drift.sign_and_send(tx).await?;
Floating limit orders automatically adjust their price as the oracle price changes, maintaining the specified offset.

Canceling Orders

Cancel All Orders

// Cancel all orders across all markets
let tx = builder
    .cancel_all_orders()
    .build();

let sig = drift.sign_and_send(tx).await?;

Cancel Orders by Market

use drift_rs::types::MarketType;

// Cancel all orders for SOL-PERP
let tx = builder
    .cancel_orders((0, MarketType::Perp), None)
    .build();

let sig = drift.sign_and_send(tx).await?;

Cancel Orders by User Order ID

// Cancel orders with user_order_id 1 and 2
let user_order_ids = vec![1, 2];
let tx = builder
    .cancel_orders_by_user_id(&user_order_ids)
    .build();

let sig = drift.sign_and_send(tx).await?;

Cancel Orders by Order ID

// Cancel orders by program-assigned order IDs
let order_ids = vec![42, 43];
let tx = builder
    .cancel_orders_by_id(&order_ids)
    .build();

let sig = drift.sign_and_send(tx).await?;

Combining Operations

Chain multiple operations in a single transaction:
let tx = builder
    .with_priority_fee(1_000, Some(200_000))  // 1000 micro-lamports, 200k CU limit
    .cancel_orders((0, MarketType::Perp), None)  // Cancel existing orders
    .place_orders(vec![  // Place new orders
        OrderParams {
            order_type: OrderType::Limit,
            market_type: MarketType::Perp,
            market_index: 0,
            direction: PositionDirection::Long,
            base_asset_amount: 5 * BASE_PRECISION_U64,
            price: 122 * PRICE_PRECISION_U64,
            post_only: PostOnlyParam::MustPostOnly,
            user_order_id: 1,
            ..Default::default()
        },
    ])
    .build();

let sig = drift.sign_and_send(tx).await?;
Combining cancel and place operations in a single transaction is an efficient pattern for market making and order management.

Complete Example: Market Maker

use drift_rs::{
    types::{OrderParams, OrderType, PositionDirection, MarketType, PostOnlyParam},
    math::constants::{BASE_PRECISION_U64, PRICE_PRECISION_U64},
    TransactionBuilder, DriftClient, Wallet,
};
use std::time::Duration;

async fn place_market_maker_orders(
    drift: &DriftClient,
    subaccount_address: solana_sdk::pubkey::Pubkey,
    market_index: u16,
) -> Result<(), Box<dyn std::error::Error>> {
    // Get current account state
    let subaccount_data = drift.get_user_account(&subaccount_address).await?;
    
    // Get oracle price
    let oracle_price = drift.oracle_price(
        drift_rs::MarketId::perp(market_index)
    ).await?;
    let oracle_price_u64 = oracle_price as u64;
    
    let quote_size = 5 * BASE_PRECISION_U64;  // 5 SOL
    let spread = 1 * PRICE_PRECISION_U64;  // $1 spread
    
    let orders = vec![
        // Bid
        OrderParams {
            order_type: OrderType::Limit,
            market_type: MarketType::Perp,
            market_index,
            direction: PositionDirection::Long,
            base_asset_amount: quote_size,
            price: oracle_price_u64.saturating_sub(spread / 2),
            post_only: PostOnlyParam::MustPostOnly,
            user_order_id: 1,
            ..Default::default()
        },
        // Ask
        OrderParams {
            order_type: OrderType::Limit,
            market_type: MarketType::Perp,
            market_index,
            direction: PositionDirection::Short,
            base_asset_amount: quote_size,
            price: oracle_price_u64.saturating_add(spread / 2),
            post_only: PostOnlyParam::MustPostOnly,
            user_order_id: 2,
            ..Default::default()
        },
    ];
    
    let tx = TransactionBuilder::new(
        drift.program_data(),
        subaccount_address,
        std::borrow::Cow::Borrowed(&subaccount_data),
        false,
    )
    .with_priority_fee(1_000, Some(100_000))
    .cancel_orders((market_index, MarketType::Perp), None)
    .place_orders(orders)
    .build();
    
    match drift.sign_and_send(tx).await {
        Ok(sig) => {
            println!("Orders placed: {}", sig);
            Ok(())
        }
        Err(err) => {
            eprintln!("Failed to place orders: {}", err);
            Err(err.into())
        }
    }
}

Priority Fees

Add priority fees to increase transaction processing speed:
let tx = builder
    .with_priority_fee(
        1_000,      // microlamports per compute unit
        Some(200_000)  // compute unit limit
    )
    .place_orders(orders)
    .build();
Always set appropriate compute unit limits to avoid overpaying for priority fees.

Order Validation

The SDK performs client-side validation before sending orders. Common validation failures:
  • Insufficient balance: Not enough collateral for the order
  • Invalid price: Price doesn’t meet market tick size requirements
  • Invalid size: Size doesn’t meet market quantity requirements
  • Too many orders: Exceeds the maximum orders per account

Next Steps

Build docs developers (and LLMs) love