Searchers - Limit Orders
Titan’s Limit Orders are designed to thrive in a competitive environment where searchers play a central role. By participating as a searcher, you gain access to a marketplace of on-chain limit orders.
On-chain Structure of Limit Orders
use pinocchio::pubkey::{create_program_address, Pubkey};
use bytemuck::{Pod, Zeroable};
/// Limit order structure
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct LimitOrder {
// The public key of the order,
pub maker: Pubkey,
// Input mint of the limit order
pub input_mint: Pubkey,
// Output mint of the limit order
pub output_mint: Pubkey,
// Slot which the order was created
pub creation_slot: u64,
// The slot at which the order expires
pub expiration_slot: u64,
// The amount of input tokens to be exchanged
pub amount: u64,
// The amount of input tokens that have been filled
pub amount_filled: u64,
// The amount of output tokens that have been exchanged.
pub out_amount_filled: u64,
// The amount of output tokens that the maker has withdrawn.
pub out_amount_withdrawn: u64,
// The amount of fees paid in the smallest unit of from_token mint.
pub fees_paid: u64,
// Price base in the order, in the smallest unit of output token
pub price_base: u64,
// Price exponent, price is calculated as price_base * 10^(-price_exponent)
pub price_exponent: u8,
// The status of the order
pub status: u8,
// Bump seed for the limit order PDA
pub bump: u8,
// Unique identifier for the order, used to differentiate orders for same
// (owner, input_mint, output_mint) tuple
pub id: u8,
// Bump seed for the input mint vault PDA
pub input_mint_vault_bump: u8,
// Bump seed for the output mint vault PDA
pub output_mint_vault_bump: u8,
// Time in order
pub time_in_force: u8,
// Fees ticks rate for the order from takers.
pub fee_ticks: u8,
}
/// Time in Force (TIF) for limit orders. Provides different behaviors for
/// how long an order remains active and how it can be filled.
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum TimeInForce {
/// Order is good until cancelled. Partial takes are allowed.
GoodTillCancelled = 0,
/// After taking any amount, order is closed.
TakeCancelsOrder = 1,
/// Takes must completely fill the order.
AllOrNothing = 2,
/// Same as TakeCancelsOrder but it must be filled at the same time of creation.
ImmediateOrCancel = 3,
/// Same as AllOrNothing but it must be filled at the same time of creation.
FillOrKill = 4,
}
/// Order status for limit orders. Indicates the current state of the order
/// and how it can be interacted with.
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum OrderStatus {
/// Order is open, can be partially filled, filled, cancelled
Open = 0,
/// Order is partially filled, can be filled or cancelled
PartiallyFilled = 1,
/// Order is filled, terminates, used for event logging
Filled = 2,
/// Order is cancelled, terminates, used for event logging
Cancelled = 3,
}
/// Fee tick units
pub const FEE_TICK_UNITS: u8 = 25;
/// 1e6 units = 0.0001, used to convert fee tick rate to fee basis points
pub const FEE_TICK_DIVISOR: u128 = 1_000_000;
/// Limit Order methods
impl LimitOrder {
pub const SEEDS: &'static [u8] = b"order";
pub const LEN: usize = 168;
/// Get the pda address for the limit order, given the maker, input mint,
/// output mint, id and bump.
pub fn get_pda_address(
maker: &Pubkey,
input_mint: &Pubkey,
output_mint: &Pubkey,
id: u8,
bump: u8,
) -> Result<Pubkey, ProgramError> {
let b0 = &[id];
let b1 = &[bump];
let seeds_with_bump = [
LimitOrder::SEEDS,
maker.as_ref(),
input_mint.as_ref(),
output_mint.as_ref(),
b0,
b1,
]
.to_vec();
create_program_address(&seeds_with_bump, &crate::ID)
}
/// Calculate the costs and fees for a given amount and fee ticks.
pub fn calculate_costs_and_fee(
&self,
amount: u64,
fee_ticks: u8,
) -> Result<(u64, u64), ProgramError> {
let amount_u128 = amount as u128;
let price_base = self.price_base as u128;
let price_exponent = 10u128.pow(self.price_exponent as u32);
let fee_units_u128 = (fee_ticks as u16).saturating_mul(FEE_TICK_UNITS as u16) as u128;
// Calculate the transfer amount and fee amount.
// Should never overflow, since its u64 * u64
// Use method to perform ceiling math division: (a + b - 1) / b
let cost_u128 = amount_u128
.saturating_mul(price_base)
.checked_add(price_exponent.saturating_sub(1))
.ok_or(ProgramError::ArithmeticOverflow)?
.saturating_div(price_exponent);
let cost: u64 = cost_u128
.try_into()
.map_err(|_| ProgramError::ArithmeticOverflow)?;
let fees: u64 = cost_u128
.saturating_mul(fee_units_u128)
.saturating_div(FEE_TICK_DIVISOR)
.try_into()
.map_err(|_| ProgramError::ArithmeticOverflow)?;
Ok((cost, fees.max(1)))
}
/// Amount left to be filled in the order.
pub fn get_remaining_amount(&self) -> u64 {
self.amount.saturating_sub(self.amount_filled)
}
}Placing Taker Orders
Example: 100 USDC -> 1 SOL w/ 5 BPS fee
Other Examples
TakeOrder Call
Error Codes
Limit Order Events
Last updated

