aboutsummaryrefslogtreecommitdiffstats
path: root/packages/asset-buyer/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/asset-buyer/src')
-rw-r--r--packages/asset-buyer/src/asset_buyer.ts259
-rw-r--r--packages/asset-buyer/src/constants.ts24
-rw-r--r--packages/asset-buyer/src/index.ts5
-rw-r--r--packages/asset-buyer/src/standard_relayer_api_asset_buyer_manager.ts133
-rw-r--r--packages/asset-buyer/src/types.ts39
-rw-r--r--packages/asset-buyer/src/utils/buy_quote_calculator.ts15
-rw-r--r--packages/asset-buyer/src/utils/order_provider_response_processor.ts76
7 files changed, 194 insertions, 357 deletions
diff --git a/packages/asset-buyer/src/asset_buyer.ts b/packages/asset-buyer/src/asset_buyer.ts
index 409e34e74..53bbe427b 100644
--- a/packages/asset-buyer/src/asset_buyer.ts
+++ b/packages/asset-buyer/src/asset_buyer.ts
@@ -1,6 +1,8 @@
+import { HttpClient } from '@0xproject/connect';
import { ContractWrappers } from '@0xproject/contract-wrappers';
import { schemas } from '@0xproject/json-schemas';
import { SignedOrder } from '@0xproject/order-utils';
+import { ObjectMap } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import { Provider } from 'ethereum-types';
@@ -11,11 +13,13 @@ import { BasicOrderProvider } from './order_providers/basic_order_provider';
import { StandardRelayerAPIOrderProvider } from './order_providers/standard_relayer_api_order_provider';
import {
AssetBuyerError,
- AssetBuyerOrdersAndFillableAmounts,
+ AssetBuyerOpts,
BuyQuote,
+ BuyQuoteExecutionOpts,
BuyQuoteRequestOpts,
OrderProvider,
OrderProviderResponse,
+ OrdersAndFillableAmounts,
} from './types';
import { assert } from './utils/assert';
@@ -23,24 +27,26 @@ import { assetDataUtils } from './utils/asset_data_utils';
import { buyQuoteCalculator } from './utils/buy_quote_calculator';
import { orderProviderResponseProcessor } from './utils/order_provider_response_processor';
+interface OrdersEntry {
+ ordersAndFillableAmounts: OrdersAndFillableAmounts;
+ lastRefreshTime: number;
+}
+
export class AssetBuyer {
public readonly provider: Provider;
- public readonly assetData: string;
public readonly orderProvider: OrderProvider;
public readonly networkId: number;
public readonly orderRefreshIntervalMs: number;
public readonly expiryBufferSeconds: number;
private readonly _contractWrappers: ContractWrappers;
- private _lastRefreshTimeIfExists?: number;
- private _currentOrdersAndFillableAmountsIfExists?: AssetBuyerOrdersAndFillableAmounts;
+ // 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 networkId The ethereum network id. Defaults to 1 (mainnet).
- * @param orderRefreshIntervalMs The interval in ms that getBuyQuoteAsync should trigger an refresh of orders and order states. Defaults to 10000ms (10s).
- * @param expiryBufferSeconds The number of seconds to add when calculating whether an order is expired or not. Defaults to 15s.
+ * @param options Initialization options for the AssetBuyer. See type definition for details.
*
* @return An instance of AssetBuyer
*/
@@ -48,171 +54,99 @@ export class AssetBuyer {
provider: Provider,
orders: SignedOrder[],
feeOrders: SignedOrder[] = [],
- networkId: number = constants.MAINNET_NETWORK_ID,
- orderRefreshIntervalMs: number = constants.DEFAULT_ORDER_REFRESH_INTERVAL_MS,
- expiryBufferSeconds: number = constants.DEFAULT_EXPIRY_BUFFER_SECONDS,
+ options: Partial<AssetBuyerOpts> = {},
): AssetBuyer {
assert.isWeb3Provider('provider', provider);
assert.doesConformToSchema('orders', orders, schemas.signedOrdersSchema);
assert.doesConformToSchema('feeOrders', feeOrders, schemas.signedOrdersSchema);
- assert.isNumber('networkId', networkId);
- assert.isNumber('orderRefreshIntervalMs', orderRefreshIntervalMs);
assert.areValidProvidedOrders('orders', orders);
assert.areValidProvidedOrders('feeOrders', feeOrders);
assert.assert(orders.length !== 0, `Expected orders to contain at least one order`);
- const assetData = orders[0].makerAssetData;
const orderProvider = new BasicOrderProvider(_.concat(orders, feeOrders));
- const assetBuyer = new AssetBuyer(
- provider,
- assetData,
- orderProvider,
- networkId,
- orderRefreshIntervalMs,
- expiryBufferSeconds,
- );
+ const assetBuyer = new AssetBuyer(provider, orderProvider, options);
return assetBuyer;
}
/**
- * Instantiates a new AssetBuyer instance given the desired assetData and a [Standard Relayer API](https://github.com/0xProject/standard-relayer-api) endpoint
+ * 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 assetData The assetData that identifies the desired asset to buy.
* @param sraApiUrl The standard relayer API base HTTP url you would like to source orders from.
- * @param networkId The ethereum network id. Defaults to 1 (mainnet).
- * @param orderRefreshIntervalMs The interval in ms that getBuyQuoteAsync should trigger an refresh of orders and order states. Defaults to 10000ms (10s).
- * @param expiryBufferSeconds The number of seconds to add when calculating whether an order is expired or not. Defaults to 15s.
+ * @param options Initialization options for the AssetBuyer. See type definition for details.
*
* @return An instance of AssetBuyer
*/
- public static getAssetBuyerForAssetData(
+ public static getAssetBuyerForStandardRelayerAPIUrl(
provider: Provider,
- assetData: string,
sraApiUrl: string,
- networkId: number = constants.MAINNET_NETWORK_ID,
- orderRefreshIntervalMs: number = constants.DEFAULT_ORDER_REFRESH_INTERVAL_MS,
- expiryBufferSeconds: number = constants.DEFAULT_EXPIRY_BUFFER_SECONDS,
+ options: Partial<AssetBuyerOpts> = {},
): AssetBuyer {
assert.isWeb3Provider('provider', provider);
- assert.isHexString('assetData', assetData);
assert.isWebUri('sraApiUrl', sraApiUrl);
- assert.isNumber('networkId', networkId);
- assert.isNumber('orderRefreshIntervalMs', orderRefreshIntervalMs);
const orderProvider = new StandardRelayerAPIOrderProvider(sraApiUrl);
- const assetBuyer = new AssetBuyer(
- provider,
- assetData,
- orderProvider,
- networkId,
- orderRefreshIntervalMs,
- expiryBufferSeconds,
- );
- return assetBuyer;
- }
- /**
- * Instantiates a new AssetBuyer instance given the desired ERC20 token address and 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 tokenAddress The ERC20 token address that identifies the desired asset to buy.
- * @param sraApiUrl The standard relayer API base HTTP url you would like to source orders from.
- * @param networkId The ethereum network id. Defaults to 1 (mainnet).
- * @param orderRefreshIntervalMs The interval in ms that getBuyQuoteAsync should trigger an refresh of orders and order states. Defaults to 10000ms (10s).
- * @param expiryBufferSeconds The number of seconds to add when calculating whether an order is expired or not. Defaults to 15s.
- * @return An instance of AssetBuyer
- */
- public static getAssetBuyerForERC20TokenAddress(
- provider: Provider,
- tokenAddress: string,
- sraApiUrl: string,
- networkId: number = constants.MAINNET_NETWORK_ID,
- orderRefreshIntervalMs: number = constants.DEFAULT_ORDER_REFRESH_INTERVAL_MS,
- expiryBufferSeconds: number = constants.DEFAULT_EXPIRY_BUFFER_SECONDS,
- ): AssetBuyer {
- assert.isWeb3Provider('provider', provider);
- assert.isETHAddressHex('tokenAddress', tokenAddress);
- assert.isWebUri('sraApiUrl', sraApiUrl);
- assert.isNumber('networkId', networkId);
- assert.isNumber('orderRefreshIntervalMs', orderRefreshIntervalMs);
- const assetData = assetDataUtils.encodeERC20AssetData(tokenAddress);
- const assetBuyer = AssetBuyer.getAssetBuyerForAssetData(
- provider,
- assetData,
- sraApiUrl,
- networkId,
- orderRefreshIntervalMs,
- expiryBufferSeconds,
- );
+ 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 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 orderProvider An object that conforms to OrderProvider, see type for definition.
- * @param networkId The ethereum network id. Defaults to 1 (mainnet).
- * @param orderRefreshIntervalMs The interval in ms that getBuyQuoteAsync should trigger an refresh of orders and order states. Defaults to 10000ms (10s).
- * @param expiryBufferSeconds The number of seconds to add when calculating whether an order is expired or not. Defaults to 15s.
+ * @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,
- assetData: string,
- orderProvider: OrderProvider,
- networkId: number = constants.MAINNET_NETWORK_ID,
- orderRefreshIntervalMs: number = constants.DEFAULT_ORDER_REFRESH_INTERVAL_MS,
- expiryBufferSeconds: number = constants.DEFAULT_EXPIRY_BUFFER_SECONDS,
- ) {
+ constructor(provider: Provider, orderProvider: OrderProvider, options: Partial<AssetBuyerOpts> = {}) {
+ const { networkId, orderRefreshIntervalMs, expiryBufferSeconds } = {
+ ...constants.DEFAULT_ASSET_BUYER_OPTS,
+ ...options,
+ };
assert.isWeb3Provider('provider', provider);
- assert.isString('assetData', assetData);
assert.isValidOrderProvider('orderProvider', orderProvider);
assert.isNumber('networkId', networkId);
assert.isNumber('orderRefreshIntervalMs', orderRefreshIntervalMs);
+ assert.isNumber('expiryBufferSeconds', expiryBufferSeconds);
this.provider = provider;
- this.assetData = assetData;
this.orderProvider = orderProvider;
this.networkId = networkId;
- this.expiryBufferSeconds = expiryBufferSeconds;
this.orderRefreshIntervalMs = orderRefreshIntervalMs;
+ this.expiryBufferSeconds = expiryBufferSeconds;
this._contractWrappers = new ContractWrappers(this.provider, {
networkId,
});
}
/**
- * Get a `BuyQuote` containing all information relevant to fulfilling a buy.
+ * 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 feePercentage The affiliate fee percentage. Defaults to 0.
- * @param forceOrderRefresh If set to true, new orders and state will be fetched instead of waiting for
- * the next orderRefreshIntervalMs. Defaults to false.
+ * @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(assetBuyAmount: BigNumber, options: Partial<BuyQuoteRequestOpts>): Promise<BuyQuote> {
+ public async getBuyQuoteAsync(
+ assetData: string,
+ assetBuyAmount: BigNumber,
+ options: Partial<BuyQuoteRequestOpts>,
+ ): Promise<BuyQuote> {
const { feePercentage, shouldForceOrderRefresh, slippagePercentage } = {
- ...options,
...constants.DEFAULT_BUY_QUOTE_REQUEST_OPTS,
+ ...options,
};
+ assert.isString('assetData', assetData);
assert.isBigNumber('assetBuyAmount', assetBuyAmount);
assert.isValidPercentage('feePercentage', feePercentage);
assert.isBoolean('shouldForceOrderRefresh', shouldForceOrderRefresh);
- // 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(this._currentOrdersAndFillableAmountsIfExists) ||
- shouldForceOrderRefresh ||
- (!_.isUndefined(this._lastRefreshTimeIfExists) &&
- this._lastRefreshTimeIfExists + this.orderRefreshIntervalMs < Date.now());
- let ordersAndFillableAmounts: AssetBuyerOrdersAndFillableAmounts;
- if (shouldRefresh) {
- ordersAndFillableAmounts = await this._getLatestOrdersAndFillableAmountsAsync();
- this._lastRefreshTimeIfExists = Date.now();
- this._currentOrdersAndFillableAmountsIfExists = ordersAndFillableAmounts;
- } else {
- // it is safe to cast to AssetBuyerOrdersAndFillableAmounts because shouldRefresh catches the undefined case above
- ordersAndFillableAmounts = this
- ._currentOrdersAndFillableAmountsIfExists as AssetBuyerOrdersAndFillableAmounts;
+ assert.isNumber('slippagePercentage', slippagePercentage);
+ const zrxTokenAssetData = this._getZrxTokenAssetDataOrThrow();
+ const [ordersAndFillableAmounts, feeOrdersAndFillableAmounts] = await Promise.all([
+ this._getOrdersAndFillableAmountsAsync(assetData, shouldForceOrderRefresh),
+ 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,
@@ -220,19 +154,37 @@ export class AssetBuyer {
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;
+ }
+ /**
* 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 rate The desired rate to execute the buy at. Affects the amount of ETH sent with the transaction, defaults to buyQuote.maxRate.
- * @param takerAddress The address to perform the buy. Defaults to the first available address from the provider.
- * @param feeRecipient The address where affiliate fees are sent. Defaults to null address (0x000...000).
+ * @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,
- rate?: BigNumber,
- takerAddress?: string,
- feeRecipient: string = constants.NULL_ADDRESS,
- ): Promise<string> {
+ public async executeBuyQuoteAsync(buyQuote: BuyQuote, options: Partial<BuyQuoteExecutionOpts>): Promise<string> {
+ const { rate, takerAddress, feeRecipient } = {
+ ...constants.DEFAULT_BUY_QUOTE_EXECUTION_OPTS,
+ ...options,
+ };
assert.isValidBuyQuote('buyQuote', buyQuote);
if (!_.isUndefined(rate)) {
assert.isBigNumber('rate', rate);
@@ -272,39 +224,54 @@ export class AssetBuyer {
return txHash;
}
/**
- * Ask the order Provider for orders and process them.
+ * Grab orders from the map, if there is a miss or it is time to refresh, fetch and process the orders
*/
- private async _getLatestOrdersAndFillableAmountsAsync(): Promise<AssetBuyerOrdersAndFillableAmounts> {
+ private 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 ||
+ ordersEntryIfExists.lastRefreshTime + this.orderRefreshIntervalMs < Date.now();
+ if (!shouldRefresh) {
+ const result = ordersEntryIfExists.ordersAndFillableAmounts;
+ return result;
+ }
const etherTokenAssetData = this._getEtherTokenAssetDataOrThrow();
const zrxTokenAssetData = this._getZrxTokenAssetDataOrThrow();
- // construct order Provider requests
- const targetOrderProviderRequest = {
- makerAssetData: this.assetData,
- takerAssetData: etherTokenAssetData,
- networkId: this.networkId,
- };
- const feeOrderProviderRequest = {
- makerAssetData: zrxTokenAssetData,
+ // construct orderProvider request
+ const orderProviderRequest = {
+ makerAssetData: assetData,
takerAssetData: etherTokenAssetData,
networkId: this.networkId,
};
- const requests = [targetOrderProviderRequest, feeOrderProviderRequest];
- // fetch orders and possible fillable amounts
- const [targetOrderProviderResponse, feeOrderProviderResponse] = await Promise.all(
- _.map(requests, async request => this.orderProvider.getOrdersAsync(request)),
- );
+ 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(targetOrderProviderResponse, targetOrderProviderRequest);
- orderProviderResponseProcessor.throwIfInvalidResponse(feeOrderProviderResponse, feeOrderProviderRequest);
+ orderProviderResponseProcessor.throwIfInvalidResponse(response, request);
// process the responses into one object
+ const isMakerAssetZrxToken = assetData === zrxTokenAssetData;
const ordersAndFillableAmounts = await orderProviderResponseProcessor.processAsync(
- targetOrderProviderResponse,
- feeOrderProviderResponse,
- zrxTokenAssetData,
+ response,
+ isMakerAssetZrxToken,
this.expiryBufferSeconds,
this._contractWrappers.orderValidator,
);
+ const lastRefreshTime = Date.now();
+ const updatedOrdersEntry = {
+ ordersAndFillableAmounts,
+ lastRefreshTime,
+ };
+ this._ordersEntryMap[assetData] = updatedOrdersEntry;
return ordersAndFillableAmounts;
}
/**
diff --git a/packages/asset-buyer/src/constants.ts b/packages/asset-buyer/src/constants.ts
index 79b5d9052..e1056e39b 100644
--- a/packages/asset-buyer/src/constants.ts
+++ b/packages/asset-buyer/src/constants.ts
@@ -1,6 +1,15 @@
import { BigNumber } from '@0xproject/utils';
-import { BuyQuoteRequestOpts } from './types';
+import { AssetBuyerOpts, BuyQuoteExecutionOpts, BuyQuoteRequestOpts } 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: 15,
+};
const DEFAULT_BUY_QUOTE_REQUEST_OPTS: BuyQuoteRequestOpts = {
feePercentage: 0,
@@ -8,13 +17,18 @@ const DEFAULT_BUY_QUOTE_REQUEST_OPTS: BuyQuoteRequestOpts = {
slippagePercentage: 0.2, // 20% slippage protection
};
+// Other default values are dynamically determined
+const DEFAULT_BUY_QUOTE_EXECUTION_OPTS: BuyQuoteExecutionOpts = {
+ feeRecipient: NULL_ADDRESS,
+};
+
export const constants = {
ZERO_AMOUNT: new BigNumber(0),
- NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
- MAINNET_NETWORK_ID: 1,
- DEFAULT_ORDER_REFRESH_INTERVAL_MS: 10000, // 10 seconds
+ NULL_ADDRESS,
+ MAINNET_NETWORK_ID,
ETHER_TOKEN_DECIMALS: 18,
+ DEFAULT_ASSET_BUYER_OPTS,
+ DEFAULT_BUY_QUOTE_EXECUTION_OPTS,
DEFAULT_BUY_QUOTE_REQUEST_OPTS,
MAX_PER_PAGE: 10000,
- DEFAULT_EXPIRY_BUFFER_SECONDS: 15,
};
diff --git a/packages/asset-buyer/src/index.ts b/packages/asset-buyer/src/index.ts
index 8ef529ac0..2da2724d7 100644
--- a/packages/asset-buyer/src/index.ts
+++ b/packages/asset-buyer/src/index.ts
@@ -5,13 +5,14 @@ export { BigNumber } from '@0xproject/utils';
export { AssetBuyer } from './asset_buyer';
export { BasicOrderProvider } from './order_providers/basic_order_provider';
export { StandardRelayerAPIOrderProvider } from './order_providers/standard_relayer_api_order_provider';
-export { StandardRelayerAPIAssetBuyerManager } from './standard_relayer_api_asset_buyer_manager';
export {
AssetBuyerError,
+ AssetBuyerOpts,
BuyQuote,
+ BuyQuoteExecutionOpts,
+ BuyQuoteRequestOpts,
OrderProvider,
OrderProviderRequest,
OrderProviderResponse,
SignedOrderWithRemainingFillableMakerAssetAmount,
- StandardRelayerApiAssetBuyerManagerError,
} from './types';
diff --git a/packages/asset-buyer/src/standard_relayer_api_asset_buyer_manager.ts b/packages/asset-buyer/src/standard_relayer_api_asset_buyer_manager.ts
deleted file mode 100644
index 947c738a1..000000000
--- a/packages/asset-buyer/src/standard_relayer_api_asset_buyer_manager.ts
+++ /dev/null
@@ -1,133 +0,0 @@
-import { HttpClient } from '@0xproject/connect';
-import { ContractWrappers } from '@0xproject/contract-wrappers';
-import { ObjectMap } from '@0xproject/types';
-import { Provider } from 'ethereum-types';
-import * as _ from 'lodash';
-
-import { AssetBuyer } from './asset_buyer';
-import { constants } from './constants';
-import { assert } from './utils/assert';
-import { assetDataUtils } from './utils/asset_data_utils';
-
-import { OrderProvider, StandardRelayerApiAssetBuyerManagerError } from './types';
-
-export class StandardRelayerAPIAssetBuyerManager {
- // Map of assetData to AssetBuyer for that assetData
- private readonly _assetBuyerMap: ObjectMap<AssetBuyer>;
- /**
- * Returns an array of all assetDatas available at the provided sraApiUrl
- * @param sraApiUrl The standard relayer API base HTTP url you would like to source orders from.
- * @param pairedWithAssetData Optional filter argument to return assetDatas that only pair with this assetData value.
- *
- * @return An array of all assetDatas available at the provider sraApiUrl
- */
- public static async getAllAvailableAssetDatasAsync(
- sraApiUrl: string,
- pairedWithAssetData?: string,
- ): Promise<string[]> {
- const client = new HttpClient(sraApiUrl);
- const params = {
- assetDataA: pairedWithAssetData,
- perPage: constants.MAX_PER_PAGE,
- };
- const assetPairsResponse = await client.getAssetPairsAsync(params);
- return _.uniq(_.map(assetPairsResponse.records, pairsItem => pairsItem.assetDataB.assetData));
- }
- /**
- * Instantiates a new StandardRelayerAPIAssetBuyerManager instance with all available assetDatas at the provided sraApiUrl
- * @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 orderProvider An object that conforms to OrderProvider, see type for definition.
- * @param networkId The ethereum network id. Defaults to 1 (mainnet).
- * @param orderRefreshIntervalMs The interval in ms that getBuyQuoteAsync should trigger an refresh of orders and order states.
- * Defaults to 10000ms (10s).
- * @return An promise of an instance of StandardRelayerAPIAssetBuyerManager
- */
- public static async getAssetBuyerManagerWithAllAvailableAssetDatasAsync(
- provider: Provider,
- sraApiUrl: string,
- orderProvider: OrderProvider,
- networkId: number = constants.MAINNET_NETWORK_ID,
- orderRefreshIntervalMs?: number,
- ): Promise<StandardRelayerAPIAssetBuyerManager> {
- const contractWrappers = new ContractWrappers(provider, { networkId });
- const etherTokenAssetData = assetDataUtils.getEtherTokenAssetDataOrThrow(contractWrappers);
- const assetDatas = await StandardRelayerAPIAssetBuyerManager.getAllAvailableAssetDatasAsync(
- sraApiUrl,
- etherTokenAssetData,
- );
- return new StandardRelayerAPIAssetBuyerManager(
- provider,
- assetDatas,
- orderProvider,
- networkId,
- orderRefreshIntervalMs,
- );
- }
- /**
- * Instantiates a new StandardRelayerAPIAssetBuyerManager instance
- * @param provider The Provider instance you would like to use for interacting with the Ethereum network.
- * @param assetDatas The assetDatas of the desired assets to buy (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
- * @param orderProvider An object that conforms to OrderProvider, see type for definition.
- * @param networkId The ethereum network id. Defaults to 1 (mainnet).
- * @param orderRefreshIntervalMs The interval in ms that getBuyQuoteAsync should trigger an refresh of orders and order states.
- * Defaults to 10000ms (10s).
- * @return An instance of StandardRelayerAPIAssetBuyerManager
- */
- constructor(
- provider: Provider,
- assetDatas: string[],
- orderProvider: OrderProvider,
- networkId?: number,
- orderRefreshIntervalMs?: number,
- ) {
- assert.assert(assetDatas.length > 0, `Expected 'assetDatas' to be a non-empty array.`);
- this._assetBuyerMap = _.reduce(
- assetDatas,
- (accAssetBuyerMap: ObjectMap<AssetBuyer>, assetData: string) => {
- accAssetBuyerMap[assetData] = new AssetBuyer(
- provider,
- assetData,
- orderProvider,
- networkId,
- orderRefreshIntervalMs,
- );
- return accAssetBuyerMap;
- },
- {},
- );
- }
- /**
- * Get an AssetBuyer for the provided assetData
- * @param assetData The desired assetData.
- *
- * @return An instance of AssetBuyer
- */
- public getAssetBuyerFromAssetData(assetData: string): AssetBuyer {
- const assetBuyer = this._assetBuyerMap[assetData];
- if (_.isUndefined(assetBuyer)) {
- throw new Error(
- `${StandardRelayerApiAssetBuyerManagerError.AssetBuyerNotFound}: For assetData ${assetData}`,
- );
- }
- return assetBuyer;
- }
- /**
- * Get an AssetBuyer for the provided ERC20 tokenAddress
- * @param tokenAddress The desired tokenAddress.
- *
- * @return An instance of AssetBuyer
- */
- public getAssetBuyerFromERC20TokenAddress(tokenAddress: string): AssetBuyer {
- const assetData = assetDataUtils.encodeERC20AssetData(tokenAddress);
- return this.getAssetBuyerFromAssetData(assetData);
- }
- /**
- * Get a list of all the assetDatas that the instance supports
- *
- * @return An array of assetData strings
- */
- public getAssetDatas(): string[] {
- return _.keys(this._assetBuyerMap);
- }
-}
diff --git a/packages/asset-buyer/src/types.ts b/packages/asset-buyer/src/types.ts
index ee6858525..8d3dcbfe6 100644
--- a/packages/asset-buyer/src/types.ts
+++ b/packages/asset-buyer/src/types.ts
@@ -52,6 +52,11 @@ export interface BuyQuote {
feePercentage?: number;
}
+/**
+ * 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;
@@ -59,6 +64,28 @@ export interface BuyQuoteRequestOpts {
}
/**
+ * rate: The desired rate to execute the buy at. Affects the amount of ETH sent with the transaction, defaults to buyQuote.maxRate.
+ * takerAddress: The address to perform the buy. Defaults to the first available address from the provider.
+ * feeRecipient: The address where affiliate fees are sent. Defaults to null address (0x000...000).
+ */
+export interface BuyQuoteExecutionOpts {
+ rate?: BigNumber;
+ takerAddress?: string;
+ 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 15s.
+ */
+export interface AssetBuyerOpts {
+ networkId: number;
+ orderRefreshIntervalMs: number;
+ expiryBufferSeconds: number;
+}
+
+/**
* Possible errors thrown by an AssetBuyer instance or associated static methods.
*/
export enum AssetBuyerError {
@@ -69,18 +96,10 @@ export enum AssetBuyerError {
InsufficientZrxLiquidity = 'INSUFFICIENT_ZRX_LIQUIDITY',
NoAddressAvailable = 'NO_ADDRESS_AVAILABLE',
InvalidOrderProviderResponse = 'INVALID_ORDER_PROVIDER_RESPONSE',
+ AssetUnavailable = 'ASSET_UNAVAILABLE',
}
-/**
- * Possible errors thrown by an StandardRelayerApiAssetBuyerManager instance or associated static methods.
- */
-export enum StandardRelayerApiAssetBuyerManagerError {
- AssetBuyerNotFound = 'ASSET_BUYER_NOT_FOUND',
-}
-
-export interface AssetBuyerOrdersAndFillableAmounts {
+export interface OrdersAndFillableAmounts {
orders: SignedOrder[];
- feeOrders: SignedOrder[];
remainingFillableMakerAssetAmounts: BigNumber[];
- remainingFillableFeeAmounts: BigNumber[];
}
diff --git a/packages/asset-buyer/src/utils/buy_quote_calculator.ts b/packages/asset-buyer/src/utils/buy_quote_calculator.ts
index 9946924ef..b706ea143 100644
--- a/packages/asset-buyer/src/utils/buy_quote_calculator.ts
+++ b/packages/asset-buyer/src/utils/buy_quote_calculator.ts
@@ -3,24 +3,23 @@ import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
import { constants } from '../constants';
-import { AssetBuyerError, AssetBuyerOrdersAndFillableAmounts, BuyQuote } from '../types';
+import { AssetBuyerError, BuyQuote, 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: AssetBuyerOrdersAndFillableAmounts,
+ ordersAndFillableAmounts: OrdersAndFillableAmounts,
+ feeOrdersAndFillableAmounts: OrdersAndFillableAmounts,
assetBuyAmount: BigNumber,
feePercentage: number,
slippagePercentage: number,
): BuyQuote {
- const {
- orders,
- feeOrders,
- remainingFillableMakerAssetAmounts,
- remainingFillableFeeAmounts,
- } = ordersAndFillableAmounts;
+ const orders = ordersAndFillableAmounts.orders;
+ const remainingFillableMakerAssetAmounts = ordersAndFillableAmounts.remainingFillableMakerAssetAmounts;
+ const feeOrders = feeOrdersAndFillableAmounts.orders;
+ const remainingFillableFeeAmounts = feeOrdersAndFillableAmounts.remainingFillableMakerAssetAmounts;
const slippageBufferAmount = assetBuyAmount.mul(slippagePercentage).round();
const {
resultOrders,
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 31fdcc182..74eec162d 100644
--- a/packages/asset-buyer/src/utils/order_provider_response_processor.ts
+++ b/packages/asset-buyer/src/utils/order_provider_response_processor.ts
@@ -8,19 +8,14 @@ import * as _ from 'lodash';
import { constants } from '../constants';
import {
AssetBuyerError,
- AssetBuyerOrdersAndFillableAmounts,
OrderProviderRequest,
OrderProviderResponse,
+ OrdersAndFillableAmounts,
SignedOrderWithRemainingFillableMakerAssetAmount,
} from '../types';
import { orderUtils } from './order_utils';
-interface OrdersAndRemainingFillableMakerAssetAmounts {
- orders: SignedOrder[];
- remainingFillableMakerAssetAmounts: BigNumber[];
-}
-
export const orderProviderResponseProcessor = {
throwIfInvalidResponse(response: OrderProviderResponse, request: OrderProviderRequest): void {
const { makerAssetData, takerAssetData } = request;
@@ -38,65 +33,40 @@ export const orderProviderResponseProcessor = {
* - Sort by rate
*/
async processAsync(
- targetOrderProviderResponse: OrderProviderResponse,
- feeOrderProviderResponse: OrderProviderResponse,
- zrxTokenAssetData: string,
+ orderProviderResponse: OrderProviderResponse,
+ isMakerAssetZrxToken: boolean,
expiryBufferSeconds: number,
orderValidator?: OrderValidatorWrapper,
- ): Promise<AssetBuyerOrdersAndFillableAmounts> {
+ ): Promise<OrdersAndFillableAmounts> {
// drop orders that are expired or not open
- const filteredTargetOrders = filterOutExpiredAndNonOpenOrders(
- targetOrderProviderResponse.orders,
- expiryBufferSeconds,
- );
- const filteredFeeOrders = filterOutExpiredAndNonOpenOrders(
- feeOrderProviderResponse.orders,
- expiryBufferSeconds,
- );
+ const filteredOrders = filterOutExpiredAndNonOpenOrders(orderProviderResponse.orders, expiryBufferSeconds);
// set the orders to be sorted equal to the filtered orders
- let unsortedTargetOrders = filteredTargetOrders;
- let unsortedFeeOrders = filteredFeeOrders;
+ let unsortedOrders = filteredOrders;
// if an orderValidator is provided, use on chain information to calculate remaining fillable makerAsset amounts
if (!_.isUndefined(orderValidator)) {
// TODO(bmillman): improvement
- // try/catch these requests and throw a more domain specific error
- // TODO(bmillman): optimization
- // reduce this to once RPC call buy combining orders into one array and then splitting up the response
- const [targetOrdersAndTradersInfo, feeOrdersAndTradersInfo] = await Promise.all(
- _.map([filteredTargetOrders, filteredFeeOrders], ordersToBeValidated => {
- const takerAddresses = _.map(ordersToBeValidated, () => constants.NULL_ADDRESS);
- return orderValidator.getOrdersAndTradersInfoAsync(ordersToBeValidated, takerAddresses);
- }),
- );
- // take orders + on chain information and find the valid orders and remaining fillable maker asset amounts
- unsortedTargetOrders = getValidOrdersWithRemainingFillableMakerAssetAmountsFromOnChain(
- filteredTargetOrders,
- targetOrdersAndTradersInfo,
- zrxTokenAssetData,
+ // try/catch this request and throw a more domain specific error
+ const takerAddresses = _.map(filteredOrders, () => constants.NULL_ADDRESS);
+ const ordersAndTradersInfo = await orderValidator.getOrdersAndTradersInfoAsync(
+ filteredOrders,
+ takerAddresses,
);
// take orders + on chain information and find the valid orders and remaining fillable maker asset amounts
- unsortedFeeOrders = getValidOrdersWithRemainingFillableMakerAssetAmountsFromOnChain(
- filteredFeeOrders,
- feeOrdersAndTradersInfo,
- zrxTokenAssetData,
+ unsortedOrders = getValidOrdersWithRemainingFillableMakerAssetAmountsFromOnChain(
+ filteredOrders,
+ ordersAndTradersInfo,
+ isMakerAssetZrxToken,
);
}
// 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 sortedTargetOrders = sortingUtils.sortOrdersByFeeAdjustedRate(unsortedTargetOrders);
- const sortedFeeOrders = sortingUtils.sortFeeOrdersByFeeAdjustedRate(unsortedFeeOrders);
+ const sortedOrders = isMakerAssetZrxToken
+ ? sortingUtils.sortFeeOrdersByFeeAdjustedRate(unsortedOrders)
+ : sortingUtils.sortOrdersByFeeAdjustedRate(unsortedOrders);
// unbundle orders and fillable amounts and compile final result
- const targetOrdersAndRemainingFillableMakerAssetAmounts = unbundleOrdersWithAmounts(sortedTargetOrders);
- const feeOrdersAndRemainingFillableMakerAssetAmounts = unbundleOrdersWithAmounts(sortedFeeOrders);
- return {
- orders: targetOrdersAndRemainingFillableMakerAssetAmounts.orders,
- feeOrders: feeOrdersAndRemainingFillableMakerAssetAmounts.orders,
- remainingFillableMakerAssetAmounts:
- targetOrdersAndRemainingFillableMakerAssetAmounts.remainingFillableMakerAssetAmounts,
- remainingFillableFeeAmounts:
- feeOrdersAndRemainingFillableMakerAssetAmounts.remainingFillableMakerAssetAmounts,
- };
+ const result = unbundleOrdersWithAmounts(sortedOrders);
+ return result;
},
};
@@ -120,7 +90,7 @@ function filterOutExpiredAndNonOpenOrders(
function getValidOrdersWithRemainingFillableMakerAssetAmountsFromOnChain(
inputOrders: SignedOrder[],
ordersAndTradersInfo: OrderAndTraderInfo[],
- zrxAssetData: string,
+ 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
@@ -147,7 +117,7 @@ function getValidOrdersWithRemainingFillableMakerAssetAmountsFromOnChain(
const remainingFillableCalculator = new RemainingFillableCalculator(
order.makerFee,
order.makerAssetAmount,
- order.makerAssetData === zrxAssetData,
+ isMakerAssetZrxToken,
transferrableAssetAmount,
transferrableFeeAssetAmount,
remainingMakerAssetAmount,
@@ -175,7 +145,7 @@ function getValidOrdersWithRemainingFillableMakerAssetAmountsFromOnChain(
*/
function unbundleOrdersWithAmounts(
ordersWithAmounts: SignedOrderWithRemainingFillableMakerAssetAmount[],
-): OrdersAndRemainingFillableMakerAssetAmounts {
+): OrdersAndFillableAmounts {
const result = _.reduce(
ordersWithAmounts,
(acc, orderWithAmount) => {