lib/order/structure/getTakerOrders.js

  1. // const withExecuteTxns = require('./taker/withExecuteTxns');
  2. const getTakerOrderInformation = require('./taker/getTakerOrderInformation');
  3. const getCutTakerOrders = require('./taker/getCutTakerOrders');
  4. // const fromBaseUnits = require('../../utils/units/fromBaseUnits');
  5. const logger = require('../../logger');
  6. /**
  7. *
  8. * @param {string} takerWalletAddr
  9. * @param {boolean} isSellingAssetAsTakerOrder
  10. * @param {Object[]} allOrderBookOrders
  11. * @return {Array}
  12. * @ignore
  13. */
  14. function getQueuedTakerOrders(
  15. takerWalletAddr,
  16. isSellingAssetAsTakerOrder,
  17. allOrderBookOrders,
  18. ) {
  19. const queuedOrders = [];
  20. // getAllOrderBookEscrowOrders is UI dependant and needs to be customized for the React version
  21. if (allOrderBookOrders == null || allOrderBookOrders.length === 0) {
  22. return [];
  23. }
  24. // FIXME: don't allow executions against own orders! check wallet address doesn't match
  25. // takerWalletAddr
  26. for (let i = 0; i < allOrderBookOrders.length; i++) {
  27. const orderBookEntry = allOrderBookOrders[i];
  28. if (orderBookEntry['escrowOrderType'] === 'buy' && !isSellingAssetAsTakerOrder) {
  29. // only look for sell orders in this case
  30. continue;
  31. }
  32. if (orderBookEntry['escrowOrderType'] === 'sell' && isSellingAssetAsTakerOrder) {
  33. // only look for buy orders in this case
  34. continue;
  35. }
  36. orderBookEntry.price = parseFloat(orderBookEntry.price);
  37. queuedOrders.push(orderBookEntry);
  38. }
  39. if (isSellingAssetAsTakerOrder) {
  40. // sort highest first (index 0) to lowest (last index)
  41. // these are buy orders, so we want to sell to the highest first
  42. queuedOrders.sort((a, b) => (a.price < b.price) ? 1 : (a.price === b.price) ? ((a.price < b.price) ? 1 : -1) : -1);
  43. } else {
  44. // sort lowest first (index 0) to highest (last index)
  45. // these are sell orders, so we want to buy the lowest first
  46. queuedOrders.sort((a, b) => (a.price > b.price) ? 1 : (a.price === b.price) ? ((a.price > b.price) ? 1 : -1) : -1);
  47. }
  48. return queuedOrders;
  49. }
  50. /**
  51. * @deprecated
  52. * @param {number} assetId
  53. * @param {AlgodexApi} api
  54. * @return {Promise<*>}
  55. * @ignore
  56. */
  57. async function getOrderbook(assetId, api) {
  58. logger.warn(`Fetching Orderbook for ${assetId}`);
  59. const res = await api.http.dexd.fetchAssetOrders(assetId);
  60. return api.http.dexd.mapToAllEscrowOrders({
  61. buy: res.buyASAOrdersInEscrow,
  62. sell: res.sellASAOrdersInEscrow,
  63. });
  64. }
  65. /**
  66. *
  67. * * # 🏃 getTakerOrders
  68. *
  69. * Accepts an {@link Order} with execution of [Taker]{@tutorial Taker} and matches the criteria with [Executable]{@tutorial Executable} orders in the [Orderbook]{@tutorial Orderbook}.
  70. *
  71. * If executable orders exist then the relevant transactions are generated.
  72. *
  73. * The generated transactions fall into one of the two categories below:
  74. * ### SingleOrderExecution
  75. * **Condition:** For when the desired user "total" amount is less than the available escrow amount
  76. * * Example: There is an order in the orderbook at the user's desired price and desired amount
  77. *
  78. * **Return value:** An array of length=1 containing {@link Order} object with an txnArr attached to the contract
  79. *
  80. *
  81. * ### MultiOrderExececution
  82. * **Condition:** For when the desired user total amount is greater than the available escrow amount
  83. * * Example: There are multiple orders in the orderbook at the user's desired price, but no entry contains the user's desired amount.
  84. *
  85. * **Return value:** An array of variable length. Each item represents a group of transactions.
  86. *
  87. * ### When is it used?
  88. * This method and the corresponding factories are used anytime a user is executing upon an existing [Algodex Orderbook]{@tutorial Orderbook} [Order]{@link Order}.
  89. *
  90. * This method is used to generate the taker transactions in [getMakerTakerTxns]{@link module/structure.getMakerTakerTxns}
  91. *
  92. * This method would be ideal for use in algorithmic trading strategies.
  93. *
  94. * @example
  95. * const [AlgodexAPI]{@link AlgodexApi} = require(@algodex/algodex-sdk)
  96. * const api = new [AlgodexAPI]{@link AlgodexApi}(require('../config.json'))
  97. * const order = {
  98. * "client": api.algod,
  99. * "indexer": api.indexer,
  100. * "asset": {
  101. * "id": 15322902,
  102. * "decimals": 6,
  103. * },
  104. * "address": "TJFFNUYWHPPIYDE4DGGYPGHWKGAPJEWP3DGE5THZS3B2M2XIAPQ2WY3X4I",
  105. * "price": 2.22,
  106. * "amount": 1,
  107. * "total": 2,
  108. * "execution": "taker",
  109. * "type": "buy",
  110. * "appId": 22045503,
  111. * "version": 6
  112. * }
  113. *
  114. * // Scenario: singleOrder
  115. * //order.execution === 'taker'
  116. * let res = await getTakerOrders(api, order)
  117. * console.log(res.contract.txns)
  118. * //Outputs an array with structure of:
  119. * [makeExecuteAssetTxns]{@link module:txns/sell.makeExecuteAssetTxns} || [makeExecuteAlgoTxns]{@link module:txns/buy.makeExecuteAlgoTxns}
  120. *
  121. *
  122. *
  123. * @example
  124. * const [AlgodexAPI]{@link AlgodexApi} = require(@algodex/algodex-sdk)
  125. * const api = new [AlgodexAPI]{@link AlgodexApi}(require('../config.json'))
  126. * const order = {
  127. * "client": api.algod,
  128. * "indexer": api.indexer,
  129. * "asset": {
  130. * "id": 15322902,
  131. * "decimals": 6,
  132. * },
  133. * "address": "TJFFNUYWHPPIYDE4DGGYPGHWKGAPJEWP3DGE5THZS3B2M2XIAPQ2WY3X4I",
  134. * "price": 2.22,
  135. * "amount": 1,
  136. * "total": 2,
  137. * "execution": "taker",
  138. * "type": "buy",
  139. * "appId": 22045503,
  140. * "version": 6
  141. * }
  142. * // Scenario: multiOrder
  143. * //order.execution === 'taker'
  144. * let res = await getTakerOrders(api, order)
  145. * console.log(res)
  146. * //Outputs an array with each item being:
  147. * [withExecuteAssetTxns]{@link module:txns/sell.withExecuteAssetTxns} || [withExecuteAlgoTxns]{@link module:txns/buy.withExecuteAlgoTxns}
  148. *
  149. * @param {AlgodexApi} api The Algodex API
  150. * @param {Order} order The User's Order
  151. * @return {Promise<Structure[]>}
  152. * @throws ValidationError
  153. * @see [makeExecuteAssetTxns]{@link module:txns/sell.makeExecuteAssetTxns} || [makeExecuteAlgoTxns]{@link module:txns/buy.makeExecuteAlgoTxns} || [withExecuteAssetTxns]{@link module:txns/sell.withExecuteAssetTxns} || [withExecuteAlgoTxns]{@link module:txns/buy.withExecuteAlgoTxns}
  154. * @memberOf module:order/structure
  155. */
  156. async function getTakerOrders(api, order) {
  157. if (order.execution !== 'taker' && order.execution !== 'market' && order.execution !== 'both') {
  158. throw new TypeError(`Unsupported execution of ${order.execution}, use [taker, market, both] for automated orderbook matching`);
  159. }
  160. // Fetch Orderbook if it doesn't exist
  161. const _orderbook = !order.asset?.orderbook ?
  162. // TODO: Move to new Orderbook Shape
  163. await getOrderbook(order.asset.id, api) :
  164. order.asset.orderbook;
  165. // Clone Object for mutations
  166. const _order = {...order, asset: {...order.asset, orderbook: _orderbook}};
  167. /**
  168. * @todo Move to new Orderbook Shape, send User's Order instance as the first parameter
  169. * @type {Array}
  170. */
  171. const _queuedOrders = getQueuedTakerOrders(_order.address, _order.type === 'sell', _orderbook);
  172. // Exit if no taker orders
  173. if (_queuedOrders.length === 0) {
  174. logger.warn({address: order.address, type: _order.type},
  175. 'No orders exist for user to execute',
  176. );
  177. // Exit early
  178. return [];
  179. }
  180. /**
  181. * First Order
  182. * @type {Order}
  183. */
  184. const _firstOrder = _queuedOrders[0]; // rough implementation will change name/ placement later
  185. /**
  186. * Balance of the First Order
  187. * @type {Number}
  188. */
  189. // const firstOrderBalance = fromBaseUnits(
  190. // _order.type === 'buy' ?
  191. // _firstOrder.asaBalance :
  192. // _firstOrder.algoBalance/ _firstOrder.price, // to get assetAmount
  193. // );
  194. // We want to see if the escrow amount is larger than order amount because comparing by order total can lead to unexpected results
  195. // If user is selling below market price we want to make sure they get the best deal possible for their "amount" sold so we should ignore their total.
  196. /**
  197. * Check to see if the order fits
  198. * @type {boolean}
  199. */
  200. // const isMultiOrderExecution = _order.amount > firstOrderBalance;
  201. // We should always check by amounts, totals can be misleading when users input prices that over/under
  202. /**
  203. * Flag for if the User's order has Executable Orders
  204. * @type {boolean}
  205. */
  206. const isExecutable = _order.
  207. type === 'buy' ?
  208. _order.price >= _firstOrder.price :
  209. _order.price <= _firstOrder.price;
  210. // No Taker Orders Found
  211. if (!isExecutable) {
  212. logger.warn({
  213. userPrice: _order.price, totalOrders: _queuedOrders.length, spreadPrice: _firstOrder.price,
  214. }, 'No orders exist at the price.');
  215. // Exit early
  216. return [];
  217. }
  218. // User's order "fits" into the top order. Execute against that order
  219. // if (!isMultiOrderExecution) {
  220. // // Closeout if the order matches the balance
  221. // const withCloseout = firstOrderBalance === _order.total;
  222. //
  223. // const _price = parseFloat(_firstOrder.price);
  224. // /**
  225. // * Mapped Order from API
  226. // *
  227. // * @todo This should come from the API and we should only need to set amount/total
  228. // * @type {Order}
  229. // * @private
  230. // */
  231. // const _mappedOrder = {
  232. // execution: 'execute',
  233. // client: _order.client,
  234. // indexer: api.indexer,
  235. // address: _firstOrder.orderCreatorAddr,
  236. // type: _firstOrder.escrowOrderType,
  237. // price: _price,
  238. // amount: _order.amount,
  239. // total: _price * _order.amount,
  240. // appId: parseInt(_firstOrder.appId),
  241. // asset: {
  242. // id: _firstOrder.assetId,
  243. // },
  244. // contract: {
  245. // N: _firstOrder.n,
  246. // D: _firstOrder.d,
  247. // min: _firstOrder.min,
  248. // entry: _firstOrder.orderEntry,
  249. // escrow: _firstOrder.escrowAddr,
  250. // creator: _firstOrder.orderCreatorAddr,
  251. // },
  252. // version: _firstOrder.version,
  253. // wallet: _order.wallet,
  254. // };
  255. //
  256. // // Return an Array with the compiled order
  257. // return [await withExecuteTxns(_mappedOrder, withCloseout)];
  258. // }
  259. //
  260. // // Order is overflowing, split it and generate TakerTxns
  261. // if (isMultiOrderExecution) {
  262. // TODO: Handle Market Orders
  263. return await getCutTakerOrders(
  264. api,
  265. {..._order, indexer: api.indexer},
  266. _queuedOrders,
  267. await getTakerOrderInformation({..._order, indexer: api.indexer}, _queuedOrders));
  268. // }
  269. }
  270. module.exports = getTakerOrders;
  271. JAVASCRIPT
    Copied!