diff options
Diffstat (limited to 'packages/asset-buyer/src')
-rw-r--r-- | packages/asset-buyer/src/constants.ts | 6 | ||||
-rw-r--r-- | packages/asset-buyer/src/forwarder_helper_factory.ts | 261 | ||||
-rw-r--r-- | packages/asset-buyer/src/forwarder_helper_impl.ts | 64 | ||||
-rw-r--r-- | packages/asset-buyer/src/globals.d.ts | 6 | ||||
-rw-r--r-- | packages/asset-buyer/src/index.ts | 2 | ||||
-rw-r--r-- | packages/asset-buyer/src/types.ts | 49 | ||||
-rw-r--r-- | packages/asset-buyer/src/utils/forwarder_helper_impl_config_utils.ts | 92 | ||||
-rw-r--r-- | packages/asset-buyer/src/utils/order_utils.ts | 21 |
8 files changed, 501 insertions, 0 deletions
diff --git a/packages/asset-buyer/src/constants.ts b/packages/asset-buyer/src/constants.ts new file mode 100644 index 000000000..c0a1b090e --- /dev/null +++ b/packages/asset-buyer/src/constants.ts @@ -0,0 +1,6 @@ +import { BigNumber } from '@0xproject/utils'; + +export const constants = { + ZERO_AMOUNT: new BigNumber(0), + NULL_ADDRESS: '0x0000000000000000000000000000000000000000', +}; diff --git a/packages/asset-buyer/src/forwarder_helper_factory.ts b/packages/asset-buyer/src/forwarder_helper_factory.ts new file mode 100644 index 000000000..2b37ac98f --- /dev/null +++ b/packages/asset-buyer/src/forwarder_helper_factory.ts @@ -0,0 +1,261 @@ +import { assert } from '@0xproject/assert'; +import { APIOrder, HttpClient, OrderbookResponse } 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, ForwarderHelperFactoryError } from './types'; +import { orderUtils } from './utils/order_utils'; + +export const forwarderHelperFactory = { + /** + * 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 + */ + getForwarderHelperForOrders(orders: SignedOrder[], feeOrders: SignedOrder[] = []): ForwarderHelper { + assert.doesConformToSchema('orders', orders, schemas.signedOrdersSchema); + assert.doesConformToSchema('feeOrders', orders, schemas.signedOrdersSchema); + // TODO: Add assertion here for orders all having the same makerAsset and takerAsset + const config: ForwarderHelperImplConfig = { + orders, + feeOrders, + }; + 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, + sraUrl: string, + rpcUrl?: string, + networkId: number = 1, + ): Promise<ForwarderHelper> { + assert.isHexString('makerAssetData', makerAssetData); + 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 }; + let makerAssetOrderbook: OrderbookResponse; + let zrxOrderbook: OrderbookResponse; + try { + [makerAssetOrderbook, zrxOrderbook] = await Promise.all( + _.map(orderbookRequests, request => sraClient.getOrderbookAsync(request, requestOpts)), + ); + } catch (err) { + throw new Error(ForwarderHelperFactoryError.StandardRelayerApiError); + } + // 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 = getOpenAsksFromOrderbook(makerAssetOrderbook); + const feeOrdersFromSra = getOpenAsksFromOrderbook(zrxOrderbook); + // TODO: 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 [makerAssetOrdersAndTradersInfo, feeOrdersAndTradersInfo] = await Promise.all( + _.map([ordersFromSra, feeOrdersFromSra], ordersToBeValidated => { + const takerAddresses = _.map(ordersToBeValidated, () => constants.NULL_ADDRESS); + 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) => { + // get current accumulations + const { orders, remainingFillableMakerAssetAmounts } = acc; + // get order and metadata + const { order, metaData } = apiOrder; + // if the order is expired or not open, move on + if (orderUtils.isOrderExpired(order) || !orderUtils.isOpenOrder(order)) { + return acc; + } + // calculate remainingFillableMakerAssetAmount from api metadata, else assume order is completely fillable + 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 to the accumulations 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; +} + +function getOpenAsksFromOrderbook(orderbookResponse: OrderbookResponse): SignedOrder[] { + const asks = _.map(orderbookResponse.asks.records, apiOrder => apiOrder.order); + const result = _.filter(asks, ask => orderUtils.isOpenOrder(ask)); + return result; +} diff --git a/packages/asset-buyer/src/forwarder_helper_impl.ts b/packages/asset-buyer/src/forwarder_helper_impl.ts new file mode 100644 index 000000000..a90edb0bb --- /dev/null +++ b/packages/asset-buyer/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/asset-buyer/src/globals.d.ts b/packages/asset-buyer/src/globals.d.ts new file mode 100644 index 000000000..94e63a32d --- /dev/null +++ b/packages/asset-buyer/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/asset-buyer/src/index.ts b/packages/asset-buyer/src/index.ts new file mode 100644 index 000000000..eb3a34bd5 --- /dev/null +++ b/packages/asset-buyer/src/index.ts @@ -0,0 +1,2 @@ +export { forwarderHelperFactory } from './forwarder_helper_factory'; +export { ForwarderHelper, ForwarderHelperError, MarketBuyOrdersInfoRequest, MarketBuyOrdersInfo } from './types'; diff --git a/packages/asset-buyer/src/types.ts b/packages/asset-buyer/src/types.ts new file mode 100644 index 000000000..a7f02ff8d --- /dev/null +++ b/packages/asset-buyer/src/types.ts @@ -0,0 +1,49 @@ +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', + StandardRelayerApiError = 'STANDARD_RELAYER_API_ERROR', +} + +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/asset-buyer/src/utils/forwarder_helper_impl_config_utils.ts b/packages/asset-buyer/src/utils/forwarder_helper_impl_config_utils.ts new file mode 100644 index 000000000..253384f65 --- /dev/null +++ b/packages/asset-buyer/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, + }; +} 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; + }, +}; |