mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-04 21:15:47 +00:00
153 lines
4.9 KiB
TypeScript
153 lines
4.9 KiB
TypeScript
/* eslint-disable max-lines-per-function -- Needs to process orderbooks. */
|
|
import BigNumber from 'bignumber.js'
|
|
import flatMap from 'lodash/flatMap'
|
|
|
|
import type { Client } from '../client'
|
|
import { ValidationError } from '../errors'
|
|
import { LedgerIndex } from '../models/common'
|
|
import { OfferFlags } from '../models/ledger/Offer'
|
|
import {
|
|
BookOffer,
|
|
BookOffersRequest,
|
|
TakerAmount,
|
|
} from '../models/methods/bookOffers'
|
|
|
|
const DEFAULT_LIMIT = 20
|
|
|
|
function sortOffers(offers: BookOffer[]): BookOffer[] {
|
|
return offers.sort((offerA, offerB) => {
|
|
const qualityA = offerA.quality ?? 0
|
|
const qualityB = offerB.quality ?? 0
|
|
|
|
return new BigNumber(qualityA).comparedTo(qualityB)
|
|
})
|
|
}
|
|
|
|
const getOrderbookOptionsSet = new Set([
|
|
'limit',
|
|
'ledger_index',
|
|
'ledger_hash',
|
|
'taker',
|
|
])
|
|
|
|
/**
|
|
* Fetch orderbook (buy/sell orders) between two currency pairs. This checks both sides of the orderbook
|
|
* by making two `order_book` requests (with the second reversing takerPays and takerGets). Returned offers are
|
|
* not normalized in this function, so either currency could be takerGets or takerPays.
|
|
*
|
|
* @param this - Client.
|
|
* @param currency1 - Specification of one currency involved. (With a currency code and optionally an issuer)
|
|
* @param currency2 - Specification of a second currency involved. (With a currency code and optionally an issuer)
|
|
* @param options - Options allowing the client to specify ledger_index,
|
|
* ledger_hash, filter by taker, and/or limit number of orders.
|
|
* @param options.ledger_index - Retrieve the orderbook at a given ledger_index.
|
|
* @param options.ledger_hash - Retrieve the orderbook at the ledger with a
|
|
* given ledger_hash.
|
|
* @param options.taker - Filter orders by taker.
|
|
* @param options.limit - The limit passed into each book_offers request.
|
|
* Can return more than this due to two calls being made. Defaults to 20.
|
|
* @returns An object containing buy and sell objects.
|
|
*/
|
|
// eslint-disable-next-line max-params, complexity -- Once bound to Client, getOrderbook only has 3 parameters.
|
|
async function getOrderbook(
|
|
this: Client,
|
|
currency1: TakerAmount,
|
|
currency2: TakerAmount,
|
|
options: {
|
|
limit?: number
|
|
ledger_index?: LedgerIndex
|
|
ledger_hash?: string | null
|
|
taker?: string | null
|
|
} = {},
|
|
): Promise<{
|
|
buy: BookOffer[]
|
|
sell: BookOffer[]
|
|
}> {
|
|
Object.keys(options).forEach((key) => {
|
|
if (!getOrderbookOptionsSet.has(key)) {
|
|
throw new ValidationError(`Unexpected option: ${key}`, options)
|
|
}
|
|
})
|
|
|
|
if (options.limit && typeof options.limit !== 'number') {
|
|
throw new ValidationError('limit must be a number', options.limit)
|
|
}
|
|
|
|
if (
|
|
options.ledger_index &&
|
|
!(
|
|
typeof options.ledger_index === 'number' ||
|
|
(typeof options.ledger_index === 'string' &&
|
|
['validated', 'closed', 'current'].includes(options.ledger_index))
|
|
)
|
|
) {
|
|
throw new ValidationError(
|
|
'ledger_index must be a number or a string of "validated", "closed", or "current"',
|
|
options.ledger_index,
|
|
)
|
|
}
|
|
|
|
if (
|
|
options.ledger_hash !== undefined &&
|
|
options.ledger_hash !== null &&
|
|
typeof options.ledger_hash !== 'string'
|
|
) {
|
|
throw new ValidationError(
|
|
'ledger_hash must be a string',
|
|
options.ledger_hash,
|
|
)
|
|
}
|
|
|
|
if (options.taker !== undefined && typeof options.taker !== 'string') {
|
|
throw new ValidationError('taker must be a string', options.taker)
|
|
}
|
|
|
|
const request: BookOffersRequest = {
|
|
command: 'book_offers',
|
|
taker_pays: currency1,
|
|
taker_gets: currency2,
|
|
ledger_index: options.ledger_index ?? 'validated',
|
|
ledger_hash: options.ledger_hash === null ? undefined : options.ledger_hash,
|
|
limit: options.limit ?? DEFAULT_LIMIT,
|
|
taker: options.taker ? options.taker : undefined,
|
|
}
|
|
// 2. Make Request
|
|
const directOfferResults = await this.requestAll(request)
|
|
request.taker_gets = currency1
|
|
request.taker_pays = currency2
|
|
const reverseOfferResults = await this.requestAll(request)
|
|
// 3. Return Formatted Response
|
|
const directOffers = flatMap(
|
|
directOfferResults,
|
|
(directOfferResult) => directOfferResult.result.offers,
|
|
)
|
|
const reverseOffers = flatMap(
|
|
reverseOfferResults,
|
|
(reverseOfferResult) => reverseOfferResult.result.offers,
|
|
)
|
|
|
|
const orders = [...directOffers, ...reverseOffers]
|
|
// separate out the buy and sell orders
|
|
const buy: BookOffer[] = []
|
|
const sell: BookOffer[] = []
|
|
orders.forEach((order) => {
|
|
// eslint-disable-next-line no-bitwise -- necessary for flags check
|
|
if ((order.Flags & OfferFlags.lsfSell) === 0) {
|
|
buy.push(order)
|
|
} else {
|
|
sell.push(order)
|
|
}
|
|
})
|
|
/*
|
|
* Sort the orders
|
|
* for both buys and sells, lowest quality is closest to mid-market
|
|
* we sort the orders so that earlier orders are closer to mid-market
|
|
*/
|
|
return {
|
|
buy: sortOffers(buy).slice(0, options.limit),
|
|
sell: sortOffers(sell).slice(0, options.limit),
|
|
}
|
|
}
|
|
|
|
export default getOrderbook
|