Snippets

chromawallet FT3 order book

Created by Alex Mizrahi
@mount('ft3')
abstract module;

import ft3_a: lib.ft3.account;
import ft3: lib.ft3.core;
import helper: lib.ft3_helper;


abstract function on_order_filled(order, fill_amount: integer);
abstract function on_order_canceled(order);

entity order_book {
	key traded_asset: ft3.asset, payment_asset: ft3.asset;
}

entity order {
	index expires: timestamp;
	index ft3_a.account, order_book;
	is_bid: boolean;
	index order_book, is_bid, price: decimal;
	mutable amount: integer;
}

@log
entity trade_history_entry {
	account: ft3_a.account;
	index account, order_book, transaction;
	index account, transaction;
	is_bid: boolean;
	price: decimal;
	amount: integer;
}

function find_best_matching_order (order_book, is_bid: boolean, price: decimal): order? {
	if (is_bid) {
		// search for asks with price lower than given
		return order @? { order_book, .price <= price, .is_bid == false }
		(@omit @sort .price, order) limit 1;
	} else {
		// search for bids with price higher than given
		return order @? { order_book, .price >= price, .is_bid == true }
		(@omit @sort_desc .price, order) limit 1;
	}
}

function payment_amount( amount: integer, price: decimal ): integer {
	return (amount * price).to_integer();
}

function _cancel_order(order) {
	helper.transfer_asset(
		helper.ensure_locked_account("ORDER", order.account),
		order.account, 
		if (order.is_bid) order.order_book.payment_asset else order.order_book.traded_asset,
		if (order.is_bid) payment_amount(order.amount, order.price) else order.amount
	);
	delete order;
}

operation cancel_order(account_id: byte_array, auth_descriptor_id: byte_array, order) {
	val account = ft3_a.auth_and_log(account_id, auth_descriptor_id, ["T"]);
	require(order.account == account);
	_cancel_order(order);
}

function fill_order(order_book, ft3_a.account, order, amount: integer): integer {
	val order_account = order.account;
	val fill_amount = min(amount, order.amount);
	val payment_amount = payment_amount(fill_amount, order.price);
	val payment_asset = order_book.payment_asset;

	val order_is_bid = order.is_bid;	
	val account_which_sends_asset = if (order_is_bid) account else order_account;
	val account_which_sends_payment = if (order_is_bid) order_account else account;

	helper.transfer_asset(
		if (order_is_bid)
		// if bid, send from balance locked in bid
			helper.ensure_locked_account("ORDER", account_which_sends_payment) 
		else
			account_which_sends_payment,
		account_which_sends_asset,
		payment_asset, payment_amount
	);
	
	helper.transfer_asset(
		if (not order_is_bid)
		// if ask, send from balance locked in ask
			helper.ensure_locked_account("ORDER", account_which_sends_asset)
			else account_which_sends_asset,
		 account_which_sends_payment,
		order_book.traded_asset, fill_amount	
	);
	
	create trade_history_entry(
		order_account, order_book,
		order_is_bid, order.price, amount=fill_amount
	);
	create trade_history_entry(
		account, order_book,
		not order_is_bid, order.price, amount=fill_amount
	);
	
	on_order_filled(order, fill_amount);
	
	if (fill_amount == order.amount)
		delete order;
	else update order (.amount -= fill_amount);
		
	return fill_amount;
}

function remove_expired_orders()  {
	while (true) {
		val expired = order @* { .expires < op_context.last_block_time} limit 100;
		for (order in expired) _cancel_order(order);
		if (expired.size() < 100) break;		 
	}
}

operation submit_order(account_id: byte_array, auth_descriptor_id: byte_array,
	order_book, price: decimal, is_bid: boolean, amount: integer
) {
	_submit_order(account_id, auth_descriptor_id, order_book, price, is_bid, amount);
}

