diff options
Diffstat (limited to 'packages/asset-buyer/src')
-rw-r--r-- | packages/asset-buyer/src/asset_buyer.ts | 47 | ||||
-rw-r--r-- | packages/asset-buyer/src/index.ts | 3 | ||||
-rw-r--r-- | packages/asset-buyer/src/types.ts | 19 | ||||
-rw-r--r-- | packages/asset-buyer/src/utils/buy_quote_calculator.ts | 12 | ||||
-rw-r--r-- | packages/asset-buyer/src/utils/calculate_liquidity.ts | 34 | ||||
-rw-r--r-- | packages/asset-buyer/src/utils/order_provider_response_processor.ts | 7 | ||||
-rw-r--r-- | packages/asset-buyer/src/utils/order_utils.ts | 28 |
7 files changed, 122 insertions, 28 deletions
diff --git a/packages/asset-buyer/src/asset_buyer.ts b/packages/asset-buyer/src/asset_buyer.ts index 934410c55..b22b1a122 100644 --- a/packages/asset-buyer/src/asset_buyer.ts +++ b/packages/asset-buyer/src/asset_buyer.ts @@ -16,14 +16,16 @@ import { BuyQuote, BuyQuoteExecutionOpts, BuyQuoteRequestOpts, + LiquidityForAssetData, + LiquidityRequestOpts, OrderProvider, - OrderProviderResponse, OrdersAndFillableAmounts, } from './types'; import { assert } from './utils/assert'; import { assetDataUtils } from './utils/asset_data_utils'; import { buyQuoteCalculator } from './utils/buy_quote_calculator'; +import { calculateLiquidity } from './utils/calculate_liquidity'; import { orderProviderResponseProcessor } from './utils/order_provider_response_processor'; interface OrdersEntry { @@ -138,10 +140,10 @@ export class AssetBuyer { // get the relevant orders for the makerAsset and fees // if the requested assetData is ZRX, don't get the fee info const [ordersAndFillableAmounts, feeOrdersAndFillableAmounts] = await Promise.all([ - this._getOrdersAndFillableAmountsAsync(assetData, shouldForceOrderRefresh), + this.getOrdersAndFillableAmountsAsync(assetData, shouldForceOrderRefresh), isMakerAssetZrxToken ? Promise.resolve(constants.EMPTY_ORDERS_AND_FILLABLE_AMOUNTS) - : this._getOrdersAndFillableAmountsAsync(zrxTokenAssetData, shouldForceOrderRefresh), + : this.getOrdersAndFillableAmountsAsync(zrxTokenAssetData, shouldForceOrderRefresh), shouldForceOrderRefresh, ]); if (ordersAndFillableAmounts.orders.length === 0) { @@ -178,6 +180,41 @@ export class AssetBuyer { return buyQuote; } /** + * Returns information about available liquidity for an asset + * Does not factor in slippage or fees + * @param assetData The assetData of the desired asset to buy (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md). + * @param options Options for the request. See type definition for more information. + * + * @return An object that conforms to LiquidityForAssetData that satisfies the request. See type definition for more information. + */ + public async getLiquidityForAssetDataAsync( + assetData: string, + options: Partial<LiquidityRequestOpts> = {}, + ): Promise<LiquidityForAssetData> { + const shouldForceOrderRefresh = + options.shouldForceOrderRefresh !== undefined ? options.shouldForceOrderRefresh : false; + assert.isString('assetData', assetData); + assetDataUtils.decodeAssetDataOrThrow(assetData); + assert.isBoolean('options.shouldForceOrderRefresh', shouldForceOrderRefresh); + + const assetPairs = await this.orderProvider.getAvailableMakerAssetDatasAsync(assetData); + const etherTokenAssetData = this._getEtherTokenAssetDataOrThrow(); + if (!assetPairs.includes(etherTokenAssetData)) { + return { + tokensAvailableInBaseUnits: new BigNumber(0), + ethValueAvailableInWei: new BigNumber(0), + }; + } + + const ordersAndFillableAmounts = await this.getOrdersAndFillableAmountsAsync( + assetData, + shouldForceOrderRefresh, + ); + + return calculateLiquidity(ordersAndFillableAmounts); + } + + /** * Given a BuyQuote and desired rate, attempt to execute the buy. * @param buyQuote An object that conforms to BuyQuote. See type definition for more information. * @param options Options for the execution of the BuyQuote. See type definition for more information. @@ -260,8 +297,10 @@ export class AssetBuyer { } /** * Grab orders from the map, if there is a miss or it is time to refresh, fetch and process the orders + * @param assetData The assetData of the desired asset to buy (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md). + * @param shouldForceOrderRefresh If set to true, new orders and state will be fetched instead of waiting for the next orderRefreshIntervalMs. */ - private async _getOrdersAndFillableAmountsAsync( + public async getOrdersAndFillableAmountsAsync( assetData: string, shouldForceOrderRefresh: boolean, ): Promise<OrdersAndFillableAmounts> { diff --git a/packages/asset-buyer/src/index.ts b/packages/asset-buyer/src/index.ts index a42d7e272..f69cfad69 100644 --- a/packages/asset-buyer/src/index.ts +++ b/packages/asset-buyer/src/index.ts @@ -19,6 +19,9 @@ export { BuyQuoteExecutionOpts, BuyQuoteInfo, BuyQuoteRequestOpts, + LiquidityForAssetData, + LiquidityRequestOpts, + OrdersAndFillableAmounts, OrderProvider, OrderProviderRequest, OrderProviderResponse, diff --git a/packages/asset-buyer/src/types.ts b/packages/asset-buyer/src/types.ts index d5d6be695..46a2338ce 100644 --- a/packages/asset-buyer/src/types.ts +++ b/packages/asset-buyer/src/types.ts @@ -75,6 +75,13 @@ export interface BuyQuoteRequestOpts { slippagePercentage: number; } +/* + * Options for checking liquidity + * + * shouldForceOrderRefresh: If set to true, new orders and state will be fetched instead of waiting for the next orderRefreshIntervalMs. Defaults to false. + */ +export type LiquidityRequestOpts = Pick<BuyQuoteRequestOpts, 'shouldForceOrderRefresh'>; + /** * ethAmount: The desired amount of eth to spend. Defaults to buyQuote.worstCaseQuoteInfo.totalEthAmount. * takerAddress: The address to perform the buy. Defaults to the first available address from the provider. @@ -117,7 +124,19 @@ export enum AssetBuyerError { TransactionValueTooLow = 'TRANSACTION_VALUE_TOO_LOW', } +/** + * orders: An array of signed orders + * remainingFillableMakerAssetAmounts: A list of fillable amounts for the signed orders. The index of an item in the array associates the amount with the corresponding order. + */ export interface OrdersAndFillableAmounts { orders: SignedOrder[]; remainingFillableMakerAssetAmounts: BigNumber[]; } + +/** + * Represents available liquidity for a given assetData + */ +export interface LiquidityForAssetData { + tokensAvailableInBaseUnits: BigNumber; + ethValueAvailableInWei: BigNumber; +} diff --git a/packages/asset-buyer/src/utils/buy_quote_calculator.ts b/packages/asset-buyer/src/utils/buy_quote_calculator.ts index fcded6ab1..125841094 100644 --- a/packages/asset-buyer/src/utils/buy_quote_calculator.ts +++ b/packages/asset-buyer/src/utils/buy_quote_calculator.ts @@ -22,7 +22,7 @@ export const buyQuoteCalculator = { const remainingFillableMakerAssetAmounts = ordersAndFillableAmounts.remainingFillableMakerAssetAmounts; const feeOrders = feeOrdersAndFillableAmounts.orders; const remainingFillableFeeAmounts = feeOrdersAndFillableAmounts.remainingFillableMakerAssetAmounts; - const slippageBufferAmount = assetBuyAmount.mul(slippagePercentage).round(); + const slippageBufferAmount = assetBuyAmount.multipliedBy(slippagePercentage).integerValue(); // find the orders that cover the desired assetBuyAmount (with slippage) const { resultOrders, @@ -43,7 +43,9 @@ export const buyQuoteCalculator = { const multiplierNeededWithSlippage = new BigNumber(1).plus(slippagePercentage); // Given amountAvailableToFillConsideringSlippage * multiplierNeededWithSlippage = amountAbleToFill // We divide amountUnableToFill by multiplierNeededWithSlippage to determine amountAvailableToFillConsideringSlippage - const amountAvailableToFillConsideringSlippage = amountAbleToFill.div(multiplierNeededWithSlippage).floor(); + const amountAvailableToFillConsideringSlippage = amountAbleToFill + .div(multiplierNeededWithSlippage) + .integerValue(BigNumber.ROUND_FLOOR); throw new InsufficientAssetLiquidityError(amountAvailableToFillConsideringSlippage); } @@ -131,7 +133,7 @@ function calculateQuoteInfo( zrxEthAmount = findEthAmountNeededToBuyZrx(feeOrdersAndFillableAmounts, zrxAmountToBuyAsset); } // eth amount needed to buy the affiliate fee - const affiliateFeeEthAmount = assetEthAmount.mul(feePercentage).ceil(); + const affiliateFeeEthAmount = assetEthAmount.multipliedBy(feePercentage).integerValue(BigNumber.ROUND_CEIL); // eth amount needed for fees is the sum of affiliate fee and zrx fee const feeEthAmount = affiliateFeeEthAmount.plus(zrxEthAmount); // eth amount needed in total is the sum of the amount needed for the asset and the amount needed for fees @@ -168,9 +170,9 @@ function findEthAmountNeededToBuyZrx( order, makerFillAmount, ); - const extraFeeAmount = remainingFillableMakerAssetAmount.greaterThanOrEqualTo(adjustedMakerFillAmount) + const extraFeeAmount = remainingFillableMakerAssetAmount.isGreaterThanOrEqualTo(adjustedMakerFillAmount) ? constants.ZERO_AMOUNT - : adjustedMakerFillAmount.sub(makerFillAmount); + : adjustedMakerFillAmount.minus(makerFillAmount); return { totalEthAmount: totalEthAmount.plus(takerFillAmount), remainingZrxBuyAmount: BigNumber.max( diff --git a/packages/asset-buyer/src/utils/calculate_liquidity.ts b/packages/asset-buyer/src/utils/calculate_liquidity.ts new file mode 100644 index 000000000..a8d165b4b --- /dev/null +++ b/packages/asset-buyer/src/utils/calculate_liquidity.ts @@ -0,0 +1,34 @@ +import { BigNumber } from '@0x/utils'; + +import { LiquidityForAssetData, OrdersAndFillableAmounts } from '../types'; + +import { orderUtils } from './order_utils'; + +export const calculateLiquidity = (ordersAndFillableAmounts: OrdersAndFillableAmounts): LiquidityForAssetData => { + const { orders, remainingFillableMakerAssetAmounts } = ordersAndFillableAmounts; + const liquidityInBigNumbers = orders.reduce( + (acc, order, curIndex) => { + const availableMakerAssetAmount = remainingFillableMakerAssetAmounts[curIndex]; + if (availableMakerAssetAmount === undefined) { + throw new Error(`No corresponding fillableMakerAssetAmounts at index ${curIndex}`); + } + + const tokensAvailableForCurrentOrder = availableMakerAssetAmount; + const ethValueAvailableForCurrentOrder = orderUtils.getTakerFillAmount(order, availableMakerAssetAmount); + return { + tokensAvailableInBaseUnits: acc.tokensAvailableInBaseUnits.plus(tokensAvailableForCurrentOrder), + ethValueAvailableInWei: acc.ethValueAvailableInWei.plus(ethValueAvailableForCurrentOrder), + }; + }, + { + tokensAvailableInBaseUnits: new BigNumber(0), + ethValueAvailableInWei: new BigNumber(0), + }, + ); + + // Turn into regular numbers + return { + tokensAvailableInBaseUnits: liquidityInBigNumbers.tokensAvailableInBaseUnits, + ethValueAvailableInWei: liquidityInBigNumbers.ethValueAvailableInWei, + }; +}; diff --git a/packages/asset-buyer/src/utils/order_provider_response_processor.ts b/packages/asset-buyer/src/utils/order_provider_response_processor.ts index 4244d196c..f08cd6150 100644 --- a/packages/asset-buyer/src/utils/order_provider_response_processor.ts +++ b/packages/asset-buyer/src/utils/order_provider_response_processor.ts @@ -109,11 +109,8 @@ function getValidOrdersWithRemainingFillableMakerAssetAmountsFromOnChain( return accOrders; } // if the order IS fillable, add the order and calculate the remaining fillable amount - const transferrableAssetAmount = BigNumber.min([traderInfo.makerAllowance, traderInfo.makerBalance]); - const transferrableFeeAssetAmount = BigNumber.min([ - traderInfo.makerZrxAllowance, - traderInfo.makerZrxBalance, - ]); + const transferrableAssetAmount = BigNumber.min(traderInfo.makerAllowance, traderInfo.makerBalance); + const transferrableFeeAssetAmount = BigNumber.min(traderInfo.makerZrxAllowance, traderInfo.makerZrxBalance); const remainingTakerAssetAmount = order.takerAssetAmount.minus(orderInfo.orderTakerAssetFilledAmount); const remainingMakerAssetAmount = orderUtils.getRemainingMakerAmount(order, remainingTakerAssetAmount); const remainingFillableCalculator = new RemainingFillableCalculator( diff --git a/packages/asset-buyer/src/utils/order_utils.ts b/packages/asset-buyer/src/utils/order_utils.ts index 1cc2cf95f..3ea3cafd3 100644 --- a/packages/asset-buyer/src/utils/order_utils.ts +++ b/packages/asset-buyer/src/utils/order_utils.ts @@ -9,8 +9,8 @@ export const orderUtils = { }, willOrderExpire(order: SignedOrder, secondsFromNow: number): boolean { const millisecondsInSecond = 1000; - const currentUnixTimestampSec = new BigNumber(Date.now() / millisecondsInSecond).round(); - return order.expirationTimeSeconds.lessThan(currentUnixTimestampSec.plus(secondsFromNow)); + const currentUnixTimestampSec = new BigNumber(Date.now() / millisecondsInSecond).integerValue(); + return order.expirationTimeSeconds.isLessThan(currentUnixTimestampSec.plus(secondsFromNow)); }, isOpenOrder(order: SignedOrder): boolean { return order.takerAddress === constants.NULL_ADDRESS; @@ -20,43 +20,43 @@ export const orderUtils = { const remainingMakerAmount = remainingTakerAmount .times(order.makerAssetAmount) .div(order.takerAssetAmount) - .floor(); + .integerValue(BigNumber.ROUND_FLOOR); return remainingMakerAmount; }, // given a desired amount of makerAsset, calculate how much takerAsset is required to fill that amount getTakerFillAmount(order: SignedOrder, makerFillAmount: BigNumber): BigNumber { // Round up because exchange rate favors Maker const takerFillAmount = makerFillAmount - .mul(order.takerAssetAmount) + .multipliedBy(order.takerAssetAmount) .div(order.makerAssetAmount) - .ceil(); + .integerValue(BigNumber.ROUND_CEIL); return takerFillAmount; }, // given a desired amount of takerAsset to fill, calculate how much fee is required by the taker to fill that amount getTakerFeeAmount(order: SignedOrder, takerFillAmount: BigNumber): BigNumber { // Round down because Taker fee rate favors Taker const takerFeeAmount = takerFillAmount - .mul(order.takerFee) + .multipliedBy(order.takerFee) .div(order.takerAssetAmount) - .floor(); + .integerValue(BigNumber.ROUND_FLOOR); return takerFeeAmount; }, // given a desired amount of takerAsset to fill, calculate how much makerAsset will be filled getMakerFillAmount(order: SignedOrder, takerFillAmount: BigNumber): BigNumber { // Round down because exchange rate favors Maker const makerFillAmount = takerFillAmount - .mul(order.makerAssetAmount) + .multipliedBy(order.makerAssetAmount) .div(order.takerAssetAmount) - .floor(); + .integerValue(BigNumber.ROUND_FLOOR); return makerFillAmount; }, // given a desired amount of makerAsset, calculate how much fee is required by the maker to fill that amount getMakerFeeAmount(order: SignedOrder, makerFillAmount: BigNumber): BigNumber { // Round down because Maker fee rate favors Maker const makerFeeAmount = makerFillAmount - .mul(order.makerFee) + .multipliedBy(order.makerFee) .div(order.makerAssetAmount) - .floor(); + .integerValue(BigNumber.ROUND_FLOOR); return makerFeeAmount; }, // given a desired amount of ZRX from a fee order, calculate how much takerAsset is required to fill that amount @@ -64,9 +64,9 @@ export const orderUtils = { getTakerFillAmountForFeeOrder(order: SignedOrder, makerFillAmount: BigNumber): [BigNumber, BigNumber] { // For each unit of TakerAsset we buy (MakerAsset - TakerFee) const adjustedTakerFillAmount = makerFillAmount - .mul(order.takerAssetAmount) - .div(order.makerAssetAmount.sub(order.takerFee)) - .ceil(); + .multipliedBy(order.takerAssetAmount) + .div(order.makerAssetAmount.minus(order.takerFee)) + .integerValue(BigNumber.ROUND_CEIL); // The amount that we buy will be greater than makerFillAmount, since we buy some amount for fees. const adjustedMakerFillAmount = orderUtils.getMakerFillAmount(order, adjustedTakerFillAmount); return [adjustedTakerFillAmount, adjustedMakerFillAmount]; |