refactored ERC <> ERC for asset-buyer
minor update to interface
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
"description": "Convenience package for discovering and buying assets with Ether.",
|
||||
"description": "Convenience package for discovering and buying assets for both on chain and off chain needs.",
|
||||
"main": "lib/src/index.js",
|
||||
"types": "lib/src/index.d.ts",
|
||||
"scripts": {
|
||||
|
@@ -124,39 +124,39 @@ export class AssetBuyer {
|
||||
* @return An object that conforms to BuyQuote that satisfies the request. See type definition for more information.
|
||||
*/
|
||||
public async getBuyQuoteAsync(
|
||||
assetData: string,
|
||||
assetBuyAmount: BigNumber,
|
||||
makerAssetData: string,
|
||||
takerAssetData: string,
|
||||
makerAssetBuyAmount: BigNumber,
|
||||
options: Partial<BuyQuoteRequestOpts> = {},
|
||||
): Promise<BuyQuote> {
|
||||
const { feePercentage, shouldForceOrderRefresh, slippagePercentage } = _.merge(
|
||||
const { shouldForceOrderRefresh, slippagePercentage } = _.merge(
|
||||
{},
|
||||
constants.DEFAULT_BUY_QUOTE_REQUEST_OPTS,
|
||||
options,
|
||||
);
|
||||
assert.isString('assetData', assetData);
|
||||
assert.isBigNumber('assetBuyAmount', assetBuyAmount);
|
||||
assert.isValidPercentage('feePercentage', feePercentage);
|
||||
assert.isString('makerAssetData', makerAssetData);
|
||||
assert.isString('takerAssetData', takerAssetData);
|
||||
assert.isBigNumber('makerAssetBuyAmount', makerAssetBuyAmount);
|
||||
assert.isBoolean('shouldForceOrderRefresh', shouldForceOrderRefresh);
|
||||
assert.isNumber('slippagePercentage', slippagePercentage);
|
||||
const zrxTokenAssetData = this._getZrxTokenAssetDataOrThrow();
|
||||
const isMakerAssetZrxToken = assetData === zrxTokenAssetData;
|
||||
const isMakerAssetZrxToken = makerAssetData === zrxTokenAssetData;
|
||||
// get the relevant orders for the makerAsset and fees
|
||||
// if the requested assetData is ZRX, don't get the fee info
|
||||
const [ordersAndFillableAmounts, feeOrdersAndFillableAmounts] = await Promise.all([
|
||||
this.getOrdersAndFillableAmountsAsync(assetData, shouldForceOrderRefresh),
|
||||
this.getOrdersAndFillableAmountsAsync(makerAssetData, takerAssetData, shouldForceOrderRefresh),
|
||||
isMakerAssetZrxToken
|
||||
? Promise.resolve(constants.EMPTY_ORDERS_AND_FILLABLE_AMOUNTS)
|
||||
: this.getOrdersAndFillableAmountsAsync(zrxTokenAssetData, shouldForceOrderRefresh),
|
||||
: this.getOrdersAndFillableAmountsAsync(zrxTokenAssetData, takerAssetData, shouldForceOrderRefresh),
|
||||
shouldForceOrderRefresh,
|
||||
]);
|
||||
if (ordersAndFillableAmounts.orders.length === 0) {
|
||||
throw new Error(`${AssetBuyerError.AssetUnavailable}: For assetData ${assetData}`);
|
||||
throw new Error(`${AssetBuyerError.AssetUnavailable}: For makerAssetdata ${makerAssetData} and takerAssetdata ${takerAssetData}`);
|
||||
}
|
||||
const buyQuote = buyQuoteCalculator.calculate(
|
||||
ordersAndFillableAmounts,
|
||||
feeOrdersAndFillableAmounts,
|
||||
assetBuyAmount,
|
||||
feePercentage,
|
||||
makerAssetBuyAmount,
|
||||
slippagePercentage,
|
||||
isMakerAssetZrxToken,
|
||||
);
|
||||
@@ -172,14 +172,17 @@ export class AssetBuyer {
|
||||
* @return An object that conforms to BuyQuote that satisfies the request. See type definition for more information.
|
||||
*/
|
||||
public async getBuyQuoteForERC20TokenAddressAsync(
|
||||
tokenAddress: string,
|
||||
assetBuyAmount: BigNumber,
|
||||
makerTokenAddress: string,
|
||||
takerTokenAddress: string,
|
||||
makerAssetBuyAmount: BigNumber,
|
||||
options: Partial<BuyQuoteRequestOpts> = {},
|
||||
): Promise<BuyQuote> {
|
||||
assert.isETHAddressHex('tokenAddress', tokenAddress);
|
||||
assert.isBigNumber('assetBuyAmount', assetBuyAmount);
|
||||
const assetData = assetDataUtils.encodeERC20AssetData(tokenAddress);
|
||||
const buyQuote = this.getBuyQuoteAsync(assetData, assetBuyAmount, options);
|
||||
assert.isETHAddressHex('makerTokenAddress', makerTokenAddress);
|
||||
assert.isETHAddressHex('takerTokenAddress', takerTokenAddress);
|
||||
assert.isBigNumber('makerAssetBuyAmount', makerAssetBuyAmount);
|
||||
const makerAssetData = assetDataUtils.encodeERC20AssetData(makerTokenAddress);
|
||||
const takerAssetData = assetDataUtils.encodeERC20AssetData(takerTokenAddress);
|
||||
const buyQuote = this.getBuyQuoteAsync(makerAssetData, takerAssetData, makerAssetBuyAmount, options);
|
||||
return buyQuote;
|
||||
}
|
||||
/**
|
||||
@@ -190,125 +193,162 @@ export class AssetBuyer {
|
||||
*
|
||||
* @return An object that conforms to LiquidityForAssetData that satisfies the request. See type definition for more information.
|
||||
*/
|
||||
public async getLiquidityForAssetDataAsync(
|
||||
assetData: string,
|
||||
public async getLiquidityForMakerTakerAssetdataPairAsync(
|
||||
makerAssetData: string,
|
||||
takerAssetData: string,
|
||||
options: Partial<LiquidityRequestOpts> = {},
|
||||
): Promise<LiquidityForAssetData> {
|
||||
const shouldForceOrderRefresh =
|
||||
options.shouldForceOrderRefresh !== undefined ? options.shouldForceOrderRefresh : false;
|
||||
assert.isString('assetData', assetData);
|
||||
assetDataUtils.decodeAssetDataOrThrow(assetData);
|
||||
assert.isString('makerAssetDataa', makerAssetData);
|
||||
assert.isString('takerAssetData', takerAssetData);
|
||||
assetDataUtils.decodeAssetDataOrThrow(makerAssetData);
|
||||
assetDataUtils.decodeAssetDataOrThrow(takerAssetData);
|
||||
assert.isBoolean('options.shouldForceOrderRefresh', shouldForceOrderRefresh);
|
||||
|
||||
const assetPairs = await this.orderProvider.getAvailableMakerAssetDatasAsync(assetData);
|
||||
const etherTokenAssetData = this._getEtherTokenAssetDataOrThrow();
|
||||
if (!assetPairs.includes(etherTokenAssetData)) {
|
||||
const assetPairs = await this.orderProvider.getAvailableMakerAssetDatasAsync(takerAssetData);
|
||||
if (!assetPairs.includes(makerAssetData)) {
|
||||
return {
|
||||
tokensAvailableInBaseUnits: new BigNumber(0),
|
||||
ethValueAvailableInWei: new BigNumber(0),
|
||||
makerTokensAvailableInBaseUnits: new BigNumber(0),
|
||||
takerTokensAvailableInBaseUnits: new BigNumber(0),
|
||||
};
|
||||
}
|
||||
|
||||
const ordersAndFillableAmounts = await this.getOrdersAndFillableAmountsAsync(
|
||||
assetData,
|
||||
makerAssetData,
|
||||
takerAssetData,
|
||||
shouldForceOrderRefresh,
|
||||
);
|
||||
|
||||
return calculateLiquidity(ordersAndFillableAmounts);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 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 options Options for the execution of the BuyQuote. See type definition for more information.
|
||||
// *
|
||||
// * @return A promise of the txHash.
|
||||
// */
|
||||
// public async executeBuyQuoteAsync(
|
||||
// buyQuote: BuyQuote,
|
||||
// options: Partial<BuyQuoteExecutionOpts> = {},
|
||||
// ): Promise<string> {
|
||||
// const { ethAmount, takerAddress, feeRecipient, gasLimit, gasPrice } = _.merge(
|
||||
// {},
|
||||
// constants.DEFAULT_BUY_QUOTE_EXECUTION_OPTS,
|
||||
// options,
|
||||
// );
|
||||
// assert.isValidBuyQuote('buyQuote', buyQuote);
|
||||
// if (ethAmount !== undefined) {
|
||||
// assert.isBigNumber('ethAmount', ethAmount);
|
||||
// }
|
||||
// if (takerAddress !== undefined) {
|
||||
// assert.isETHAddressHex('takerAddress', takerAddress);
|
||||
// }
|
||||
// assert.isETHAddressHex('feeRecipient', feeRecipient);
|
||||
// if (gasLimit !== undefined) {
|
||||
// assert.isNumber('gasLimit', gasLimit);
|
||||
// }
|
||||
// if (gasPrice !== undefined) {
|
||||
// assert.isBigNumber('gasPrice', gasPrice);
|
||||
// }
|
||||
// const { orders, feeOrders, makerAssetBuyAmount, worstCaseQuoteInfo } = buyQuote;
|
||||
// // TODO(dave4506) upgrade logic for asset-buyer2.0
|
||||
// // if no takerAddress is provided, try to get one from the provider
|
||||
// // let finalTakerAddress;
|
||||
// // if (takerAddress !== undefined) {
|
||||
// // finalTakerAddress = takerAddress;
|
||||
// // } else {
|
||||
// // const web3Wrapper = new Web3Wrapper(this.provider);
|
||||
// // const availableAddresses = await web3Wrapper.getAvailableAddressesAsync();
|
||||
// // const firstAvailableAddress = _.head(availableAddresses);
|
||||
// // if (firstAvailableAddress !== undefined) {
|
||||
// // finalTakerAddress = firstAvailableAddress;
|
||||
// // } else {
|
||||
// // throw new Error(AssetBuyerError.NoAddressAvailable);
|
||||
// // }
|
||||
// // }
|
||||
// // try {
|
||||
// // // if no ethAmount is provided, default to the worst ethAmount from buyQuote
|
||||
// // const txHash = await this._contractWrappers.forwarder.marketBuyOrdersWithEthAsync(
|
||||
// // orders,
|
||||
// // assetBuyAmount,
|
||||
// // finalTakerAddress,
|
||||
// // ethAmount || worstCaseQuoteInfo.totalEthAmount,
|
||||
// // feeOrders,
|
||||
// // feePercentage,
|
||||
// // feeRecipient,
|
||||
// // {
|
||||
// // gasLimit,
|
||||
// // gasPrice,
|
||||
// // shouldValidate: true,
|
||||
// // },
|
||||
// // );
|
||||
// // return txHash;
|
||||
// // } catch (err) {
|
||||
// // if (_.includes(err.message, ContractWrappersError.SignatureRequestDenied)) {
|
||||
// // throw new Error(AssetBuyerError.SignatureRequestDenied);
|
||||
// // } else if (_.includes(err.message, ForwarderWrapperError.CompleteFillFailed)) {
|
||||
// // throw new Error(AssetBuyerError.TransactionValueTooLow);
|
||||
// // } else {
|
||||
// // throw err;
|
||||
// // }
|
||||
// // }
|
||||
// return Promise.resolve('test');
|
||||
// }
|
||||
|
||||
/**
|
||||
* 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 options Options for the execution of the BuyQuote. See type definition for more information.
|
||||
*
|
||||
* @return A promise of the txHash.
|
||||
*/
|
||||
public async executeBuyQuoteAsync(
|
||||
buyQuote: BuyQuote,
|
||||
options: Partial<BuyQuoteExecutionOpts> = {},
|
||||
): Promise<string> {
|
||||
const { ethAmount, takerAddress, feeRecipient, gasLimit, gasPrice } = _.merge(
|
||||
{},
|
||||
constants.DEFAULT_BUY_QUOTE_EXECUTION_OPTS,
|
||||
options,
|
||||
);
|
||||
assert.isValidBuyQuote('buyQuote', buyQuote);
|
||||
if (ethAmount !== undefined) {
|
||||
assert.isBigNumber('ethAmount', ethAmount);
|
||||
}
|
||||
if (takerAddress !== undefined) {
|
||||
assert.isETHAddressHex('takerAddress', takerAddress);
|
||||
}
|
||||
assert.isETHAddressHex('feeRecipient', feeRecipient);
|
||||
if (gasLimit !== undefined) {
|
||||
assert.isNumber('gasLimit', gasLimit);
|
||||
}
|
||||
if (gasPrice !== undefined) {
|
||||
assert.isBigNumber('gasPrice', gasPrice);
|
||||
}
|
||||
const { orders, feeOrders, feePercentage, assetBuyAmount, worstCaseQuoteInfo } = buyQuote;
|
||||
// if no takerAddress is provided, try to get one from the provider
|
||||
let finalTakerAddress;
|
||||
if (takerAddress !== undefined) {
|
||||
finalTakerAddress = takerAddress;
|
||||
} else {
|
||||
const web3Wrapper = new Web3Wrapper(this.provider);
|
||||
const availableAddresses = await web3Wrapper.getAvailableAddressesAsync();
|
||||
const firstAvailableAddress = _.head(availableAddresses);
|
||||
if (firstAvailableAddress !== undefined) {
|
||||
finalTakerAddress = firstAvailableAddress;
|
||||
} else {
|
||||
throw new Error(AssetBuyerError.NoAddressAvailable);
|
||||
}
|
||||
}
|
||||
try {
|
||||
// if no ethAmount is provided, default to the worst ethAmount from buyQuote
|
||||
const txHash = await this._contractWrappers.forwarder.marketBuyOrdersWithEthAsync(
|
||||
orders,
|
||||
assetBuyAmount,
|
||||
finalTakerAddress,
|
||||
ethAmount || worstCaseQuoteInfo.totalEthAmount,
|
||||
feeOrders,
|
||||
feePercentage,
|
||||
feeRecipient,
|
||||
{
|
||||
gasLimit,
|
||||
gasPrice,
|
||||
shouldValidate: true,
|
||||
},
|
||||
);
|
||||
return txHash;
|
||||
} catch (err) {
|
||||
if (_.includes(err.message, ContractWrappersError.SignatureRequestDenied)) {
|
||||
throw new Error(AssetBuyerError.SignatureRequestDenied);
|
||||
} else if (_.includes(err.message, ForwarderWrapperError.CompleteFillFailed)) {
|
||||
throw new Error(AssetBuyerError.TransactionValueTooLow);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get the asset data of all assets that are purchaseable with ether token (wETH) in the order provider passed in at init.
|
||||
* Get the asset data of all assets that can be used to purchase makerAssetData in the order provider passed in at init.
|
||||
*
|
||||
* @return An array of asset data strings that can be purchased using wETH.
|
||||
*/
|
||||
public async getAvailableAssetDatasAsync(): Promise<string[]> {
|
||||
const etherTokenAssetData = this._getEtherTokenAssetDataOrThrow();
|
||||
return this.orderProvider.getAvailableMakerAssetDatasAsync(etherTokenAssetData);
|
||||
public async getAvailableTakerAssetDatasAsync(makerAssetData: string): Promise<string[]> {
|
||||
assert.isString('makerAssetDataa', makerAssetData);
|
||||
assetDataUtils.decodeAssetDataOrThrow(makerAssetData);
|
||||
return this.orderProvider.getAvailableTakerAssetDatasAsync(makerAssetData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the asset data of all assets that are purchaseable with makerAssetData in the order provider passed in at init.
|
||||
*
|
||||
* @return An array of asset data strings that can be purchased using wETH.
|
||||
*/
|
||||
public async getAvailableMakerAssetDatasAsync(takerAssetData: string): Promise<string[]> {
|
||||
assert.isString('takerAssetData', takerAssetData);
|
||||
assetDataUtils.decodeAssetDataOrThrow(takerAssetData);
|
||||
return this.orderProvider.getAvailableMakerAssetDatasAsync(takerAssetData);
|
||||
}
|
||||
|
||||
/**
|
||||
* validates that the taker + maker asset pair is availalbe from the order provider passed
|
||||
*
|
||||
* @return A boolean on if the taker + maker pair exists
|
||||
*/
|
||||
public async isTakerMakerAssetDataPairAvailableAsync(makerAssetData: string, takerAssetData: string): Promise<boolean> {
|
||||
assert.isString('makerAssetDataa', makerAssetData);
|
||||
assert.isString('takerAssetData', takerAssetData);
|
||||
assetDataUtils.decodeAssetDataOrThrow(makerAssetData);
|
||||
assetDataUtils.decodeAssetDataOrThrow(takerAssetData);
|
||||
return _.findIndex(await this.getAvailableMakerAssetDatasAsync(takerAssetData), makerAssetData) !== -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grab orders from the map, if there is a miss or it is time to refresh, fetch and process the orders
|
||||
* @param assetData The assetData of the desired asset to buy (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
|
||||
* @param shouldForceOrderRefresh If set to true, new orders and state will be fetched instead of waiting for the next orderRefreshIntervalMs.
|
||||
*/
|
||||
public async getOrdersAndFillableAmountsAsync(
|
||||
assetData: string,
|
||||
makerAssetData: string,
|
||||
takerAssetData: string,
|
||||
shouldForceOrderRefresh: boolean,
|
||||
): Promise<OrdersAndFillableAmounts> {
|
||||
assert.isString('makerAssetDataa', makerAssetData);
|
||||
assert.isString('takerAssetData', takerAssetData);
|
||||
assetDataUtils.decodeAssetDataOrThrow(makerAssetData);
|
||||
assetDataUtils.decodeAssetDataOrThrow(takerAssetData);
|
||||
// try to get ordersEntry from the map
|
||||
const ordersEntryIfExists = this._ordersEntryMap[assetData];
|
||||
const ordersEntryIfExists = this._ordersEntryMap[this._getOrdersEntryMapKey(makerAssetData, takerAssetData)];
|
||||
// we should refresh if:
|
||||
// we do not have any orders OR
|
||||
// we are forced to OR
|
||||
@@ -322,12 +362,11 @@ export class AssetBuyer {
|
||||
const result = ordersEntryIfExists.ordersAndFillableAmounts;
|
||||
return result;
|
||||
}
|
||||
const etherTokenAssetData = this._getEtherTokenAssetDataOrThrow();
|
||||
const zrxTokenAssetData = this._getZrxTokenAssetDataOrThrow();
|
||||
// construct orderProvider request
|
||||
const orderProviderRequest = {
|
||||
makerAssetData: assetData,
|
||||
takerAssetData: etherTokenAssetData,
|
||||
makerAssetData,
|
||||
takerAssetData,
|
||||
networkId: this.networkId,
|
||||
};
|
||||
const request = orderProviderRequest;
|
||||
@@ -337,7 +376,7 @@ export class AssetBuyer {
|
||||
// ie. it should only return maker/taker assetDatas that are specified
|
||||
orderProviderResponseProcessor.throwIfInvalidResponse(response, request);
|
||||
// process the responses into one object
|
||||
const isMakerAssetZrxToken = assetData === zrxTokenAssetData;
|
||||
const isMakerAssetZrxToken = makerAssetData === zrxTokenAssetData;
|
||||
const ordersAndFillableAmounts = await orderProviderResponseProcessor.processAsync(
|
||||
response,
|
||||
isMakerAssetZrxToken,
|
||||
@@ -349,16 +388,26 @@ export class AssetBuyer {
|
||||
ordersAndFillableAmounts,
|
||||
lastRefreshTime,
|
||||
};
|
||||
this._ordersEntryMap[assetData] = updatedOrdersEntry;
|
||||
this._ordersEntryMap[this._getOrdersEntryMapKey(makerAssetData, takerAssetData)] = updatedOrdersEntry;
|
||||
return ordersAndFillableAmounts;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* get the key for _orderEntryMap for maker + taker asset pair
|
||||
*/
|
||||
// tslint:disable-next-line: prefer-function-over-method
|
||||
private _getOrdersEntryMapKey(makerAssetData: string, takerAssetData: string): string {
|
||||
return `${makerAssetData}_${takerAssetData}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the assetData that represents the WETH token.
|
||||
* Will throw if WETH does not exist for the current network.
|
||||
*/
|
||||
private _getEtherTokenAssetDataOrThrow(): string {
|
||||
return assetDataUtils.getEtherTokenAssetData(this._contractWrappers);
|
||||
}
|
||||
// private _getEtherTokenAssetDataOrThrow(): string {
|
||||
// return assetDataUtils.getEtherTokenAssetData(this._contractWrappers);
|
||||
// }
|
||||
/**
|
||||
* Get the assetData that represents the ZRX token.
|
||||
* Will throw if ZRX does not exist for the current network.
|
||||
|
@@ -13,13 +13,17 @@ export {
|
||||
Web3JsV2Provider,
|
||||
Web3JsV3Provider,
|
||||
} from 'ethereum-types';
|
||||
|
||||
// TODO(dave4506): if this lives under the 0x.js library, then these type exports should be removed in favor of minimizing redundancy
|
||||
export { SignedOrder } from '@0x/types';
|
||||
export { BigNumber } from '@0x/utils';
|
||||
|
||||
export { AssetBuyer } from './asset_buyer';
|
||||
export { InsufficientAssetLiquidityError } from './errors';
|
||||
|
||||
export { BasicOrderProvider } from './order_providers/basic_order_provider';
|
||||
export { StandardRelayerAPIOrderProvider } from './order_providers/standard_relayer_api_order_provider';
|
||||
|
||||
export {
|
||||
AssetBuyerError,
|
||||
AssetBuyerOpts,
|
||||
|
@@ -38,4 +38,13 @@ export class BasicOrderProvider implements OrderProvider {
|
||||
const ordersWithTakerAssetData = _.filter(this.orders, { takerAssetData });
|
||||
return _.map(ordersWithTakerAssetData, order => order.makerAssetData);
|
||||
}
|
||||
/**
|
||||
* Given a maker asset data string, return all availabled paired taker asset data strings.
|
||||
* @param makerAssetData A string representing the maker asset data.
|
||||
* @return An array of asset data strings that can be used to purchased makerAssetData.
|
||||
*/
|
||||
public async getAvailableTakerAssetDatasAsync(makerAssetData: string): Promise<string[]> {
|
||||
const ordersWithMakerAssetData = _.filter(this.orders, { makerAssetData });
|
||||
return _.map(ordersWithMakerAssetData, order => order.takerAssetData);
|
||||
}
|
||||
}
|
||||
|
@@ -111,4 +111,32 @@ export class StandardRelayerAPIOrderProvider implements OrderProvider {
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Given a maker asset data string, return all availabled paired taker asset data strings.
|
||||
* @param makerAssetData A string representing the maker asset data.
|
||||
* @return An array of asset data strings that can be used to purchased makerAssetData.
|
||||
*/
|
||||
public async getAvailableTakerAssetDatasAsync(makerAssetData: string): Promise<string[]> {
|
||||
// Return a maximum of 1000 asset datas
|
||||
const maxPerPage = 1000;
|
||||
const requestOpts = { networkId: this.networkId, perPage: maxPerPage };
|
||||
const assetPairsRequest = { assetDataA: makerAssetData };
|
||||
const fullRequest = {
|
||||
...requestOpts,
|
||||
...assetPairsRequest,
|
||||
};
|
||||
let response: AssetPairsResponse;
|
||||
try {
|
||||
response = await this._sraClient.getAssetPairsAsync(fullRequest);
|
||||
} catch (err) {
|
||||
throw new Error(AssetBuyerError.StandardRelayerApiError);
|
||||
}
|
||||
return _.map(response.records, item => {
|
||||
if (item.assetDataA.assetData === makerAssetData) {
|
||||
return item.assetDataB.assetData;
|
||||
} else {
|
||||
return item.assetDataA.assetData;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@ export interface OrderProviderResponse {
|
||||
export interface SignedOrderWithRemainingFillableMakerAssetAmount extends SignedOrder {
|
||||
remainingFillableMakerAssetAmount?: BigNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* gerOrdersAsync: Given an OrderProviderRequest, get an OrderProviderResponse.
|
||||
* getAvailableMakerAssetDatasAsync: Given a taker asset data string, return all availabled paired maker asset data strings.
|
||||
@@ -32,6 +33,7 @@ export interface SignedOrderWithRemainingFillableMakerAssetAmount extends Signed
|
||||
export interface OrderProvider {
|
||||
getOrdersAsync: (orderProviderRequest: OrderProviderRequest) => Promise<OrderProviderResponse>;
|
||||
getAvailableMakerAssetDatasAsync: (takerAssetData: string) => Promise<string[]>;
|
||||
getAvailableTakerAssetDatasAsync: (makerAssetData: string) => Promise<string[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,13 +46,15 @@ export interface OrderProvider {
|
||||
* worstCaseQuoteInfo: Info about the worst case price for the asset.
|
||||
*/
|
||||
export interface BuyQuote {
|
||||
assetData: string;
|
||||
assetBuyAmount: BigNumber;
|
||||
takerAssetData: string;
|
||||
makerAssetData: string;
|
||||
makerAssetBuyAmount: BigNumber;
|
||||
orders: SignedOrder[];
|
||||
feeOrders: SignedOrder[];
|
||||
feePercentage?: number;
|
||||
bestCaseQuoteInfo: BuyQuoteInfo;
|
||||
worstCaseQuoteInfo: BuyQuoteInfo;
|
||||
toAddress: string; // exchange address, coordinator address
|
||||
isUsingCoordinator: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,18 +63,16 @@ export interface BuyQuote {
|
||||
* totalEthAmount: The total amount of eth required to complete the buy (filling orders, feeOrders, and paying affiliate fee).
|
||||
*/
|
||||
export interface BuyQuoteInfo {
|
||||
assetEthAmount: BigNumber;
|
||||
feeEthAmount: BigNumber;
|
||||
totalEthAmount: BigNumber;
|
||||
takerTokenAmount: BigNumber;
|
||||
feeTakerTokenAmount: BigNumber;
|
||||
totalTakerTokenAmount: BigNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* feePercentage: The affiliate fee percentage. Defaults to 0.
|
||||
* shouldForceOrderRefresh: If set to true, new orders and state will be fetched instead of waiting for the next orderRefreshIntervalMs. Defaults to false.
|
||||
* slippagePercentage: The percentage buffer to add to account for slippage. Affects max ETH price estimates. Defaults to 0.2 (20%).
|
||||
*/
|
||||
export interface BuyQuoteRequestOpts {
|
||||
feePercentage: number;
|
||||
shouldForceOrderRefresh: boolean;
|
||||
slippagePercentage: number;
|
||||
}
|
||||
@@ -137,6 +139,6 @@ export interface OrdersAndFillableAmounts {
|
||||
* Represents available liquidity for a given assetData
|
||||
*/
|
||||
export interface LiquidityForAssetData {
|
||||
tokensAvailableInBaseUnits: BigNumber;
|
||||
ethValueAvailableInWei: BigNumber;
|
||||
makerTokensAvailableInBaseUnits: BigNumber;
|
||||
takerTokensAvailableInBaseUnits: BigNumber;
|
||||
}
|
||||
|
@@ -6,20 +6,24 @@ import { BuyQuote, BuyQuoteInfo, OrderProvider, OrderProviderRequest } from '../
|
||||
export const assert = {
|
||||
...sharedAssert,
|
||||
isValidBuyQuote(variableName: string, buyQuote: BuyQuote): void {
|
||||
sharedAssert.isHexString(`${variableName}.assetData`, buyQuote.assetData);
|
||||
sharedAssert.isHexString(`${variableName}.takerAssetData`, buyQuote.takerAssetData);
|
||||
sharedAssert.isHexString(`${variableName}.makerAssetData`, buyQuote.makerAssetData);
|
||||
sharedAssert.doesConformToSchema(`${variableName}.orders`, buyQuote.orders, schemas.signedOrdersSchema);
|
||||
sharedAssert.doesConformToSchema(`${variableName}.feeOrders`, buyQuote.feeOrders, schemas.signedOrdersSchema);
|
||||
assert.isValidBuyQuoteInfo(`${variableName}.bestCaseQuoteInfo`, buyQuote.bestCaseQuoteInfo);
|
||||
assert.isValidBuyQuoteInfo(`${variableName}.worstCaseQuoteInfo`, buyQuote.worstCaseQuoteInfo);
|
||||
sharedAssert.isBigNumber(`${variableName}.assetBuyAmount`, buyQuote.assetBuyAmount);
|
||||
if (buyQuote.feePercentage !== undefined) {
|
||||
sharedAssert.isNumber(`${variableName}.feePercentage`, buyQuote.feePercentage);
|
||||
}
|
||||
sharedAssert.isBigNumber(`${variableName}.makerAssetBuyAmount`, buyQuote.makerAssetBuyAmount);
|
||||
assert.isETHAddressHex(`${variableName}.toAddress`, buyQuote.toAddress);
|
||||
assert.isBoolean(`${variableName}.isUsingCoordinator`, buyQuote.isUsingCoordinator);
|
||||
// TODO(dave4506) Remove once forwarder features are reimplemented
|
||||
// if (buyQuote.feePercentage !== undefined) {
|
||||
// sharedAssert.isNumber(`${variableName}.feePercentage`, buyQuote.feePercentage);
|
||||
// }
|
||||
},
|
||||
isValidBuyQuoteInfo(variableName: string, buyQuoteInfo: BuyQuoteInfo): void {
|
||||
sharedAssert.isBigNumber(`${variableName}.assetEthAmount`, buyQuoteInfo.assetEthAmount);
|
||||
sharedAssert.isBigNumber(`${variableName}.feeEthAmount`, buyQuoteInfo.feeEthAmount);
|
||||
sharedAssert.isBigNumber(`${variableName}.totalEthAmount`, buyQuoteInfo.totalEthAmount);
|
||||
sharedAssert.isBigNumber(`${variableName}.takerTokenAmount`, buyQuoteInfo.takerTokenAmount);
|
||||
sharedAssert.isBigNumber(`${variableName}.feeTakerTokenAmount`, buyQuoteInfo.feeTakerTokenAmount);
|
||||
sharedAssert.isBigNumber(`${variableName}.totalTakerTokenAmount`, buyQuoteInfo.totalTakerTokenAmount);
|
||||
},
|
||||
isValidOrderProvider(variableName: string, orderFetcher: OrderProvider): void {
|
||||
sharedAssert.isFunction(`${variableName}.getOrdersAsync`, orderFetcher.getOrdersAsync);
|
||||
|
@@ -11,8 +11,7 @@ export const buyQuoteCalculator = {
|
||||
calculate(
|
||||
ordersAndFillableAmounts: OrdersAndFillableAmounts,
|
||||
feeOrdersAndFillableAmounts: OrdersAndFillableAmounts,
|
||||
assetBuyAmount: BigNumber,
|
||||
feePercentage: number,
|
||||
makerAssetBuyAmount: BigNumber,
|
||||
slippagePercentage: number,
|
||||
isMakerAssetZrxToken: boolean,
|
||||
): BuyQuote {
|
||||
@@ -20,20 +19,20 @@ export const buyQuoteCalculator = {
|
||||
const remainingFillableMakerAssetAmounts = ordersAndFillableAmounts.remainingFillableMakerAssetAmounts;
|
||||
const feeOrders = feeOrdersAndFillableAmounts.orders;
|
||||
const remainingFillableFeeAmounts = feeOrdersAndFillableAmounts.remainingFillableMakerAssetAmounts;
|
||||
const slippageBufferAmount = assetBuyAmount.multipliedBy(slippagePercentage).integerValue();
|
||||
const slippageBufferAmount = makerAssetBuyAmount.multipliedBy(slippagePercentage).integerValue();
|
||||
// find the orders that cover the desired assetBuyAmount (with slippage)
|
||||
const {
|
||||
resultOrders,
|
||||
remainingFillAmount,
|
||||
ordersRemainingFillableMakerAssetAmounts,
|
||||
} = marketUtils.findOrdersThatCoverMakerAssetFillAmount(orders, assetBuyAmount, {
|
||||
} = marketUtils.findOrdersThatCoverMakerAssetFillAmount(orders, makerAssetBuyAmount, {
|
||||
remainingFillableMakerAssetAmounts,
|
||||
slippageBufferAmount,
|
||||
});
|
||||
// if we do not have enough orders to cover the desired assetBuyAmount, throw
|
||||
if (remainingFillAmount.gt(constants.ZERO_AMOUNT)) {
|
||||
// We needed the amount they requested to buy, plus the amount for slippage
|
||||
const totalAmountRequested = assetBuyAmount.plus(slippageBufferAmount);
|
||||
const totalAmountRequested = makerAssetBuyAmount.plus(slippageBufferAmount);
|
||||
const amountAbleToFill = totalAmountRequested.minus(remainingFillAmount);
|
||||
// multiplierNeededWithSlippage represents what we need to multiply the assetBuyAmount by
|
||||
// in order to get the total amount needed considering slippage
|
||||
@@ -73,7 +72,9 @@ export const buyQuoteCalculator = {
|
||||
}
|
||||
|
||||
// assetData information for the result
|
||||
const assetData = orders[0].makerAssetData;
|
||||
const takerAssetData = orders[0].takerAssetData;
|
||||
const makerAssetData = orders[0].makerAssetData;
|
||||
|
||||
// compile the resulting trimmed set of orders for makerAsset and feeOrders that are needed for assetBuyAmount
|
||||
const trimmedOrdersAndFillableAmounts: OrdersAndFillableAmounts = {
|
||||
orders: resultOrders,
|
||||
@@ -86,26 +87,28 @@ export const buyQuoteCalculator = {
|
||||
const bestCaseQuoteInfo = calculateQuoteInfo(
|
||||
trimmedOrdersAndFillableAmounts,
|
||||
trimmedFeeOrdersAndFillableAmounts,
|
||||
assetBuyAmount,
|
||||
feePercentage,
|
||||
makerAssetBuyAmount,
|
||||
isMakerAssetZrxToken,
|
||||
);
|
||||
// in order to calculate the maxRate, reverse the ordersAndFillableAmounts such that they are sorted from worst rate to best rate
|
||||
const worstCaseQuoteInfo = calculateQuoteInfo(
|
||||
reverseOrdersAndFillableAmounts(trimmedOrdersAndFillableAmounts),
|
||||
reverseOrdersAndFillableAmounts(trimmedFeeOrdersAndFillableAmounts),
|
||||
assetBuyAmount,
|
||||
feePercentage,
|
||||
makerAssetBuyAmount,
|
||||
isMakerAssetZrxToken,
|
||||
);
|
||||
|
||||
return {
|
||||
assetData,
|
||||
takerAssetData,
|
||||
makerAssetData,
|
||||
makerAssetBuyAmount,
|
||||
orders: resultOrders,
|
||||
feeOrders: resultFeeOrders,
|
||||
bestCaseQuoteInfo,
|
||||
worstCaseQuoteInfo,
|
||||
assetBuyAmount,
|
||||
feePercentage,
|
||||
// TODO(dave4506): coordinator metadata for buy quote
|
||||
toAddress: constants.NULL_ADDRESS,
|
||||
isUsingCoordinator: false,
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -113,33 +116,29 @@ export const buyQuoteCalculator = {
|
||||
function calculateQuoteInfo(
|
||||
ordersAndFillableAmounts: OrdersAndFillableAmounts,
|
||||
feeOrdersAndFillableAmounts: OrdersAndFillableAmounts,
|
||||
assetBuyAmount: BigNumber,
|
||||
feePercentage: number,
|
||||
makserAssetBuyAmount: BigNumber,
|
||||
isMakerAssetZrxToken: boolean,
|
||||
): BuyQuoteInfo {
|
||||
// find the total eth and zrx needed to buy assetAmount from the resultOrders from left to right
|
||||
let assetEthAmount = constants.ZERO_AMOUNT;
|
||||
let zrxEthAmount = constants.ZERO_AMOUNT;
|
||||
let takerTokenAmount = constants.ZERO_AMOUNT;
|
||||
let zrxTakerTokenAmount = constants.ZERO_AMOUNT;
|
||||
if (isMakerAssetZrxToken) {
|
||||
assetEthAmount = findEthAmountNeededToBuyZrx(ordersAndFillableAmounts, assetBuyAmount);
|
||||
takerTokenAmount = findTakerTokenAmountNeededToBuyZrx(ordersAndFillableAmounts, makserAssetBuyAmount);
|
||||
} else {
|
||||
// find eth and zrx amounts needed to buy
|
||||
const ethAndZrxAmountToBuyAsset = findEthAndZrxAmountNeededToBuyAsset(ordersAndFillableAmounts, assetBuyAmount);
|
||||
assetEthAmount = ethAndZrxAmountToBuyAsset[0];
|
||||
const zrxAmountToBuyAsset = ethAndZrxAmountToBuyAsset[1];
|
||||
const takerTokenAndZrxAmountToBuyAsset = findTakerTokenAndZrxAmountNeededToBuyAsset(ordersAndFillableAmounts, makserAssetBuyAmount);
|
||||
takerTokenAmount = takerTokenAndZrxAmountToBuyAsset[0];
|
||||
const zrxAmountToBuyAsset = takerTokenAndZrxAmountToBuyAsset[1];
|
||||
// find eth amount needed to buy zrx
|
||||
zrxEthAmount = findEthAmountNeededToBuyZrx(feeOrdersAndFillableAmounts, zrxAmountToBuyAsset);
|
||||
zrxTakerTokenAmount = findTakerTokenAmountNeededToBuyZrx(feeOrdersAndFillableAmounts, zrxAmountToBuyAsset);
|
||||
}
|
||||
// eth amount needed to buy the affiliate fee
|
||||
const affiliateFeeEthAmount = assetEthAmount.multipliedBy(feePercentage).integerValue(BigNumber.ROUND_CEIL);
|
||||
// eth amount needed for fees is the sum of affiliate fee and zrx fee
|
||||
const feeEthAmount = affiliateFeeEthAmount.plus(zrxEthAmount);
|
||||
const feeTakerTokenAmount = zrxTakerTokenAmount;
|
||||
// eth amount needed in total is the sum of the amount needed for the asset and the amount needed for fees
|
||||
const totalEthAmount = assetEthAmount.plus(feeEthAmount);
|
||||
const totalTakerTokenAmount = takerTokenAmount.plus(feeTakerTokenAmount);
|
||||
return {
|
||||
assetEthAmount,
|
||||
feeEthAmount,
|
||||
totalEthAmount,
|
||||
takerTokenAmount,
|
||||
feeTakerTokenAmount,
|
||||
totalTakerTokenAmount,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -153,7 +152,7 @@ function reverseOrdersAndFillableAmounts(ordersAndFillableAmounts: OrdersAndFill
|
||||
};
|
||||
}
|
||||
|
||||
function findEthAmountNeededToBuyZrx(
|
||||
function findTakerTokenAmountNeededToBuyZrx(
|
||||
feeOrdersAndFillableAmounts: OrdersAndFillableAmounts,
|
||||
zrxBuyAmount: BigNumber,
|
||||
): BigNumber {
|
||||
@@ -161,18 +160,19 @@ function findEthAmountNeededToBuyZrx(
|
||||
const result = _.reduce(
|
||||
orders,
|
||||
(acc, order, index) => {
|
||||
const { totalEthAmount, remainingZrxBuyAmount } = acc;
|
||||
const { totalTakerTokenAmount, remainingZrxBuyAmount } = acc;
|
||||
const remainingFillableMakerAssetAmount = remainingFillableMakerAssetAmounts[index];
|
||||
const makerFillAmount = BigNumber.min(remainingZrxBuyAmount, remainingFillableMakerAssetAmount);
|
||||
const [takerFillAmount, adjustedMakerFillAmount] = orderCalculationUtils.getTakerFillAmountForFeeOrder(
|
||||
order,
|
||||
makerFillAmount,
|
||||
);
|
||||
// TODO(dave4506) may remove if this is for affiliate fees (asset-buyer2.0)
|
||||
const extraFeeAmount = remainingFillableMakerAssetAmount.isGreaterThanOrEqualTo(adjustedMakerFillAmount)
|
||||
? constants.ZERO_AMOUNT
|
||||
: adjustedMakerFillAmount.minus(makerFillAmount);
|
||||
return {
|
||||
totalEthAmount: totalEthAmount.plus(takerFillAmount),
|
||||
totalTakerTokenAmount: totalTakerTokenAmount.plus(takerFillAmount),
|
||||
remainingZrxBuyAmount: BigNumber.max(
|
||||
constants.ZERO_AMOUNT,
|
||||
remainingZrxBuyAmount.minus(makerFillAmount).plus(extraFeeAmount),
|
||||
@@ -180,40 +180,40 @@ function findEthAmountNeededToBuyZrx(
|
||||
};
|
||||
},
|
||||
{
|
||||
totalEthAmount: constants.ZERO_AMOUNT,
|
||||
totalTakerTokenAmount: constants.ZERO_AMOUNT,
|
||||
remainingZrxBuyAmount: zrxBuyAmount,
|
||||
},
|
||||
);
|
||||
return result.totalEthAmount;
|
||||
return result.totalTakerTokenAmount;
|
||||
}
|
||||
|
||||
function findEthAndZrxAmountNeededToBuyAsset(
|
||||
function findTakerTokenAndZrxAmountNeededToBuyAsset(
|
||||
ordersAndFillableAmounts: OrdersAndFillableAmounts,
|
||||
assetBuyAmount: BigNumber,
|
||||
makerAssetBuyAmount: BigNumber,
|
||||
): [BigNumber, BigNumber] {
|
||||
const { orders, remainingFillableMakerAssetAmounts } = ordersAndFillableAmounts;
|
||||
const result = _.reduce(
|
||||
orders,
|
||||
(acc, order, index) => {
|
||||
const { totalEthAmount, totalZrxAmount, remainingAssetBuyAmount } = acc;
|
||||
const { totalTakerTokenAmount, totalZrxAmount, remainingMakerAssetBuyAmount } = acc;
|
||||
const remainingFillableMakerAssetAmount = remainingFillableMakerAssetAmounts[index];
|
||||
const makerFillAmount = BigNumber.min(acc.remainingAssetBuyAmount, remainingFillableMakerAssetAmount);
|
||||
const makerFillAmount = BigNumber.min(acc.remainingMakerAssetBuyAmount, remainingFillableMakerAssetAmount);
|
||||
const takerFillAmount = orderCalculationUtils.getTakerFillAmount(order, makerFillAmount);
|
||||
const takerFeeAmount = orderCalculationUtils.getTakerFeeAmount(order, takerFillAmount);
|
||||
return {
|
||||
totalEthAmount: totalEthAmount.plus(takerFillAmount),
|
||||
totalTakerTokenAmount: totalTakerTokenAmount.plus(takerFillAmount),
|
||||
totalZrxAmount: totalZrxAmount.plus(takerFeeAmount),
|
||||
remainingAssetBuyAmount: BigNumber.max(
|
||||
remainingMakerAssetBuyAmount: BigNumber.max(
|
||||
constants.ZERO_AMOUNT,
|
||||
remainingAssetBuyAmount.minus(makerFillAmount),
|
||||
remainingMakerAssetBuyAmount.minus(makerFillAmount),
|
||||
),
|
||||
};
|
||||
},
|
||||
{
|
||||
totalEthAmount: constants.ZERO_AMOUNT,
|
||||
totalTakerTokenAmount: constants.ZERO_AMOUNT,
|
||||
totalZrxAmount: constants.ZERO_AMOUNT,
|
||||
remainingAssetBuyAmount: assetBuyAmount,
|
||||
remainingMakerAssetBuyAmount: makerAssetBuyAmount,
|
||||
},
|
||||
);
|
||||
return [result.totalEthAmount, result.totalZrxAmount];
|
||||
return [result.totalTakerTokenAmount, result.totalZrxAmount];
|
||||
}
|
||||
|
@@ -12,25 +12,25 @@ export const calculateLiquidity = (ordersAndFillableAmounts: OrdersAndFillableAm
|
||||
throw new Error(`No corresponding fillableMakerAssetAmounts at index ${curIndex}`);
|
||||
}
|
||||
|
||||
const tokensAvailableForCurrentOrder = availableMakerAssetAmount;
|
||||
const ethValueAvailableForCurrentOrder = orderCalculationUtils.getTakerFillAmount(
|
||||
const makerTokensAvailableForCurrentOrder = availableMakerAssetAmount;
|
||||
const takerTokensAvailableForCurrentOrder = orderCalculationUtils.getTakerFillAmount(
|
||||
order,
|
||||
availableMakerAssetAmount,
|
||||
makerTokensAvailableForCurrentOrder,
|
||||
);
|
||||
return {
|
||||
tokensAvailableInBaseUnits: acc.tokensAvailableInBaseUnits.plus(tokensAvailableForCurrentOrder),
|
||||
ethValueAvailableInWei: acc.ethValueAvailableInWei.plus(ethValueAvailableForCurrentOrder),
|
||||
makerTokensAvailableInBaseUnits: acc.makerTokensAvailableInBaseUnits.plus(makerTokensAvailableForCurrentOrder),
|
||||
takerTokensAvailableInBaseUnits: acc.takerTokensAvailableInBaseUnits.plus(takerTokensAvailableForCurrentOrder),
|
||||
};
|
||||
},
|
||||
{
|
||||
tokensAvailableInBaseUnits: new BigNumber(0),
|
||||
ethValueAvailableInWei: new BigNumber(0),
|
||||
makerTokensAvailableInBaseUnits: new BigNumber(0),
|
||||
takerTokensAvailableInBaseUnits: new BigNumber(0),
|
||||
},
|
||||
);
|
||||
|
||||
// Turn into regular numbers
|
||||
return {
|
||||
tokensAvailableInBaseUnits: liquidityInBigNumbers.tokensAvailableInBaseUnits,
|
||||
ethValueAvailableInWei: liquidityInBigNumbers.ethValueAvailableInWei,
|
||||
makerTokensAvailableInBaseUnits: liquidityInBigNumbers.makerTokensAvailableInBaseUnits,
|
||||
takerTokensAvailableInBaseUnits: liquidityInBigNumbers.takerTokensAvailableInBaseUnits,
|
||||
};
|
||||
};
|
||||
|
@@ -16,6 +16,10 @@ class OrderProviderClass implements OrderProvider {
|
||||
public async getAvailableMakerAssetDatasAsync(takerAssetData: string): Promise<string[]> {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
// tslint:disable-next-line:prefer-function-over-method
|
||||
public async getAvailableTakerAssetDatasAsync(takerAssetData: string): Promise<string[]> {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
}
|
||||
|
||||
export const orderProviderMock = () => {
|
||||
|
Reference in New Issue
Block a user