Module 0xdee9::clob
- Struct
PoolCreated
- Struct
OrderPlacedV2
- Struct
OrderCanceled
- Struct
OrderFilledV2
- Struct
Order
- Struct
TickLevel
- Resource
Pool
- Struct
OrderPlaced
- Struct
OrderFilled
- Constants
- Function
destroy_empty_level
- Function
create_account
- Function
create_pool
- Function
deposit_base
- Function
deposit_quote
- Function
withdraw_base
- Function
withdraw_quote
- Function
swap_exact_base_for_quote
- Function
swap_exact_quote_for_base
- Function
match_bid_with_quote_quantity
- Function
match_bid
- Function
match_ask
- Function
place_market_order
- Function
inject_limit_order
- Function
place_limit_order
- Function
order_is_bid
- Function
emit_order_canceled
- Function
emit_order_filled
- Function
cancel_order
- Function
remove_order
- Function
cancel_all_orders
- Function
batch_cancel_order
- Function
list_open_orders
- Function
account_balance
- Function
get_market_price
- Function
get_level2_book_status_bid_side
- Function
get_level2_book_status_ask_side
- Function
get_level2_book_status
- Function
get_order_status
use 0x1::option;
use 0x1::type_name;
use 0x2::balance;
use 0x2::clock;
use 0x2::coin;
use 0x2::event;
use 0x2::linked_table;
use 0x2::object;
use 0x2::sui;
use 0x2::table;
use 0x2::tx_context;
use 0xdee9::critbit;
use 0xdee9::custodian;
use 0xdee9::math;
Struct PoolCreated
Emitted when a new pool is created
struct PoolCreated has copy, drop, store
Fields
- pool_id: object::ID
- object ID of the newly created pool
- base_asset: type_name::TypeName
- quote_asset: type_name::TypeName
- taker_fee_rate: u64
- maker_rebate_rate: u64
- tick_size: u64
- lot_size: u64
Struct OrderPlacedV2
Emitted when a maker order is injected into the order book.
struct OrderPlacedV2<BaseAsset, QuoteAsset> has copy, drop, store
Fields
- pool_id: object::ID
- object ID of the pool the order was placed on
- order_id: u64
- ID of the order within the pool
- is_bid: bool
- owner: object::ID
- object ID of the AccountCap that placed the order
- base_asset_quantity_placed: u64
- price: u64
- expire_timestamp: u64
Struct OrderCanceled
Emitted when a maker order is canceled.
struct OrderCanceled<BaseAsset, QuoteAsset> has copy, drop, store
Fields
- pool_id: object::ID
- object ID of the pool the order was placed on
- order_id: u64
- ID of the order within the pool
- is_bid: bool
- owner: object::ID
- object ID of the AccountCap that placed the order
- base_asset_quantity_canceled: u64
- price: u64
Struct OrderFilledV2
Emitted only when a maker order is filled.
struct OrderFilledV2<BaseAsset, QuoteAsset> has copy, drop, store
Fields
- pool_id: object::ID
- object ID of the pool the order was placed on
- order_id: u64
- ID of the order within the pool
- is_bid: bool
- owner: object::ID
- object ID of the AccountCap that placed the order
- total_quantity: u64
- base_asset_quantity_filled: u64
- base_asset_quantity_remaining: u64
- price: u64
- taker_commission: u64
- maker_rebates: u64
Struct Order
struct Order has drop, store
Fields
- order_id: u64
- price: u64
- quantity: u64
- is_bid: bool
- owner: object::ID
- expire_timestamp: u64
Struct TickLevel
struct TickLevel has store
Fields
- price: u64
- open_orders: linked_table::LinkedTable<u64, clob::Order>
Resource Pool
struct Pool<BaseAsset, QuoteAsset> has key
Fields
- id: object::UID
- bids: critbit::CritbitTree<clob::TickLevel>
- asks: critbit::CritbitTree<clob::TickLevel>
- next_bid_order_id: u64
- next_ask_order_id: u64
- usr_open_orders: table::Table<object::ID, linked_table::LinkedTable<u64, u64>>
- taker_fee_rate: u64
- maker_rebate_rate: u64
- tick_size: u64
- lot_size: u64
- base_custodian: custodian::Custodian<BaseAsset>
- quote_custodian: custodian::Custodian<QuoteAsset>
- creation_fee: balance::Balance<sui::SUI>
- base_asset_trading_fees: balance::Balance<BaseAsset>
- quote_asset_trading_fees: balance::Balance<QuoteAsset>
Struct OrderPlaced
Deprecated since v1.0.0, use OrderPlacedV2 instead.
struct OrderPlaced<BaseAsset, QuoteAsset> has copy, drop, store
Fields
- pool_id: object::ID
- object ID of the pool the order was placed on
- order_id: u64
- ID of the order within the pool
- is_bid: bool
- owner: object::ID
- object ID of the AccountCap that placed the order
- base_asset_quantity_placed: u64
- price: u64
Struct OrderFilled
Deprecated since v1.0.0, use OrderFilledV2 instead.
struct OrderFilled<BaseAsset, QuoteAsset> has copy, drop, store
Fields
- pool_id: object::ID
- object ID of the pool the order was placed on
- order_id: u64
- ID of the order within the pool
- is_bid: bool
- owner: object::ID
- object ID of the AccountCap that placed the order
- total_quantity: u64
- base_asset_quantity_filled: u64
- base_asset_quantity_remaining: u64
- price: u64
Constants
const FLOAT_SCALING: u64 = 1000000000;
const DEPRECATED: u64 = 0;
const EInsufficientBaseCoin: u64 = 7;
const EInsufficientQuoteCoin: u64 = 8;
const EInvalidExpireTimestamp: u64 = 19;
const EInvalidOrderId: u64 = 3;
const EInvalidPrice: u64 = 5;
const EInvalidQuantity: u64 = 6;
const EInvalidRestriction: u64 = 14;
const EInvalidTickPrice: u64 = 11;
const EInvalidUser: u64 = 12;
const EOrderCannotBeFullyFilled: u64 = 9;
const EOrderCannotBeFullyPassive: u64 = 10;
const EUnauthorizedCancel: u64 = 4;
const FILL_OR_KILL: u8 = 2;
const IMMEDIATE_OR_CANCEL: u8 = 1;
const MAX_PRICE: u64 = 9223372036854775808;
const MIN_ASK_ORDER_ID: u64 = 9223372036854775808;
const MIN_PRICE: u64 = 0;
const NO_RESTRICTION: u8 = 0;
const POST_OR_ABORT: u8 = 3;
Function destroy_empty_level
fun destroy_empty_level(level: clob::TickLevel)
Implementation
fun destroy_empty_level(level: TickLevel) {
let TickLevel {
price: _,
open_orders: orders,
} = level;
linked_table::destroy_empty(orders);
}
Function create_account
public fun create_account(_ctx: &mut tx_context::TxContext): custodian::AccountCap
Implementation
public fun create_account(_ctx: &mut TxContext): AccountCap {
abort DEPRECATED
}
Function create_pool
public fun create_pool<BaseAsset, QuoteAsset>(_tick_size: u64, _lot_size: u64, _creation_fee: coin::Coin<sui::SUI>, _ctx: &mut tx_context::TxContext)
Implementation
public fun create_pool<BaseAsset, QuoteAsset>(
_tick_size: u64,
_lot_size: u64,
_creation_fee: Coin<SUI>,
_ctx: &mut TxContext,
) {
abort DEPRECATED
}
Function deposit_base
public fun deposit_base<BaseAsset, QuoteAsset>(pool: &mut clob::Pool<BaseAsset, QuoteAsset>, coin: coin::Coin<BaseAsset>, account_cap: &custodian::AccountCap)
Implementation
public fun deposit_base<BaseAsset, QuoteAsset>(
pool: &mut Pool<BaseAsset, QuoteAsset>,
coin: Coin<BaseAsset>,
account_cap: &AccountCap
) {
assert!(coin::value(&coin) != 0, EInsufficientBaseCoin);
custodian::increase_user_available_balance(
&mut pool.base_custodian,
object::id(account_cap),
coin::into_balance(coin)
)
}
Function deposit_quote
public fun deposit_quote<BaseAsset, QuoteAsset>(pool: &mut clob::Pool<BaseAsset, QuoteAsset>, coin: coin::Coin<QuoteAsset>, account_cap: &custodian::AccountCap)
Implementation
public fun deposit_quote<BaseAsset, QuoteAsset>(
pool: &mut Pool<BaseAsset, QuoteAsset>,
coin: Coin<QuoteAsset>,
account_cap: &AccountCap
) {
assert!(coin::value(&coin) != 0, EInsufficientQuoteCoin);
custodian::increase_user_available_balance(
&mut pool.quote_custodian,
object::id(account_cap),
coin::into_balance(coin)
)
}
Function withdraw_base
public fun withdraw_base<BaseAsset, QuoteAsset>(pool: &mut clob::Pool<BaseAsset, QuoteAsset>, quantity: u64, account_cap: &custodian::AccountCap, ctx: &mut tx_context::TxContext): coin::Coin<BaseAsset>
Implementation
public fun withdraw_base<BaseAsset, QuoteAsset>(
pool: &mut Pool<BaseAsset, QuoteAsset>,
quantity: u64,
account_cap: &AccountCap,
ctx: &mut TxContext
): Coin<BaseAsset> {
assert!(quantity > 0, EInvalidQuantity);
custodian::withdraw_asset(&mut pool.base_custodian, quantity, account_cap, ctx)
}
Function withdraw_quote
public fun withdraw_quote<BaseAsset, QuoteAsset>(pool: &mut clob::Pool<BaseAsset, QuoteAsset>, quantity: u64, account_cap: &custodian::AccountCap, ctx: &mut tx_context::TxContext): coin::Coin<QuoteAsset>
Implementation
public fun withdraw_quote<BaseAsset, QuoteAsset>(
pool: &mut Pool<BaseAsset, QuoteAsset>,
quantity: u64,
account_cap: &AccountCap,
ctx: &mut TxContext
): Coin<QuoteAsset> {
assert!(quantity > 0, EInvalidQuantity);
custodian::withdraw_asset(&mut pool.quote_custodian, quantity, account_cap, ctx)
}
Function swap_exact_base_for_quote
public fun swap_exact_base_for_quote<BaseAsset, QuoteAsset>(pool: &mut clob::Pool<BaseAsset, QuoteAsset>, quantity: u64, base_coin: coin::Coin<BaseAsset>, quote_coin: coin::Coin<QuoteAsset>, clock: &clock::Clock, ctx: &mut tx_context::TxContext): (coin::Coin<BaseAsset>, coin::Coin<QuoteAsset>, u64)
Implementation
public fun swap_exact_base_for_quote<BaseAsset, QuoteAsset>(
pool: &mut Pool<BaseAsset, QuoteAsset>,
quantity: u64,
base_coin: Coin<BaseAsset>,
quote_coin: Coin<QuoteAsset>,
clock: &Clock,
ctx: &mut TxContext,
): (Coin<BaseAsset>, Coin<QuoteAsset>, u64) {
assert!(quantity > 0, EInvalidQuantity);
assert!(coin::value(&base_coin) >= quantity, EInsufficientBaseCoin);
let original_val = coin::value("e_coin);
let (ret_base_coin, ret_quote_coin) = place_market_order(
pool,
quantity,
false,
base_coin,
quote_coin,
clock,
ctx
);
let ret_val = coin::value(&ret_quote_coin);
(ret_base_coin, ret_quote_coin, ret_val - original_val)
}
Function swap_exact_quote_for_base
public fun swap_exact_quote_for_base<BaseAsset, QuoteAsset>(pool: &mut clob::Pool<BaseAsset, QuoteAsset>, quantity: u64, clock: &clock::Clock, quote_coin: coin::Coin<QuoteAsset>, ctx: &mut tx_context::TxContext): (coin::Coin<BaseAsset>, coin::Coin<QuoteAsset>, u64)
Implementation
public fun swap_exact_quote_for_base<BaseAsset, QuoteAsset>(
pool: &mut Pool<BaseAsset, QuoteAsset>,
quantity: u64,
clock: &Clock,
quote_coin: Coin<QuoteAsset>,
ctx: &mut TxContext,
): (Coin<BaseAsset>, Coin<QuoteAsset>, u64) {
assert!(quantity > 0, EInvalidQuantity);
assert!(coin::value("e_coin) >= quantity, EInsufficientQuoteCoin);
let (base_asset_balance, quote_asset_balance) = match_bid_with_quote_quantity(
pool,
quantity,
MAX_PRICE,
clock::timestamp_ms(clock),
coin::into_balance(quote_coin)
);
let val = balance::value(&base_asset_balance);
(coin::from_balance(base_asset_balance, ctx), coin::from_balance(quote_asset_balance, ctx), val)
}
Function match_bid_with_quote_quantity
fun match_bid_with_quote_quantity<BaseAsset, QuoteAsset>(pool: &mut clob::Pool<BaseAsset, QuoteAsset>, quantity: u64, price_limit: u64, current_timestamp: u64, quote_balance: balance::Balance<QuoteAsset>): (balance::Balance<BaseAsset>, balance::Balance<QuoteAsset>)
Implementation
fun match_bid_with_quote_quantity<BaseAsset, QuoteAsset>(
pool: &mut Pool<BaseAsset, QuoteAsset>,
quantity: u64,
price_limit: u64,
current_timestamp: u64,
quote_balance: Balance<QuoteAsset>,
): (Balance<BaseAsset>, Balance<QuoteAsset>) {
// Base balance received by taker, taking into account of taker commission.
// Need to individually keep track of the remaining base quantity to be filled to avoid infinite loop.
let pool_id = *object::uid_as_inner(&pool.id);
let mut taker_quote_quantity_remaining = quantity;
let mut base_balance_filled = balance::zero<BaseAsset>();
let mut quote_balance_left = quote_balance;
let all_open_orders = &mut pool.asks;
if (critbit::is_empty(all_open_orders)) {
return (base_balance_filled, quote_balance_left)
};
let (mut tick_price, mut tick_index) = min_leaf(all_open_orders);
let mut terminate_loop = false;
while (!is_empty<TickLevel>(all_open_orders) && tick_price <= price_limit) {
let tick_level = borrow_mut_leaf_by_index(all_open_orders, tick_index);
let mut order_id = *option::borrow(linked_table::front(&tick_level.open_orders));
while (!linked_table::is_empty(&tick_level.open_orders)) {
let maker_order = linked_table::borrow(&tick_level.open_orders, order_id);
let mut maker_base_quantity = maker_order.quantity;
let mut skip_order = false;
if (maker_order.expire_timestamp <= current_timestamp) {
skip_order = true;
custodian::unlock_balance(&mut pool.base_custodian, maker_order.owner, maker_order.quantity);
emit_order_canceled<BaseAsset, QuoteAsset>(pool_id, maker_order);
} else {
// Calculate how much quote asset (maker_quote_quantity) is required, including the commission, to fill the maker order.
let maker_quote_quantity_without_commission = clob_math::mul(
maker_base_quantity,
maker_order.price
);
let (is_round_down, mut taker_commission) = clob_math::unsafe_mul_round(
maker_quote_quantity_without_commission,
pool.taker_fee_rate
);
if (is_round_down) taker_commission = taker_commission + 1;
let maker_quote_quantity = maker_quote_quantity_without_commission + taker_commission;
// Total base quantity filled.
let mut filled_base_quantity: u64;
// Total quote quantity filled, excluding commission and rebate.
let mut filled_quote_quantity: u64;
// Total quote quantity paid by taker.
// filled_quote_quantity_without_commission * (FLOAT_SCALING + taker_fee_rate) = filled_quote_quantity
let mut filled_quote_quantity_without_commission: u64;
if (taker_quote_quantity_remaining > maker_quote_quantity) {
filled_quote_quantity = maker_quote_quantity;
filled_quote_quantity_without_commission = maker_quote_quantity_without_commission;
filled_base_quantity = maker_base_quantity;
} else {
terminate_loop = true;
// if not enough quote quantity to pay for taker commission, then no quantity will be filled
filled_quote_quantity_without_commission = clob_math::unsafe_div(
taker_quote_quantity_remaining,
FLOAT_SCALING + pool.taker_fee_rate
);
// filled_base_quantity = 0 is permitted since filled_quote_quantity_without_commission can be 0
filled_base_quantity = clob_math::unsafe_div(
filled_quote_quantity_without_commission,
maker_order.price
);
let filled_base_lot = filled_base_quantity / pool.lot_size;
filled_base_quantity = filled_base_lot * pool.lot_size;
// filled_quote_quantity_without_commission = 0 is permitted here since filled_base_quantity could be 0
filled_quote_quantity_without_commission = clob_math::unsafe_mul(
filled_base_quantity,
maker_order.price
);
// if taker_commission = 0 due to underflow, round it up to 1
let (round_down, mut taker_commission) = clob_math::unsafe_mul_round(
filled_quote_quantity_without_commission,
pool.taker_fee_rate
);
if (round_down) {
taker_commission = taker_commission + 1;
};
filled_quote_quantity = filled_quote_quantity_without_commission + taker_commission;
};
// if maker_rebate = 0 due to underflow, maker will not receive a rebate
let maker_rebate = clob_math::unsafe_mul(
filled_quote_quantity_without_commission,
pool.maker_rebate_rate
);
maker_base_quantity = maker_base_quantity - filled_base_quantity;
// maker in ask side, decrease maker's locked base asset, increase maker's available quote asset
taker_quote_quantity_remaining = taker_quote_quantity_remaining - filled_quote_quantity;
let locked_base_balance = custodian::decrease_user_locked_balance<BaseAsset>(
&mut pool.base_custodian,
maker_order.owner,
filled_base_quantity
);
let mut quote_balance_filled = balance::split(
&mut quote_balance_left,
filled_quote_quantity,
);
// Send quote asset including rebate to maker.
custodian::increase_user_available_balance<QuoteAsset>(
&mut pool.quote_custodian,
maker_order.owner,
balance::split(
&mut quote_balance_filled,
maker_rebate + filled_quote_quantity_without_commission,
),
);
// Send remaining of commission - rebate to the protocol.
// commission - rebate = filled_quote_quantity_without_commission - filled_quote_quantity - maker_rebate
balance::join(&mut pool.quote_asset_trading_fees, quote_balance_filled);
balance::join(&mut base_balance_filled, locked_base_balance);
emit_order_filled<BaseAsset, QuoteAsset>(
*object::uid_as_inner(&pool.id),
maker_order,
filled_base_quantity,
// taker_commission = filled_quote_quantity - filled_quote_quantity_without_commission
// This guarantees that the subtraction will not underflow
filled_quote_quantity - filled_quote_quantity_without_commission,
maker_rebate
)
};
if (skip_order || maker_base_quantity == 0) {
// Remove the maker order.
let old_order_id = order_id;
let maybe_order_id = linked_table::next(&tick_level.open_orders, order_id);
if (!option::is_none(maybe_order_id)) {
order_id = *option::borrow(maybe_order_id);
};
let usr_open_order_ids = table::borrow_mut(&mut pool.usr_open_orders, maker_order.owner);
linked_table::remove(usr_open_order_ids, old_order_id);
linked_table::remove(&mut tick_level.open_orders, old_order_id);
} else {
// Update the maker order.
let maker_order_mut = linked_table::borrow_mut(
&mut tick_level.open_orders,
order_id);
maker_order_mut.quantity = maker_base_quantity;
};
if (terminate_loop) {
break
};
};
if (linked_table::is_empty(&tick_level.open_orders)) {
(tick_price, _) = next_leaf(all_open_orders, tick_price);
destroy_empty_level(remove_leaf_by_index(all_open_orders, tick_index));
(_, tick_index) = find_leaf(all_open_orders, tick_price);
};
if (terminate_loop) {
break
};
};
return (base_balance_filled, quote_balance_left)
}
Function match_bid
fun match_bid<BaseAsset, QuoteAsset>(pool: &mut clob::Pool<BaseAsset, QuoteAsset>, quantity: u64, price_limit: u64, current_timestamp: u64, quote_balance: balance::Balance<QuoteAsset>): (balance::Balance<BaseAsset>, balance::Balance<QuoteAsset>)
Implementation
fun match_bid<BaseAsset, QuoteAsset>(
pool: &mut Pool<BaseAsset, QuoteAsset>,
quantity: u64,
price_limit: u64,
current_timestamp: u64,
quote_balance: Balance<QuoteAsset>,
): (Balance<BaseAsset>, Balance<QuoteAsset>) {
let pool_id = *object::uid_as_inner(&pool.id);
// Base balance received by taker.
// Need to individually keep track of the remaining base quantity to be filled to avoid infinite loop.
let mut taker_base_quantity_remaining = quantity;
let mut base_balance_filled = balance::zero<BaseAsset>();
let mut quote_balance_left = quote_balance;
let all_open_orders = &mut pool.asks;
if (critbit::is_empty(all_open_orders)) {
return (base_balance_filled, quote_balance_left)
};
let (mut tick_price, mut tick_index) = min_leaf(all_open_orders);
while (!is_empty<TickLevel>(all_open_orders) && tick_price <= price_limit) {
let tick_level = borrow_mut_leaf_by_index(all_open_orders, tick_index);
let mut order_id = *option::borrow(linked_table::front(&tick_level.open_orders));
while (!linked_table::is_empty(&tick_level.open_orders)) {
let maker_order = linked_table::borrow(&tick_level.open_orders, order_id);
let mut maker_base_quantity = maker_order.quantity;
let mut skip_order = false;
if (maker_order.expire_timestamp <= current_timestamp) {
skip_order = true;
custodian::unlock_balance(&mut pool.base_custodian, maker_order.owner, maker_order.quantity);
emit_order_canceled<BaseAsset, QuoteAsset>(pool_id, maker_order);
} else {
let filled_base_quantity =
if (taker_base_quantity_remaining > maker_base_quantity) { maker_base_quantity }
else { taker_base_quantity_remaining };
let filled_quote_quantity = clob_math::mul(filled_base_quantity, maker_order.price);
// if maker_rebate = 0 due to underflow, maker will not receive a rebate
let maker_rebate = clob_math::unsafe_mul(filled_quote_quantity, pool.maker_rebate_rate);
// if taker_commission = 0 due to underflow, round it up to 1
let (is_round_down, mut taker_commission) = clob_math::unsafe_mul_round(
filled_quote_quantity,
pool.taker_fee_rate
);
if (is_round_down) taker_commission = taker_commission + 1;
maker_base_quantity = maker_base_quantity - filled_base_quantity;
// maker in ask side, decrease maker's locked base asset, increase maker's available quote asset
taker_base_quantity_remaining = taker_base_quantity_remaining - filled_base_quantity;
let locked_base_balance = custodian::decrease_user_locked_balance<BaseAsset>(
&mut pool.base_custodian,
maker_order.owner,
filled_base_quantity
);
let mut taker_commission_balance = balance::split(
&mut quote_balance_left,
taker_commission,
);
custodian::increase_user_available_balance<QuoteAsset>(
&mut pool.quote_custodian,
maker_order.owner,
balance::split(
&mut taker_commission_balance,
maker_rebate,
),
);
balance::join(&mut pool.quote_asset_trading_fees, taker_commission_balance);
balance::join(&mut base_balance_filled, locked_base_balance);
custodian::increase_user_available_balance<QuoteAsset>(
&mut pool.quote_custodian,
maker_order.owner,
balance::split(
&mut quote_balance_left,
filled_quote_quantity,
),
);
emit_order_filled<BaseAsset, QuoteAsset>(
*object::uid_as_inner(&pool.id),
maker_order,
filled_base_quantity,
taker_commission,
maker_rebate
);
};
if (skip_order || maker_base_quantity == 0) {
// Remove the maker order.
let old_order_id = order_id;
let maybe_order_id = linked_table::next(&tick_level.open_orders, order_id);
if (!option::is_none(maybe_order_id)) {
order_id = *option::borrow(maybe_order_id);
};
let usr_open_order_ids = table::borrow_mut(&mut pool.usr_open_orders, maker_order.owner);
linked_table::remove(usr_open_order_ids, old_order_id);
linked_table::remove(&mut tick_level.open_orders, old_order_id);
} else {
// Update the maker order.
let maker_order_mut = linked_table::borrow_mut(
&mut tick_level.open_orders,
order_id);
maker_order_mut.quantity = maker_base_quantity;
};
if (taker_base_quantity_remaining == 0) {
break
};
};
if (linked_table::is_empty(&tick_level.open_orders)) {
(tick_price, _) = next_leaf(all_open_orders, tick_price);
destroy_empty_level(remove_leaf_by_index(all_open_orders, tick_index));
(_, tick_index) = find_leaf(all_open_orders, tick_price);
};
if (taker_base_quantity_remaining == 0) {
break
};
};
return (base_balance_filled, quote_balance_left)
}
Function match_ask
fun match_ask<BaseAsset, QuoteAsset>(pool: &mut clob::Pool<BaseAsset, QuoteAsset>, price_limit: u64, current_timestamp: u64, base_balance: balance::Balance<BaseAsset>): (balance::Balance<BaseAsset>, balance::Balance<QuoteAsset>)
Implementation
fun match_ask<BaseAsset, QuoteAsset>(
pool: &mut Pool<BaseAsset, QuoteAsset>,
price_limit: u64,
current_timestamp: u64,
base_balance: Balance<BaseAsset>,
): (Balance<BaseAsset>, Balance<QuoteAsset>) {
let pool_id = *object::uid_as_inner(&pool.id);
let mut base_balance_left = base_balance;
// Base balance received by taker, taking into account of taker commission.
let mut quote_balance_filled = balance::zero<QuoteAsset>();
let all_open_orders = &mut pool.bids;
if (critbit::is_empty(all_open_orders)) {
return (base_balance_left, quote_balance_filled)
};
let (mut tick_price, mut tick_index) = max_leaf(all_open_orders);
while (!is_empty<TickLevel>(all_open_orders) && tick_price >= price_limit) {
let tick_level = borrow_mut_leaf_by_index(all_open_orders, tick_index);
let mut order_id = *option::borrow(linked_table::front(&tick_level.open_orders));
while (!linked_table::is_empty(&tick_level.open_orders)) {
let maker_order = linked_table::borrow(&tick_level.open_orders, order_id);
let mut maker_base_quantity = maker_order.quantity;
let mut skip_order = false;
if (maker_order.expire_timestamp <= current_timestamp) {
skip_order = true;
let maker_quote_quantity = clob_math::mul(maker_order.quantity, maker_order.price);
custodian::unlock_balance(&mut pool.quote_custodian, maker_order.owner, maker_quote_quantity);
emit_order_canceled<BaseAsset, QuoteAsset>(pool_id, maker_order);
} else {
let taker_base_quantity_remaining = balance::value(&base_balance_left);
let filled_base_quantity =
if (taker_base_quantity_remaining >= maker_base_quantity) { maker_base_quantity }
else { taker_base_quantity_remaining };
let filled_quote_quantity = clob_math::mul(filled_base_quantity, maker_order.price);
// if maker_rebate = 0 due to underflow, maker will not receive a rebate
let maker_rebate = clob_math::unsafe_mul(filled_quote_quantity, pool.maker_rebate_rate);
// if taker_commission = 0 due to underflow, round it up to 1
let (is_round_down, mut taker_commission) = clob_math::unsafe_mul_round(
filled_quote_quantity,
pool.taker_fee_rate
);
if (is_round_down) taker_commission = taker_commission + 1;
maker_base_quantity = maker_base_quantity - filled_base_quantity;
// maker in bid side, decrease maker's locked quote asset, increase maker's available base asset
let mut locked_quote_balance = custodian::decrease_user_locked_balance<QuoteAsset>(
&mut pool.quote_custodian,
maker_order.owner,
filled_quote_quantity
);
let mut taker_commission_balance = balance::split(
&mut locked_quote_balance,
taker_commission,
);
custodian::increase_user_available_balance<QuoteAsset>(
&mut pool.quote_custodian,
maker_order.owner,
balance::split(
&mut taker_commission_balance,
maker_rebate,
),
);
balance::join(&mut pool.quote_asset_trading_fees, taker_commission_balance);
balance::join(&mut quote_balance_filled, locked_quote_balance);
custodian::increase_user_available_balance<BaseAsset>(
&mut pool.base_custodian,
maker_order.owner,
balance::split(
&mut base_balance_left,
filled_base_quantity,
),
);
emit_order_filled<BaseAsset, QuoteAsset>(
*object::uid_as_inner(&pool.id),
maker_order,
filled_base_quantity,
taker_commission,
maker_rebate
);
};
if (skip_order || maker_base_quantity == 0) {
// Remove the maker order.
let old_order_id = order_id;
let maybe_order_id = linked_table::next(&tick_level.open_orders, order_id);
if (!option::is_none(maybe_order_id)) {
order_id = *option::borrow(maybe_order_id);
};
let usr_open_order_ids = table::borrow_mut(&mut pool.usr_open_orders, maker_order.owner);
linked_table::remove(usr_open_order_ids, old_order_id);
linked_table::remove(&mut tick_level.open_orders, old_order_id);
} else {
// Update the maker order.
let maker_order_mut = linked_table::borrow_mut(
&mut tick_level.open_orders,
order_id);
maker_order_mut.quantity = maker_base_quantity;
};
if (balance::value(&base_balance_left) == 0) {
break
};
};
if (linked_table::is_empty(&tick_level.open_orders)) {
(tick_price, _) = previous_leaf(all_open_orders, tick_price);
destroy_empty_level(remove_leaf_by_index(all_open_orders, tick_index));
(_, tick_index) = find_leaf(all_open_orders, tick_price);
};
if (balance::value(&base_balance_left) == 0) {
break
};
};
return (base_balance_left, quote_balance_filled)
}
Function place_market_order
Place a market order to the order book.
public fun place_market_order<BaseAsset, QuoteAsset>(pool: &mut clob::Pool<BaseAsset, QuoteAsset>, quantity: u64, is_bid: bool, base_coin: coin::Coin<BaseAsset>, quote_coin: coin::Coin<QuoteAsset>, clock: &clock::Clock, ctx: &mut tx_context::TxContext): (coin::Coin<BaseAsset>, coin::Coin<QuoteAsset>)
Implementation
public fun place_market_order<BaseAsset, QuoteAsset>(
pool: &mut Pool<BaseAsset, QuoteAsset>,
quantity: u64,
is_bid: bool,
mut base_coin: Coin<BaseAsset>,
mut quote_coin: Coin<QuoteAsset>,
clock: &Clock,
ctx: &mut TxContext,
): (Coin<BaseAsset>, Coin<QuoteAsset>) {
// If market bid order, match against the open ask orders. Otherwise, match against the open bid orders.
// Take market bid order for example.
// We first retrieve the PriceLevel with the lowest price by calling min_leaf on the asks Critbit Tree.
// We then match the market order by iterating through open orders on that price level in ascending order of the order id.
// Open orders that are being filled are removed from the order book.
// We stop the iteration untill all quantities are filled.
// If the total quantity of open orders at the lowest price level is not large enough to fully fill the market order,
// we move on to the next price level by calling next_leaf on the asks Critbit Tree and repeat the same procedure.
// Continue iterating over the price levels in ascending order until the market order is completely filled.
// If ther market order cannot be completely filled even after consuming all the open ask orders,
// the unfilled quantity will be cancelled.
// Market ask order follows similar procedure.
// The difference is that market ask order is matched against the open bid orders.
// We start with the bid PriceLeve with the highest price by calling max_leaf on the bids Critbit Tree.
// The inner loop for iterating over the open orders in ascending orders of order id is the same as above.
// Then iterate over the price levels in descending order until the market order is completely filled.
assert!(quantity % pool.lot_size == 0, EInvalidQuantity);
assert!(quantity != 0, EInvalidQuantity);
if (is_bid) {
let (base_balance_filled, quote_balance_left) = match_bid(
pool,
quantity,
MAX_PRICE,
clock::timestamp_ms(clock),
coin::into_balance(quote_coin),
);
join(
&mut base_coin,
coin::from_balance(base_balance_filled, ctx),
);
quote_coin = coin::from_balance(quote_balance_left, ctx);
} else {
assert!(quantity <= coin::value(&base_coin), EInsufficientBaseCoin);
let (base_balance_left, quote_balance_filled) = match_ask(
pool,
MIN_PRICE,
clock::timestamp_ms(clock),
coin::into_balance(base_coin),
);
base_coin = coin::from_balance(base_balance_left, ctx);
join(
&mut quote_coin,
coin::from_balance(quote_balance_filled, ctx),
);
};
(base_coin, quote_coin)
}
Function inject_limit_order
Injects a maker order to the order book. Returns the order id.
fun inject_limit_order<BaseAsset, QuoteAsset>(pool: &mut clob::Pool<BaseAsset, QuoteAsset>, price: u64, quantity: u64, is_bid: bool, expire_timestamp: u64, account_cap: &custodian::AccountCap, ctx: &mut tx_context::TxContext): u64
Implementation
fun inject_limit_order<BaseAsset, QuoteAsset>(
pool: &mut Pool<BaseAsset, QuoteAsset>,
price: u64,
quantity: u64,
is_bid: bool,
expire_timestamp: u64,
account_cap: &AccountCap,
ctx: &mut TxContext
): u64 {
let user = object::id(account_cap);
let order_id: u64;
let open_orders: &mut CritbitTree<TickLevel>;
if (is_bid) {
let quote_quantity = clob_math::mul(quantity, price);
custodian::lock_balance<QuoteAsset>(&mut pool.quote_custodian, account_cap, quote_quantity);
order_id = pool.next_bid_order_id;
pool.next_bid_order_id = pool.next_bid_order_id + 1;
open_orders = &mut pool.bids;
} else {
custodian::lock_balance<BaseAsset>(&mut pool.base_custodian, account_cap, quantity);
order_id = pool.next_ask_order_id;
pool.next_ask_order_id = pool.next_ask_order_id + 1;
open_orders = &mut pool.asks;
};
let order = Order {
order_id,
price,
quantity,
is_bid,
owner: user,
expire_timestamp,
};
let (tick_exists, mut tick_index) = find_leaf(open_orders, price);
if (!tick_exists) {
tick_index = insert_leaf(
open_orders,
price,
TickLevel {
price,
open_orders: linked_table::new(ctx),
});
};
let tick_level = borrow_mut_leaf_by_index(open_orders, tick_index);
linked_table::push_back(&mut tick_level.open_orders, order_id, order);
event::emit(OrderPlacedV2<BaseAsset, QuoteAsset> {
pool_id: *object::uid_as_inner(&pool.id),
order_id,
is_bid,
owner: user,
base_asset_quantity_placed: quantity,
price,
expire_timestamp
});
if (!contains(&pool.usr_open_orders, user)) {
add(&mut pool.usr_open_orders, user, linked_table::new(ctx));
};
linked_table::push_back(borrow_mut(&mut pool.usr_open_orders, user), order_id, price);
return order_id
}
Function place_limit_order
Place a limit order to the order book. Returns (base quantity filled, quote quantity filled, whether a maker order is being placed, order id of the maker order). When the limit order is not successfully placed, we return false to indicate that and also returns a meaningless order_id 0. When the limit order is successfully placed, we return true to indicate that and also the corresponding order_id. So please check that boolean value first before using the order id.
public fun place_limit_order<BaseAsset, QuoteAsset>(pool: &mut clob::Pool<BaseAsset, QuoteAsset>, price: u64, quantity: u64, is_bid: bool, expire_timestamp: u64, restriction: u8, clock: &clock::Clock, account_cap: &custodian::AccountCap, ctx: &mut tx_context::TxContext): (u64, u64, bool, u64)
Implementation
public fun place_limit_order<BaseAsset, QuoteAsset>(
pool: &mut Pool<BaseAsset, QuoteAsset>,
price: u64,
quantity: u64,
is_bid: bool,
expire_timestamp: u64, // Expiration timestamp in ms in absolute value inclusive.
restriction: u8,
clock: &Clock,
account_cap: &AccountCap,
ctx: &mut TxContext
): (u64, u64, bool, u64) {
// If limit bid order, check whether the price is lower than the lowest ask order by checking the min_leaf of asks Critbit Tree.
// If so, assign the sequence id of the order to be next_bid_order_id and increment next_bid_order_id by 1.
// Inject the new order to the bids Critbit Tree according to the price and order id.
// Otherwise, find the price level from the asks Critbit Tree that is no greater than the input price.
// Match the bid order against the asks Critbit Tree in the same way as a market order but up until the price level found in the previous step.
// If the bid order is not completely filled, inject the remaining quantity to the bids Critbit Tree according to the input price and order id.
// If limit ask order, vice versa.
assert!(quantity > 0, EInvalidQuantity);
assert!(price > 0, EInvalidPrice);
assert!(price % pool.tick_size == 0, EInvalidPrice);
assert!(quantity % pool.lot_size == 0, EInvalidQuantity);
assert!(expire_timestamp > clock::timestamp_ms(clock), EInvalidExpireTimestamp);
let user = object::id(account_cap);
let base_quantity_filled;
let quote_quantity_filled;
if (is_bid) {
let quote_quantity_original = custodian::account_available_balance<QuoteAsset>(
&pool.quote_custodian,
user,
);
let quote_balance = custodian::decrease_user_available_balance<QuoteAsset>(
&mut pool.quote_custodian,
account_cap,
quote_quantity_original,
);
let (base_balance_filled, quote_balance_left) = match_bid(
pool,
quantity,
price,
clock::timestamp_ms(clock),
quote_balance,
);
base_quantity_filled = balance::value(&base_balance_filled);
quote_quantity_filled = quote_quantity_original - balance::value("e_balance_left);
custodian::increase_user_available_balance<BaseAsset>(
&mut pool.base_custodian,
user,
base_balance_filled,
);
custodian::increase_user_available_balance<QuoteAsset>(
&mut pool.quote_custodian,
user,
quote_balance_left,
);
} else {
let base_balance = custodian::decrease_user_available_balance<BaseAsset>(
&mut pool.base_custodian,
account_cap,
quantity,
);
let (base_balance_left, quote_balance_filled) = match_ask(
pool,
price,
clock::timestamp_ms(clock),
base_balance,
);
base_quantity_filled = quantity - balance::value(&base_balance_left);
quote_quantity_filled = balance::value("e_balance_filled);
custodian::increase_user_available_balance<BaseAsset>(
&mut pool.base_custodian,
user,
base_balance_left,
);
custodian::increase_user_available_balance<QuoteAsset>(
&mut pool.quote_custodian,
user,
quote_balance_filled,
);
};
let order_id;
if (restriction == IMMEDIATE_OR_CANCEL) {
return (base_quantity_filled, quote_quantity_filled, false, 0)
};
if (restriction == FILL_OR_KILL) {
assert!(base_quantity_filled == quantity, EOrderCannotBeFullyFilled);
return (base_quantity_filled, quote_quantity_filled, false, 0)
};
if (restriction == POST_OR_ABORT) {
assert!(base_quantity_filled == 0, EOrderCannotBeFullyPassive);
order_id = inject_limit_order(pool, price, quantity, is_bid, expire_timestamp, account_cap, ctx);
return (base_quantity_filled, quote_quantity_filled, true, order_id)
} else {
assert!(restriction == NO_RESTRICTION, EInvalidRestriction);
if (quantity > base_quantity_filled) {
order_id = inject_limit_order(
pool,
price,
quantity - base_quantity_filled,
is_bid,
expire_timestamp,
account_cap,
ctx
);
return (base_quantity_filled, quote_quantity_filled, true, order_id)
};
return (base_quantity_filled, quote_quantity_filled, false, 0)
}
}
Function order_is_bid
fun order_is_bid(order_id: u64): bool
Implementation
fun order_is_bid(order_id: u64): bool {
return order_id < MIN_ASK_ORDER_ID
}
Function emit_order_canceled
fun emit_order_canceled<BaseAsset, QuoteAsset>(pool_id: object::ID, order: &clob::Order)
Implementation
fun emit_order_canceled<BaseAsset, QuoteAsset>(
pool_id: ID,
order: &Order
) {
event::emit(OrderCanceled<BaseAsset, QuoteAsset> {
pool_id,
order_id: order.order_id,
is_bid: order.is_bid,
owner: order.owner,
base_asset_quantity_canceled: order.quantity,
price: order.price
})
}
Function emit_order_filled
fun emit_order_filled<BaseAsset, QuoteAsset>(pool_id: object::ID, order: &clob::Order, base_asset_quantity_filled: u64, taker_commission: u64, maker_rebates: u64)
Implementation
fun emit_order_filled<BaseAsset, QuoteAsset>(
pool_id: ID,
order: &Order,
base_asset_quantity_filled: u64,
taker_commission: u64,
maker_rebates: u64
) {
event::emit(OrderFilledV2<BaseAsset, QuoteAsset> {
pool_id,
order_id: order.order_id,
is_bid: order.is_bid,
owner: order.owner,
total_quantity: order.quantity,
base_asset_quantity_filled,
// order.quantity = base_asset_quantity_filled + base_asset_quantity_remaining
// This guarantees that the subtraction will not underflow
base_asset_quantity_remaining: order.quantity - base_asset_quantity_filled,
price: order.price,
taker_commission,
maker_rebates
})
}
Function cancel_order
Cancel and opening order. Abort if order_id is invalid or if the order is not submitted by the transaction sender.
public fun cancel_order<BaseAsset, QuoteAsset>(pool: &mut clob::Pool<BaseAsset, QuoteAsset>, order_id: u64, account_cap: &custodian::AccountCap)
Implementation
public fun cancel_order<BaseAsset, QuoteAsset>(
pool: &mut Pool<BaseAsset, QuoteAsset>,
order_id: u64,
account_cap: &AccountCap
) {
// First check the highest bit of the order id to see whether it's bid or ask.
// Then retrieve the price using the order id.
// Using the price to retrieve the corresponding PriceLevel from the bids / asks Critbit Tree.
// Retrieve and remove the order from open orders of the PriceLevel.
let user = object::id(account_cap);
assert!(contains(&pool.usr_open_orders, user), EInvalidUser);
let usr_open_orders = borrow_mut(&mut pool.usr_open_orders, user);
assert!(linked_table::contains(usr_open_orders, order_id), EInvalidOrderId);
let tick_price = *linked_table::borrow(usr_open_orders, order_id);
let is_bid = order_is_bid(order_id);
let (tick_exists, tick_index) = find_leaf(
if (is_bid) { &pool.bids } else { &pool.asks },
tick_price);
assert!(tick_exists, EInvalidOrderId);
let order = remove_order(
if (is_bid) { &mut pool.bids } else { &mut pool.asks },
usr_open_orders,
tick_index,
order_id,
user
);
if (is_bid) {
let balance_locked = clob_math::mul(order.quantity, order.price);
custodian::unlock_balance(&mut pool.quote_custodian, user, balance_locked);
} else {
custodian::unlock_balance(&mut pool.base_custodian, user, order.quantity);
};
emit_order_canceled<BaseAsset, QuoteAsset>(*object::uid_as_inner(&pool.id), &order);
}
Function remove_order
fun remove_order(open_orders: &mut critbit::CritbitTree<clob::TickLevel>, usr_open_orders: &mut linked_table::LinkedTable<u64, u64>, tick_index: u64, order_id: u64, user: object::ID): clob::Order
Implementation
fun remove_order(
open_orders: &mut CritbitTree<TickLevel>,
usr_open_orders: &mut LinkedTable<u64, u64>,
tick_index: u64,
order_id: u64,
user: ID,
): Order {
linked_table::remove(usr_open_orders, order_id);
let tick_level = borrow_leaf_by_index(open_orders, tick_index);
assert!(linked_table::contains(&tick_level.open_orders, order_id), EInvalidOrderId);
let mut_tick_level = borrow_mut_leaf_by_index(open_orders, tick_index);
let order = linked_table::remove(&mut mut_tick_level.open_orders, order_id);
assert!(order.owner == user, EUnauthorizedCancel);
if (linked_table::is_empty(&mut_tick_level.open_orders)) {
destroy_empty_level(remove_leaf_by_index(open_orders, tick_index));
};
order
}
Function cancel_all_orders
public fun cancel_all_orders<BaseAsset, QuoteAsset>(pool: &mut clob::Pool<BaseAsset, QuoteAsset>, account_cap: &custodian::AccountCap)
Implementation
public fun cancel_all_orders<BaseAsset, QuoteAsset>(
pool: &mut Pool<BaseAsset, QuoteAsset>,
account_cap: &AccountCap
) {
let pool_id = *object::uid_as_inner(&pool.id);
let user = object::id(account_cap);
assert!(contains(&pool.usr_open_orders, user), EInvalidUser);
let usr_open_order_ids = table::borrow_mut(&mut pool.usr_open_orders, user);
while (!linked_table::is_empty(usr_open_order_ids)) {
let order_id = *option::borrow(linked_table::back(usr_open_order_ids));
let order_price = *linked_table::borrow(usr_open_order_ids, order_id);
let is_bid = order_is_bid(order_id);
let open_orders =
if (is_bid) { &mut pool.bids }
else { &mut pool.asks };
let (_, tick_index) = critbit::find_leaf(open_orders, order_price);
let order = remove_order(
open_orders,
usr_open_order_ids,
tick_index,
order_id,
user
);
if (is_bid) {
let balance_locked = clob_math::mul(order.quantity, order.price);
custodian::unlock_balance(&mut pool.quote_custodian, user, balance_locked);
} else {
custodian::unlock_balance(&mut pool.base_custodian, user, order.quantity);
};
emit_order_canceled<BaseAsset, QuoteAsset>(pool_id, &order);
};
}
Function batch_cancel_order
Batch cancel limit orders to save gas cost. Abort if any of the order_ids are not submitted by the sender. Skip any order_id that is invalid. Note that this function can reduce gas cost even further if caller has multiple orders at the same price level, and if orders with the same price are grouped together in the vector. For example, if we have the following order_id to price mapping, {0: 100., 1: 200., 2: 100., 3: 200.}. Grouping order_ids like [0, 2, 1, 3] would make it the most gas efficient.
public fun batch_cancel_order<BaseAsset, QuoteAsset>(pool: &mut clob::Pool<BaseAsset, QuoteAsset>, order_ids: vector<u64>, account_cap: &custodian::AccountCap)
Implementation
public fun batch_cancel_order<BaseAsset, QuoteAsset>(
pool: &mut Pool<BaseAsset, QuoteAsset>,
order_ids: vector<u64>,
account_cap: &AccountCap
) {
let pool_id = *object::uid_as_inner(&pool.id);
// First group the order ids according to price level,
// so that we don't have to retrieve the PriceLevel multiple times if there are orders at the same price level.
// Iterate over each price level, retrieve the corresponding PriceLevel.
// Iterate over the order ids that need to be canceled at that price level,
// retrieve and remove the order from open orders of the PriceLevel.
let user = object::id(account_cap);
assert!(contains(&pool.usr_open_orders, user), 0);
let mut tick_index: u64 = 0;
let mut tick_price: u64 = 0;
let n_order = vector::length(&order_ids);
let mut i_order = 0;
let usr_open_orders = borrow_mut(&mut pool.usr_open_orders, user);
while (i_order < n_order) {
let order_id = *vector::borrow(&order_ids, i_order);
assert!(linked_table::contains(usr_open_orders, order_id), EInvalidOrderId);
let new_tick_price = *linked_table::borrow(usr_open_orders, order_id);
let is_bid = order_is_bid(order_id);
if (new_tick_price != tick_price) {
tick_price = new_tick_price;
let (tick_exists, new_tick_index) = find_leaf(
if (is_bid) { &pool.bids } else { &pool.asks },
tick_price
);
assert!(tick_exists, EInvalidTickPrice);
tick_index = new_tick_index;
};
let order = remove_order(
if (is_bid) { &mut pool.bids } else { &mut pool.asks },
usr_open_orders,
tick_index,
order_id,
user
);
if (is_bid) {
let balance_locked = clob_math::mul(order.quantity, order.price);
custodian::unlock_balance(&mut pool.quote_custodian, user, balance_locked);
} else {
custodian::unlock_balance(&mut pool.base_custodian, user, order.quantity);
};
emit_order_canceled<BaseAsset, QuoteAsset>(pool_id, &order);
i_order = i_order + 1;
}
}
Function list_open_orders
public fun list_open_orders<BaseAsset, QuoteAsset>(pool: &clob::Pool<BaseAsset, QuoteAsset>, account_cap: &custodian::AccountCap): vector<clob::Order>
Implementation
public fun list_open_orders<BaseAsset, QuoteAsset>(
pool: &Pool<BaseAsset, QuoteAsset>,
account_cap: &AccountCap
): vector<Order> {
let user = object::id(account_cap);
let usr_open_order_ids = table::borrow(&pool.usr_open_orders, user);
let mut open_orders = vector::empty<Order>();
let mut order_id = linked_table::front(usr_open_order_ids);
while (!option::is_none(order_id)) {
let order_price = *linked_table::borrow(usr_open_order_ids, *option::borrow(order_id));
let tick_level =
if (order_is_bid(*option::borrow(order_id))) borrow_leaf_by_key(&pool.bids, order_price)
else borrow_leaf_by_key(&pool.asks, order_price);
let order = linked_table::borrow(&tick_level.open_orders, *option::borrow(order_id));
vector::push_back(&mut open_orders, Order {
order_id: order.order_id,
price: order.price,
quantity: order.quantity,
is_bid: order.is_bid,
owner: order.owner,
expire_timestamp: order.expire_timestamp
});
order_id = linked_table::next(usr_open_order_ids, *option::borrow(order_id));
};
open_orders
}
Function account_balance
query user balance inside custodian
public fun account_balance<BaseAsset, QuoteAsset>(pool: &clob::Pool<BaseAsset, QuoteAsset>, account_cap: &custodian::AccountCap): (u64, u64, u64, u64)
Implementation
public fun account_balance<BaseAsset, QuoteAsset>(
pool: &Pool<BaseAsset, QuoteAsset>,
account_cap: &AccountCap
): (u64, u64, u64, u64) {
let user = object::id(account_cap);
let (base_avail, base_locked) = custodian::account_balance(&pool.base_custodian, user);
let (quote_avail, quote_locked) = custodian::account_balance(&pool.quote_custodian, user);
(base_avail, base_locked, quote_avail, quote_locked)
}
Function get_market_price
Query the market price of order book returns (best_bid_price, best_ask_price)
public fun get_market_price<BaseAsset, QuoteAsset>(pool: &clob::Pool<BaseAsset, QuoteAsset>): (u64, u64)
Implementation
public fun get_market_price<BaseAsset, QuoteAsset>(
pool: &Pool<BaseAsset, QuoteAsset>
): (u64, u64){
let (bid_price, _) = critbit::max_leaf(&pool.bids);
let (ask_price, _) = critbit::min_leaf(&pool.asks);
return (bid_price, ask_price)
}
Function get_level2_book_status_bid_side
Enter a price range and return the level2 order depth of all valid prices within this price range in bid side returns two vectors of u64 The previous is a list of all valid prices The latter is the corresponding depth list
public fun get_level2_book_status_bid_side<BaseAsset, QuoteAsset>(pool: &clob::Pool<BaseAsset, QuoteAsset>, price_low: u64, price_high: u64, clock: &clock::Clock): (vector<u64>, vector<u64>)
Implementation
public fun get_level2_book_status_bid_side<BaseAsset, QuoteAsset>(
pool: &Pool<BaseAsset, QuoteAsset>,
mut price_low: u64,
mut price_high: u64,
clock: &Clock
): (vector<u64>, vector<u64>) {
let (price_low_, _) = critbit::min_leaf(&pool.bids);
if (price_low < price_low_) price_low = price_low_;
let (price_high_, _) = critbit::max_leaf(&pool.bids);
if (price_high > price_high_) price_high = price_high_;
price_low = critbit::find_closest_key(&pool.bids, price_low);
price_high = critbit::find_closest_key(&pool.bids, price_high);
let mut price_vec = vector::empty<u64>();
let mut depth_vec = vector::empty<u64>();
if (price_low == 0) { return (price_vec, depth_vec) };
while (price_low <= price_high) {
let depth = get_level2_book_status(
&pool.bids,
price_low,
clock::timestamp_ms(clock)
);
vector::push_back(&mut price_vec, price_low);
vector::push_back(&mut depth_vec, depth);
let (next_price, _) = critbit::next_leaf(&pool.bids, price_low);
if (next_price == 0) { break }
else { price_low = next_price };
};
(price_vec, depth_vec)
}
Function get_level2_book_status_ask_side
Enter a price range and return the level2 order depth of all valid prices within this price range in ask side returns two vectors of u64 The previous is a list of all valid prices The latter is the corresponding depth list
public fun get_level2_book_status_ask_side<BaseAsset, QuoteAsset>(pool: &clob::Pool<BaseAsset, QuoteAsset>, price_low: u64, price_high: u64, clock: &clock::Clock): (vector<u64>, vector<u64>)
Implementation
public fun get_level2_book_status_ask_side<BaseAsset, QuoteAsset>(
pool: &Pool<BaseAsset, QuoteAsset>,
mut price_low: u64,
mut price_high: u64,
clock: &Clock
): (vector<u64>, vector<u64>) {
let (price_low_, _) = critbit::min_leaf(&pool.asks);
if (price_low < price_low_) price_low = price_low_;
let (price_high_, _) = critbit::max_leaf(&pool.asks);
if (price_high > price_high_) price_high = price_high_;
price_low = critbit::find_closest_key(&pool.asks, price_low);
price_high = critbit::find_closest_key(&pool.asks, price_high);
let mut price_vec = vector::empty<u64>();
let mut depth_vec = vector::empty<u64>();
if (price_low == 0) { return (price_vec, depth_vec) };
while (price_low <= price_high) {
let depth = get_level2_book_status(
&pool.asks,
price_low,
clock::timestamp_ms(clock)
);
vector::push_back(&mut price_vec, price_low);
vector::push_back(&mut depth_vec, depth);
let (next_price, _) = critbit::next_leaf(&pool.asks, price_low);
if (next_price == 0) { break }
else { price_low = next_price };
};
(price_vec, depth_vec)
}
Function get_level2_book_status
internal func to retrive single depth of a tick price
fun get_level2_book_status(open_orders: &critbit::CritbitTree<clob::TickLevel>, price: u64, time_stamp: u64): u64
Implementation
fun get_level2_book_status(
open_orders: &CritbitTree<TickLevel>,
price: u64,
time_stamp: u64
): u64 {
let tick_level = critbit::borrow_leaf_by_key(open_orders, price);
let tick_open_orders = &tick_level.open_orders;
let mut depth = 0;
let mut order_id = linked_table::front(tick_open_orders);
let mut order: &Order;
while (!option::is_none(order_id)) {
order = linked_table::borrow(tick_open_orders, *option::borrow(order_id));
if (order.expire_timestamp > time_stamp) depth = depth + order.quantity;
order_id = linked_table::next(tick_open_orders, *option::borrow(order_id));
};
depth
}
Function get_order_status
public fun get_order_status<BaseAsset, QuoteAsset>(pool: &clob::Pool<BaseAsset, QuoteAsset>, order_id: u64, account_cap: &custodian::AccountCap): &clob::Order
Implementation
public fun get_order_status<BaseAsset, QuoteAsset>(
pool: &Pool<BaseAsset, QuoteAsset>,
order_id: u64,
account_cap: &AccountCap
): &Order {
let user = object::id(account_cap);
assert!(table::contains(&pool.usr_open_orders, user), EInvalidUser);
let usr_open_order_ids = table::borrow(&pool.usr_open_orders, user);
assert!(linked_table::contains(usr_open_order_ids, order_id), EInvalidOrderId);
let order_price = *linked_table::borrow(usr_open_order_ids, order_id);
let open_orders =
if (order_id < MIN_ASK_ORDER_ID) { &pool.bids }
else { &pool.asks };
let tick_level = critbit::borrow_leaf_by_key(open_orders, order_price);
let tick_open_orders = &tick_level.open_orders;
let order = linked_table::borrow(tick_open_orders, order_id);
order
}