|
|
|
|
@@ -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;
|
|
|
|
|
}
|
|
|
|
|
|