diff options
Diffstat (limited to 'packages/asset-buyer/src/asset_buyer.ts')
-rw-r--r-- | packages/asset-buyer/src/asset_buyer.ts | 365 |
1 files changed, 0 insertions, 365 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(); - } -} |