diff options
Diffstat (limited to 'packages/asset-buyer/src')
-rw-r--r-- | packages/asset-buyer/src/asset_buyer.ts | 365 | ||||
-rw-r--r-- | packages/asset-buyer/src/constants.ts | 40 | ||||
-rw-r--r-- | packages/asset-buyer/src/errors.ts | 22 | ||||
-rw-r--r-- | packages/asset-buyer/src/globals.d.ts | 6 | ||||
-rw-r--r-- | packages/asset-buyer/src/index.ts | 29 | ||||
-rw-r--r-- | packages/asset-buyer/src/order_providers/basic_order_provider.ts | 41 | ||||
-rw-r--r-- | packages/asset-buyer/src/order_providers/standard_relayer_api_order_provider.ts | 111 | ||||
-rw-r--r-- | packages/asset-buyer/src/types.ts | 142 | ||||
-rw-r--r-- | packages/asset-buyer/src/utils/assert.ts | 39 | ||||
-rw-r--r-- | packages/asset-buyer/src/utils/asset_data_utils.ts | 12 | ||||
-rw-r--r-- | packages/asset-buyer/src/utils/buy_quote_calculator.ts | 221 | ||||
-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 | 171 | ||||
-rw-r--r-- | packages/asset-buyer/src/utils/order_utils.ts | 74 |
14 files changed, 0 insertions, 1307 deletions
diff --git a/packages/asset-buyer/src/asset_buyer.ts b/packages/asset-buyer/src/asset_buyer.ts deleted file mode 100644 index ad4b3bb60..000000000 --- a/packages/asset-buyer/src/asset_buyer.ts +++ /dev/null @@ -1,365 +0,0 @@ -import { ContractWrappers, ContractWrappersError, ForwarderWrapperError } from '@0x/contract-wrappers'; -import { schemas } from '@0x/json-schemas'; -import { SignedOrder } from '@0x/order-utils'; -import { ObjectMap } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import { Provider } from 'ethereum-types'; -import * as _ from 'lodash'; - -import { constants } from './constants'; -import { BasicOrderProvider } from './order_providers/basic_order_provider'; -import { StandardRelayerAPIOrderProvider } from './order_providers/standard_relayer_api_order_provider'; -import { - AssetBuyerError, - AssetBuyerOpts, - BuyQuote, - BuyQuoteExecutionOpts, - BuyQuoteRequestOpts, - LiquidityForAssetData, - LiquidityRequestOpts, - OrderProvider, - 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 { - ordersAndFillableAmounts: OrdersAndFillableAmounts; - lastRefreshTime: number; -} - -export class AssetBuyer { - public readonly provider: Provider; - public readonly orderProvider: OrderProvider; - public readonly networkId: number; - public readonly orderRefreshIntervalMs: number; - public readonly expiryBufferSeconds: number; - private readonly _contractWrappers: ContractWrappers; - // cache of orders along with the time last updated keyed by assetData - private readonly _ordersEntryMap: ObjectMap<OrdersEntry> = {}; - /** - * Instantiates a new AssetBuyer instance given existing liquidity in the form of orders and feeOrders. - * @param provider The Provider instance you would like to use for interacting with the Ethereum network. - * @param orders A non-empty array of objects that conform to SignedOrder. All orders must have the same makerAssetData and takerAssetData (WETH). - * @param feeOrders A array of objects that conform to SignedOrder. All orders must have the same makerAssetData (ZRX) and takerAssetData (WETH). Defaults to an empty array. - * @param options Initialization options for the AssetBuyer. See type definition for details. - * - * @return An instance of AssetBuyer - */ - public static getAssetBuyerForProvidedOrders( - provider: Provider, - orders: SignedOrder[], - options: Partial<AssetBuyerOpts> = {}, - ): AssetBuyer { - assert.isWeb3Provider('provider', provider); - assert.doesConformToSchema('orders', orders, schemas.signedOrdersSchema); - assert.assert(orders.length !== 0, `Expected orders to contain at least one order`); - const orderProvider = new BasicOrderProvider(orders); - const assetBuyer = new AssetBuyer(provider, orderProvider, options); - return assetBuyer; - } - /** - * Instantiates a new AssetBuyer instance given a [Standard Relayer API](https://github.com/0xProject/standard-relayer-api) endpoint - * @param provider The Provider instance you would like to use for interacting with the Ethereum network. - * @param sraApiUrl The standard relayer API base HTTP url you would like to source orders from. - * @param options Initialization options for the AssetBuyer. See type definition for details. - * - * @return An instance of AssetBuyer - */ - public static getAssetBuyerForStandardRelayerAPIUrl( - provider: Provider, - sraApiUrl: string, - options: Partial<AssetBuyerOpts> = {}, - ): AssetBuyer { - assert.isWeb3Provider('provider', provider); - assert.isWebUri('sraApiUrl', sraApiUrl); - const networkId = options.networkId || constants.DEFAULT_ASSET_BUYER_OPTS.networkId; - const orderProvider = new StandardRelayerAPIOrderProvider(sraApiUrl, networkId); - const assetBuyer = new AssetBuyer(provider, orderProvider, options); - return assetBuyer; - } - /** - * Instantiates a new AssetBuyer instance - * @param provider The Provider instance you would like to use for interacting with the Ethereum network. - * @param orderProvider An object that conforms to OrderProvider, see type for definition. - * @param options Initialization options for the AssetBuyer. See type definition for details. - * - * @return An instance of AssetBuyer - */ - constructor(provider: Provider, orderProvider: OrderProvider, options: Partial<AssetBuyerOpts> = {}) { - const { networkId, orderRefreshIntervalMs, expiryBufferSeconds } = _.merge( - {}, - constants.DEFAULT_ASSET_BUYER_OPTS, - options, - ); - assert.isWeb3Provider('provider', provider); - assert.isValidOrderProvider('orderProvider', orderProvider); - assert.isNumber('networkId', networkId); - assert.isNumber('orderRefreshIntervalMs', orderRefreshIntervalMs); - assert.isNumber('expiryBufferSeconds', expiryBufferSeconds); - this.provider = provider; - this.orderProvider = orderProvider; - this.networkId = networkId; - this.orderRefreshIntervalMs = orderRefreshIntervalMs; - this.expiryBufferSeconds = expiryBufferSeconds; - this._contractWrappers = new ContractWrappers(this.provider, { - networkId, - }); - } - /** - * Get a `BuyQuote` containing all information relevant to fulfilling a buy given a desired assetData. - * You can then pass the `BuyQuote` to `executeBuyQuoteAsync` to execute the buy. - * @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 assetBuyAmount The amount of asset to buy. - * @param options Options for the request. See type definition for more information. - * - * @return An object that conforms to BuyQuote that satisfies the request. See type definition for more information. - */ - public async getBuyQuoteAsync( - assetData: string, - assetBuyAmount: BigNumber, - options: Partial<BuyQuoteRequestOpts> = {}, - ): Promise<BuyQuote> { - const { feePercentage, shouldForceOrderRefresh, slippagePercentage } = _.merge( - {}, - constants.DEFAULT_BUY_QUOTE_REQUEST_OPTS, - options, - ); - assert.isString('assetData', assetData); - assert.isBigNumber('assetBuyAmount', assetBuyAmount); - assert.isValidPercentage('feePercentage', feePercentage); - assert.isBoolean('shouldForceOrderRefresh', shouldForceOrderRefresh); - assert.isNumber('slippagePercentage', slippagePercentage); - const zrxTokenAssetData = this._getZrxTokenAssetDataOrThrow(); - const isMakerAssetZrxToken = assetData === zrxTokenAssetData; - // 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), - isMakerAssetZrxToken - ? Promise.resolve(constants.EMPTY_ORDERS_AND_FILLABLE_AMOUNTS) - : this.getOrdersAndFillableAmountsAsync(zrxTokenAssetData, shouldForceOrderRefresh), - shouldForceOrderRefresh, - ]); - if (ordersAndFillableAmounts.orders.length === 0) { - throw new Error(`${AssetBuyerError.AssetUnavailable}: For assetData ${assetData}`); - } - const buyQuote = buyQuoteCalculator.calculate( - ordersAndFillableAmounts, - feeOrdersAndFillableAmounts, - assetBuyAmount, - feePercentage, - slippagePercentage, - isMakerAssetZrxToken, - ); - return buyQuote; - } - /** - * Get a `BuyQuote` containing all information relevant to fulfilling a buy given a desired ERC20 token address. - * You can then pass the `BuyQuote` to `executeBuyQuoteAsync` to execute the buy. - * @param tokenAddress The ERC20 token address. - * @param assetBuyAmount The amount of asset to buy. - * @param options Options for the request. See type definition for more information. - * - * @return An object that conforms to BuyQuote that satisfies the request. See type definition for more information. - */ - public async getBuyQuoteForERC20TokenAddressAsync( - tokenAddress: string, - assetBuyAmount: BigNumber, - options: Partial<BuyQuoteRequestOpts> = {}, - ): Promise<BuyQuote> { - assert.isETHAddressHex('tokenAddress', tokenAddress); - assert.isBigNumber('assetBuyAmount', assetBuyAmount); - const assetData = assetDataUtils.encodeERC20AssetData(tokenAddress); - const buyQuote = this.getBuyQuoteAsync(assetData, assetBuyAmount, options); - 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; - 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. - * - * @return A promise of the txHash. - */ - public async executeBuyQuoteAsync( - buyQuote: BuyQuote, - options: Partial<BuyQuoteExecutionOpts> = {}, - ): Promise<string> { - const { ethAmount, takerAddress, feeRecipient, gasLimit, gasPrice } = _.merge( - {}, - constants.DEFAULT_BUY_QUOTE_EXECUTION_OPTS, - options, - ); - assert.isValidBuyQuote('buyQuote', buyQuote); - if (!_.isUndefined(ethAmount)) { - assert.isBigNumber('ethAmount', ethAmount); - } - if (!_.isUndefined(takerAddress)) { - assert.isETHAddressHex('takerAddress', takerAddress); - } - assert.isETHAddressHex('feeRecipient', feeRecipient); - if (!_.isUndefined(gasLimit)) { - assert.isNumber('gasLimit', gasLimit); - } - if (!_.isUndefined(gasPrice)) { - assert.isBigNumber('gasPrice', gasPrice); - } - const { orders, feeOrders, feePercentage, assetBuyAmount, worstCaseQuoteInfo } = buyQuote; - // if no takerAddress is provided, try to get one from the provider - let finalTakerAddress; - if (!_.isUndefined(takerAddress)) { - finalTakerAddress = takerAddress; - } else { - const web3Wrapper = new Web3Wrapper(this.provider); - const availableAddresses = await web3Wrapper.getAvailableAddressesAsync(); - const firstAvailableAddress = _.head(availableAddresses); - if (!_.isUndefined(firstAvailableAddress)) { - finalTakerAddress = firstAvailableAddress; - } else { - throw new Error(AssetBuyerError.NoAddressAvailable); - } - } - try { - // if no ethAmount is provided, default to the worst ethAmount from buyQuote - const txHash = await this._contractWrappers.forwarder.marketBuyOrdersWithEthAsync( - orders, - assetBuyAmount, - finalTakerAddress, - ethAmount || worstCaseQuoteInfo.totalEthAmount, - feeOrders, - feePercentage, - feeRecipient, - { - gasLimit, - gasPrice, - shouldValidate: true, - }, - ); - return txHash; - } catch (err) { - if (_.includes(err.message, ContractWrappersError.SignatureRequestDenied)) { - throw new Error(AssetBuyerError.SignatureRequestDenied); - } else if (_.includes(err.message, ForwarderWrapperError.CompleteFillFailed)) { - throw new Error(AssetBuyerError.TransactionValueTooLow); - } else { - throw err; - } - } - } - /** - * Get the asset data of all assets that are purchaseable with ether token (wETH) in the order provider passed in at init. - * - * @return An array of asset data strings that can be purchased using wETH. - */ - public async getAvailableAssetDatasAsync(): Promise<string[]> { - const etherTokenAssetData = this._getEtherTokenAssetDataOrThrow(); - return this.orderProvider.getAvailableMakerAssetDatasAsync(etherTokenAssetData); - } - /** - * 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. - */ - public async getOrdersAndFillableAmountsAsync( - assetData: string, - shouldForceOrderRefresh: boolean, - ): Promise<OrdersAndFillableAmounts> { - // try to get ordersEntry from the map - const ordersEntryIfExists = this._ordersEntryMap[assetData]; - // we should refresh if: - // we do not have any orders OR - // we are forced to OR - // we have some last refresh time AND that time was sufficiently long ago - const shouldRefresh = - _.isUndefined(ordersEntryIfExists) || - shouldForceOrderRefresh || - // tslint:disable:restrict-plus-operands - ordersEntryIfExists.lastRefreshTime + this.orderRefreshIntervalMs < Date.now(); - if (!shouldRefresh) { - const result = ordersEntryIfExists.ordersAndFillableAmounts; - return result; - } - const etherTokenAssetData = this._getEtherTokenAssetDataOrThrow(); - const zrxTokenAssetData = this._getZrxTokenAssetDataOrThrow(); - // construct orderProvider request - const orderProviderRequest = { - makerAssetData: assetData, - takerAssetData: etherTokenAssetData, - networkId: this.networkId, - }; - const request = orderProviderRequest; - // get provider response - const response = await this.orderProvider.getOrdersAsync(request); - // since the order provider is an injected dependency, validate that it respects the API - // ie. it should only return maker/taker assetDatas that are specified - orderProviderResponseProcessor.throwIfInvalidResponse(response, request); - // process the responses into one object - const isMakerAssetZrxToken = assetData === zrxTokenAssetData; - const ordersAndFillableAmounts = await orderProviderResponseProcessor.processAsync( - response, - isMakerAssetZrxToken, - this.expiryBufferSeconds, - this._contractWrappers.orderValidator, - ); - const lastRefreshTime = Date.now(); - const updatedOrdersEntry = { - ordersAndFillableAmounts, - lastRefreshTime, - }; - this._ordersEntryMap[assetData] = updatedOrdersEntry; - return ordersAndFillableAmounts; - } - /** - * Get the assetData that represents the WETH token. - * Will throw if WETH does not exist for the current network. - */ - private _getEtherTokenAssetDataOrThrow(): string { - return assetDataUtils.getEtherTokenAssetData(this._contractWrappers); - } - /** - * Get the assetData that represents the ZRX token. - * Will throw if ZRX does not exist for the current network. - */ - private _getZrxTokenAssetDataOrThrow(): string { - return this._contractWrappers.exchange.getZRXAssetData(); - } -} diff --git a/packages/asset-buyer/src/constants.ts b/packages/asset-buyer/src/constants.ts deleted file mode 100644 index c0e1bf27d..000000000 --- a/packages/asset-buyer/src/constants.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; - -import { AssetBuyerOpts, BuyQuoteExecutionOpts, BuyQuoteRequestOpts, OrdersAndFillableAmounts } from './types'; - -const NULL_ADDRESS = '0x0000000000000000000000000000000000000000'; -const MAINNET_NETWORK_ID = 1; - -const DEFAULT_ASSET_BUYER_OPTS: AssetBuyerOpts = { - networkId: MAINNET_NETWORK_ID, - orderRefreshIntervalMs: 10000, // 10 seconds - expiryBufferSeconds: 120, // 2 minutes -}; - -const DEFAULT_BUY_QUOTE_REQUEST_OPTS: BuyQuoteRequestOpts = { - feePercentage: 0, - shouldForceOrderRefresh: false, - slippagePercentage: 0.2, // 20% slippage protection -}; - -// Other default values are dynamically determined -const DEFAULT_BUY_QUOTE_EXECUTION_OPTS: BuyQuoteExecutionOpts = { - feeRecipient: NULL_ADDRESS, -}; - -const EMPTY_ORDERS_AND_FILLABLE_AMOUNTS: OrdersAndFillableAmounts = { - orders: [] as SignedOrder[], - remainingFillableMakerAssetAmounts: [] as BigNumber[], -}; - -export const constants = { - ZERO_AMOUNT: new BigNumber(0), - NULL_ADDRESS, - MAINNET_NETWORK_ID, - ETHER_TOKEN_DECIMALS: 18, - DEFAULT_ASSET_BUYER_OPTS, - DEFAULT_BUY_QUOTE_EXECUTION_OPTS, - DEFAULT_BUY_QUOTE_REQUEST_OPTS, - EMPTY_ORDERS_AND_FILLABLE_AMOUNTS, -}; diff --git a/packages/asset-buyer/src/errors.ts b/packages/asset-buyer/src/errors.ts deleted file mode 100644 index ec5fe548c..000000000 --- a/packages/asset-buyer/src/errors.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { BigNumber } from '@0x/utils'; - -import { AssetBuyerError } from './types'; - -/** - * Error class representing insufficient asset liquidity - */ -export class InsufficientAssetLiquidityError extends Error { - /** - * The amount availabe to fill (in base units) factoring in slippage. - */ - public amountAvailableToFill: BigNumber; - /** - * @param amountAvailableToFill The amount availabe to fill (in base units) factoring in slippage - */ - constructor(amountAvailableToFill: BigNumber) { - super(AssetBuyerError.InsufficientAssetLiquidity); - this.amountAvailableToFill = amountAvailableToFill; - // Setting prototype so instanceof works. See https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work - Object.setPrototypeOf(this, InsufficientAssetLiquidityError.prototype); - } -} diff --git a/packages/asset-buyer/src/globals.d.ts b/packages/asset-buyer/src/globals.d.ts deleted file mode 100644 index 94e63a32d..000000000 --- a/packages/asset-buyer/src/globals.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -declare module '*.json' { - const json: any; - /* tslint:disable */ - export default json; - /* tslint:enable */ -} diff --git a/packages/asset-buyer/src/index.ts b/packages/asset-buyer/src/index.ts deleted file mode 100644 index f69cfad69..000000000 --- a/packages/asset-buyer/src/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -export { - JSONRPCRequestPayload, - JSONRPCResponsePayload, - JSONRPCResponseError, - JSONRPCErrorCallback, - Provider, -} from 'ethereum-types'; -export { SignedOrder } from '@0x/types'; -export { BigNumber } from '@0x/utils'; - -export { AssetBuyer } from './asset_buyer'; -export { InsufficientAssetLiquidityError } from './errors'; -export { BasicOrderProvider } from './order_providers/basic_order_provider'; -export { StandardRelayerAPIOrderProvider } from './order_providers/standard_relayer_api_order_provider'; -export { - AssetBuyerError, - AssetBuyerOpts, - BuyQuote, - BuyQuoteExecutionOpts, - BuyQuoteInfo, - BuyQuoteRequestOpts, - LiquidityForAssetData, - LiquidityRequestOpts, - OrdersAndFillableAmounts, - OrderProvider, - OrderProviderRequest, - OrderProviderResponse, - SignedOrderWithRemainingFillableMakerAssetAmount, -} from './types'; diff --git a/packages/asset-buyer/src/order_providers/basic_order_provider.ts b/packages/asset-buyer/src/order_providers/basic_order_provider.ts deleted file mode 100644 index 76685f27a..000000000 --- a/packages/asset-buyer/src/order_providers/basic_order_provider.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { schemas } from '@0x/json-schemas'; -import { SignedOrder } from '@0x/types'; -import * as _ from 'lodash'; - -import { OrderProvider, OrderProviderRequest, OrderProviderResponse } from '../types'; -import { assert } from '../utils/assert'; - -export class BasicOrderProvider implements OrderProvider { - public readonly orders: SignedOrder[]; - /** - * Instantiates a new BasicOrderProvider instance - * @param orders An array of objects that conform to SignedOrder to fetch from. - * @return An instance of BasicOrderProvider - */ - constructor(orders: SignedOrder[]) { - assert.doesConformToSchema('orders', orders, schemas.signedOrdersSchema); - this.orders = orders; - } - /** - * Given an object that conforms to OrderFetcherRequest, return the corresponding OrderProviderResponse that satisfies the request. - * @param orderProviderRequest An instance of OrderFetcherRequest. See type for more information. - * @return An instance of OrderProviderResponse. See type for more information. - */ - public async getOrdersAsync(orderProviderRequest: OrderProviderRequest): Promise<OrderProviderResponse> { - assert.isValidOrderProviderRequest('orderProviderRequest', orderProviderRequest); - const { makerAssetData, takerAssetData } = orderProviderRequest; - const orders = _.filter(this.orders, order => { - return order.makerAssetData === makerAssetData && order.takerAssetData === takerAssetData; - }); - return { orders }; - } - /** - * Given a taker asset data string, return all availabled paired maker asset data strings. - * @param takerAssetData A string representing the taker asset data. - * @return An array of asset data strings that can be purchased using takerAssetData. - */ - public async getAvailableMakerAssetDatasAsync(takerAssetData: string): Promise<string[]> { - const ordersWithTakerAssetData = _.filter(this.orders, { takerAssetData }); - return _.map(ordersWithTakerAssetData, order => order.makerAssetData); - } -} diff --git a/packages/asset-buyer/src/order_providers/standard_relayer_api_order_provider.ts b/packages/asset-buyer/src/order_providers/standard_relayer_api_order_provider.ts deleted file mode 100644 index 813c9923b..000000000 --- a/packages/asset-buyer/src/order_providers/standard_relayer_api_order_provider.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { HttpClient } from '@0x/connect'; -import { APIOrder, AssetPairsResponse, OrderbookResponse } from '@0x/types'; -import * as _ from 'lodash'; - -import { - AssetBuyerError, - OrderProvider, - OrderProviderRequest, - OrderProviderResponse, - SignedOrderWithRemainingFillableMakerAssetAmount, -} from '../types'; -import { assert } from '../utils/assert'; -import { orderUtils } from '../utils/order_utils'; - -export class StandardRelayerAPIOrderProvider implements OrderProvider { - public readonly apiUrl: string; - public readonly networkId: number; - private readonly _sraClient: HttpClient; - /** - * Given an array of APIOrder objects from a standard relayer api, return an array - * of SignedOrderWithRemainingFillableMakerAssetAmounts - */ - private static _getSignedOrderWithRemainingFillableMakerAssetAmountFromApi( - apiOrders: APIOrder[], - ): SignedOrderWithRemainingFillableMakerAssetAmount[] { - const result = _.map(apiOrders, apiOrder => { - const { order, metaData } = apiOrder; - // calculate remainingFillableMakerAssetAmount from api metadata, else assume order is completely fillable - const remainingFillableTakerAssetAmount = _.get( - metaData, - 'remainingTakerAssetAmount', - order.takerAssetAmount, - ); - const remainingFillableMakerAssetAmount = orderUtils.getRemainingMakerAmount( - order, - remainingFillableTakerAssetAmount, - ); - const newOrder = { - ...order, - remainingFillableMakerAssetAmount, - }; - return newOrder; - }); - return result; - } - /** - * Instantiates a new StandardRelayerAPIOrderProvider instance - * @param apiUrl The standard relayer API base HTTP url you would like to source orders from. - * @param networkId The ethereum network id. - * @return An instance of StandardRelayerAPIOrderProvider - */ - constructor(apiUrl: string, networkId: number) { - assert.isWebUri('apiUrl', apiUrl); - assert.isNumber('networkId', networkId); - this.apiUrl = apiUrl; - this.networkId = networkId; - this._sraClient = new HttpClient(apiUrl); - } - /** - * Given an object that conforms to OrderProviderRequest, return the corresponding OrderProviderResponse that satisfies the request. - * @param orderProviderRequest An instance of OrderProviderRequest. See type for more information. - * @return An instance of OrderProviderResponse. See type for more information. - */ - public async getOrdersAsync(orderProviderRequest: OrderProviderRequest): Promise<OrderProviderResponse> { - assert.isValidOrderProviderRequest('orderProviderRequest', orderProviderRequest); - const { makerAssetData, takerAssetData } = orderProviderRequest; - const orderbookRequest = { baseAssetData: makerAssetData, quoteAssetData: takerAssetData }; - const requestOpts = { networkId: this.networkId }; - let orderbook: OrderbookResponse; - try { - orderbook = await this._sraClient.getOrderbookAsync(orderbookRequest, requestOpts); - } catch (err) { - throw new Error(AssetBuyerError.StandardRelayerApiError); - } - const apiOrders = orderbook.asks.records; - const orders = StandardRelayerAPIOrderProvider._getSignedOrderWithRemainingFillableMakerAssetAmountFromApi( - apiOrders, - ); - return { - orders, - }; - } - /** - * Given a taker asset data string, return all availabled paired maker asset data strings. - * @param takerAssetData A string representing the taker asset data. - * @return An array of asset data strings that can be purchased using takerAssetData. - */ - public async getAvailableMakerAssetDatasAsync(takerAssetData: string): Promise<string[]> { - // Return a maximum of 1000 asset datas - const maxPerPage = 1000; - const requestOpts = { networkId: this.networkId, perPage: maxPerPage }; - const assetPairsRequest = { assetDataA: takerAssetData }; - const fullRequest = { - ...requestOpts, - ...assetPairsRequest, - }; - let response: AssetPairsResponse; - try { - response = await this._sraClient.getAssetPairsAsync(fullRequest); - } catch (err) { - throw new Error(AssetBuyerError.StandardRelayerApiError); - } - return _.map(response.records, item => { - if (item.assetDataA.assetData === takerAssetData) { - return item.assetDataB.assetData; - } else { - return item.assetDataA.assetData; - } - }); - } -} diff --git a/packages/asset-buyer/src/types.ts b/packages/asset-buyer/src/types.ts deleted file mode 100644 index 46a2338ce..000000000 --- a/packages/asset-buyer/src/types.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; - -/** - * makerAssetData: The assetData representing the desired makerAsset. - * takerAssetData: The assetData representing the desired takerAsset. - * networkId: The networkId that the desired orders should be for. - */ -export interface OrderProviderRequest { - makerAssetData: string; - takerAssetData: string; -} - -/** - * orders: An array of orders with optional remaining fillable makerAsset amounts. See type for more info. - */ -export interface OrderProviderResponse { - orders: SignedOrderWithRemainingFillableMakerAssetAmount[]; -} - -/** - * A normal SignedOrder with one extra optional property `remainingFillableMakerAssetAmount` - * remainingFillableMakerAssetAmount: The amount of the makerAsset that is available to be filled - */ -export interface SignedOrderWithRemainingFillableMakerAssetAmount extends SignedOrder { - remainingFillableMakerAssetAmount?: BigNumber; -} -/** - * gerOrdersAsync: Given an OrderProviderRequest, get an OrderProviderResponse. - * getAvailableMakerAssetDatasAsync: Given a taker asset data string, return all availabled paired maker asset data strings. - */ -export interface OrderProvider { - getOrdersAsync: (orderProviderRequest: OrderProviderRequest) => Promise<OrderProviderResponse>; - getAvailableMakerAssetDatasAsync: (takerAssetData: string) => Promise<string[]>; -} - -/** - * assetData: String that represents a specific asset (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md). - * assetBuyAmount: The amount of asset to buy. - * orders: An array of objects conforming to SignedOrder. These orders can be used to cover the requested assetBuyAmount plus slippage. - * feeOrders: An array of objects conforming to SignedOrder. These orders can be used to cover the fees for the orders param above. - * feePercentage: Optional affiliate fee percentage used to calculate the eth amounts above. - * bestCaseQuoteInfo: Info about the best case price for the asset. - * worstCaseQuoteInfo: Info about the worst case price for the asset. - */ -export interface BuyQuote { - assetData: string; - assetBuyAmount: BigNumber; - orders: SignedOrder[]; - feeOrders: SignedOrder[]; - feePercentage?: number; - bestCaseQuoteInfo: BuyQuoteInfo; - worstCaseQuoteInfo: BuyQuoteInfo; -} - -/** - * assetEthAmount: The amount of eth required to pay for the requested asset. - * feeEthAmount: The amount of eth required to pay the affiliate fee. - * totalEthAmount: The total amount of eth required to complete the buy (filling orders, feeOrders, and paying affiliate fee). - */ -export interface BuyQuoteInfo { - assetEthAmount: BigNumber; - feeEthAmount: BigNumber; - totalEthAmount: BigNumber; -} - -/** - * feePercentage: The affiliate fee percentage. Defaults to 0. - * shouldForceOrderRefresh: If set to true, new orders and state will be fetched instead of waiting for the next orderRefreshIntervalMs. Defaults to false. - * slippagePercentage: The percentage buffer to add to account for slippage. Affects max ETH price estimates. Defaults to 0.2 (20%). - */ -export interface BuyQuoteRequestOpts { - feePercentage: number; - shouldForceOrderRefresh: boolean; - 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. - * gasLimit: The amount of gas to send with a transaction (in Gwei). Defaults to an eth_estimateGas rpc call. - * gasPrice: Gas price in Wei to use for a transaction - * feeRecipient: The address where affiliate fees are sent. Defaults to null address (0x000...000). - */ -export interface BuyQuoteExecutionOpts { - ethAmount?: BigNumber; - takerAddress?: string; - gasLimit?: number; - gasPrice?: BigNumber; - feeRecipient: string; -} - -/** - * networkId: The ethereum network id. Defaults to 1 (mainnet). - * orderRefreshIntervalMs: The interval in ms that getBuyQuoteAsync should trigger an refresh of orders and order states. Defaults to 10000ms (10s). - * expiryBufferSeconds: The number of seconds to add when calculating whether an order is expired or not. Defaults to 300s (5m). - */ -export interface AssetBuyerOpts { - networkId: number; - orderRefreshIntervalMs: number; - expiryBufferSeconds: number; -} - -/** - * Possible error messages thrown by an AssetBuyer instance or associated static methods. - */ -export enum AssetBuyerError { - NoEtherTokenContractFound = 'NO_ETHER_TOKEN_CONTRACT_FOUND', - NoZrxTokenContractFound = 'NO_ZRX_TOKEN_CONTRACT_FOUND', - StandardRelayerApiError = 'STANDARD_RELAYER_API_ERROR', - InsufficientAssetLiquidity = 'INSUFFICIENT_ASSET_LIQUIDITY', - InsufficientZrxLiquidity = 'INSUFFICIENT_ZRX_LIQUIDITY', - NoAddressAvailable = 'NO_ADDRESS_AVAILABLE', - InvalidOrderProviderResponse = 'INVALID_ORDER_PROVIDER_RESPONSE', - AssetUnavailable = 'ASSET_UNAVAILABLE', - SignatureRequestDenied = 'SIGNATURE_REQUEST_DENIED', - 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/assert.ts b/packages/asset-buyer/src/utils/assert.ts deleted file mode 100644 index fcf9b0d0e..000000000 --- a/packages/asset-buyer/src/utils/assert.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { assert as sharedAssert } from '@0x/assert'; -import { schemas } from '@0x/json-schemas'; -import * as _ from 'lodash'; - -import { BuyQuote, BuyQuoteInfo, OrderProvider, OrderProviderRequest } from '../types'; - -export const assert = { - ...sharedAssert, - isValidBuyQuote(variableName: string, buyQuote: BuyQuote): void { - sharedAssert.isHexString(`${variableName}.assetData`, buyQuote.assetData); - sharedAssert.doesConformToSchema(`${variableName}.orders`, buyQuote.orders, schemas.signedOrdersSchema); - sharedAssert.doesConformToSchema(`${variableName}.feeOrders`, buyQuote.feeOrders, schemas.signedOrdersSchema); - assert.isValidBuyQuoteInfo(`${variableName}.bestCaseQuoteInfo`, buyQuote.bestCaseQuoteInfo); - assert.isValidBuyQuoteInfo(`${variableName}.worstCaseQuoteInfo`, buyQuote.worstCaseQuoteInfo); - sharedAssert.isBigNumber(`${variableName}.assetBuyAmount`, buyQuote.assetBuyAmount); - if (!_.isUndefined(buyQuote.feePercentage)) { - sharedAssert.isNumber(`${variableName}.feePercentage`, buyQuote.feePercentage); - } - }, - isValidBuyQuoteInfo(variableName: string, buyQuoteInfo: BuyQuoteInfo): void { - sharedAssert.isBigNumber(`${variableName}.assetEthAmount`, buyQuoteInfo.assetEthAmount); - sharedAssert.isBigNumber(`${variableName}.feeEthAmount`, buyQuoteInfo.feeEthAmount); - sharedAssert.isBigNumber(`${variableName}.totalEthAmount`, buyQuoteInfo.totalEthAmount); - }, - isValidOrderProvider(variableName: string, orderFetcher: OrderProvider): void { - sharedAssert.isFunction(`${variableName}.getOrdersAsync`, orderFetcher.getOrdersAsync); - }, - isValidOrderProviderRequest(variableName: string, orderFetcherRequest: OrderProviderRequest): void { - sharedAssert.isHexString(`${variableName}.makerAssetData`, orderFetcherRequest.makerAssetData); - sharedAssert.isHexString(`${variableName}.takerAssetData`, orderFetcherRequest.takerAssetData); - }, - isValidPercentage(variableName: string, percentage: number): void { - assert.isNumber(variableName, percentage); - assert.assert( - percentage >= 0 && percentage <= 1, - `Expected ${variableName} to be between 0 and 1, but is ${percentage}`, - ); - }, -}; diff --git a/packages/asset-buyer/src/utils/asset_data_utils.ts b/packages/asset-buyer/src/utils/asset_data_utils.ts deleted file mode 100644 index 70f646902..000000000 --- a/packages/asset-buyer/src/utils/asset_data_utils.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ContractWrappers } from '@0x/contract-wrappers'; -import { assetDataUtils as sharedAssetDataUtils } from '@0x/order-utils'; -import * as _ from 'lodash'; - -export const assetDataUtils = { - ...sharedAssetDataUtils, - getEtherTokenAssetData(contractWrappers: ContractWrappers): string { - const etherTokenAddress = contractWrappers.forwarder.etherTokenAddress; - const etherTokenAssetData = sharedAssetDataUtils.encodeERC20AssetData(etherTokenAddress); - return etherTokenAssetData; - }, -}; diff --git a/packages/asset-buyer/src/utils/buy_quote_calculator.ts b/packages/asset-buyer/src/utils/buy_quote_calculator.ts deleted file mode 100644 index 125841094..000000000 --- a/packages/asset-buyer/src/utils/buy_quote_calculator.ts +++ /dev/null @@ -1,221 +0,0 @@ -import { marketUtils, SignedOrder } from '@0x/order-utils'; -import { BigNumber } from '@0x/utils'; -import * as _ from 'lodash'; - -import { constants } from '../constants'; -import { InsufficientAssetLiquidityError } from '../errors'; -import { AssetBuyerError, BuyQuote, BuyQuoteInfo, OrdersAndFillableAmounts } from '../types'; - -import { orderUtils } from './order_utils'; - -// Calculates a buy quote for orders that have WETH as the takerAsset -export const buyQuoteCalculator = { - calculate( - ordersAndFillableAmounts: OrdersAndFillableAmounts, - feeOrdersAndFillableAmounts: OrdersAndFillableAmounts, - assetBuyAmount: BigNumber, - feePercentage: number, - slippagePercentage: number, - isMakerAssetZrxToken: boolean, - ): BuyQuote { - const orders = ordersAndFillableAmounts.orders; - const remainingFillableMakerAssetAmounts = ordersAndFillableAmounts.remainingFillableMakerAssetAmounts; - const feeOrders = feeOrdersAndFillableAmounts.orders; - const remainingFillableFeeAmounts = feeOrdersAndFillableAmounts.remainingFillableMakerAssetAmounts; - const slippageBufferAmount = assetBuyAmount.multipliedBy(slippagePercentage).integerValue(); - // find the orders that cover the desired assetBuyAmount (with slippage) - const { - resultOrders, - remainingFillAmount, - ordersRemainingFillableMakerAssetAmounts, - } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(orders, assetBuyAmount, { - remainingFillableMakerAssetAmounts, - slippageBufferAmount, - }); - // if we do not have enough orders to cover the desired assetBuyAmount, throw - if (remainingFillAmount.gt(constants.ZERO_AMOUNT)) { - // We needed the amount they requested to buy, plus the amount for slippage - const totalAmountRequested = assetBuyAmount.plus(slippageBufferAmount); - const amountAbleToFill = totalAmountRequested.minus(remainingFillAmount); - // multiplierNeededWithSlippage represents what we need to multiply the assetBuyAmount by - // in order to get the total amount needed considering slippage - // i.e. if slippagePercent was 0.2 (20%), multiplierNeededWithSlippage would be 1.2 - const multiplierNeededWithSlippage = new BigNumber(1).plus(slippagePercentage); - // Given amountAvailableToFillConsideringSlippage * multiplierNeededWithSlippage = amountAbleToFill - // We divide amountUnableToFill by multiplierNeededWithSlippage to determine amountAvailableToFillConsideringSlippage - const amountAvailableToFillConsideringSlippage = amountAbleToFill - .div(multiplierNeededWithSlippage) - .integerValue(BigNumber.ROUND_FLOOR); - - throw new InsufficientAssetLiquidityError(amountAvailableToFillConsideringSlippage); - } - // if we are not buying ZRX: - // given the orders calculated above, find the fee-orders that cover the desired assetBuyAmount (with slippage) - // TODO(bmillman): optimization - // update this logic to find the minimum amount of feeOrders to cover the worst case as opposed to - // finding order that cover all fees, this will help with estimating ETH and minimizing gas usage - let resultFeeOrders = [] as SignedOrder[]; - let feeOrdersRemainingFillableMakerAssetAmounts = [] as BigNumber[]; - if (!isMakerAssetZrxToken) { - const feeOrdersAndRemainingFeeAmount = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders( - resultOrders, - feeOrders, - { - remainingFillableMakerAssetAmounts: ordersRemainingFillableMakerAssetAmounts, - remainingFillableFeeAmounts, - }, - ); - // if we do not have enough feeOrders to cover the fees, throw - if (feeOrdersAndRemainingFeeAmount.remainingFeeAmount.gt(constants.ZERO_AMOUNT)) { - throw new Error(AssetBuyerError.InsufficientZrxLiquidity); - } - resultFeeOrders = feeOrdersAndRemainingFeeAmount.resultFeeOrders; - feeOrdersRemainingFillableMakerAssetAmounts = - feeOrdersAndRemainingFeeAmount.feeOrdersRemainingFillableMakerAssetAmounts; - } - - // assetData information for the result - const assetData = orders[0].makerAssetData; - // compile the resulting trimmed set of orders for makerAsset and feeOrders that are needed for assetBuyAmount - const trimmedOrdersAndFillableAmounts: OrdersAndFillableAmounts = { - orders: resultOrders, - remainingFillableMakerAssetAmounts: ordersRemainingFillableMakerAssetAmounts, - }; - const trimmedFeeOrdersAndFillableAmounts: OrdersAndFillableAmounts = { - orders: resultFeeOrders, - remainingFillableMakerAssetAmounts: feeOrdersRemainingFillableMakerAssetAmounts, - }; - const bestCaseQuoteInfo = calculateQuoteInfo( - trimmedOrdersAndFillableAmounts, - trimmedFeeOrdersAndFillableAmounts, - assetBuyAmount, - feePercentage, - isMakerAssetZrxToken, - ); - // in order to calculate the maxRate, reverse the ordersAndFillableAmounts such that they are sorted from worst rate to best rate - const worstCaseQuoteInfo = calculateQuoteInfo( - reverseOrdersAndFillableAmounts(trimmedOrdersAndFillableAmounts), - reverseOrdersAndFillableAmounts(trimmedFeeOrdersAndFillableAmounts), - assetBuyAmount, - feePercentage, - isMakerAssetZrxToken, - ); - return { - assetData, - orders: resultOrders, - feeOrders: resultFeeOrders, - bestCaseQuoteInfo, - worstCaseQuoteInfo, - assetBuyAmount, - feePercentage, - }; - }, -}; - -function calculateQuoteInfo( - ordersAndFillableAmounts: OrdersAndFillableAmounts, - feeOrdersAndFillableAmounts: OrdersAndFillableAmounts, - assetBuyAmount: BigNumber, - feePercentage: number, - isMakerAssetZrxToken: boolean, -): BuyQuoteInfo { - // find the total eth and zrx needed to buy assetAmount from the resultOrders from left to right - let assetEthAmount = constants.ZERO_AMOUNT; - let zrxEthAmount = constants.ZERO_AMOUNT; - if (isMakerAssetZrxToken) { - assetEthAmount = findEthAmountNeededToBuyZrx(ordersAndFillableAmounts, assetBuyAmount); - } else { - // find eth and zrx amounts needed to buy - const ethAndZrxAmountToBuyAsset = findEthAndZrxAmountNeededToBuyAsset(ordersAndFillableAmounts, assetBuyAmount); - assetEthAmount = ethAndZrxAmountToBuyAsset[0]; - const zrxAmountToBuyAsset = ethAndZrxAmountToBuyAsset[1]; - // find eth amount needed to buy zrx - zrxEthAmount = findEthAmountNeededToBuyZrx(feeOrdersAndFillableAmounts, zrxAmountToBuyAsset); - } - // eth amount needed to buy the affiliate fee - 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 - const totalEthAmount = assetEthAmount.plus(feeEthAmount); - return { - assetEthAmount, - feeEthAmount, - totalEthAmount, - }; -} - -// given an OrdersAndFillableAmounts, reverse the orders and remainingFillableMakerAssetAmounts properties -function reverseOrdersAndFillableAmounts(ordersAndFillableAmounts: OrdersAndFillableAmounts): OrdersAndFillableAmounts { - const ordersCopy = _.clone(ordersAndFillableAmounts.orders); - const remainingFillableMakerAssetAmountsCopy = _.clone(ordersAndFillableAmounts.remainingFillableMakerAssetAmounts); - return { - orders: ordersCopy.reverse(), - remainingFillableMakerAssetAmounts: remainingFillableMakerAssetAmountsCopy.reverse(), - }; -} - -function findEthAmountNeededToBuyZrx( - feeOrdersAndFillableAmounts: OrdersAndFillableAmounts, - zrxBuyAmount: BigNumber, -): BigNumber { - const { orders, remainingFillableMakerAssetAmounts } = feeOrdersAndFillableAmounts; - const result = _.reduce( - orders, - (acc, order, index) => { - const { totalEthAmount, remainingZrxBuyAmount } = acc; - const remainingFillableMakerAssetAmount = remainingFillableMakerAssetAmounts[index]; - const makerFillAmount = BigNumber.min(remainingZrxBuyAmount, remainingFillableMakerAssetAmount); - const [takerFillAmount, adjustedMakerFillAmount] = orderUtils.getTakerFillAmountForFeeOrder( - order, - makerFillAmount, - ); - const extraFeeAmount = remainingFillableMakerAssetAmount.isGreaterThanOrEqualTo(adjustedMakerFillAmount) - ? constants.ZERO_AMOUNT - : adjustedMakerFillAmount.minus(makerFillAmount); - return { - totalEthAmount: totalEthAmount.plus(takerFillAmount), - remainingZrxBuyAmount: BigNumber.max( - constants.ZERO_AMOUNT, - remainingZrxBuyAmount.minus(makerFillAmount).plus(extraFeeAmount), - ), - }; - }, - { - totalEthAmount: constants.ZERO_AMOUNT, - remainingZrxBuyAmount: zrxBuyAmount, - }, - ); - return result.totalEthAmount; -} - -function findEthAndZrxAmountNeededToBuyAsset( - ordersAndFillableAmounts: OrdersAndFillableAmounts, - assetBuyAmount: BigNumber, -): [BigNumber, BigNumber] { - const { orders, remainingFillableMakerAssetAmounts } = ordersAndFillableAmounts; - const result = _.reduce( - orders, - (acc, order, index) => { - const { totalEthAmount, totalZrxAmount, remainingAssetBuyAmount } = acc; - const remainingFillableMakerAssetAmount = remainingFillableMakerAssetAmounts[index]; - const makerFillAmount = BigNumber.min(acc.remainingAssetBuyAmount, remainingFillableMakerAssetAmount); - const takerFillAmount = orderUtils.getTakerFillAmount(order, makerFillAmount); - const takerFeeAmount = orderUtils.getTakerFeeAmount(order, takerFillAmount); - return { - totalEthAmount: totalEthAmount.plus(takerFillAmount), - totalZrxAmount: totalZrxAmount.plus(takerFeeAmount), - remainingAssetBuyAmount: BigNumber.max( - constants.ZERO_AMOUNT, - remainingAssetBuyAmount.minus(makerFillAmount), - ), - }; - }, - { - totalEthAmount: constants.ZERO_AMOUNT, - totalZrxAmount: constants.ZERO_AMOUNT, - remainingAssetBuyAmount: assetBuyAmount, - }, - ); - return [result.totalEthAmount, result.totalZrxAmount]; -} diff --git a/packages/asset-buyer/src/utils/calculate_liquidity.ts b/packages/asset-buyer/src/utils/calculate_liquidity.ts deleted file mode 100644 index a8d165b4b..000000000 --- a/packages/asset-buyer/src/utils/calculate_liquidity.ts +++ /dev/null @@ -1,34 +0,0 @@ -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 deleted file mode 100644 index f08cd6150..000000000 --- a/packages/asset-buyer/src/utils/order_provider_response_processor.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { OrderAndTraderInfo, OrderStatus, OrderValidatorWrapper } from '@0x/contract-wrappers'; -import { sortingUtils } from '@0x/order-utils'; -import { RemainingFillableCalculator } from '@0x/order-utils/lib/src/remaining_fillable_calculator'; -import { SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import * as _ from 'lodash'; - -import { constants } from '../constants'; -import { - AssetBuyerError, - OrderProviderRequest, - OrderProviderResponse, - OrdersAndFillableAmounts, - SignedOrderWithRemainingFillableMakerAssetAmount, -} from '../types'; - -import { orderUtils } from './order_utils'; - -export const orderProviderResponseProcessor = { - throwIfInvalidResponse(response: OrderProviderResponse, request: OrderProviderRequest): void { - const { makerAssetData, takerAssetData } = request; - _.forEach(response.orders, order => { - if (order.makerAssetData !== makerAssetData || order.takerAssetData !== takerAssetData) { - throw new Error(AssetBuyerError.InvalidOrderProviderResponse); - } - }); - }, - /** - * Take the responses for the target orders to buy and fee orders and process them. - * Processing includes: - * - Drop orders that are expired or not open orders (null taker address) - * - If shouldValidateOnChain, attempt to grab fillable amounts from on-chain otherwise assume completely fillable - * - Sort by rate - */ - async processAsync( - orderProviderResponse: OrderProviderResponse, - isMakerAssetZrxToken: boolean, - expiryBufferSeconds: number, - orderValidator?: OrderValidatorWrapper, - ): Promise<OrdersAndFillableAmounts> { - // drop orders that are expired or not open - const filteredOrders = filterOutExpiredAndNonOpenOrders(orderProviderResponse.orders, expiryBufferSeconds); - // set the orders to be sorted equal to the filtered orders - let unsortedOrders = filteredOrders; - // if an orderValidator is provided, use on chain information to calculate remaining fillable makerAsset amounts - if (!_.isUndefined(orderValidator)) { - const takerAddresses = _.map(filteredOrders, () => constants.NULL_ADDRESS); - try { - const ordersAndTradersInfo = await orderValidator.getOrdersAndTradersInfoAsync( - filteredOrders, - takerAddresses, - ); - // take orders + on chain information and find the valid orders and remaining fillable maker asset amounts - unsortedOrders = getValidOrdersWithRemainingFillableMakerAssetAmountsFromOnChain( - filteredOrders, - ordersAndTradersInfo, - isMakerAssetZrxToken, - ); - } catch (err) { - // Sometimes we observe this call to orderValidator fail with response `0x` - // Because of differences in Parity / Geth implementations, its very hard to tell if this response is a "system error" - // or a revert. In this case we just swallow these errors and fallback to partial fill information from the SRA. - // TODO(bmillman): report these errors so we have an idea of how often we're getting these failures. - } - } - // sort orders by rate - // TODO(bmillman): optimization - // provide a feeRate to the sorting function to more accurately sort based on the current market for ZRX tokens - const sortedOrders = isMakerAssetZrxToken - ? sortingUtils.sortFeeOrdersByFeeAdjustedRate(unsortedOrders) - : sortingUtils.sortOrdersByFeeAdjustedRate(unsortedOrders); - // unbundle orders and fillable amounts and compile final result - const result = unbundleOrdersWithAmounts(sortedOrders); - return result; - }, -}; - -/** - * Given an array of orders, return a new array with expired and non open orders filtered out. - */ -function filterOutExpiredAndNonOpenOrders( - orders: SignedOrderWithRemainingFillableMakerAssetAmount[], - expiryBufferSeconds: number, -): SignedOrderWithRemainingFillableMakerAssetAmount[] { - const result = _.filter(orders, order => { - return orderUtils.isOpenOrder(order) && !orderUtils.willOrderExpire(order, expiryBufferSeconds); - }); - return result; -} - -/** - * Given an array of orders and corresponding on-chain infos, return a subset of the orders - * that are still fillable orders with their corresponding remainingFillableMakerAssetAmounts. - */ -function getValidOrdersWithRemainingFillableMakerAssetAmountsFromOnChain( - inputOrders: SignedOrder[], - ordersAndTradersInfo: OrderAndTraderInfo[], - isMakerAssetZrxToken: boolean, -): SignedOrderWithRemainingFillableMakerAssetAmount[] { - // iterate through the input orders and find the ones that are still fillable - // for the orders that are still fillable, calculate the remaining fillable maker asset amount - const result = _.reduce( - inputOrders, - (accOrders, order, index) => { - // get corresponding on-chain state for the order - const { orderInfo, traderInfo } = ordersAndTradersInfo[index]; - // if the order IS NOT fillable, do not add anything to the accumulations and continue iterating - if (orderInfo.orderStatus !== OrderStatus.Fillable) { - 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 remainingTakerAssetAmount = order.takerAssetAmount.minus(orderInfo.orderTakerAssetFilledAmount); - const remainingMakerAssetAmount = orderUtils.getRemainingMakerAmount(order, remainingTakerAssetAmount); - const remainingFillableCalculator = new RemainingFillableCalculator( - order.makerFee, - order.makerAssetAmount, - isMakerAssetZrxToken, - transferrableAssetAmount, - transferrableFeeAssetAmount, - remainingMakerAssetAmount, - ); - const remainingFillableAmount = remainingFillableCalculator.computeRemainingFillable(); - // if the order does not have any remaining fillable makerAsset, do not add anything to the accumulations and continue iterating - if (remainingFillableAmount.lte(constants.ZERO_AMOUNT)) { - return accOrders; - } - const orderWithRemainingFillableMakerAssetAmount = { - ...order, - remainingFillableMakerAssetAmount: remainingFillableAmount, - }; - const newAccOrders = _.concat(accOrders, orderWithRemainingFillableMakerAssetAmount); - return newAccOrders; - }, - [] as SignedOrderWithRemainingFillableMakerAssetAmount[], - ); - return result; -} - -/** - * Given an array of orders with remaining fillable maker asset amounts. Unbundle into an instance of OrdersAndRemainingFillableMakerAssetAmounts. - * If an order is missing a corresponding remainingFillableMakerAssetAmount, assume it is completely fillable. - */ -function unbundleOrdersWithAmounts( - ordersWithAmounts: SignedOrderWithRemainingFillableMakerAssetAmount[], -): OrdersAndFillableAmounts { - const result = _.reduce( - ordersWithAmounts, - (acc, orderWithAmount) => { - const { orders, remainingFillableMakerAssetAmounts } = acc; - const { remainingFillableMakerAssetAmount, ...order } = orderWithAmount; - // if we are still missing a remainingFillableMakerAssetAmount, assume the order is completely fillable - const newRemainingAmount = remainingFillableMakerAssetAmount || order.makerAssetAmount; - // if remaining amount is less than or equal to zero, do not add it - if (newRemainingAmount.lte(constants.ZERO_AMOUNT)) { - return acc; - } - const newAcc = { - orders: _.concat(orders, order), - remainingFillableMakerAssetAmounts: _.concat(remainingFillableMakerAssetAmounts, newRemainingAmount), - }; - return newAcc; - }, - { - orders: [] as SignedOrder[], - remainingFillableMakerAssetAmounts: [] as BigNumber[], - }, - ); - return result; -} diff --git a/packages/asset-buyer/src/utils/order_utils.ts b/packages/asset-buyer/src/utils/order_utils.ts deleted file mode 100644 index 3ea3cafd3..000000000 --- a/packages/asset-buyer/src/utils/order_utils.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; - -import { constants } from '../constants'; - -export const orderUtils = { - isOrderExpired(order: SignedOrder): boolean { - return orderUtils.willOrderExpire(order, 0); - }, - willOrderExpire(order: SignedOrder, secondsFromNow: number): boolean { - const millisecondsInSecond = 1000; - 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; - }, - // given a remaining amount of takerAsset, calculate how much makerAsset is available - getRemainingMakerAmount(order: SignedOrder, remainingTakerAmount: BigNumber): BigNumber { - const remainingMakerAmount = remainingTakerAmount - .times(order.makerAssetAmount) - .div(order.takerAssetAmount) - .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 - .multipliedBy(order.takerAssetAmount) - .div(order.makerAssetAmount) - .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 - .multipliedBy(order.takerFee) - .div(order.takerAssetAmount) - .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 - .multipliedBy(order.makerAssetAmount) - .div(order.takerAssetAmount) - .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 - .multipliedBy(order.makerFee) - .div(order.makerAssetAmount) - .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 - // also calculate how much ZRX needs to be bought in order fill the desired amount + takerFee - getTakerFillAmountForFeeOrder(order: SignedOrder, makerFillAmount: BigNumber): [BigNumber, BigNumber] { - // For each unit of TakerAsset we buy (MakerAsset - TakerFee) - const adjustedTakerFillAmount = makerFillAmount - .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]; - }, -}; |