aboutsummaryrefslogtreecommitdiffstats
path: root/packages/asset-buyer/src/asset_buyer.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/asset-buyer/src/asset_buyer.ts')
-rw-r--r--packages/asset-buyer/src/asset_buyer.ts365
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();
- }
-}