renamed asset-buyer + adding consumers
This commit is contained in:
parent
64e3b6f5ee
commit
549bfe98f1
@ -11,29 +11,28 @@ import { constants } from './constants';
|
||||
import { BasicOrderProvider } from './order_providers/basic_order_provider';
|
||||
import { StandardRelayerAPIOrderProvider } from './order_providers/standard_relayer_api_order_provider';
|
||||
import {
|
||||
AssetBuyerError,
|
||||
AssetBuyerOpts,
|
||||
BuyQuote,
|
||||
BuyQuoteExecutionOpts,
|
||||
BuyQuoteRequestOpts,
|
||||
AssetSwapQuoterError,
|
||||
AssetSwapQuoterOpts,
|
||||
LiquidityForAssetData,
|
||||
LiquidityRequestOpts,
|
||||
OrderProvider,
|
||||
OrdersAndFillableAmounts,
|
||||
SwapQuote,
|
||||
SwapQuoteRequestOpts,
|
||||
} from './types';
|
||||
|
||||
import { assert } from './utils/assert';
|
||||
import { assetDataUtils } from './utils/asset_data_utils';
|
||||
import { buyQuoteCalculator } from './utils/buy_quote_calculator';
|
||||
import { calculateLiquidity } from './utils/calculate_liquidity';
|
||||
import { orderProviderResponseProcessor } from './utils/order_provider_response_processor';
|
||||
import { swapQuoteCalculator } from './utils/swap_quote_calculator';
|
||||
|
||||
interface OrdersEntry {
|
||||
ordersAndFillableAmounts: OrdersAndFillableAmounts;
|
||||
lastRefreshTime: number;
|
||||
}
|
||||
|
||||
export class AssetBuyer {
|
||||
export class AssetSwapQuoter {
|
||||
public readonly provider: ZeroExProvider;
|
||||
public readonly orderProvider: OrderProvider;
|
||||
public readonly networkId: number;
|
||||
@ -44,7 +43,7 @@ export class AssetBuyer {
|
||||
private readonly _ordersEntryMap: ObjectMap<OrdersEntry> = {};
|
||||
/**
|
||||
* Instantiates a new AssetBuyer instance given existing liquidity in the form of orders and feeOrders.
|
||||
* @param supportedProvider The Provider instance you would like to use for interacting with the Ethereum network.
|
||||
* @param supportedProvider The Provider instance you would like to use for ikknteracting with the Ethereum network.
|
||||
* @param orders A non-empty array of objects that conform to SignedOrder. All orders must have the same makerAssetData and takerAssetData (WETH).
|
||||
* @param feeOrders A array of objects that conform to SignedOrder. All orders must have the same makerAssetData (ZRX) and takerAssetData (WETH). Defaults to an empty array.
|
||||
* @param options Initialization options for the AssetBuyer. See type definition for details.
|
||||
@ -54,12 +53,12 @@ export class AssetBuyer {
|
||||
public static getAssetBuyerForProvidedOrders(
|
||||
supportedProvider: SupportedProvider,
|
||||
orders: SignedOrder[],
|
||||
options: Partial<AssetBuyerOpts> = {},
|
||||
): AssetBuyer {
|
||||
options: Partial<AssetSwapQuoterOpts> = {},
|
||||
): AssetSwapQuoter {
|
||||
assert.doesConformToSchema('orders', orders, schemas.signedOrdersSchema);
|
||||
assert.assert(orders.length !== 0, `Expected orders to contain at least one order`);
|
||||
const orderProvider = new BasicOrderProvider(orders);
|
||||
const assetBuyer = new AssetBuyer(supportedProvider, orderProvider, options);
|
||||
const assetBuyer = new AssetSwapQuoter(supportedProvider, orderProvider, options);
|
||||
return assetBuyer;
|
||||
}
|
||||
/**
|
||||
@ -73,13 +72,13 @@ export class AssetBuyer {
|
||||
public static getAssetBuyerForStandardRelayerAPIUrl(
|
||||
supportedProvider: SupportedProvider,
|
||||
sraApiUrl: string,
|
||||
options: Partial<AssetBuyerOpts> = {},
|
||||
): AssetBuyer {
|
||||
options: Partial<AssetSwapQuoterOpts> = {},
|
||||
): AssetSwapQuoter {
|
||||
const provider = providerUtils.standardizeOrThrow(supportedProvider);
|
||||
assert.isWebUri('sraApiUrl', sraApiUrl);
|
||||
const networkId = options.networkId || constants.DEFAULT_ASSET_BUYER_OPTS.networkId;
|
||||
const networkId = options.networkId || constants.DEFAULT_ASSET_SWAP_QUOTER_OPTS.networkId;
|
||||
const orderProvider = new StandardRelayerAPIOrderProvider(sraApiUrl, networkId);
|
||||
const assetBuyer = new AssetBuyer(provider, orderProvider, options);
|
||||
const assetBuyer = new AssetSwapQuoter(provider, orderProvider, options);
|
||||
return assetBuyer;
|
||||
}
|
||||
/**
|
||||
@ -93,11 +92,11 @@ export class AssetBuyer {
|
||||
constructor(
|
||||
supportedProvider: SupportedProvider,
|
||||
orderProvider: OrderProvider,
|
||||
options: Partial<AssetBuyerOpts> = {},
|
||||
options: Partial<AssetSwapQuoter> = {},
|
||||
) {
|
||||
const { networkId, orderRefreshIntervalMs, expiryBufferSeconds } = _.merge(
|
||||
{},
|
||||
constants.DEFAULT_ASSET_BUYER_OPTS,
|
||||
constants.DEFAULT_ASSET_SWAP_QUOTER_OPTS,
|
||||
options,
|
||||
);
|
||||
const provider = providerUtils.standardizeOrThrow(supportedProvider);
|
||||
@ -115,23 +114,23 @@ export class AssetBuyer {
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Get a `BuyQuote` containing all information relevant to fulfilling a buy given a desired assetData.
|
||||
* You can then pass the `BuyQuote` to `executeBuyQuoteAsync` to execute the buy.
|
||||
* Get a `SwapQuote` containing all information relevant to fulfilling a buy given a desired assetData.
|
||||
* You can then pass the `SwapQuote` to `executeSwapQuoteAsync` to execute the buy.
|
||||
* @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 assetBuyAmount The amount of asset to buy.
|
||||
* @param options Options for the request. See type definition for more information.
|
||||
*
|
||||
* @return An object that conforms to BuyQuote that satisfies the request. See type definition for more information.
|
||||
* @return An object that conforms to SwapQuote that satisfies the request. See type definition for more information.
|
||||
*/
|
||||
public async getBuyQuoteAsync(
|
||||
public async getSwapQuoteAsync(
|
||||
makerAssetData: string,
|
||||
takerAssetData: string,
|
||||
makerAssetBuyAmount: BigNumber,
|
||||
options: Partial<BuyQuoteRequestOpts> = {},
|
||||
): Promise<BuyQuote> {
|
||||
options: Partial<SwapQuoteRequestOpts> = {},
|
||||
): Promise<SwapQuote> {
|
||||
const { shouldForceOrderRefresh, slippagePercentage } = _.merge(
|
||||
{},
|
||||
constants.DEFAULT_BUY_QUOTE_REQUEST_OPTS,
|
||||
constants.DEFAULT_SWAP_QUOTE_REQUEST_OPTS,
|
||||
options,
|
||||
);
|
||||
assert.isString('makerAssetData', makerAssetData);
|
||||
@ -151,39 +150,39 @@ export class AssetBuyer {
|
||||
shouldForceOrderRefresh,
|
||||
]);
|
||||
if (ordersAndFillableAmounts.orders.length === 0) {
|
||||
throw new Error(`${AssetBuyerError.AssetUnavailable}: For makerAssetdata ${makerAssetData} and takerAssetdata ${takerAssetData}`);
|
||||
throw new Error(`${AssetSwapQuoterError.AssetUnavailable}: For makerAssetdata ${makerAssetData} and takerAssetdata ${takerAssetData}`);
|
||||
}
|
||||
const buyQuote = buyQuoteCalculator.calculate(
|
||||
const swapQuote = swapQuoteCalculator.calculate(
|
||||
ordersAndFillableAmounts,
|
||||
feeOrdersAndFillableAmounts,
|
||||
makerAssetBuyAmount,
|
||||
slippagePercentage,
|
||||
isMakerAssetZrxToken,
|
||||
);
|
||||
return buyQuote;
|
||||
return swapQuote;
|
||||
}
|
||||
/**
|
||||
* Get a `BuyQuote` containing all information relevant to fulfilling a buy given a desired ERC20 token address.
|
||||
* You can then pass the `BuyQuote` to `executeBuyQuoteAsync` to execute the buy.
|
||||
* Get a `SwapQuote` containing all information relevant to fulfilling a buy given a desired ERC20 token address.
|
||||
* You can then pass the `SwapQuote` to `executeSwapQuoteAsync` to execute the buy.
|
||||
* @param tokenAddress The ERC20 token address.
|
||||
* @param assetBuyAmount The amount of asset to buy.
|
||||
* @param options Options for the request. See type definition for more information.
|
||||
*
|
||||
* @return An object that conforms to BuyQuote that satisfies the request. See type definition for more information.
|
||||
* @return An object that conforms to SwapQuote that satisfies the request. See type definition for more information.
|
||||
*/
|
||||
public async getBuyQuoteForERC20TokenAddressAsync(
|
||||
public async getSwapQuoteForERC20TokenAddressAsync(
|
||||
makerTokenAddress: string,
|
||||
takerTokenAddress: string,
|
||||
makerAssetBuyAmount: BigNumber,
|
||||
options: Partial<BuyQuoteRequestOpts> = {},
|
||||
): Promise<BuyQuote> {
|
||||
options: Partial<SwapQuoteRequestOpts> = {},
|
||||
): Promise<SwapQuote> {
|
||||
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;
|
||||
const swapQuote = this.getSwapQuoteAsync(makerAssetData, takerAssetData, makerAssetBuyAmount, options);
|
||||
return swapQuote;
|
||||
}
|
||||
/**
|
||||
* Returns information about available liquidity for an asset
|
||||
@ -224,13 +223,13 @@ export class AssetBuyer {
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 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.
|
||||
// * Given a SwapQuote and desired rate, attempt to execute the buy.
|
||||
// * @param SwapQuote An object that conforms to SwapQuote. See type definition for more information.
|
||||
// * @param options Options for the execution of the SwapQuote. See type definition for more information.
|
||||
// *
|
||||
// * @return A promise of the txHash.
|
||||
// */
|
||||
// public async executeBuyQuoteAsync(
|
||||
// public async executeSwapQuoteAsync(
|
||||
// buyQuote: BuyQuote,
|
||||
// options: Partial<BuyQuoteExecutionOpts> = {},
|
||||
// ): Promise<string> {
|
||||
@ -401,13 +400,6 @@ export class AssetBuyer {
|
||||
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);
|
||||
// }
|
||||
/**
|
||||
* Get the assetData that represents the ZRX token.
|
||||
* Will throw if ZRX does not exist for the current network.
|
||||
|
@ -1,40 +1,35 @@
|
||||
import { SignedOrder } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { AssetBuyerOpts, BuyQuoteExecutionOpts, BuyQuoteRequestOpts, OrdersAndFillableAmounts } from './types';
|
||||
import { AssetSwapQuoterOpts, ForwarderSwapQuoteExecutionOpts, OrdersAndFillableAmounts, SwapQuoteRequestOpts, SwapQuoteExecutionOpts } from './types';
|
||||
|
||||
const NULL_ADDRESS = '0x0000000000000000000000000000000000000000';
|
||||
const MAINNET_NETWORK_ID = 1;
|
||||
|
||||
const DEFAULT_ASSET_BUYER_OPTS: AssetBuyerOpts = {
|
||||
const DEFAULT_ASSET_SWAP_QUOTER_OPTS: AssetSwapQuoterOpts = {
|
||||
networkId: MAINNET_NETWORK_ID,
|
||||
orderRefreshIntervalMs: 10000, // 10 seconds
|
||||
expiryBufferSeconds: 120, // 2 minutes
|
||||
};
|
||||
|
||||
const DEFAULT_BUY_QUOTE_REQUEST_OPTS: BuyQuoteRequestOpts = {
|
||||
feePercentage: 0,
|
||||
const DEFAULT_SWAP_QUOTE_REQUEST_OPTS: SwapQuoteRequestOpts = {
|
||||
shouldForceOrderRefresh: false,
|
||||
slippagePercentage: 0.2, // 20% slippage protection
|
||||
};
|
||||
|
||||
// Other default values are dynamically determined
|
||||
const DEFAULT_BUY_QUOTE_EXECUTION_OPTS: BuyQuoteExecutionOpts = {
|
||||
feeRecipient: NULL_ADDRESS,
|
||||
slippagePercentage: 0.2, // 20% slippage protection,
|
||||
allowMarketBuyOrders: true,
|
||||
};
|
||||
|
||||
const EMPTY_ORDERS_AND_FILLABLE_AMOUNTS: OrdersAndFillableAmounts = {
|
||||
orders: [] as SignedOrder[],
|
||||
remainingFillableMakerAssetAmounts: [] as BigNumber[],
|
||||
};
|
||||
|
||||
|
||||
export const constants = {
|
||||
ZERO_AMOUNT: new BigNumber(0),
|
||||
NULL_ADDRESS,
|
||||
MAINNET_NETWORK_ID,
|
||||
ETHER_TOKEN_DECIMALS: 18,
|
||||
DEFAULT_ASSET_BUYER_OPTS,
|
||||
DEFAULT_BUY_QUOTE_EXECUTION_OPTS,
|
||||
DEFAULT_BUY_QUOTE_REQUEST_OPTS,
|
||||
ONE_AMOUNT: new BigNumber(1),
|
||||
DEFAULT_ASSET_SWAP_QUOTER_OPTS,
|
||||
DEFAULT_SWAP_QUOTE_REQUEST_OPTS,
|
||||
EMPTY_ORDERS_AND_FILLABLE_AMOUNTS,
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { AssetBuyerError } from './types';
|
||||
import { AssetSwapQuoterError } from './types';
|
||||
|
||||
/**
|
||||
* Error class representing insufficient asset liquidity
|
||||
@ -14,7 +14,7 @@ export class InsufficientAssetLiquidityError extends Error {
|
||||
* @param amountAvailableToFill The amount availabe to fill (in base units) factoring in slippage
|
||||
*/
|
||||
constructor(amountAvailableToFill: BigNumber) {
|
||||
super(AssetBuyerError.InsufficientAssetLiquidity);
|
||||
super(AssetSwapQuoterError.InsufficientAssetLiquidity);
|
||||
this.amountAvailableToFill = amountAvailableToFill;
|
||||
// Setting prototype so instanceof works. See https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work
|
||||
Object.setPrototypeOf(this, InsufficientAssetLiquidityError.prototype);
|
||||
|
@ -18,19 +18,19 @@ export {
|
||||
export { SignedOrder } from '@0x/types';
|
||||
export { BigNumber } from '@0x/utils';
|
||||
|
||||
export { AssetBuyer } from './asset_buyer';
|
||||
export { AssetSwapQuoter } 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,
|
||||
BuyQuote,
|
||||
BuyQuoteExecutionOpts,
|
||||
BuyQuoteInfo,
|
||||
BuyQuoteRequestOpts,
|
||||
AssetSwapQuoterError,
|
||||
AssetSwapQuoterOpts,
|
||||
SwapQuote,
|
||||
SwapQuoteExecutionOpts,
|
||||
SwapQuoteInfo,
|
||||
SwapQuoteRequestOpts,
|
||||
LiquidityForAssetData,
|
||||
LiquidityRequestOpts,
|
||||
OrdersAndFillableAmounts,
|
||||
|
@ -5,7 +5,7 @@ import { BigNumber } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import {
|
||||
AssetBuyerError,
|
||||
AssetSwapQuoterError,
|
||||
OrderProvider,
|
||||
OrderProviderRequest,
|
||||
OrderProviderResponse,
|
||||
@ -73,7 +73,7 @@ export class StandardRelayerAPIOrderProvider implements OrderProvider {
|
||||
try {
|
||||
orderbook = await this._sraClient.getOrderbookAsync(orderbookRequest, requestOpts);
|
||||
} catch (err) {
|
||||
throw new Error(AssetBuyerError.StandardRelayerApiError);
|
||||
throw new Error(AssetSwapQuoterError.StandardRelayerApiError);
|
||||
}
|
||||
const apiOrders = orderbook.asks.records;
|
||||
const orders = StandardRelayerAPIOrderProvider._getSignedOrderWithRemainingFillableMakerAssetAmountFromApi(
|
||||
@ -101,7 +101,7 @@ export class StandardRelayerAPIOrderProvider implements OrderProvider {
|
||||
try {
|
||||
response = await this._sraClient.getAssetPairsAsync(fullRequest);
|
||||
} catch (err) {
|
||||
throw new Error(AssetBuyerError.StandardRelayerApiError);
|
||||
throw new Error(AssetSwapQuoterError.StandardRelayerApiError);
|
||||
}
|
||||
return _.map(response.records, item => {
|
||||
if (item.assetDataA.assetData === takerAssetData) {
|
||||
@ -129,7 +129,7 @@ export class StandardRelayerAPIOrderProvider implements OrderProvider {
|
||||
try {
|
||||
response = await this._sraClient.getAssetPairsAsync(fullRequest);
|
||||
} catch (err) {
|
||||
throw new Error(AssetBuyerError.StandardRelayerApiError);
|
||||
throw new Error(AssetSwapQuoterError.StandardRelayerApiError);
|
||||
}
|
||||
return _.map(response.records, item => {
|
||||
if (item.assetDataA.assetData === makerAssetData) {
|
||||
|
@ -0,0 +1,172 @@
|
||||
import { ContractWrappers, ContractWrappersError, ForwarderWrapperError } from '@0x/contract-wrappers';
|
||||
import { BigNumber, providerUtils } from '@0x/utils';
|
||||
import { SupportedProvider, Web3Wrapper, ZeroExProvider } from '@0x/web3-wrapper';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { constants } from '../constants';
|
||||
import {
|
||||
AssetSwapQuoterError,
|
||||
CalldataInformation,
|
||||
SmartContractParams,
|
||||
SwapQuote,
|
||||
SwapQuoteConsumer,
|
||||
SwapQuoteConsumerOpts,
|
||||
SwapQuoteExecutionOpts,
|
||||
SwapQuoteGetOutputOpts,
|
||||
SwapQuoteInfo,
|
||||
Web3TransactionParams} from '../types';
|
||||
import { assert } from '../utils/assert';
|
||||
|
||||
export interface ForwarderSwapQuoteGetOutputOpts extends SwapQuoteGetOutputOpts {
|
||||
feePercentage: number;
|
||||
feeRecipient: string;
|
||||
}
|
||||
|
||||
export const FORWARDER_SWAP_QUOTE_CONSUMER_OPTS = {
|
||||
feePercentage: 0,
|
||||
feeRecipient: constants.NULL_ADDRESS,
|
||||
};
|
||||
|
||||
const addAffiliateFeeToSwapQuoteInfo = (quoteInfo: SwapQuoteInfo, feePercentage: number): SwapQuoteInfo => {
|
||||
const newQuoteInfo = _.clone(quoteInfo);
|
||||
const affiliateFeeAmount = newQuoteInfo.takerTokenAmount.multipliedBy(feePercentage).integerValue(BigNumber.ROUND_CEIL);
|
||||
const newFeeAmount = newQuoteInfo.feeTakerTokenAmount.plus(affiliateFeeAmount);
|
||||
newQuoteInfo.feeTakerTokenAmount = newFeeAmount;
|
||||
newQuoteInfo.totalTakerTokenAmount = newFeeAmount.plus(newQuoteInfo.takerTokenAmount);
|
||||
return newQuoteInfo;
|
||||
};
|
||||
|
||||
const addAffiliateFeeToSwapQuote = (quote: SwapQuote, feePercentage: number): SwapQuote => {
|
||||
const newQuote = _.clone(quote);
|
||||
newQuote.bestCaseQuoteInfo = addAffiliateFeeToSwapQuoteInfo(newQuote.bestCaseQuoteInfo, feePercentage);
|
||||
newQuote.worstCaseQuoteInfo = addAffiliateFeeToSwapQuoteInfo(newQuote.worstCaseQuoteInfo, feePercentage);
|
||||
return newQuote;
|
||||
};
|
||||
|
||||
export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumer {
|
||||
|
||||
public readonly provider: ZeroExProvider;
|
||||
public readonly networkId: number;
|
||||
|
||||
private readonly _contractWrappers: ContractWrappers;
|
||||
|
||||
constructor(
|
||||
supportedProvider: SupportedProvider,
|
||||
options: Partial<SwapQuoteConsumerOpts> = {},
|
||||
) {
|
||||
const { networkId } = _.merge(
|
||||
{},
|
||||
constants.DEFAULT_ASSET_SWAP_QUOTER_OPTS,
|
||||
options,
|
||||
);
|
||||
const provider = providerUtils.standardizeOrThrow(supportedProvider);
|
||||
assert.isNumber('networkId', networkId);
|
||||
this.provider = provider;
|
||||
this.networkId = networkId;
|
||||
this._contractWrappers = new ContractWrappers(this.provider, {
|
||||
networkId,
|
||||
});
|
||||
}
|
||||
|
||||
public getCalldataOrThrowAsync = async (quote: SwapQuote, opts: Partial<ForwarderSwapQuoteGetOutputOpts>): Promise<CalldataInformation> => {
|
||||
|
||||
}
|
||||
|
||||
public getWeb3TransactionParamsOrThrowAsync = async (quote: SwapQuote, opts: Partial<ForwarderSwapQuoteGetOutputOpts>): Promise<Web3TransactionParams> => {
|
||||
|
||||
}
|
||||
|
||||
public getSmartContractParamsOrThrowAsync = async (quote: SwapQuote, opts: Partial<ForwarderSwapQuoteGetOutputOpts>): Promise<SmartContractParams> => {
|
||||
const { feeRecipient, feePercentage } = _.merge(
|
||||
{},
|
||||
FORWARDER_SWAP_QUOTE_CONSUMER_OPTS,
|
||||
opts,
|
||||
);
|
||||
|
||||
assert.isValidSwapQuote('quote', quote);
|
||||
assert.isNumber('feePercentage', feePercentage);
|
||||
assert.isETHAddressHex('feeRecipient', feeRecipient);
|
||||
|
||||
const swapQuoteWithFeeAdded = addAffiliateFeeToSwapQuote(quote, feePercentage);
|
||||
|
||||
const { orders, feeOrders, makerAssetBuyAmount, worstCaseQuoteInfo } = swapQuoteWithFeeAdded;
|
||||
|
||||
const params = {
|
||||
orders: [],
|
||||
makerAssetFillAmount: makerAssetBuyAmount,
|
||||
signatures: [],
|
||||
feeOrders: [],
|
||||
feeSignatures: [],
|
||||
feePercentage: [],
|
||||
feeRecipient: [],
|
||||
};
|
||||
}
|
||||
|
||||
public executeSwapQuoteOrThrowAsync = async (quote: SwapQuote, opts: Partial<SwapQuoteExecutionOpts>): Promise<string> => {
|
||||
const { ethAmount, takerAddress, feeRecipient, gasLimit, gasPrice, feePercentage } = _.merge(
|
||||
{},
|
||||
FORWARDER_SWAP_QUOTE_CONSUMER_OPTS,
|
||||
opts,
|
||||
);
|
||||
|
||||
assert.isValidSwapQuote('quote', quote);
|
||||
|
||||
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 swapQuoteWithFeeAdded = addAffiliateFeeToSwapQuote(quote, feePercentage);
|
||||
|
||||
const { orders, feeOrders, makerAssetBuyAmount, worstCaseQuoteInfo } = swapQuoteWithFeeAdded;
|
||||
|
||||
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(AssetSwapQuoterError.NoAddressAvailable);
|
||||
}
|
||||
}
|
||||
try {
|
||||
const txHash = await this._contractWrappers.forwarder.marketBuyOrdersWithEthAsync(
|
||||
orders,
|
||||
makerAssetBuyAmount,
|
||||
finalTakerAddress,
|
||||
ethAmount || worstCaseQuoteInfo.totalTakerTokenAmount,
|
||||
feeOrders,
|
||||
feePercentage,
|
||||
feeRecipient,
|
||||
{
|
||||
gasLimit,
|
||||
gasPrice,
|
||||
shouldValidate: true,
|
||||
},
|
||||
);
|
||||
return txHash;
|
||||
} catch (err) {
|
||||
if (_.includes(err.message, ContractWrappersError.SignatureRequestDenied)) {
|
||||
throw new Error(AssetSwapQuoterError.SignatureRequestDenied);
|
||||
} else if (_.includes(err.message, ForwarderWrapperError.CompleteFillFailed)) {
|
||||
throw new Error(AssetSwapQuoterError.TransactionValueTooLow);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -36,25 +36,70 @@ export interface OrderProvider {
|
||||
getAvailableTakerAssetDatasAsync: (makerAssetData: string) => Promise<string[]>;
|
||||
}
|
||||
|
||||
export interface CalldataInformation {
|
||||
calldataHexString: string;
|
||||
to: string;
|
||||
value: BigNumber;
|
||||
}
|
||||
|
||||
export interface Web3TransactionParams {
|
||||
from: string;
|
||||
to?: string;
|
||||
value?: string;
|
||||
gas?: string;
|
||||
gasPrice?: string;
|
||||
data?: string;
|
||||
nonce?: string;
|
||||
}
|
||||
|
||||
export interface SmartContractParams {
|
||||
params: { [name: string]: any };
|
||||
to: string;
|
||||
value: BigNumber;
|
||||
}
|
||||
|
||||
export interface SwapQuoteConsumer {
|
||||
getCalldataOrThrowAsync(quote: SwapQuote, opts: Partial<SwapQuoteConsumerOpts>): Promise<CalldataInformation>;
|
||||
getWeb3TransactionParamsOrThrowAsync(quote: SwapQuote, opts: Partial<SwapQuoteConsumerOpts>): Promise<Web3TransactionParams>;
|
||||
getSmartContractParamsOrThrowAsync(quote: SwapQuote, opts: Partial<SwapQuoteConsumerOpts>): Promise<SmartContractParams>;
|
||||
executeSwapQuoteOrThrowAsync(quote: SwapQuote, opts: Partial<SwapQuoteExecutionOpts>): Promise<string>;
|
||||
}
|
||||
|
||||
export interface SwapQuoteConsumerOpts {
|
||||
networkId: number;
|
||||
}
|
||||
|
||||
export interface SwapQuoteGetOutputOpts {}
|
||||
|
||||
/**
|
||||
* ethAmount: The desired amount of eth to spend. Defaults to buyQuote.worstCaseQuoteInfo.totalEthAmount.
|
||||
* takerAddress: The address to perform the buy. Defaults to the first available address from the provider.
|
||||
* gasLimit: The amount of gas to send with a transaction (in Gwei). Defaults to an eth_estimateGas rpc call.
|
||||
* gasPrice: Gas price in Wei to use for a transaction
|
||||
*/
|
||||
export interface SwapQuoteExecutionOpts extends SwapQuoteConsumerOpts {
|
||||
ethAmount?: BigNumber;
|
||||
takerAddress?: string;
|
||||
gasLimit?: number;
|
||||
gasPrice?: BigNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* assetData: String that represents a specific asset (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
|
||||
* assetBuyAmount: The amount of asset to buy.
|
||||
* 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.
|
||||
* feePercentage: Optional affiliate fee percentage used to calculate the eth amounts above.
|
||||
* bestCaseQuoteInfo: Info about the best case price for the asset.
|
||||
* worstCaseQuoteInfo: Info about the worst case price for the asset.
|
||||
*/
|
||||
export interface BuyQuote {
|
||||
export interface SwapQuote {
|
||||
takerAssetData: string;
|
||||
makerAssetData: string;
|
||||
makerAssetBuyAmount: BigNumber;
|
||||
orders: SignedOrder[];
|
||||
feeOrders: SignedOrder[];
|
||||
bestCaseQuoteInfo: BuyQuoteInfo;
|
||||
worstCaseQuoteInfo: BuyQuoteInfo;
|
||||
toAddress: string; // exchange address, coordinator address
|
||||
isUsingCoordinator: boolean;
|
||||
bestCaseQuoteInfo: SwapQuoteInfo;
|
||||
worstCaseQuoteInfo: SwapQuoteInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -62,7 +107,7 @@ export interface BuyQuote {
|
||||
* feeEthAmount: The amount of eth required to pay the affiliate fee.
|
||||
* totalEthAmount: The total amount of eth required to complete the buy (filling orders, feeOrders, and paying affiliate fee).
|
||||
*/
|
||||
export interface BuyQuoteInfo {
|
||||
export interface SwapQuoteInfo {
|
||||
takerTokenAmount: BigNumber;
|
||||
feeTakerTokenAmount: BigNumber;
|
||||
totalTakerTokenAmount: BigNumber;
|
||||
@ -72,9 +117,10 @@ export interface BuyQuoteInfo {
|
||||
* 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 {
|
||||
export interface SwapQuoteRequestOpts {
|
||||
shouldForceOrderRefresh: boolean;
|
||||
slippagePercentage: number;
|
||||
allowMarketBuyOrders: boolean;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -82,21 +128,10 @@ export interface BuyQuoteRequestOpts {
|
||||
*
|
||||
* shouldForceOrderRefresh: If set to true, new orders and state will be fetched instead of waiting for the next orderRefreshIntervalMs. Defaults to false.
|
||||
*/
|
||||
export type LiquidityRequestOpts = Pick<BuyQuoteRequestOpts, 'shouldForceOrderRefresh'>;
|
||||
export type LiquidityRequestOpts = Pick<SwapQuoteRequestOpts, 'shouldForceOrderRefresh'>;
|
||||
|
||||
/**
|
||||
* ethAmount: The desired amount of eth to spend. Defaults to buyQuote.worstCaseQuoteInfo.totalEthAmount.
|
||||
* takerAddress: The address to perform the buy. Defaults to the first available address from the provider.
|
||||
* gasLimit: The amount of gas to send with a transaction (in Gwei). Defaults to an eth_estimateGas rpc call.
|
||||
* gasPrice: Gas price in Wei to use for a transaction
|
||||
* feeRecipient: The address where affiliate fees are sent. Defaults to null address (0x000...000).
|
||||
*/
|
||||
export interface BuyQuoteExecutionOpts {
|
||||
ethAmount?: BigNumber;
|
||||
takerAddress?: string;
|
||||
gasLimit?: number;
|
||||
gasPrice?: BigNumber;
|
||||
feeRecipient: string;
|
||||
export interface ForwarderSwapQuoteExecutionOpts extends SwapQuoteExecutionOpts {
|
||||
feeRecipient?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -104,7 +139,7 @@ export interface BuyQuoteExecutionOpts {
|
||||
* orderRefreshIntervalMs: The interval in ms that getBuyQuoteAsync should trigger an refresh of orders and order states. Defaults to 10000ms (10s).
|
||||
* expiryBufferSeconds: The number of seconds to add when calculating whether an order is expired or not. Defaults to 300s (5m).
|
||||
*/
|
||||
export interface AssetBuyerOpts {
|
||||
export interface AssetSwapQuoterOpts {
|
||||
networkId: number;
|
||||
orderRefreshIntervalMs: number;
|
||||
expiryBufferSeconds: number;
|
||||
@ -113,7 +148,7 @@ export interface AssetBuyerOpts {
|
||||
/**
|
||||
* Possible error messages thrown by an AssetBuyer instance or associated static methods.
|
||||
*/
|
||||
export enum AssetBuyerError {
|
||||
export enum AssetSwapQuoterError {
|
||||
NoEtherTokenContractFound = 'NO_ETHER_TOKEN_CONTRACT_FOUND',
|
||||
NoZrxTokenContractFound = 'NO_ZRX_TOKEN_CONTRACT_FOUND',
|
||||
StandardRelayerApiError = 'STANDARD_RELAYER_API_ERROR',
|
||||
|
@ -1,29 +1,27 @@
|
||||
import { assert as sharedAssert } from '@0x/assert';
|
||||
import { schemas } from '@0x/json-schemas';
|
||||
|
||||
import { BuyQuote, BuyQuoteInfo, OrderProvider, OrderProviderRequest } from '../types';
|
||||
import { OrderProvider, OrderProviderRequest, SwapQuote, SwapQuoteInfo } from '../types';
|
||||
|
||||
export const assert = {
|
||||
...sharedAssert,
|
||||
isValidBuyQuote(variableName: string, buyQuote: BuyQuote): void {
|
||||
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}.makerAssetBuyAmount`, buyQuote.makerAssetBuyAmount);
|
||||
assert.isETHAddressHex(`${variableName}.toAddress`, buyQuote.toAddress);
|
||||
assert.isBoolean(`${variableName}.isUsingCoordinator`, buyQuote.isUsingCoordinator);
|
||||
isValidSwapQuote(variableName: string, swapQuote: SwapQuote): void {
|
||||
sharedAssert.isHexString(`${variableName}.takerAssetData`, swapQuote.takerAssetData);
|
||||
sharedAssert.isHexString(`${variableName}.makerAssetData`, swapQuote.makerAssetData);
|
||||
sharedAssert.doesConformToSchema(`${variableName}.orders`, swapQuote.orders, schemas.signedOrdersSchema);
|
||||
sharedAssert.doesConformToSchema(`${variableName}.feeOrders`, swapQuote.feeOrders, schemas.signedOrdersSchema);
|
||||
assert.isValidSwapQuoteInfo(`${variableName}.bestCaseQuoteInfo`, swapQuote.bestCaseQuoteInfo);
|
||||
assert.isValidSwapQuoteInfo(`${variableName}.worstCaseQuoteInfo`, swapQuote.worstCaseQuoteInfo);
|
||||
sharedAssert.isBigNumber(`${variableName}.makerAssetBuyAmount`, swapQuote.makerAssetBuyAmount);
|
||||
// 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}.takerTokenAmount`, buyQuoteInfo.takerTokenAmount);
|
||||
sharedAssert.isBigNumber(`${variableName}.feeTakerTokenAmount`, buyQuoteInfo.feeTakerTokenAmount);
|
||||
sharedAssert.isBigNumber(`${variableName}.totalTakerTokenAmount`, buyQuoteInfo.totalTakerTokenAmount);
|
||||
isValidSwapQuoteInfo(variableName: string, swapQuoteInfo: SwapQuoteInfo): void {
|
||||
sharedAssert.isBigNumber(`${variableName}.takerTokenAmount`, swapQuoteInfo.takerTokenAmount);
|
||||
sharedAssert.isBigNumber(`${variableName}.feeTakerTokenAmount`, swapQuoteInfo.feeTakerTokenAmount);
|
||||
sharedAssert.isBigNumber(`${variableName}.totalTakerTokenAmount`, swapQuoteInfo.totalTakerTokenAmount);
|
||||
},
|
||||
isValidOrderProvider(variableName: string, orderFetcher: OrderProvider): void {
|
||||
sharedAssert.isFunction(`${variableName}.getOrdersAsync`, orderFetcher.getOrdersAsync);
|
||||
|
@ -7,7 +7,7 @@ import * as _ from 'lodash';
|
||||
|
||||
import { constants } from '../constants';
|
||||
import {
|
||||
AssetBuyerError,
|
||||
AssetSwapQuoterError,
|
||||
OrderProviderRequest,
|
||||
OrderProviderResponse,
|
||||
OrdersAndFillableAmounts,
|
||||
@ -19,7 +19,7 @@ export const orderProviderResponseProcessor = {
|
||||
const { makerAssetData, takerAssetData } = request;
|
||||
_.forEach(response.orders, order => {
|
||||
if (order.makerAssetData !== makerAssetData || order.takerAssetData !== takerAssetData) {
|
||||
throw new Error(AssetBuyerError.InvalidOrderProviderResponse);
|
||||
throw new Error(AssetSwapQuoterError.InvalidOrderProviderResponse);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
@ -4,17 +4,17 @@ import * as _ from 'lodash';
|
||||
|
||||
import { constants } from '../constants';
|
||||
import { InsufficientAssetLiquidityError } from '../errors';
|
||||
import { AssetBuyerError, BuyQuote, BuyQuoteInfo, OrdersAndFillableAmounts } from '../types';
|
||||
import { AssetSwapQuoterError, OrdersAndFillableAmounts, SwapQuote, SwapQuoteInfo } from '../types';
|
||||
|
||||
// Calculates a buy quote for orders that have WETH as the takerAsset
|
||||
export const buyQuoteCalculator = {
|
||||
// Calculates a swap quote for orders
|
||||
export const swapQuoteCalculator = {
|
||||
calculate(
|
||||
ordersAndFillableAmounts: OrdersAndFillableAmounts,
|
||||
feeOrdersAndFillableAmounts: OrdersAndFillableAmounts,
|
||||
makerAssetBuyAmount: BigNumber,
|
||||
slippagePercentage: number,
|
||||
isMakerAssetZrxToken: boolean,
|
||||
): BuyQuote {
|
||||
): SwapQuote {
|
||||
const orders = ordersAndFillableAmounts.orders;
|
||||
const remainingFillableMakerAssetAmounts = ordersAndFillableAmounts.remainingFillableMakerAssetAmounts;
|
||||
const feeOrders = feeOrdersAndFillableAmounts.orders;
|
||||
@ -64,7 +64,7 @@ export const buyQuoteCalculator = {
|
||||
);
|
||||
// if we do not have enough feeOrders to cover the fees, throw
|
||||
if (feeOrdersAndRemainingFeeAmount.remainingFeeAmount.gt(constants.ZERO_AMOUNT)) {
|
||||
throw new Error(AssetBuyerError.InsufficientZrxLiquidity);
|
||||
throw new Error(AssetSwapQuoterError.InsufficientZrxLiquidity);
|
||||
}
|
||||
resultFeeOrders = feeOrdersAndRemainingFeeAmount.resultFeeOrders;
|
||||
feeOrdersRemainingFillableMakerAssetAmounts =
|
||||
@ -106,9 +106,6 @@ export const buyQuoteCalculator = {
|
||||
feeOrders: resultFeeOrders,
|
||||
bestCaseQuoteInfo,
|
||||
worstCaseQuoteInfo,
|
||||
// TODO(dave4506): coordinator metadata for buy quote
|
||||
toAddress: constants.NULL_ADDRESS,
|
||||
isUsingCoordinator: false,
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -118,7 +115,7 @@ function calculateQuoteInfo(
|
||||
feeOrdersAndFillableAmounts: OrdersAndFillableAmounts,
|
||||
makserAssetBuyAmount: BigNumber,
|
||||
isMakerAssetZrxToken: boolean,
|
||||
): BuyQuoteInfo {
|
||||
): SwapQuoteInfo {
|
||||
// find the total eth and zrx needed to buy assetAmount from the resultOrders from left to right
|
||||
let takerTokenAmount = constants.ZERO_AMOUNT;
|
||||
let zrxTakerTokenAmount = constants.ZERO_AMOUNT;
|
||||
@ -132,7 +129,9 @@ function calculateQuoteInfo(
|
||||
// find eth amount needed to buy zrx
|
||||
zrxTakerTokenAmount = findTakerTokenAmountNeededToBuyZrx(feeOrdersAndFillableAmounts, zrxAmountToBuyAsset);
|
||||
}
|
||||
|
||||
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 totalTakerTokenAmount = takerTokenAmount.plus(feeTakerTokenAmount);
|
||||
return {
|
12
packages/asset-buyer/src/utils/utils.ts
Normal file
12
packages/asset-buyer/src/utils/utils.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
|
||||
import { constants } from '../constants';
|
||||
|
||||
export const utils = {
|
||||
numberPercentageToEtherTokenAmountPercentage(percentage: number): BigNumber {
|
||||
return Web3Wrapper.toBaseUnitAmount(constants.ONE_AMOUNT, constants.ETHER_TOKEN_DECIMALS).multipliedBy(
|
||||
percentage,
|
||||
);
|
||||
},
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user