Files
xahau.js/packages/xrpl/src/sugar/getOrderbook.ts
Jackson Mills 2e6d2dc805 Update orderbook docs and params (#2294)
* Update orderbook docs and params
2023-05-09 08:29:53 -07:00

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