aboutsummaryrefslogtreecommitdiffstats
path: root/packages/forwarder-helper/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/forwarder-helper/src')
-rw-r--r--packages/forwarder-helper/src/constants.ts5
-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/globals.d.ts6
-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
7 files changed, 237 insertions, 0 deletions
diff --git a/packages/forwarder-helper/src/constants.ts b/packages/forwarder-helper/src/constants.ts
new file mode 100644
index 000000000..0ad30e4c0
--- /dev/null
+++ b/packages/forwarder-helper/src/constants.ts
@@ -0,0 +1,5 @@
+import { BigNumber } from '@0xproject/utils';
+
+export const constants = {
+ ZERO_AMOUNT: new BigNumber(0),
+};
diff --git a/packages/forwarder-helper/src/forwarder_helper_factory.ts b/packages/forwarder-helper/src/forwarder_helper_factory.ts
new file mode 100644
index 000000000..95f11f555
--- /dev/null
+++ b/packages/forwarder-helper/src/forwarder_helper_factory.ts
@@ -0,0 +1,25 @@
+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
new file mode 100644
index 000000000..a90edb0bb
--- /dev/null
+++ b/packages/forwarder-helper/src/forwarder_helper_impl.ts
@@ -0,0 +1,64 @@
+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/globals.d.ts b/packages/forwarder-helper/src/globals.d.ts
new file mode 100644
index 000000000..94e63a32d
--- /dev/null
+++ b/packages/forwarder-helper/src/globals.d.ts
@@ -0,0 +1,6 @@
+declare module '*.json' {
+ const json: any;
+ /* tslint:disable */
+ export default json;
+ /* tslint:enable */
+}
diff --git a/packages/forwarder-helper/src/index.ts b/packages/forwarder-helper/src/index.ts
new file mode 100644
index 000000000..eb3a34bd5
--- /dev/null
+++ b/packages/forwarder-helper/src/index.ts
@@ -0,0 +1,2 @@
+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
new file mode 100644
index 000000000..fb171cc90
--- /dev/null
+++ b/packages/forwarder-helper/src/types.ts
@@ -0,0 +1,43 @@
+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
new file mode 100644
index 000000000..253384f65
--- /dev/null
+++ b/packages/forwarder-helper/src/utils/forwarder_helper_impl_config_utils.ts
@@ -0,0 +1,92 @@
+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,
+ };
+}