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:
- Initialized a
DriftClient connected to your desired network
- A funded wallet with USDC deposits in your Drift account
- 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