aboutsummaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/asset-buyer/.npmignore (renamed from packages/forwarder-helper/.npmignore)0
-rw-r--r--packages/asset-buyer/CHANGELOG.json10
-rw-r--r--packages/asset-buyer/CHANGELOG.md1
-rw-r--r--packages/asset-buyer/README.md (renamed from packages/forwarder-helper/README.md)16
-rw-r--r--packages/asset-buyer/package.json (renamed from packages/forwarder-helper/package.json)15
-rw-r--r--packages/asset-buyer/src/asset_buyer.ts318
-rw-r--r--packages/asset-buyer/src/constants.ts (renamed from packages/forwarder-helper/src/constants.ts)2
-rw-r--r--packages/asset-buyer/src/globals.d.ts (renamed from packages/forwarder-helper/src/globals.d.ts)0
-rw-r--r--packages/asset-buyer/src/index.ts15
-rw-r--r--packages/asset-buyer/src/order_fetchers/provided_order_fetcher.ts32
-rw-r--r--packages/asset-buyer/src/order_fetchers/standard_relayer_api_order_fetcher.ts78
-rw-r--r--packages/asset-buyer/src/types.ts72
-rw-r--r--packages/asset-buyer/src/utils/assert.ts44
-rw-r--r--packages/asset-buyer/src/utils/buy_quote_calculator.ts60
-rw-r--r--packages/asset-buyer/src/utils/order_fetcher_response_processor.ts184
-rw-r--r--packages/asset-buyer/src/utils/order_utils.ts21
-rw-r--r--packages/asset-buyer/test/utils/chai_setup.ts (renamed from packages/forwarder-helper/test/utils/chai_setup.ts)0
-rw-r--r--packages/asset-buyer/tsconfig.json (renamed from packages/forwarder-helper/tsconfig.json)0
-rw-r--r--packages/asset-buyer/tslint.json (renamed from packages/forwarder-helper/tslint.json)0
-rw-r--r--packages/asset-buyer/typedoc-tsconfig.json (renamed from packages/forwarder-helper/typedoc-tsconfig.json)0
-rw-r--r--packages/connect/src/http_client.ts2
-rw-r--r--packages/forwarder-helper/CHANGELOG.json39
-rw-r--r--packages/forwarder-helper/CHANGELOG.md22
-rw-r--r--packages/forwarder-helper/src/forwarder_helper_factory.ts25
-rw-r--r--packages/forwarder-helper/src/forwarder_helper_impl.ts64
-rw-r--r--packages/forwarder-helper/src/index.ts2
-rw-r--r--packages/forwarder-helper/src/types.ts43
-rw-r--r--packages/forwarder-helper/src/utils/forwarder_helper_impl_config_utils.ts92
-rw-r--r--packages/forwarder-helper/test/forwarder_helper_impl_test.ts136
29 files changed, 856 insertions, 437 deletions
diff --git a/packages/forwarder-helper/.npmignore b/packages/asset-buyer/.npmignore
index 5333847e7..5333847e7 100644
--- a/packages/forwarder-helper/.npmignore
+++ b/packages/asset-buyer/.npmignore
diff --git a/packages/asset-buyer/CHANGELOG.json b/packages/asset-buyer/CHANGELOG.json
new file mode 100644
index 000000000..2e4db1f6e
--- /dev/null
+++ b/packages/asset-buyer/CHANGELOG.json
@@ -0,0 +1,10 @@
+[
+ {
+ "version": "1.0.0-rc.1",
+ "changes": [
+ {
+ "note": "Init"
+ }
+ ]
+ }
+]
diff --git a/packages/asset-buyer/CHANGELOG.md b/packages/asset-buyer/CHANGELOG.md
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/packages/asset-buyer/CHANGELOG.md
@@ -0,0 +1 @@
+
diff --git a/packages/forwarder-helper/README.md b/packages/asset-buyer/README.md
index c74526910..717849bf2 100644
--- a/packages/forwarder-helper/README.md
+++ b/packages/asset-buyer/README.md
@@ -1,25 +1,25 @@
-## @0xproject/forwarder-helper
+## @0xproject/asset-buyer
-Provides convenience objects to help work with the Forwarder Contract
+Convenience package for buying assets
-### Read the [Documentation](https://0xproject.com/docs/forwarder-helper).
+### Read the [Documentation](https://0xproject.com/docs/asset-buyer).
## Installation
```bash
-yarn add @0xproject/forwarder-helper
+yarn add @0xproject/asset-buyer
```
**Import**
```typescript
-import { forwarderHelperFactory } from '@0xproject/forwarder-helper';
+import { AssetBuyer } from '@0xproject/asset-buyer';
```
or
```javascript
-var forwarderHelperFactory = require('@0xproject/forwarder-helper').forwarderHelperFactory;
+var AssetBuyer = require('@0xproject/asset-buyer').AssetBuyer;
```
If your project is in [TypeScript](https://www.typescriptlang.org/), add the following to your `tsconfig.json`:
@@ -55,13 +55,13 @@ yarn install
To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory:
```bash
-PKG=@0xproject/forwarder-helper yarn build
+PKG=@0xproject/asset-buyer yarn build
```
Or continuously rebuild on change:
```bash
-PKG=@0xproject/forwarder-helper yarn watch
+PKG=@0xproject/asset-buyer yarn watch
```
### Clean
diff --git a/packages/forwarder-helper/package.json b/packages/asset-buyer/package.json
index 64613561d..f36044ae2 100644
--- a/packages/forwarder-helper/package.json
+++ b/packages/asset-buyer/package.json
@@ -1,10 +1,10 @@
{
- "name": "@0xproject/forwarder-helper",
- "version": "1.0.2",
+ "name": "@0xproject/asset-buyer",
+ "version": "1.0.0-rc.1",
"engines": {
"node": ">=6.12"
},
- "description": "Convenience object for working with the forwarder contract",
+ "description": "Convenience package for buying assets",
"main": "lib/src/index.js",
"types": "lib/src/index.d.ts",
"scripts": {
@@ -34,21 +34,26 @@
"bugs": {
"url": "https://github.com/0xProject/0x-monorepo/issues"
},
- "homepage": "https://github.com/0xProject/0x-monorepo/packages/forwarder-helper/README.md",
+ "homepage": "https://github.com/0xProject/0x-monorepo/packages/asset-buyer/README.md",
"dependencies": {
"@0xproject/assert": "^1.0.8",
+ "@0xproject/connect": "^2.0.0",
+ "@0xproject/contract-wrappers": "^1.0.1",
"@0xproject/json-schemas": "^1.0.1",
"@0xproject/order-utils": "^1.0.1",
+ "@0xproject/subproviders": "^2.0.2",
"@0xproject/types": "^1.0.1",
"@0xproject/typescript-typings": "^2.0.0",
"@0xproject/utils": "^1.0.8",
- "@types/node": "^8.0.53",
+ "@0xproject/web3-wrapper": "^2.0.2",
+ "ethereum-types": "^1.0.6",
"lodash": "^4.17.10"
},
"devDependencies": {
"@0xproject/tslint-config": "^1.0.7",
"@types/lodash": "^4.14.116",
"@types/mocha": "^2.2.42",
+ "@types/node": "^8.0.53",
"chai": "^4.0.1",
"chai-as-promised": "^7.1.0",
"chai-bignumber": "^2.0.1",
diff --git a/packages/asset-buyer/src/asset_buyer.ts b/packages/asset-buyer/src/asset_buyer.ts
new file mode 100644
index 000000000..fa1118e18
--- /dev/null
+++ b/packages/asset-buyer/src/asset_buyer.ts
@@ -0,0 +1,318 @@
+import { ContractWrappers } from '@0xproject/contract-wrappers';
+import { schemas } from '@0xproject/json-schemas';
+import { assetDataUtils, SignedOrder } from '@0xproject/order-utils';
+import { BigNumber } from '@0xproject/utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import { Provider } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { constants } from './constants';
+import { ProvidedOrderFetcher } from './order_fetchers/provided_order_fetcher';
+import { StandardRelayerAPIOrderFetcher } from './order_fetchers/standard_relayer_api_order_fetcher';
+import {
+ AssetBuyerError,
+ AssetBuyerOrdersAndFillableAmounts,
+ BuyQuote,
+ OrderFetcher,
+ OrderFetcherResponse,
+} from './types';
+
+import { assert } from './utils/assert';
+import { buyQuoteCalculator } from './utils/buy_quote_calculator';
+import { orderFetcherResponseProcessor } from './utils/order_fetcher_response_processor';
+
+const SLIPPAGE_PERCENTAGE = 0.2; // 20% slippage protection, possibly move this into request interface
+const DEFAULT_ORDER_REFRESH_INTERVAL_MS = 10000; // 10 seconds
+const DEFAULT_FEE_PERCENTAGE = 0;
+const ETHER_TOKEN_DECIMALS = 18;
+
+export class AssetBuyer {
+ public readonly provider: Provider;
+ public readonly assetData: string;
+ public readonly orderFetcher: OrderFetcher;
+ public readonly networkId: number;
+ public readonly orderRefreshIntervalMs: number;
+ private readonly _contractWrappers: ContractWrappers;
+ private _lastRefreshTimeIfExists?: number;
+ private _currentOrdersAndFillableAmountsIfExists?: AssetBuyerOrdersAndFillableAmounts;
+ /**
+ * Instantiates a new AssetBuyer instance
+ * @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).
+ * @return An instance of AssetBuyer
+ */
+ public static getAssetBuyerForProvidedOrders(
+ provider: Provider,
+ orders: SignedOrder[],
+ feeOrders: SignedOrder[] = [],
+ networkId: number = constants.MAINNET_NETWORK_ID,
+ orderRefreshIntervalMs: number = DEFAULT_ORDER_REFRESH_INTERVAL_MS,
+ ): 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 orderFetcher = new ProvidedOrderFetcher(_.concat(orders, feeOrders));
+ const assetBuyer = new AssetBuyer(provider, assetData, orderFetcher, networkId, orderRefreshIntervalMs);
+ 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 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).
+ * @return An instance of AssetBuyer
+ */
+ public static getAssetBuyerForAssetData(
+ provider: Provider,
+ assetData: string,
+ sraApiUrl: string,
+ networkId: number = constants.MAINNET_NETWORK_ID,
+ orderRefreshIntervalMs: number = DEFAULT_ORDER_REFRESH_INTERVAL_MS,
+ ): AssetBuyer {
+ assert.isWeb3Provider('provider', provider);
+ assert.isHexString('assetData', assetData);
+ assert.isWebUri('sraApiUrl', sraApiUrl);
+ assert.isNumber('networkId', networkId);
+ assert.isNumber('orderRefreshIntervalMs', orderRefreshIntervalMs);
+ const orderFetcher = new StandardRelayerAPIOrderFetcher(sraApiUrl);
+ const assetBuyer = new AssetBuyer(provider, assetData, orderFetcher, networkId, orderRefreshIntervalMs);
+ return assetBuyer;
+ }
+ /**
+ * Instantiates a new AssetBuyer instance
+ * @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).
+ * @return An instance of AssetBuyer
+ */
+ public static getAssetBuyerForERC20TokenAddress(
+ provider: Provider,
+ tokenAddress: string,
+ sraApiUrl: string,
+ networkId: number = constants.MAINNET_NETWORK_ID,
+ orderRefreshIntervalMs: number = DEFAULT_ORDER_REFRESH_INTERVAL_MS,
+ ): 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,
+ );
+ 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 orderFetcher An object that conforms to OrderFetcher, 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 AssetBuyer
+ */
+ constructor(
+ provider: Provider,
+ assetData: string,
+ orderFetcher: OrderFetcher,
+ networkId: number = constants.MAINNET_NETWORK_ID,
+ orderRefreshIntervalMs: number = DEFAULT_ORDER_REFRESH_INTERVAL_MS,
+ ) {
+ assert.isWeb3Provider('provider', provider);
+ assert.isString('assetData', assetData);
+ assert.isValidOrderFetcher('orderFetcher', orderFetcher);
+ assert.isNumber('networkId', networkId);
+ assert.isNumber('orderRefreshIntervalMs', orderRefreshIntervalMs);
+ this.provider = provider;
+ this.assetData = assetData;
+ this.orderFetcher = orderFetcher;
+ this.networkId = networkId;
+ this.orderRefreshIntervalMs = orderRefreshIntervalMs;
+ this._contractWrappers = new ContractWrappers(this.provider, {
+ networkId,
+ });
+ }
+ /**
+ * Get a BuyQuote containing all information relevant to fulfilling a buy.
+ * Pass the BuyQuote to executeBuyQuoteAsync to execute the buy.
+ * @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.
+ * @return An object that conforms to BuyQuote that satisfies the request. See type definition for more information.
+ */
+ public async getBuyQuoteAsync(
+ assetBuyAmount: BigNumber,
+ feePercentage: number = DEFAULT_FEE_PERCENTAGE,
+ forceOrderRefresh: boolean = false,
+ ): Promise<BuyQuote> {
+ assert.isBigNumber('assetBuyAmount', assetBuyAmount);
+ assert.isNumber('feePercentage', feePercentage);
+ assert.isBoolean('forceOrderRefresh', forceOrderRefresh);
+ // 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) ||
+ forceOrderRefresh ||
+ (!_.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;
+ }
+ // TODO: optimization
+ // make the slippage percentage customizable by integrator
+ const buyQuote = buyQuoteCalculator.calculate(
+ ordersAndFillableAmounts,
+ assetBuyAmount,
+ feePercentage,
+ SLIPPAGE_PERCENTAGE,
+ );
+ 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).
+ * @return A promise of the txHash.
+ */
+ public async executeBuyQuoteAsync(
+ buyQuote: BuyQuote,
+ rate?: BigNumber,
+ takerAddress?: string,
+ feeRecipient: string = constants.NULL_ADDRESS,
+ ): Promise<string> {
+ assert.isValidBuyQuote('buyQuote', buyQuote);
+ if (!_.isUndefined(rate)) {
+ assert.isBigNumber('rate', rate);
+ }
+ if (!_.isUndefined(takerAddress)) {
+ assert.isETHAddressHex('takerAddress', takerAddress);
+ }
+ assert.isETHAddressHex('feeRecipient', feeRecipient);
+ const { orders, feeOrders, feePercentage, assetBuyAmount, maxRate } = 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);
+ }
+ }
+ // if no rate is provided, default to the maxRate from buyQuote
+ const desiredRate = rate || maxRate;
+ // calculate how much eth is required to buy assetBuyAmount at the desired rate
+ const ethAmount = assetBuyAmount.dividedToIntegerBy(desiredRate);
+ // TODO: critical
+ // update the forwarder wrapper to take in feePercentage as a number instead of a BigNumber, verify with Amir that this is being done correctly
+ const feePercentageBigNumber = !_.isUndefined(feePercentage)
+ ? Web3Wrapper.toBaseUnitAmount(new BigNumber(1), ETHER_TOKEN_DECIMALS).mul(feePercentage)
+ : constants.ZERO_AMOUNT;
+ const txHash = await this._contractWrappers.forwarder.marketBuyOrdersWithEthAsync(
+ orders,
+ assetBuyAmount,
+ finalTakerAddress,
+ ethAmount,
+ feeOrders,
+ feePercentageBigNumber,
+ feeRecipient,
+ );
+ return txHash;
+ }
+ /**
+ * Ask the order fetcher for orders and process them.
+ */
+ private async _getLatestOrdersAndFillableAmountsAsync(): Promise<AssetBuyerOrdersAndFillableAmounts> {
+ // find ether token asset data
+ const etherTokenAssetData = this._getEtherTokenAssetData();
+ // find zrx token asset data
+ const zrxTokenAssetData = this._getZrxTokenAssetData();
+ // construct order fetcher requests
+ const targetOrderFetcherRequest = {
+ makerAssetData: this.assetData,
+ takerAssetData: etherTokenAssetData,
+ networkId: this.networkId,
+ };
+ const feeOrderFetcherRequest = {
+ makerAssetData: zrxTokenAssetData,
+ takerAssetData: etherTokenAssetData,
+ networkId: this.networkId,
+ };
+ const requests = [targetOrderFetcherRequest, feeOrderFetcherRequest];
+ // fetch orders and possible fillable amounts
+ const [targetOrderFetcherResponse, feeOrderFetcherResponse] = await Promise.all(
+ _.map(requests, async request => this.orderFetcher.fetchOrdersAsync(request)),
+ );
+ // process the responses into one object
+ const ordersAndFillableAmounts = await orderFetcherResponseProcessor.processAsync(
+ targetOrderFetcherResponse,
+ feeOrderFetcherResponse,
+ zrxTokenAssetData,
+ this._contractWrappers.orderValidator,
+ );
+ return ordersAndFillableAmounts;
+ }
+ /**
+ * Get the assetData that represents the WETH token.
+ * Will throw if WETH does not exist for the current network.
+ */
+ private _getEtherTokenAssetData(): string {
+ const etherTokenAddressIfExists = this._contractWrappers.etherToken.getContractAddressIfExists();
+ if (_.isUndefined(etherTokenAddressIfExists)) {
+ throw new Error(AssetBuyerError.NoEtherTokenContractFound);
+ }
+ const etherTokenAssetData = assetDataUtils.encodeERC20AssetData(etherTokenAddressIfExists);
+ return etherTokenAssetData;
+ }
+ /**
+ * Get the assetData that represents the ZRX token.
+ * Will throw if ZRX does not exist for the current network.
+ */
+ private _getZrxTokenAssetData(): string {
+ let zrxTokenAssetData: string;
+ try {
+ zrxTokenAssetData = this._contractWrappers.exchange.getZRXAssetData();
+ } catch (err) {
+ throw new Error(AssetBuyerError.NoZrxTokenContractFound);
+ }
+ return zrxTokenAssetData;
+ }
+}
diff --git a/packages/forwarder-helper/src/constants.ts b/packages/asset-buyer/src/constants.ts
index 0ad30e4c0..5785e705b 100644
--- a/packages/forwarder-helper/src/constants.ts
+++ b/packages/asset-buyer/src/constants.ts
@@ -2,4 +2,6 @@ import { BigNumber } from '@0xproject/utils';
export const constants = {
ZERO_AMOUNT: new BigNumber(0),
+ NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
+ MAINNET_NETWORK_ID: 1,
};
diff --git a/packages/forwarder-helper/src/globals.d.ts b/packages/asset-buyer/src/globals.d.ts
index 94e63a32d..94e63a32d 100644
--- a/packages/forwarder-helper/src/globals.d.ts
+++ b/packages/asset-buyer/src/globals.d.ts
diff --git a/packages/asset-buyer/src/index.ts b/packages/asset-buyer/src/index.ts
new file mode 100644
index 000000000..efd3523fd
--- /dev/null
+++ b/packages/asset-buyer/src/index.ts
@@ -0,0 +1,15 @@
+export { Provider } from 'ethereum-types';
+export { SignedOrder } from '@0xproject/types';
+export { BigNumber } from '@0xproject/utils';
+
+export { AssetBuyer } from './asset_buyer';
+export { ProvidedOrderFetcher } from './order_fetchers/provided_order_fetcher';
+export { StandardRelayerAPIOrderFetcher } from './order_fetchers/standard_relayer_api_order_fetcher';
+export {
+ AssetBuyerError,
+ BuyQuote,
+ OrderFetcher,
+ OrderFetcherRequest,
+ OrderFetcherResponse,
+ SignedOrderWithRemainingFillableMakerAssetAmount,
+} from './types';
diff --git a/packages/asset-buyer/src/order_fetchers/provided_order_fetcher.ts b/packages/asset-buyer/src/order_fetchers/provided_order_fetcher.ts
new file mode 100644
index 000000000..7a7ffcdfe
--- /dev/null
+++ b/packages/asset-buyer/src/order_fetchers/provided_order_fetcher.ts
@@ -0,0 +1,32 @@
+import { schemas } from '@0xproject/json-schemas';
+import { SignedOrder } from '@0xproject/types';
+import * as _ from 'lodash';
+
+import { OrderFetcher, OrderFetcherRequest, OrderFetcherResponse } from '../types';
+import { assert } from '../utils/assert';
+
+export class ProvidedOrderFetcher implements OrderFetcher {
+ public readonly providedOrders: SignedOrder[];
+ /**
+ * Instantiates a new ProvidedOrderFetcher instance
+ * @param providedOrders An array of objects that conform to SignedOrder to fetch from.
+ * @return An instance of ProvidedOrderFetcher
+ */
+ constructor(providedOrders: SignedOrder[]) {
+ assert.doesConformToSchema('providedOrders', providedOrders, schemas.signedOrdersSchema);
+ this.providedOrders = providedOrders;
+ }
+ /**
+ * Given an object that conforms to OrderFetcherRequest, return the corresponding OrderFetcherResponse that satisfies the request.
+ * @param orderFetchRequest An instance of OrderFetcherRequest. See type for more information.
+ * @return An instance of OrderFetcherResponse. See type for more information.
+ */
+ public async fetchOrdersAsync(orderFetchRequest: OrderFetcherRequest): Promise<OrderFetcherResponse> {
+ assert.isValidOrderFetcherRequest('orderFetchRequest', orderFetchRequest);
+ const { makerAssetData, takerAssetData } = orderFetchRequest;
+ const orders = _.filter(this.providedOrders, order => {
+ return order.makerAssetData === makerAssetData && order.takerAssetData === takerAssetData;
+ });
+ return { orders };
+ }
+}
diff --git a/packages/asset-buyer/src/order_fetchers/standard_relayer_api_order_fetcher.ts b/packages/asset-buyer/src/order_fetchers/standard_relayer_api_order_fetcher.ts
new file mode 100644
index 000000000..3b082b68d
--- /dev/null
+++ b/packages/asset-buyer/src/order_fetchers/standard_relayer_api_order_fetcher.ts
@@ -0,0 +1,78 @@
+import { APIOrder, HttpClient, OrderbookResponse } from '@0xproject/connect';
+import * as _ from 'lodash';
+
+import {
+ AssetBuyerError,
+ OrderFetcher,
+ OrderFetcherRequest,
+ OrderFetcherResponse,
+ SignedOrderWithRemainingFillableMakerAssetAmount,
+} from '../types';
+import { assert } from '../utils/assert';
+import { orderUtils } from '../utils/order_utils';
+
+export class StandardRelayerAPIOrderFetcher implements OrderFetcher {
+ public readonly apiUrl: string;
+ 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.calculateRemainingMakerAssetAmount(
+ order,
+ remainingFillableTakerAssetAmount,
+ );
+ const newOrder = {
+ ...order,
+ remainingFillableMakerAssetAmount,
+ };
+ return newOrder;
+ });
+ return result;
+ }
+ /**
+ * Instantiates a new StandardRelayerAPIOrderFetcher instance
+ * @param apiUrl The standard relayer API base HTTP url you would like to source orders from.
+ * @return An instance of StandardRelayerAPIOrderFetcher
+ */
+ constructor(apiUrl: string) {
+ assert.isWebUri('apiUrl', apiUrl);
+ this.apiUrl = apiUrl;
+ this._sraClient = new HttpClient(apiUrl);
+ }
+ /**
+ * Given an object that conforms to OrderFetcherRequest, return the corresponding OrderFetcherResponse that satisfies the request.
+ * @param orderFetchRequest An instance of OrderFetcherRequest. See type for more information.
+ * @return An instance of OrderFetcherResponse. See type for more information.
+ */
+ public async fetchOrdersAsync(orderFetchRequest: OrderFetcherRequest): Promise<OrderFetcherResponse> {
+ assert.isValidOrderFetcherRequest('orderFetchRequest', orderFetchRequest);
+ const { makerAssetData, takerAssetData, networkId } = orderFetchRequest;
+ const orderbookRequest = { baseAssetData: makerAssetData, quoteAssetData: takerAssetData };
+ const requestOpts = { 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 = StandardRelayerAPIOrderFetcher._getSignedOrderWithRemainingFillableMakerAssetAmountFromApi(
+ apiOrders,
+ );
+ return {
+ orders,
+ };
+ }
+}
diff --git a/packages/asset-buyer/src/types.ts b/packages/asset-buyer/src/types.ts
new file mode 100644
index 000000000..0da30f48d
--- /dev/null
+++ b/packages/asset-buyer/src/types.ts
@@ -0,0 +1,72 @@
+import { SignedOrder } from '@0xproject/types';
+import { BigNumber } from '@0xproject/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 OrderFetcherRequest {
+ makerAssetData: string;
+ takerAssetData: string;
+ networkId: number;
+}
+
+/**
+ * orders: An array of orders with optional remaining fillable makerAsset amounts. See type for more info.
+ */
+export interface OrderFetcherResponse {
+ 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;
+}
+/**
+ * Given an OrderFetchRequest, get an OrderFetchResponse.
+ */
+export interface OrderFetcher {
+ fetchOrdersAsync: (orderFetchRequest: OrderFetcherRequest) => Promise<OrderFetcherResponse>;
+}
+
+/**
+ * assetData: String that represents a specific asset (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
+ * 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.
+ * minRate: Min rate that needs to be paid in order to execute the buy.
+ * maxRate: Max rate that can be paid in order to execute the buy.
+ * assetBuyAmount: The amount of asset to buy.
+ * feePercentage: Optional affiliate fee percentage used to calculate the eth amounts above.
+ */
+export interface BuyQuote {
+ assetData: string;
+ orders: SignedOrder[];
+ feeOrders: SignedOrder[];
+ minRate: BigNumber;
+ maxRate: BigNumber;
+ assetBuyAmount: BigNumber;
+ feePercentage?: number;
+}
+
+/**
+ * Possible errors 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',
+}
+
+export interface AssetBuyerOrdersAndFillableAmounts {
+ orders: SignedOrderWithRemainingFillableMakerAssetAmount[];
+ feeOrders: SignedOrderWithRemainingFillableMakerAssetAmount[];
+ remainingFillableMakerAssetAmounts: BigNumber[];
+ remainingFillableFeeAmounts: BigNumber[];
+}
diff --git a/packages/asset-buyer/src/utils/assert.ts b/packages/asset-buyer/src/utils/assert.ts
new file mode 100644
index 000000000..edc90608c
--- /dev/null
+++ b/packages/asset-buyer/src/utils/assert.ts
@@ -0,0 +1,44 @@
+import { assert as sharedAssert } from '@0xproject/assert';
+import { schemas } from '@0xproject/json-schemas';
+import { SignedOrder } from '@0xproject/types';
+import * as _ from 'lodash';
+
+import { BuyQuote, OrderFetcher, OrderFetcherRequest } 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);
+ sharedAssert.isBigNumber(`${variableName}.minRate`, buyQuote.minRate);
+ sharedAssert.isBigNumber(`${variableName}.maxRate`, buyQuote.maxRate);
+ sharedAssert.isBigNumber(`${variableName}.assetBuyAmount`, buyQuote.assetBuyAmount);
+ if (!_.isUndefined(buyQuote.feePercentage)) {
+ sharedAssert.isNumber(`${variableName}.feePercentage`, buyQuote.feePercentage);
+ }
+ },
+ isValidOrderFetcher(variableName: string, orderFetcher: OrderFetcher): void {
+ sharedAssert.isFunction(`${variableName}.fetchOrdersAsync`, orderFetcher.fetchOrdersAsync);
+ },
+ isValidOrderFetcherRequest(variableName: string, orderFetcherRequest: OrderFetcherRequest): void {
+ sharedAssert.isHexString(`${variableName}.makerAssetData`, orderFetcherRequest.makerAssetData);
+ sharedAssert.isHexString(`${variableName}.takerAssetData`, orderFetcherRequest.takerAssetData);
+ sharedAssert.isNumber(`${variableName}.networkId`, orderFetcherRequest.networkId);
+ },
+ areValidProvidedOrders(variableName: string, orders: SignedOrder[]): void {
+ if (orders.length === 0) {
+ return;
+ }
+ const makerAssetData = orders[0].makerAssetData;
+ const takerAssetData = orders[0].takerAssetData;
+ const filteredOrders = _.filter(
+ orders,
+ order => order.makerAssetData === makerAssetData && order.takerAssetData === takerAssetData,
+ );
+ sharedAssert.assert(
+ orders.length === filteredOrders.length,
+ `Expected all orders in ${variableName} to have the same makerAssetData and takerAssetData.`,
+ );
+ },
+};
diff --git a/packages/asset-buyer/src/utils/buy_quote_calculator.ts b/packages/asset-buyer/src/utils/buy_quote_calculator.ts
new file mode 100644
index 000000000..e05ab1e55
--- /dev/null
+++ b/packages/asset-buyer/src/utils/buy_quote_calculator.ts
@@ -0,0 +1,60 @@
+import { marketUtils } from '@0xproject/order-utils';
+import { BigNumber } from '@0xproject/utils';
+
+import { constants } from '../constants';
+import { AssetBuyerError, AssetBuyerOrdersAndFillableAmounts, BuyQuote } from '../types';
+
+export const buyQuoteCalculator = {
+ calculate(
+ ordersAndFillableAmounts: AssetBuyerOrdersAndFillableAmounts,
+ assetBuyAmount: BigNumber,
+ feePercentage: number,
+ slippagePercentage: number,
+ ): BuyQuote {
+ const {
+ orders,
+ feeOrders,
+ remainingFillableMakerAssetAmounts,
+ remainingFillableFeeAmounts,
+ } = ordersAndFillableAmounts;
+ const slippageBufferAmount = assetBuyAmount.mul(slippagePercentage).round();
+ const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
+ orders,
+ assetBuyAmount,
+ {
+ remainingFillableMakerAssetAmounts,
+ slippageBufferAmount,
+ },
+ );
+ if (remainingFillAmount.gt(constants.ZERO_AMOUNT)) {
+ throw new Error(AssetBuyerError.InsufficientAssetLiquidity);
+ }
+ // TODO: 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
+ const { resultFeeOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
+ resultOrders,
+ feeOrders,
+ {
+ remainingFillableMakerAssetAmounts,
+ remainingFillableFeeAmounts,
+ },
+ );
+ if (remainingFeeAmount.gt(constants.ZERO_AMOUNT)) {
+ throw new Error(AssetBuyerError.InsufficientZrxLiquidity);
+ }
+ const assetData = orders[0].makerAssetData;
+ // TODO: critical
+ // calculate minRate and maxRate by calculating min and max eth usage and then dividing into
+ // assetBuyAmount to get assetData / WETH, needs to take into account feePercentage as well
+ return {
+ assetData,
+ orders: resultOrders,
+ feeOrders: resultFeeOrders,
+ minRate: constants.ZERO_AMOUNT,
+ maxRate: constants.ZERO_AMOUNT,
+ assetBuyAmount,
+ feePercentage,
+ };
+ },
+};
diff --git a/packages/asset-buyer/src/utils/order_fetcher_response_processor.ts b/packages/asset-buyer/src/utils/order_fetcher_response_processor.ts
new file mode 100644
index 000000000..f1116a80f
--- /dev/null
+++ b/packages/asset-buyer/src/utils/order_fetcher_response_processor.ts
@@ -0,0 +1,184 @@
+import { OrderAndTraderInfo, OrderStatus, OrderValidatorWrapper } from '@0xproject/contract-wrappers';
+import { sortingUtils } from '@0xproject/order-utils';
+import { RemainingFillableCalculator } from '@0xproject/order-utils/lib/src/remaining_fillable_calculator';
+import { SignedOrder } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+import * as _ from 'lodash';
+
+import { constants } from '../constants';
+import {
+ AssetBuyerOrdersAndFillableAmounts,
+ OrderFetcherResponse,
+ SignedOrderWithRemainingFillableMakerAssetAmount,
+} from '../types';
+
+import { orderUtils } from './order_utils';
+
+interface OrdersAndRemainingFillableMakerAssetAmounts {
+ orders: SignedOrder[];
+ remainingFillableMakerAssetAmounts: BigNumber[];
+}
+
+export const orderFetcherResponseProcessor = {
+ /**
+ * 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(
+ targetOrderFetcherResponse: OrderFetcherResponse,
+ feeOrderFetcherResponse: OrderFetcherResponse,
+ zrxTokenAssetData: string,
+ orderValidator?: OrderValidatorWrapper,
+ ): Promise<AssetBuyerOrdersAndFillableAmounts> {
+ // drop orders that are expired or not open
+ const filteredTargetOrders = filterOutExpiredAndNonOpenOrders(targetOrderFetcherResponse.orders);
+ const filteredFeeOrders = filterOutExpiredAndNonOpenOrders(feeOrderFetcherResponse.orders);
+ // set the orders to be sorted equal to the filtered orders
+ let unsortedTargetOrders = filteredTargetOrders;
+ let unsortedFeeOrders = filteredFeeOrders;
+ // if an orderValidator is provided, use on chain information to calculate remaining fillable makerAsset amounts
+ if (!_.isUndefined(orderValidator)) {
+ // TODO: critical
+ // try catch these requests and throw a more domain specific error
+ // TODO: 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,
+ );
+ // take orders + on chain information and find the valid orders and remaining fillable maker asset amounts
+ unsortedFeeOrders = getValidOrdersWithRemainingFillableMakerAssetAmountsFromOnChain(
+ filteredFeeOrders,
+ feeOrdersAndTradersInfo,
+ zrxTokenAssetData,
+ );
+ }
+ // sort orders by rate
+ // TODO: 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);
+ // 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,
+ };
+ },
+};
+
+/**
+ * Given an array of orders, return a new array with expired and non open orders filtered out.
+ */
+function filterOutExpiredAndNonOpenOrders(
+ orders: SignedOrderWithRemainingFillableMakerAssetAmount[],
+): SignedOrderWithRemainingFillableMakerAssetAmount[] {
+ const result = _.filter(orders, order => {
+ return orderUtils.isOpenOrder(order) && orderUtils.isOrderExpired(order);
+ });
+ 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[],
+ zrxAssetData: string,
+): 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.calculateRemainingMakerAssetAmount(
+ order,
+ remainingTakerAssetAmount,
+ );
+ const remainingFillableCalculator = new RemainingFillableCalculator(
+ order.makerFee,
+ order.makerAssetAmount,
+ order.makerAssetData === zrxAssetData,
+ 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[],
+): OrdersAndRemainingFillableMakerAssetAmounts {
+ 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
new file mode 100644
index 000000000..bb0bdb80f
--- /dev/null
+++ b/packages/asset-buyer/src/utils/order_utils.ts
@@ -0,0 +1,21 @@
+import { SignedOrder } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+
+import { constants } from '../constants';
+
+export const orderUtils = {
+ isOrderExpired(order: SignedOrder): boolean {
+ const millisecondsInSecond = 1000;
+ const currentUnixTimestampSec = new BigNumber(Date.now() / millisecondsInSecond).round();
+ return order.expirationTimeSeconds.lessThan(currentUnixTimestampSec);
+ },
+ calculateRemainingMakerAssetAmount(order: SignedOrder, remainingTakerAssetAmount: BigNumber): BigNumber {
+ const result = remainingTakerAssetAmount.eq(0)
+ ? constants.ZERO_AMOUNT
+ : remainingTakerAssetAmount.times(order.makerAssetAmount).dividedToIntegerBy(order.takerAssetAmount);
+ return result;
+ },
+ isOpenOrder(order: SignedOrder): boolean {
+ return order.takerAddress === constants.NULL_ADDRESS;
+ },
+};
diff --git a/packages/forwarder-helper/test/utils/chai_setup.ts b/packages/asset-buyer/test/utils/chai_setup.ts
index 1a8733093..1a8733093 100644
--- a/packages/forwarder-helper/test/utils/chai_setup.ts
+++ b/packages/asset-buyer/test/utils/chai_setup.ts
diff --git a/packages/forwarder-helper/tsconfig.json b/packages/asset-buyer/tsconfig.json
index 2ee711adc..2ee711adc 100644
--- a/packages/forwarder-helper/tsconfig.json
+++ b/packages/asset-buyer/tsconfig.json
diff --git a/packages/forwarder-helper/tslint.json b/packages/asset-buyer/tslint.json
index ffaefe83a..ffaefe83a 100644
--- a/packages/forwarder-helper/tslint.json
+++ b/packages/asset-buyer/tslint.json
diff --git a/packages/forwarder-helper/typedoc-tsconfig.json b/packages/asset-buyer/typedoc-tsconfig.json
index c9b0af1ae..c9b0af1ae 100644
--- a/packages/forwarder-helper/typedoc-tsconfig.json
+++ b/packages/asset-buyer/typedoc-tsconfig.json
diff --git a/packages/connect/src/http_client.ts b/packages/connect/src/http_client.ts
index 87d5c30be..bdcfdd8d2 100644
--- a/packages/connect/src/http_client.ts
+++ b/packages/connect/src/http_client.ts
@@ -29,7 +29,7 @@ const TRAILING_SLASHES_REGEX = /\/+$/;
/**
* This class includes all the functionality related to interacting with a set of HTTP endpoints
- * that implement the standard relayer API v0
+ * that implement the standard relayer API v2
*/
export class HttpClient implements Client {
private readonly _apiEndpointUrl: string;
diff --git a/packages/forwarder-helper/CHANGELOG.json b/packages/forwarder-helper/CHANGELOG.json
deleted file mode 100644
index b505cb7d1..000000000
--- a/packages/forwarder-helper/CHANGELOG.json
+++ /dev/null
@@ -1,39 +0,0 @@
-[
- {
- "timestamp": 1537265493,
- "version": "1.0.2",
- "changes": [
- {
- "note": "Dependencies updated"
- }
- ]
- },
- {
- "timestamp": 1536142250,
- "version": "1.0.1",
- "changes": [
- {
- "note": "Dependencies updated"
- }
- ]
- },
- {
- "version": "1.0.1-rc.2",
- "changes": [
- {
- "note": "Dependencies updated"
- }
- ],
- "timestamp": 1535377027
- },
- {
- "version": "1.0.1-rc.1",
- "changes": [
- {
- "note": "Add initial forwarderHelperFactory",
- "pr": 997
- }
- ],
- "timestamp": 1535133899
- }
-]
diff --git a/packages/forwarder-helper/CHANGELOG.md b/packages/forwarder-helper/CHANGELOG.md
deleted file mode 100644
index 23cba8f46..000000000
--- a/packages/forwarder-helper/CHANGELOG.md
+++ /dev/null
@@ -1,22 +0,0 @@
-<!--
-changelogUtils.file is auto-generated using the monorepo-scripts package. Don't edit directly.
-Edit the package's CHANGELOG.json file only.
--->
-
-CHANGELOG
-
-## v1.0.2 - _September 18, 2018_
-
- * Dependencies updated
-
-## v1.0.1 - _September 5, 2018_
-
- * Dependencies updated
-
-## v1.0.1-rc.2 - _August 27, 2018_
-
- * Dependencies updated
-
-## v1.0.1-rc.1 - _August 24, 2018_
-
- * Add initial forwarderHelperFactory (#997)
diff --git a/packages/forwarder-helper/src/forwarder_helper_factory.ts b/packages/forwarder-helper/src/forwarder_helper_factory.ts
deleted file mode 100644
index 95f11f555..000000000
--- a/packages/forwarder-helper/src/forwarder_helper_factory.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { assert } from '@0xproject/assert';
-import { schemas } from '@0xproject/json-schemas';
-import { SignedOrder } from '@0xproject/types';
-
-import { ForwarderHelperImpl, ForwarderHelperImplConfig } from './forwarder_helper_impl';
-import { ForwarderHelper } from './types';
-
-export const forwarderHelperFactory = {
- /**
- * Given an array of orders and an array of feeOrders
- * @param orders An array of objects conforming to SignedOrder. Each order should specify the same makerAssetData and takerAssetData
- * @param feeOrders An array of objects conforming to SignedOrder. Each order should specify ZRX as makerAssetData WETH as takerAssetData
- * @return A ForwarderHelper, see type for definition
- */
- getForwarderHelperForOrders(orders: SignedOrder[], feeOrders: SignedOrder[] = []): ForwarderHelper {
- assert.doesConformToSchema('orders', orders, schemas.signedOrdersSchema);
- assert.doesConformToSchema('feeOrders', orders, schemas.signedOrdersSchema);
- const config: ForwarderHelperImplConfig = {
- orders,
- feeOrders,
- };
- const helper = new ForwarderHelperImpl(config);
- return helper;
- },
-};
diff --git a/packages/forwarder-helper/src/forwarder_helper_impl.ts b/packages/forwarder-helper/src/forwarder_helper_impl.ts
deleted file mode 100644
index a90edb0bb..000000000
--- a/packages/forwarder-helper/src/forwarder_helper_impl.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import { marketUtils } from '@0xproject/order-utils';
-import { SignedOrder } from '@0xproject/types';
-import { BigNumber } from '@0xproject/utils';
-import * as _ from 'lodash';
-
-import { constants } from './constants';
-import { ForwarderHelper, ForwarderHelperError, MarketBuyOrdersInfo, MarketBuyOrdersInfoRequest } from './types';
-import { forwarderHelperImplConfigUtils } from './utils/forwarder_helper_impl_config_utils';
-
-const SLIPPAGE_PERCENTAGE = new BigNumber(0.2); // 20% slippage protection, possibly move this into request interface
-
-export interface ForwarderHelperImplConfig {
- orders: SignedOrder[];
- feeOrders: SignedOrder[];
- remainingFillableMakerAssetAmounts?: BigNumber[];
- remainingFillableFeeAmounts?: BigNumber[];
-}
-
-export class ForwarderHelperImpl implements ForwarderHelper {
- public readonly config: ForwarderHelperImplConfig;
- constructor(config: ForwarderHelperImplConfig) {
- this.config = forwarderHelperImplConfigUtils.sortedConfig(config);
- }
- public getMarketBuyOrdersInfo(request: MarketBuyOrdersInfoRequest): MarketBuyOrdersInfo {
- const { makerAssetFillAmount, feePercentage } = request;
- const { orders, feeOrders, remainingFillableMakerAssetAmounts, remainingFillableFeeAmounts } = this.config;
- // TODO: make the slippage percentage customizable
- const slippageBufferAmount = makerAssetFillAmount.mul(SLIPPAGE_PERCENTAGE).round();
- const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
- orders,
- makerAssetFillAmount,
- {
- remainingFillableMakerAssetAmounts,
- slippageBufferAmount,
- },
- );
- if (remainingFillAmount.gt(constants.ZERO_AMOUNT)) {
- throw new Error(ForwarderHelperError.InsufficientMakerAssetLiquidity);
- }
- // TODO: 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
- const { resultFeeOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
- resultOrders,
- feeOrders,
- {
- remainingFillableMakerAssetAmounts,
- remainingFillableFeeAmounts,
- },
- );
- if (remainingFeeAmount.gt(constants.ZERO_AMOUNT)) {
- throw new Error(ForwarderHelperError.InsufficientZrxLiquidity);
- }
- // TODO: calculate min and max eth usage
- // TODO: optimize orders call data
- return {
- makerAssetFillAmount,
- orders: resultOrders,
- feeOrders: resultFeeOrders,
- minEthAmount: constants.ZERO_AMOUNT,
- maxEthAmount: constants.ZERO_AMOUNT,
- feePercentage,
- };
- }
-}
diff --git a/packages/forwarder-helper/src/index.ts b/packages/forwarder-helper/src/index.ts
deleted file mode 100644
index eb3a34bd5..000000000
--- a/packages/forwarder-helper/src/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { forwarderHelperFactory } from './forwarder_helper_factory';
-export { ForwarderHelper, ForwarderHelperError, MarketBuyOrdersInfoRequest, MarketBuyOrdersInfo } from './types';
diff --git a/packages/forwarder-helper/src/types.ts b/packages/forwarder-helper/src/types.ts
deleted file mode 100644
index fb171cc90..000000000
--- a/packages/forwarder-helper/src/types.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import { SignedOrder } from '@0xproject/types';
-import { BigNumber } from '@0xproject/utils';
-
-export interface ForwarderHelper {
- /**
- * Given a MarketBuyOrdersInfoRequest, returns a MarketBuyOrdersInfo containing all information relevant to fulfilling the request
- * using the ForwarderContract marketBuyOrdersWithEth function.
- * @param request An object that conforms to MarketBuyOrdersInfoRequest. See type definition for more information.
- * @return An object that conforms to MarketBuyOrdersInfo that satisfies the request. See type definition for more information.
- */
- getMarketBuyOrdersInfo: (request: MarketBuyOrdersInfoRequest) => MarketBuyOrdersInfo;
-}
-
-export enum ForwarderHelperError {
- InsufficientMakerAssetLiquidity = 'INSUFFICIENT_MAKER_ASSET_LIQUIDITY',
- InsufficientZrxLiquidity = 'INSUFFICIENT_ZRX_LIQUIDITY',
-}
-
-/**
- * makerAssetFillAmount: The amount of makerAsset requesting to be filled
- * feePercentage: Optional affiliate percentage amount factoring into eth amount calculations
- */
-export interface MarketBuyOrdersInfoRequest {
- makerAssetFillAmount: BigNumber;
- feePercentage?: BigNumber;
-}
-
-/**
- * makerAssetFillAmount: The amount of makerAsset requesting to be filled
- * orders: An array of objects conforming to SignedOrder. These orders can be used to cover the requested makerAssetFillAmount plus slippage
- * feeOrders: An array of objects conforming to SignedOrder. These orders can be used to cover the fees for the orders param above
- * minEthAmount: Amount of eth in wei to send with the tx for the most optimistic case
- * maxEthAmount: Amount of eth in wei to send with the tx for the worst case
- * feePercentage: Affiliate fee percentage used to calculate the eth amounts above. Passed thru directly from the request
- */
-export interface MarketBuyOrdersInfo {
- makerAssetFillAmount: BigNumber;
- orders: SignedOrder[];
- feeOrders: SignedOrder[];
- minEthAmount: BigNumber;
- maxEthAmount: BigNumber;
- feePercentage?: BigNumber;
-}
diff --git a/packages/forwarder-helper/src/utils/forwarder_helper_impl_config_utils.ts b/packages/forwarder-helper/src/utils/forwarder_helper_impl_config_utils.ts
deleted file mode 100644
index 253384f65..000000000
--- a/packages/forwarder-helper/src/utils/forwarder_helper_impl_config_utils.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import { sortingUtils } from '@0xproject/order-utils';
-import { SignedOrder } from '@0xproject/types';
-import { BigNumber } from '@0xproject/utils';
-import * as _ from 'lodash';
-
-import { ForwarderHelperImplConfig } from '../forwarder_helper_impl';
-
-interface SignedOrderWithAmount extends SignedOrder {
- remainingFillAmount: BigNumber;
-}
-
-export const forwarderHelperImplConfigUtils = {
- sortedConfig(config: ForwarderHelperImplConfig): ForwarderHelperImplConfig {
- const { orders, feeOrders, remainingFillableMakerAssetAmounts, remainingFillableFeeAmounts } = config;
- // TODO: provide a feeRate to the sorting function to more accurately sort based on the current market for ZRX tokens
- const orderSorter = (ordersToSort: SignedOrder[]) => {
- return sortingUtils.sortOrdersByFeeAdjustedRate(ordersToSort);
- };
- const sortOrdersResult = sortOrdersAndRemainingFillAmounts(
- orderSorter,
- orders,
- remainingFillableMakerAssetAmounts,
- );
- const feeOrderSorter = (ordersToSort: SignedOrder[]) => {
- return sortingUtils.sortFeeOrdersByFeeAdjustedRate(ordersToSort);
- };
- const sortFeeOrdersResult = sortOrdersAndRemainingFillAmounts(
- feeOrderSorter,
- feeOrders,
- remainingFillableFeeAmounts,
- );
- return {
- orders: sortOrdersResult.orders,
- feeOrders: sortFeeOrdersResult.orders,
- remainingFillableMakerAssetAmounts: sortOrdersResult.remainingFillAmounts,
- remainingFillableFeeAmounts: sortFeeOrdersResult.remainingFillAmounts,
- };
- },
-};
-
-type OrderSorter = (orders: SignedOrder[]) => SignedOrder[];
-
-function sortOrdersAndRemainingFillAmounts(
- orderSorter: OrderSorter,
- orders: SignedOrder[],
- remainingFillAmounts?: BigNumber[],
-): { orders: SignedOrder[]; remainingFillAmounts?: BigNumber[] } {
- if (!_.isUndefined(remainingFillAmounts)) {
- // Bundle orders together with their remainingFillAmounts so that we can sort them together
- const orderWithAmounts = bundleSignedOrderWithAmounts(orders, remainingFillAmounts);
- // Sort
- const sortedOrderWithAmounts = orderSorter(orderWithAmounts) as SignedOrderWithAmount[];
- // Unbundle after sorting
- const unbundledSortedOrderWithAmounts = unbundleSignedOrderWithAmounts(sortedOrderWithAmounts);
- return {
- orders: unbundledSortedOrderWithAmounts.orders,
- remainingFillAmounts: unbundledSortedOrderWithAmounts.amounts,
- };
- } else {
- const sortedOrders = orderSorter(orders);
- return {
- orders: sortedOrders,
- };
- }
-}
-
-function bundleSignedOrderWithAmounts(orders: SignedOrder[], amounts: BigNumber[]): SignedOrderWithAmount[] {
- const ordersAndAmounts = _.map(orders, (order, index) => {
- return {
- ...order,
- remainingFillAmount: amounts[index],
- };
- });
- return ordersAndAmounts;
-}
-
-function unbundleSignedOrderWithAmounts(
- signedOrderWithAmounts: SignedOrderWithAmount[],
-): { orders: SignedOrder[]; amounts: BigNumber[] } {
- const orders = _.map(signedOrderWithAmounts, order => {
- const { remainingFillAmount, ...rest } = order;
- return rest;
- });
- const amounts = _.map(signedOrderWithAmounts, order => {
- const { remainingFillAmount } = order;
- return remainingFillAmount;
- });
- return {
- orders,
- amounts,
- };
-}
diff --git a/packages/forwarder-helper/test/forwarder_helper_impl_test.ts b/packages/forwarder-helper/test/forwarder_helper_impl_test.ts
deleted file mode 100644
index 3c3b6db92..000000000
--- a/packages/forwarder-helper/test/forwarder_helper_impl_test.ts
+++ /dev/null
@@ -1,136 +0,0 @@
-import { testOrderFactory } from '@0xproject/order-utils/lib/test/utils/test_order_factory';
-import { BigNumber } from '@0xproject/utils';
-import * as chai from 'chai';
-import * as _ from 'lodash';
-import 'mocha';
-
-import { ForwarderHelperImpl, ForwarderHelperImplConfig } from '../src/forwarder_helper_impl';
-import { ForwarderHelperError } from '../src/types';
-
-import { chaiSetup } from './utils/chai_setup';
-
-chaiSetup.configure();
-const expect = chai.expect;
-
-describe('ForwarderHelperImpl', () => {
- // rate: 2 takerAsset / makerAsset
- const testOrder1 = testOrderFactory.generateTestSignedOrder({
- makerAssetAmount: new BigNumber(100),
- takerAssetAmount: new BigNumber(200),
- });
- // rate: 1 takerAsset / makerAsset
- const testOrder2 = testOrderFactory.generateTestSignedOrder({
- makerAssetAmount: new BigNumber(100),
- takerAssetAmount: new BigNumber(100),
- });
- // rate: 3 takerAsset / makerAsset
- const testOrder3 = testOrderFactory.generateTestSignedOrder({
- makerAssetAmount: new BigNumber(100),
- takerAssetAmount: new BigNumber(300),
- takerFee: new BigNumber(1),
- });
- // rate: 3 WETH / ZRX
- const testFeeOrder1 = testOrderFactory.generateTestSignedOrder({
- makerAssetAmount: new BigNumber(100),
- takerAssetAmount: new BigNumber(300),
- });
- // rate: 2 WETH / ZRX
- const testFeeOrder2 = testOrderFactory.generateTestSignedOrder({
- makerAssetAmount: new BigNumber(100),
- takerAssetAmount: new BigNumber(200),
- });
- // rate: 1 WETH / ZRX
- const testFeeOrder3 = testOrderFactory.generateTestSignedOrder({
- makerAssetAmount: new BigNumber(100),
- takerAssetAmount: new BigNumber(100),
- });
- const inputForwarderHelperConfig: ForwarderHelperImplConfig = {
- orders: [testOrder1, testOrder2, testOrder3],
- feeOrders: [testFeeOrder1, testFeeOrder2, testFeeOrder3],
- remainingFillableMakerAssetAmounts: [new BigNumber(1), new BigNumber(2), new BigNumber(3)],
- remainingFillableFeeAmounts: [new BigNumber(4), new BigNumber(5), new BigNumber(6)],
- };
- describe('#constructor', () => {
- const inputForwarderHelperConfigNoRemainingAmounts: ForwarderHelperImplConfig = {
- orders: [testOrder1, testOrder2, testOrder3],
- feeOrders: [testFeeOrder1, testFeeOrder2, testFeeOrder3],
- };
- it('sorts orders', () => {
- const forwarderHelper = new ForwarderHelperImpl(inputForwarderHelperConfig);
- expect(forwarderHelper.config.orders).deep.equals([testOrder2, testOrder1, testOrder3]);
- });
- it('sorts fee orders', () => {
- const forwarderHelper = new ForwarderHelperImpl(inputForwarderHelperConfig);
- expect(forwarderHelper.config.feeOrders).deep.equals([testFeeOrder3, testFeeOrder2, testFeeOrder1]);
- });
- it('sorts remainingFillableMakerAssetAmounts', () => {
- const forwarderHelper = new ForwarderHelperImpl(inputForwarderHelperConfig);
- expect(forwarderHelper.config.remainingFillableMakerAssetAmounts).to.be.not.undefined();
- expect(_.nth(forwarderHelper.config.remainingFillableMakerAssetAmounts, 0)).to.bignumber.equal(
- new BigNumber(2),
- );
- expect(_.nth(forwarderHelper.config.remainingFillableMakerAssetAmounts, 1)).to.bignumber.equal(
- new BigNumber(1),
- );
- expect(_.nth(forwarderHelper.config.remainingFillableMakerAssetAmounts, 2)).to.bignumber.equal(
- new BigNumber(3),
- );
- });
- it('sorts remainingFillableFeeAmounts', () => {
- const forwarderHelper = new ForwarderHelperImpl(inputForwarderHelperConfig);
- expect(forwarderHelper.config.remainingFillableFeeAmounts).to.be.not.undefined();
- expect(_.nth(forwarderHelper.config.remainingFillableFeeAmounts, 0)).to.bignumber.equal(new BigNumber(6));
- expect(_.nth(forwarderHelper.config.remainingFillableFeeAmounts, 1)).to.bignumber.equal(new BigNumber(5));
- expect(_.nth(forwarderHelper.config.remainingFillableFeeAmounts, 2)).to.bignumber.equal(new BigNumber(4));
- });
- it('remainingFillableMakerAssetAmounts is undefined if none provided', () => {
- const forwarderHelper = new ForwarderHelperImpl(inputForwarderHelperConfigNoRemainingAmounts);
- expect(forwarderHelper.config.remainingFillableMakerAssetAmounts).to.be.undefined();
- });
- it('remainingFillableFeeAmounts is undefined if none provided', () => {
- const forwarderHelper = new ForwarderHelperImpl(inputForwarderHelperConfigNoRemainingAmounts);
- expect(forwarderHelper.config.remainingFillableFeeAmounts).to.be.undefined();
- });
- });
- describe('#getMarketBuyOrdersInfo', () => {
- it('throws if not enough makerAsset liquidity', () => {
- const forwarderHelper = new ForwarderHelperImpl(inputForwarderHelperConfig);
- expect(() => {
- // request for 6 makerAsset units, because we have exactly 6 available we should throw because there is a built in slippage buffer
- forwarderHelper.getMarketBuyOrdersInfo({
- makerAssetFillAmount: new BigNumber(6),
- });
- }).to.throw(ForwarderHelperError.InsufficientMakerAssetLiquidity);
- });
- it('throws if not enough ZRX liquidity', () => {
- const inputForwarderHelperConfigNoFees: ForwarderHelperImplConfig = {
- orders: [testOrder1, testOrder2, testOrder3],
- feeOrders: [],
- };
- const forwarderHelper = new ForwarderHelperImpl(inputForwarderHelperConfigNoFees);
- expect(() => {
- // request for 4 makerAsset units, we need fees but no fee orders exist, show we should throw
- forwarderHelper.getMarketBuyOrdersInfo({
- makerAssetFillAmount: new BigNumber(250),
- });
- }).to.throw(ForwarderHelperError.InsufficientZrxLiquidity);
- });
- it('passes the makerAssetFillAmount from the request to the info response', () => {
- const forwarderHelper = new ForwarderHelperImpl(inputForwarderHelperConfig);
- const makerAssetFillAmount = new BigNumber(4);
- const info = forwarderHelper.getMarketBuyOrdersInfo({
- makerAssetFillAmount,
- });
- expect(info.makerAssetFillAmount).to.bignumber.equal(makerAssetFillAmount);
- });
- it('passes the feePercentage from the request to the info response', () => {
- const forwarderHelper = new ForwarderHelperImpl(inputForwarderHelperConfig);
- const feePercentage = new BigNumber(0.2);
- const info = forwarderHelper.getMarketBuyOrdersInfo({
- makerAssetFillAmount: new BigNumber(4),
- feePercentage,
- });
- expect(info.feePercentage).to.bignumber.equal(feePercentage);
- });
- });
-});