aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/connect/src/http_client.ts2
-rw-r--r--packages/forwarder-helper/package.json5
-rw-r--r--packages/forwarder-helper/src/forwarder_helper_factory.ts229
-rw-r--r--packages/forwarder-helper/src/types.ts5
-rw-r--r--packages/forwarder-helper/src/utils/order_utils.ts16
5 files changed, 253 insertions, 4 deletions
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/package.json b/packages/forwarder-helper/package.json
index 410323f76..f849fb4ed 100644
--- a/packages/forwarder-helper/package.json
+++ b/packages/forwarder-helper/package.json
@@ -40,18 +40,21 @@
"homepage": "https://github.com/0xProject/0x-monorepo/packages/forwarder-helper/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",
"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/forwarder-helper/src/forwarder_helper_factory.ts b/packages/forwarder-helper/src/forwarder_helper_factory.ts
index 95f11f555..e3ef59388 100644
--- a/packages/forwarder-helper/src/forwarder_helper_factory.ts
+++ b/packages/forwarder-helper/src/forwarder_helper_factory.ts
@@ -1,13 +1,22 @@
import { assert } from '@0xproject/assert';
+import { APIOrder, HttpClient } from '@0xproject/connect';
+import { ContractWrappers, OrderAndTraderInfo, OrderStatus } from '@0xproject/contract-wrappers';
import { schemas } from '@0xproject/json-schemas';
+import { assetDataUtils } from '@0xproject/order-utils';
+import { RemainingFillableCalculator } from '@0xproject/order-utils/lib/src/remaining_fillable_calculator';
+import { RPCSubprovider, Web3ProviderEngine } from '@0xproject/subproviders';
import { SignedOrder } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+import * as _ from 'lodash';
+import { constants } from './constants';
import { ForwarderHelperImpl, ForwarderHelperImplConfig } from './forwarder_helper_impl';
-import { ForwarderHelper } from './types';
+import { ForwarderHelper, ForwarderHelperFactoryError } from './types';
+import { orderUtils } from './utils/order_utils';
export const forwarderHelperFactory = {
/**
- * Given an array of orders and an array of feeOrders
+ * Given an array of orders and an array of feeOrders, get a ForwarderHelper
* @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
@@ -22,4 +31,220 @@ export const forwarderHelperFactory = {
const helper = new ForwarderHelperImpl(config);
return helper;
},
+ /**
+ * Given a desired makerAsset and SRA url, get a ForwarderHelper
+ * @param makerAssetData An array of objects conforming to SignedOrder. Each order should specify the same makerAssetData and takerAssetData
+ * @param sraUrl A url pointing to an SRA v2 compliant endpoint.
+ * @param rpcUrl A url pointing to an ethereum node.
+ * @param networkId The ethereum networkId, defaults to 1 (mainnet).
+ * @return A ForwarderHelper, see type for definition
+ */
+ async getForwarderHelperForMakerAssetDataAsync(
+ makerAssetData: string,
+ takerAddress: string,
+ sraUrl: string,
+ rpcUrl?: string,
+ networkId: number = 1,
+ ): Promise<ForwarderHelper> {
+ assert.isHexString('makerAssetData', makerAssetData);
+ assert.isETHAddressHex('takerAddress', takerAddress);
+ assert.isWebUri('sraUrl', sraUrl);
+ if (!_.isUndefined(rpcUrl)) {
+ assert.isWebUri('rpcUrl', rpcUrl);
+ }
+ assert.isNumber('networkId', networkId);
+ // create provider
+ const providerEngine = new Web3ProviderEngine();
+ if (!_.isUndefined(rpcUrl)) {
+ providerEngine.addProvider(new RPCSubprovider(rpcUrl));
+ }
+ providerEngine.start();
+ // create contract wrappers given provider and networkId
+ const contractWrappers = new ContractWrappers(providerEngine, { networkId });
+ // find ether token asset data
+ const etherTokenAddressIfExists = contractWrappers.etherToken.getContractAddressIfExists();
+ if (_.isUndefined(etherTokenAddressIfExists)) {
+ throw new Error(ForwarderHelperFactoryError.NoEtherTokenContractFound);
+ }
+ const etherTokenAssetData = assetDataUtils.encodeERC20AssetData(etherTokenAddressIfExists);
+ // find zrx token asset data
+ let zrxTokenAssetData: string;
+ try {
+ zrxTokenAssetData = contractWrappers.exchange.getZRXAssetData();
+ } catch (err) {
+ throw new Error(ForwarderHelperFactoryError.NoZrxTokenContractFound);
+ }
+ // get orderbooks for makerAsset/WETH and ZRX/WETH
+ const sraClient = new HttpClient(sraUrl);
+ const orderbookRequests = [
+ { baseAssetData: makerAssetData, quoteAssetData: etherTokenAssetData },
+ { baseAssetData: zrxTokenAssetData, quoteAssetData: etherTokenAssetData },
+ ];
+ const requestOpts = { networkId };
+ // TODO: try catch these requests and throw a more domain specific error
+ const [makerAssetOrderbook, zrxOrderbook] = await Promise.all(
+ _.map(orderbookRequests, request => sraClient.getOrderbookAsync(request, requestOpts)),
+ );
+ // validate orders and find remaining fillable from on chain state or sra api
+ let ordersAndRemainingFillableMakerAssetAmounts: OrdersAndRemainingFillableMakerAssetAmounts;
+ let feeOrdersAndRemainingFillableMakerAssetAmounts: OrdersAndRemainingFillableMakerAssetAmounts;
+ if (!_.isUndefined(rpcUrl)) {
+ // if we do have an rpc url, get on-chain orders and traders info via the OrderValidatorWrapper
+ const ordersFromSra = _.map(makerAssetOrderbook.asks.records, apiOrder => apiOrder.order);
+ const feeOrdersFromSra = _.map(zrxOrderbook.asks.records, apiOrder => apiOrder.order);
+ // TODO: try catch these requests and throw a more domain specific error
+ const [makerAssetOrdersAndTradersInfo, feeOrdersAndTradersInfo] = await Promise.all(
+ _.map([ordersFromSra, feeOrdersFromSra], ordersToBeValidated => {
+ const takerAddresses = _.map(ordersToBeValidated, () => takerAddress);
+ return contractWrappers.orderValidator.getOrdersAndTradersInfoAsync(
+ ordersToBeValidated,
+ takerAddresses,
+ );
+ }),
+ );
+ // take maker asset orders from SRA + on chain information and find the valid orders and remaining fillable maker asset amounts
+ ordersAndRemainingFillableMakerAssetAmounts = getValidOrdersAndRemainingFillableMakerAssetAmountsFromOnChain(
+ ordersFromSra,
+ makerAssetOrdersAndTradersInfo,
+ zrxTokenAssetData,
+ );
+ // take fee orders from SRA + on chain information and find the valid orders and remaining fillable maker asset amounts
+ feeOrdersAndRemainingFillableMakerAssetAmounts = getValidOrdersAndRemainingFillableMakerAssetAmountsFromOnChain(
+ feeOrdersFromSra,
+ feeOrdersAndTradersInfo,
+ zrxTokenAssetData,
+ );
+ } else {
+ // if we don't have an rpc url, assume all orders are valid and fallback to optional fill amounts from SRA
+ // if fill amounts are not available from the SRA, assume all orders are completely fillable
+ const apiOrdersFromSra = makerAssetOrderbook.asks.records;
+ const feeApiOrdersFromSra = zrxOrderbook.asks.records;
+ // take maker asset orders from SRA and the valid orders and remaining fillable maker asset amounts
+ ordersAndRemainingFillableMakerAssetAmounts = getValidOrdersAndRemainingFillableMakerAssetAmountsFromApi(
+ apiOrdersFromSra,
+ );
+ // take fee orders from SRA and find the valid orders and remaining fillable maker asset amounts
+ feeOrdersAndRemainingFillableMakerAssetAmounts = getValidOrdersAndRemainingFillableMakerAssetAmountsFromApi(
+ feeApiOrdersFromSra,
+ );
+ }
+ // compile final config
+ const config: ForwarderHelperImplConfig = {
+ orders: ordersAndRemainingFillableMakerAssetAmounts.orders,
+ feeOrders: feeOrdersAndRemainingFillableMakerAssetAmounts.orders,
+ remainingFillableMakerAssetAmounts:
+ ordersAndRemainingFillableMakerAssetAmounts.remainingFillableMakerAssetAmounts,
+ remainingFillableFeeAmounts:
+ feeOrdersAndRemainingFillableMakerAssetAmounts.remainingFillableMakerAssetAmounts,
+ };
+ const helper = new ForwarderHelperImpl(config);
+ return helper;
+ },
};
+
+interface OrdersAndRemainingFillableMakerAssetAmounts {
+ orders: SignedOrder[];
+ remainingFillableMakerAssetAmounts: BigNumber[];
+}
+
+/**
+ * Given an array of APIOrder objects from a standard relayer api, return an array
+ * of fillable orders with their corresponding remainingFillableMakerAssetAmounts
+ */
+function getValidOrdersAndRemainingFillableMakerAssetAmountsFromApi(
+ apiOrders: APIOrder[],
+): OrdersAndRemainingFillableMakerAssetAmounts {
+ const result = _.reduce(
+ apiOrders,
+ (acc, apiOrder, index) => {
+ // get current accumulations
+ const { orders, remainingFillableMakerAssetAmounts } = acc;
+ // get order and metadata
+ const { order, metaData } = apiOrder;
+ // if the order is expired, move on
+ if (orderUtils.isOrderExpired(order)) {
+ return acc;
+ }
+ // calculate remainingFillableMakerAssetAmount from api metadata
+ const remainingFillableTakerAssetAmount = _.get(
+ metaData,
+ 'remainingTakerAssetAmount',
+ order.takerAssetAmount,
+ );
+ const remainingFillableMakerAssetAmount = orderUtils.calculateRemainingMakerAssetAmount(
+ order,
+ remainingFillableTakerAssetAmount,
+ );
+ // if there is some amount of maker asset left to fill and add the order and remaining amount to the accumulations
+ // if there is not any maker asset left to fill, do not add
+ if (remainingFillableMakerAssetAmount.gt(constants.ZERO_AMOUNT)) {
+ return {
+ orders: _.concat(orders, order),
+ remainingFillableMakerAssetAmounts: _.concat(
+ remainingFillableMakerAssetAmounts,
+ remainingFillableMakerAssetAmount,
+ ),
+ };
+ } else {
+ return acc;
+ }
+ },
+ { orders: [] as SignedOrder[], remainingFillableMakerAssetAmounts: [] as BigNumber[] },
+ );
+ 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 getValidOrdersAndRemainingFillableMakerAssetAmountsFromOnChain(
+ inputOrders: SignedOrder[],
+ ordersAndTradersInfo: OrderAndTraderInfo[],
+ zrxAssetData: string,
+): OrdersAndRemainingFillableMakerAssetAmounts {
+ // 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,
+ (acc, order, index) => {
+ // get current accumulations
+ const { orders, remainingFillableMakerAssetAmounts } = acc;
+ // get corresponding on-chain state for the order
+ const { orderInfo, traderInfo } = ordersAndTradersInfo[index];
+ // if the order IS NOT fillable, do not add anything and continue iterating
+ if (orderInfo.orderStatus !== OrderStatus.FILLABLE) {
+ return acc;
+ }
+ // 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();
+ return {
+ orders: _.concat(orders, order),
+ remainingFillableMakerAssetAmounts: _.concat(
+ remainingFillableMakerAssetAmounts,
+ remainingFillableAmount,
+ ),
+ };
+ },
+ { orders: [] as SignedOrder[], remainingFillableMakerAssetAmounts: [] as BigNumber[] },
+ );
+ return result;
+}
diff --git a/packages/forwarder-helper/src/types.ts b/packages/forwarder-helper/src/types.ts
index fb171cc90..5a8439257 100644
--- a/packages/forwarder-helper/src/types.ts
+++ b/packages/forwarder-helper/src/types.ts
@@ -1,6 +1,11 @@
import { SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
+export enum ForwarderHelperFactoryError {
+ NoEtherTokenContractFound = 'NO_ETHER_TOKEN_CONTRACT_FOUND',
+ NoZrxTokenContractFound = 'NO_ZRX_TOKEN_CONTRACT_FOUND',
+}
+
export interface ForwarderHelper {
/**
* Given a MarketBuyOrdersInfoRequest, returns a MarketBuyOrdersInfo containing all information relevant to fulfilling the request
diff --git a/packages/forwarder-helper/src/utils/order_utils.ts b/packages/forwarder-helper/src/utils/order_utils.ts
new file mode 100644
index 000000000..d14c6a6d7
--- /dev/null
+++ b/packages/forwarder-helper/src/utils/order_utils.ts
@@ -0,0 +1,16 @@
+import { SignedOrder } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+
+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)
+ ? new BigNumber(0)
+ : remainingTakerAssetAmount.times(order.makerAssetAmount).dividedToIntegerBy(order.takerAssetAmount);
+ return result;
+ },
+};