Create initial AssetBuyer class
This commit is contained in:
@@ -48,6 +48,8 @@
|
||||
"@0xproject/types": "^1.0.1",
|
||||
"@0xproject/typescript-typings": "^2.0.0",
|
||||
"@0xproject/utils": "^1.0.8",
|
||||
"@0xproject/web3-wrapper": "^2.0.2",
|
||||
"ethereum-types": "^1.0.6",
|
||||
"lodash": "^4.17.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
129
packages/asset-buyer/src/asset_buyers/asset_buyer.ts
Normal file
129
packages/asset-buyer/src/asset_buyers/asset_buyer.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { ContractWrappers } from '@0xproject/contract-wrappers';
|
||||
import { marketUtils } from '@0xproject/order-utils';
|
||||
import { SignedOrder } from '@0xproject/types';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
import * as _ from 'lodash';
|
||||
import { Provider } from 'ethereum-types';
|
||||
|
||||
import { constants } from '../constants';
|
||||
import { AssetBuyerError, BuyQuote, BuyQuoteRequest } from '../types';
|
||||
|
||||
const SLIPPAGE_PERCENTAGE = new BigNumber(0.2); // 20% slippage protection, possibly move this into request interface
|
||||
|
||||
export interface AssetBuyerConfig {
|
||||
orders: SignedOrder[];
|
||||
feeOrders: SignedOrder[];
|
||||
remainingFillableMakerAssetAmounts?: BigNumber[];
|
||||
remainingFillableFeeAmounts?: BigNumber[];
|
||||
networkId?: number;
|
||||
}
|
||||
|
||||
export class AssetBuyer {
|
||||
public readonly provider: Provider;
|
||||
public readonly config: AssetBuyerConfig;
|
||||
private _contractWrappers: ContractWrappers;
|
||||
constructor(provider: Provider, config: AssetBuyerConfig) {
|
||||
this.provider = provider;
|
||||
this.config = config;
|
||||
const networkId = this.config.networkId || constants.MAINNET_NETWORK_ID;
|
||||
this._contractWrappers = new ContractWrappers(this.provider, {
|
||||
networkId,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Given a BuyQuoteRequest, returns a BuyQuote containing all information relevant to fulfilling the buy. Pass the BuyQuote
|
||||
* to executeBuyQuoteAsync to execute the buy.
|
||||
* @param buyQuoteRequest An object that conforms to BuyQuoteRequest. See type definition for more information.
|
||||
* @return An object that conforms to BuyQuote that satisfies the request. See type definition for more information.
|
||||
*/
|
||||
public getBuyQuote(buyQuoteRequest: BuyQuoteRequest): BuyQuote {
|
||||
const { assetBuyAmount, feePercentage } = buyQuoteRequest;
|
||||
const { orders, feeOrders, remainingFillableMakerAssetAmounts, remainingFillableFeeAmounts } = this.config;
|
||||
// TODO: optimization
|
||||
// make the slippage percentage customizable
|
||||
const slippageBufferAmount = assetBuyAmount.mul(SLIPPAGE_PERCENTAGE).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
|
||||
return {
|
||||
assetData,
|
||||
orders: resultOrders,
|
||||
feeOrders: resultFeeOrders,
|
||||
minRate: constants.ZERO_AMOUNT,
|
||||
maxRate: constants.ZERO_AMOUNT,
|
||||
assetBuyAmount,
|
||||
feePercentage,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 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> {
|
||||
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);
|
||||
const txHash = await this._contractWrappers.forwarder.marketBuyOrdersWithEthAsync(
|
||||
orders,
|
||||
assetBuyAmount,
|
||||
finalTakerAddress,
|
||||
ethAmount,
|
||||
feeOrders,
|
||||
feePercentage,
|
||||
feeRecipient,
|
||||
);
|
||||
return txHash;
|
||||
}
|
||||
}
|
||||
@@ -3,4 +3,5 @@ import { BigNumber } from '@0xproject/utils';
|
||||
export const constants = {
|
||||
ZERO_AMOUNT: new BigNumber(0),
|
||||
NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
|
||||
MAINNET_NETWORK_ID: 1,
|
||||
};
|
||||
|
||||
@@ -1,261 +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 { 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';
|
||||
// import { constants } from './constants';
|
||||
// import { ForwarderHelperImpl, ForwarderHelperImplConfig } from '@0xproject/asset-buyer/src/asset_buyer';
|
||||
// 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;
|
||||
},
|
||||
};
|
||||
// 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[];
|
||||
}
|
||||
// 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 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;
|
||||
}
|
||||
// /**
|
||||
// * 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;
|
||||
}
|
||||
// function getOpenAsksFromOrderbook(orderbookResponse: OrderbookResponse): SignedOrder[] {
|
||||
// const asks = _.map(orderbookResponse.asks.records, apiOrder => apiOrder.order);
|
||||
// const result = _.filter(asks, ask => orderUtils.isOpenOrder(ask));
|
||||
// return result;
|
||||
// }
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
export { forwarderHelperFactory } from './forwarder_helper_factory';
|
||||
export { ForwarderHelper, ForwarderHelperError, MarketBuyOrdersInfoRequest, MarketBuyOrdersInfo } from './types';
|
||||
export { AssetBuyerError, BuyQuote, BuyQuoteRequest } from './types';
|
||||
export { AssetBuyer } from './asset_buyers/asset_buyer';
|
||||
|
||||
@@ -1,49 +1,42 @@
|
||||
import { SignedOrder } from '@0xproject/types';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
|
||||
export enum ForwarderHelperFactoryError {
|
||||
/**
|
||||
* assetBuyAmount: The amount of asset to buy.
|
||||
* feePercentage: Optional affiliate percentage amount factoring into eth amount calculations.
|
||||
*/
|
||||
export interface BuyQuoteRequest {
|
||||
assetBuyAmount: BigNumber;
|
||||
feePercentage?: BigNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* assetData: The asset information.
|
||||
* 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. Passed through directly from the request.
|
||||
* feePercentage: Affiliate fee percentage used to calculate the eth amounts above. Passed through directly from the request.
|
||||
*/
|
||||
export interface BuyQuote {
|
||||
assetData: string;
|
||||
orders: SignedOrder[];
|
||||
feeOrders: SignedOrder[];
|
||||
minRate: BigNumber;
|
||||
maxRate: BigNumber;
|
||||
assetBuyAmount: BigNumber;
|
||||
feePercentage?: BigNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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',
|
||||
}
|
||||
|
||||
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',
|
||||
InsufficientAssetLiquidity = 'INSUFFICIENT_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;
|
||||
NoAddressAvailable = 'NO_ADDRESS_AVAILABLE',
|
||||
}
|
||||
|
||||
@@ -1,92 +1,92 @@
|
||||
import { sortingUtils } from '@0xproject/order-utils';
|
||||
import { SignedOrder } from '@0xproject/types';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as _ from 'lodash';
|
||||
// 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';
|
||||
// import { ForwarderHelperImplConfig } from '@0xproject/asset-buyer/src/asset_buyer';
|
||||
|
||||
interface SignedOrderWithAmount extends SignedOrder {
|
||||
remainingFillAmount: BigNumber;
|
||||
}
|
||||
// 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,
|
||||
};
|
||||
},
|
||||
};
|
||||
// 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[];
|
||||
// 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 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 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,
|
||||
};
|
||||
}
|
||||
// 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,
|
||||
// };
|
||||
// }
|
||||
|
||||
@@ -1,136 +1,136 @@
|
||||
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 { 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 { ForwarderHelperImpl, ForwarderHelperImplConfig } from '@0xproject/asset-buyer/src/asset_buyer';
|
||||
// import { ForwarderHelperError } from '../src/types';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
// import { chaiSetup } from './utils/chai_setup';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
// 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
// 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);
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
{ "path": "./packages/0x.js" },
|
||||
{ "path": "./packages/abi-gen" },
|
||||
{ "path": "./packages/assert" },
|
||||
{ "path": "./packages/asset-buyer" },
|
||||
{ "path": "./packages/base-contract" },
|
||||
{ "path": "./packages/connect" },
|
||||
{ "path": "./packages/contract-wrappers" },
|
||||
@@ -30,7 +31,6 @@
|
||||
{ "path": "./packages/dev-utils" },
|
||||
{ "path": "./packages/ethereum-types" },
|
||||
{ "path": "./packages/fill-scenarios" },
|
||||
{ "path": "./packages/forwarder-helper" },
|
||||
{ "path": "./packages/json-schemas" },
|
||||
{ "path": "./packages/metacoin" },
|
||||
{ "path": "./packages/migrations" },
|
||||
|
||||
Reference in New Issue
Block a user