function submit_order_via_account (
	ft3_a.account, order_book, price: decimal, is_bid: boolean, amount: integer,
	expires: timestamp	
): order?
{
	if (is_bid) {
		// check if buyer can pay
		val payment_required = payment_amount(amount, price);
		require(ft3.ensure_balance(account, order_book.payment_asset).amount >= payment_required);	
	} else {		
		require(ft3.ensure_balance(account, order_book.traded_asset).amount >= amount);
	}
	
	var remaining_amount = amount;
	
	remove_expired_orders();
	
	// fill matching orders
	while (true) {
		val matching_order = find_best_matching_order(order_book, is_bid, price);
		if (exists(matching_order)) {
			val filled_amount = fill_order(order_book, account, matching_order, remaining_amount);
			remaining_amount -= filled_amount;
			if (remaining_amount == 0) break;
		} else {
			break;
		}
	}
	
	if (remaining_amount > 0) {
		if (is_bid) {
			val payment_required = payment_amount(remaining_amount, price);
			helper.transfer_asset(
				account,
				helper.ensure_locked_account("ORDER", account),
				order_book.payment_asset, payment_required
			);
		} else {
			helper.transfer_asset(
				account,
				helper.ensure_locked_account("ORDER", account),
				order_book.traded_asset, remaining_amount);
		}
		
		return create order (order_book, account, price, is_bid, 
			.amount = remaining_amount,
			expires 
		);	
	}	
	return null;
}


function _submit_order (account_id: byte_array, auth_descriptor_id: byte_array,
	 order_book, price: decimal, is_bid: boolean, amount: integer
) {
	submit_order_via_account(
		ft3_a.auth_and_log(account_id, auth_descriptor_id, ["T"]),
		order_book, price, is_bid, amount,
		 op_context.last_block_time + 86400 * 7 * 1000
	);	
}


// this can only remove one order in full (not partially), Quickfix for Jure
operation remove_order (account_id: byte_array, auth_descriptor_id: byte_array,
	order_book, is_bid: boolean, price: decimal, amount: integer
) {
	val account = ft3_a.auth_and_log(account_id, auth_descriptor_id, ["T"]);
	val order = order @ {account, order_book, is_bid, price, amount} limit 1; // there might be multiple orders with same amount, we just take the first one
	_cancel_order(order);
}



function order_book_by_name(traded_asset_name: text, payment_asset_name: text): order_book {
	return order_book @ { 
		.traded_asset == ft3.asset @ { traded_asset_name },
		.payment_asset == ft3.asset @ { payment_asset_name }
	};
}

query get_order_book_by_name(traded_asset_name: text, payment_asset_name: text): order_book {
	return order_book_by_name(traded_asset_name, payment_asset_name);
}

query get_orders(traded_asset_name: text, payment_asset_name: text) {
	return _get_orders(traded_asset_name, payment_asset_name);
}

function _get_orders (traded_asset_name: text, payment_asset_name: text) 
{
	val order_book = order_book_by_name(traded_asset_name, payment_asset_name);
	return order @* { order_book } (
		.rowid,
		.account,
		@sort .is_bid,
		@sort .price,
		.amount
	);
}

query get_orders_of_account (traded_asset_name: text, payment_asset_name: text, account_id: byte_array) {
	val order_book = order_book_by_name(traded_asset_name, payment_asset_name);
	return order @* { order_book, .account == ft3_a.account @ { account_id } } (
		@sort .is_bid,
		@sort .price,
		.amount
	);
}

query get_all_orders_of_account (account_id: byte_array) {
	return order @* {  .account == ft3_a.account @ { account_id } } (
		ta_name = order.order_book.traded_asset.name,
		pa_name = order.order_book.payment_asset.name,
	    .is_bid,
	    .price,
	    .amount
	);
}

query get_bids (traded_asset_name: text, payment_asset_name: text) 
{
	val order_book = order_book_by_name(traded_asset_name, payment_asset_name);
	return order @* { order_book, .is_bid == true } (
		@sort .price,
		.amount
	) limit 10;
}

query get_asks (traded_asset_name: text, payment_asset_name: text) 
{
	val order_book = order_book_by_name(traded_asset_name, payment_asset_name);
	return order @* { order_book, .is_bid == false } (
		@sort_desc .price,
		.amount
	) limit 10;
} 


query get_trade_history(account_id: byte_array, since: timestamp) {
	return trade_history_entry @* { .account == ft3_a.account @ { account_id },
		.transaction.block.timestamp >= since
	}
	(
	   @sort_desc timestamp = .transaction.block.timestamp,
	   .is_bid,
	   ta_name = .order_book.traded_asset.name,
	   pa_name = .order_book.payment_asset.name,
	   .price,
	   .amount,
	   tx_rid = .transaction.tx_rid	
	) limit 100;
}


function _get_exchange_account(account: ft3_a.account) {
	return helper.ensure_locked_account("ORDER", account);
}

query get_exchange_account(account_id: byte_array) {
	return _get_exchange_account(ft3_a.account@{.id == account_id});
}

Comments (0)

HTTPS SSH

You can clone a snippet to your computer for local editing. Learn more.