Merge pull request #2574 from 0xProject/rfqt-follow-ups
asset-swapper: RFQ-T follow ups
This commit is contained in:
@@ -11,9 +11,9 @@
|
||||
"build": "yarn tsc -b",
|
||||
"watch": "tsc -w -p tsconfig.json",
|
||||
"build:ci": "yarn build",
|
||||
"lint": "tslint --format stylish --project . && yarn prettier",
|
||||
"prettier": "prettier --write '**/*.{ts,tsx,json,md}' --config ../../.prettierrc",
|
||||
"fix": "tslint --fix --format stylish --project . && yarn prettier",
|
||||
"lint": "tslint --format stylish --project . && yarn prettier --check",
|
||||
"prettier": "prettier '**/*.{ts,tsx,json,md}' --config ../../.prettierrc --ignore-path ../../.prettierignore",
|
||||
"fix": "tslint --fix --format stylish --project . && yarn prettier --write",
|
||||
"test": "yarn run_mocha",
|
||||
"rebuild_and_test": "run-s clean build test",
|
||||
"test:coverage": "nyc npm run test --all && yarn coverage:report:lcov",
|
||||
|
||||
@@ -46,7 +46,8 @@ const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = {
|
||||
samplerGasLimit: 250e6,
|
||||
rfqt: {
|
||||
takerApiKeyWhitelist: [],
|
||||
makerEndpoints: [],
|
||||
makerAssetOfferings: {},
|
||||
skipBuyRequests: false,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ export {
|
||||
MarketOperation,
|
||||
MarketSellSwapQuote,
|
||||
MockedRfqtFirmQuoteResponse,
|
||||
RfqtMakerAssetOfferings,
|
||||
RfqtRequestOpts,
|
||||
SwapQuote,
|
||||
SwapQuoteConsumerBase,
|
||||
|
||||
@@ -46,6 +46,7 @@ export class SwapQuoter {
|
||||
private readonly _orderStateUtils: OrderStateUtils;
|
||||
private readonly _quoteRequestor: QuoteRequestor;
|
||||
private readonly _rfqtTakerApiKeyWhitelist: string[];
|
||||
private readonly _rfqtSkipBuyRequests: boolean;
|
||||
|
||||
/**
|
||||
* Instantiates a new SwapQuoter instance given existing liquidity in the form of orders and feeOrders.
|
||||
@@ -155,6 +156,7 @@ export class SwapQuoter {
|
||||
permittedOrderFeeTypes,
|
||||
samplerGasLimit,
|
||||
liquidityProviderRegistryAddress,
|
||||
rfqt,
|
||||
} = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
|
||||
const provider = providerUtils.standardizeOrThrow(supportedProvider);
|
||||
assert.isValidOrderbook('orderbook', orderbook);
|
||||
@@ -165,14 +167,20 @@ export class SwapQuoter {
|
||||
this.orderbook = orderbook;
|
||||
this.expiryBufferMs = expiryBufferMs;
|
||||
this.permittedOrderFeeTypes = permittedOrderFeeTypes;
|
||||
this._rfqtTakerApiKeyWhitelist = options.rfqt ? options.rfqt.takerApiKeyWhitelist || [] : [];
|
||||
this._rfqtTakerApiKeyWhitelist = rfqt ? rfqt.takerApiKeyWhitelist || [] : [];
|
||||
this._rfqtSkipBuyRequests =
|
||||
rfqt && rfqt.skipBuyRequests !== undefined
|
||||
? rfqt.skipBuyRequests
|
||||
: (r => r !== undefined && r.skipBuyRequests === true)(constants.DEFAULT_SWAP_QUOTER_OPTS.rfqt);
|
||||
this._contractAddresses = options.contractAddresses || getContractAddressesForChainOrThrow(chainId);
|
||||
this._devUtilsContract = new DevUtilsContract(this._contractAddresses.devUtils, provider);
|
||||
this._protocolFeeUtils = new ProtocolFeeUtils(constants.PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS);
|
||||
this._orderStateUtils = new OrderStateUtils(this._devUtilsContract);
|
||||
this._quoteRequestor = new QuoteRequestor(
|
||||
options.rfqt ? options.rfqt.makerEndpoints || [] : [],
|
||||
options.rfqt ? options.rfqt.warningLogger : undefined,
|
||||
rfqt ? rfqt.makerAssetOfferings || {} : {},
|
||||
rfqt ? rfqt.warningLogger : undefined,
|
||||
rfqt ? rfqt.infoLogger : undefined,
|
||||
expiryBufferMs,
|
||||
);
|
||||
const sampler = new DexOrderSampler(
|
||||
new IERC20BridgeSamplerContract(this._contractAddresses.erc20BridgeSampler, this.provider, {
|
||||
@@ -535,7 +543,8 @@ export class SwapQuoter {
|
||||
opts.rfqt &&
|
||||
opts.rfqt.intentOnFilling &&
|
||||
opts.rfqt.apiKey &&
|
||||
this._rfqtTakerApiKeyWhitelist.includes(opts.rfqt.apiKey)
|
||||
this._rfqtTakerApiKeyWhitelist.includes(opts.rfqt.apiKey) &&
|
||||
!(marketOperation === MarketOperation.Buy && this._rfqtSkipBuyRequests)
|
||||
) {
|
||||
if (!opts.rfqt.takerAddress || opts.rfqt.takerAddress === constants.NULL_ADDRESS) {
|
||||
throw new Error('RFQ-T requests must specify a taker address');
|
||||
@@ -568,7 +577,7 @@ export class SwapQuoter {
|
||||
|
||||
const calcOpts: CalculateSwapQuoteOpts = opts;
|
||||
|
||||
if (calcOpts.rfqt !== undefined && this._shouldEnableIndicativeRfqt(calcOpts.rfqt)) {
|
||||
if (calcOpts.rfqt !== undefined && this._shouldEnableIndicativeRfqt(calcOpts.rfqt, marketOperation)) {
|
||||
calcOpts.rfqt.quoteRequestor = this._quoteRequestor;
|
||||
}
|
||||
|
||||
@@ -590,12 +599,13 @@ export class SwapQuoter {
|
||||
|
||||
return swapQuote;
|
||||
}
|
||||
private _shouldEnableIndicativeRfqt(opts: CalculateSwapQuoteOpts['rfqt']): boolean {
|
||||
private _shouldEnableIndicativeRfqt(opts: CalculateSwapQuoteOpts['rfqt'], op: MarketOperation): boolean {
|
||||
return (
|
||||
opts !== undefined &&
|
||||
opts.isIndicative !== undefined &&
|
||||
opts.isIndicative &&
|
||||
this._rfqtTakerApiKeyWhitelist.includes(opts.apiKey)
|
||||
this._rfqtTakerApiKeyWhitelist.includes(opts.apiKey) &&
|
||||
!(op === MarketOperation.Buy && this._rfqtSkipBuyRequests)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,6 +208,14 @@ export interface SwapQuoteRequestOpts extends CalculateSwapQuoteOpts {
|
||||
*/
|
||||
export interface CalculateSwapQuoteOpts extends GetMarketOrdersOpts {}
|
||||
|
||||
/**
|
||||
* A mapping from RFQ-T quote provider URLs to the trading pairs they support.
|
||||
* The value type represents an array of supported asset pairs, with each array element encoded as a 2-element array of token addresses.
|
||||
*/
|
||||
export interface RfqtMakerAssetOfferings {
|
||||
[endpoint: string]: Array<[string, string]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* chainId: The ethereum chain id. Defaults to 1 (mainnet).
|
||||
* orderRefreshIntervalMs: The interval in ms that getBuyQuoteAsync should trigger an refresh of orders and order states. Defaults to 10000ms (10s).
|
||||
@@ -224,8 +232,10 @@ export interface SwapQuoterOpts extends OrderPrunerOpts {
|
||||
liquidityProviderRegistryAddress?: string;
|
||||
rfqt?: {
|
||||
takerApiKeyWhitelist: string[];
|
||||
makerEndpoints: string[];
|
||||
makerAssetOfferings: RfqtMakerAssetOfferings;
|
||||
skipBuyRequests?: boolean;
|
||||
warningLogger?: (s: string) => void;
|
||||
infoLogger?: (s: string) => void;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,22 @@ import { difference } from '../utils';
|
||||
|
||||
import { BUY_SOURCES, DEFAULT_GET_MARKET_ORDERS_OPTS, FEE_QUOTE_SOURCES, ONE_ETHER, SELL_SOURCES } from './constants';
|
||||
import { createFillPaths, getPathAdjustedRate, getPathAdjustedSlippage } from './fills';
|
||||
import { createOrdersFromPath, createSignedOrdersFromRfqtIndicativeQuotes, createSignedOrdersWithFillableAmounts, getNativeOrderTokens } from './orders';
|
||||
import {
|
||||
createOrdersFromPath,
|
||||
createSignedOrdersFromRfqtIndicativeQuotes,
|
||||
createSignedOrdersWithFillableAmounts,
|
||||
getNativeOrderTokens,
|
||||
} from './orders';
|
||||
import { findOptimalPath } from './path_optimizer';
|
||||
import { DexOrderSampler, getSampleAmounts } from './sampler';
|
||||
import { AggregationError, DexSample, ERC20BridgeSource, GetMarketOrdersOpts, OptimizedMarketOrder, OrderDomain } from './types';
|
||||
import {
|
||||
AggregationError,
|
||||
DexSample,
|
||||
ERC20BridgeSource,
|
||||
GetMarketOrdersOpts,
|
||||
OptimizedMarketOrder,
|
||||
OrderDomain,
|
||||
} from './types';
|
||||
|
||||
async function getRfqtIndicativeQuotesAsync(
|
||||
makerAssetData: string,
|
||||
|
||||
@@ -7,9 +7,25 @@ import { MarketOperation, SignedOrderWithFillableAmounts } from '../../types';
|
||||
import { RfqtIndicativeQuoteResponse } from '../quote_requestor';
|
||||
import { getCurveInfo, isCurveSource } from '../source_utils';
|
||||
|
||||
import { ERC20_PROXY_ID, NULL_ADDRESS, NULL_BYTES, ONE_HOUR_IN_SECONDS, ONE_SECOND_MS, WALLET_SIGNATURE, ZERO_AMOUNT } from './constants';
|
||||
import {
|
||||
ERC20_PROXY_ID,
|
||||
NULL_ADDRESS,
|
||||
NULL_BYTES,
|
||||
ONE_HOUR_IN_SECONDS,
|
||||
ONE_SECOND_MS,
|
||||
WALLET_SIGNATURE,
|
||||
ZERO_AMOUNT,
|
||||
} from './constants';
|
||||
import { collapsePath } from './fills';
|
||||
import { AggregationError, CollapsedFill, ERC20BridgeSource, Fill, NativeCollapsedFill, OptimizedMarketOrder, OrderDomain } from './types';
|
||||
import {
|
||||
AggregationError,
|
||||
CollapsedFill,
|
||||
ERC20BridgeSource,
|
||||
Fill,
|
||||
NativeCollapsedFill,
|
||||
OptimizedMarketOrder,
|
||||
OrderDomain,
|
||||
} from './types';
|
||||
|
||||
// tslint:disable completed-docs no-unnecessary-type-assertion
|
||||
|
||||
@@ -351,7 +367,7 @@ export function createSignedOrdersFromRfqtIndicativeQuotes(
|
||||
senderAddress: NULL_ADDRESS,
|
||||
feeRecipientAddress: NULL_ADDRESS,
|
||||
salt: ZERO_AMOUNT,
|
||||
expirationTimeSeconds: ZERO_AMOUNT,
|
||||
expirationTimeSeconds: quote.expirationTimeSeconds,
|
||||
makerFeeAssetData: NULL_BYTES,
|
||||
takerFeeAssetData: NULL_BYTES,
|
||||
makerFee: ZERO_AMOUNT,
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { schemas, SchemaValidator } from '@0x/json-schemas';
|
||||
import { assetDataUtils, SignedOrder } from '@0x/order-utils';
|
||||
import { assetDataUtils, orderCalculationUtils, SignedOrder } from '@0x/order-utils';
|
||||
import { ERC20AssetData } from '@0x/types';
|
||||
import { BigNumber, logUtils } from '@0x/utils';
|
||||
import Axios, { AxiosResponse } from 'axios';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { constants } from '../constants';
|
||||
import { MarketOperation, RfqtRequestOpts } from '../types';
|
||||
import { MarketOperation, RfqtMakerAssetOfferings, RfqtRequestOpts } from '../types';
|
||||
|
||||
/**
|
||||
* Request quotes from RFQ-T providers
|
||||
@@ -17,6 +16,7 @@ export interface RfqtIndicativeQuoteResponse {
|
||||
makerAssetAmount: BigNumber;
|
||||
takerAssetData: string;
|
||||
takerAssetAmount: BigNumber;
|
||||
expirationTimeSeconds: BigNumber;
|
||||
}
|
||||
|
||||
function getTokenAddressOrThrow(assetData: string): string {
|
||||
@@ -81,47 +81,58 @@ function hasExpectedAssetData(
|
||||
}
|
||||
|
||||
export class QuoteRequestor {
|
||||
private readonly _rfqtMakerEndpoints: string[];
|
||||
private readonly _schemaValidator: SchemaValidator = new SchemaValidator();
|
||||
private readonly _warningLogger: (s: string) => void;
|
||||
|
||||
constructor(rfqtMakerEndpoints: string[], logger: (s: string) => void = s => logUtils.warn(s)) {
|
||||
this._rfqtMakerEndpoints = rfqtMakerEndpoints;
|
||||
this._warningLogger = logger;
|
||||
}
|
||||
constructor(
|
||||
private readonly _rfqtAssetOfferings: RfqtMakerAssetOfferings,
|
||||
private readonly _warningLogger: (a: any) => void = a => logUtils.warn(a),
|
||||
private readonly _infoLogger: (a: any) => void = () => undefined,
|
||||
private readonly _expiryBufferMs: number = constants.DEFAULT_SWAP_QUOTER_OPTS.expiryBufferMs,
|
||||
) {}
|
||||
|
||||
public async requestRfqtFirmQuotesAsync(
|
||||
makerAssetData: string,
|
||||
takerAssetData: string,
|
||||
assetFillAmount: BigNumber,
|
||||
marketOperation: MarketOperation,
|
||||
options?: Partial<RfqtRequestOpts>,
|
||||
options: RfqtRequestOpts,
|
||||
): Promise<SignedOrder[]> {
|
||||
const _opts = _.merge({}, constants.DEFAULT_RFQT_REQUEST_OPTS, options);
|
||||
const _opts: RfqtRequestOpts = { ...constants.DEFAULT_RFQT_REQUEST_OPTS, ...options };
|
||||
assertTakerAddressOrThrow(_opts.takerAddress);
|
||||
|
||||
// create an array of promises for quote responses, using "undefined"
|
||||
// as a placeholder for failed requests.
|
||||
const responsesIfDefined: Array<undefined | AxiosResponse<SignedOrder>> = await Promise.all(
|
||||
this._rfqtMakerEndpoints.map(async rfqtMakerEndpoint => {
|
||||
try {
|
||||
return await Axios.get<SignedOrder>(`${rfqtMakerEndpoint}/quote`, {
|
||||
headers: { '0x-api-key': _opts.apiKey },
|
||||
params: {
|
||||
takerAddress: _opts.takerAddress,
|
||||
...inferQueryParams(marketOperation, makerAssetData, takerAssetData, assetFillAmount),
|
||||
},
|
||||
timeout: _opts.makerEndpointMaxResponseTimeMs,
|
||||
});
|
||||
} catch (err) {
|
||||
this._warningLogger(
|
||||
`Failed to get RFQ-T firm quote from market maker endpoint ${rfqtMakerEndpoint} for API key ${
|
||||
_opts.apiKey
|
||||
} for taker address ${_opts.takerAddress}`,
|
||||
);
|
||||
this._warningLogger(err);
|
||||
return undefined;
|
||||
Object.keys(this._rfqtAssetOfferings).map(async url => {
|
||||
if (this._makerSupportsPair(url, makerAssetData, takerAssetData)) {
|
||||
try {
|
||||
const timeBeforeAwait = Date.now();
|
||||
const response = await Axios.get<SignedOrder>(`${url}/quote`, {
|
||||
headers: { '0x-api-key': _opts.apiKey },
|
||||
params: {
|
||||
takerAddress: _opts.takerAddress,
|
||||
...inferQueryParams(marketOperation, makerAssetData, takerAssetData, assetFillAmount),
|
||||
},
|
||||
timeout: _opts.makerEndpointMaxResponseTimeMs,
|
||||
});
|
||||
this._infoLogger({
|
||||
rfqtFirmQuoteMakerResponseTime: {
|
||||
makerEndpoint: url,
|
||||
responseTimeMs: Date.now() - timeBeforeAwait,
|
||||
},
|
||||
});
|
||||
return response;
|
||||
} catch (err) {
|
||||
this._warningLogger(
|
||||
`Failed to get RFQ-T firm quote from market maker endpoint ${url} for API key ${
|
||||
_opts.apiKey
|
||||
} for taker address ${_opts.takerAddress}`,
|
||||
);
|
||||
this._warningLogger(err);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -150,10 +161,15 @@ export class QuoteRequestor {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (order.takerAddress.toLowerCase() !== _opts.takerAddress.toLowerCase()) {
|
||||
this._warningLogger(`Unexpected takerAddress in RFQ-T order, filtering out: ${JSON.stringify(order)}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const orders: SignedOrder[] = validatedOrdersWithStringInts.map(orderWithStringInts => {
|
||||
const validatedOrders: SignedOrder[] = validatedOrdersWithStringInts.map(orderWithStringInts => {
|
||||
return {
|
||||
...orderWithStringInts,
|
||||
makerAssetAmount: new BigNumber(orderWithStringInts.makerAssetAmount),
|
||||
@@ -165,6 +181,14 @@ export class QuoteRequestor {
|
||||
};
|
||||
});
|
||||
|
||||
const orders = validatedOrders.filter(order => {
|
||||
if (orderCalculationUtils.willOrderExpire(order, this._expiryBufferMs / constants.ONE_SECOND_MS)) {
|
||||
this._warningLogger(`Expiry too soon in RFQ-T order, filtering out: ${JSON.stringify(order)}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return orders;
|
||||
}
|
||||
|
||||
@@ -175,31 +199,42 @@ export class QuoteRequestor {
|
||||
marketOperation: MarketOperation,
|
||||
options: RfqtRequestOpts,
|
||||
): Promise<RfqtIndicativeQuoteResponse[]> {
|
||||
const _opts = _.merge({}, constants.DEFAULT_RFQT_REQUEST_OPTS, options);
|
||||
const _opts: RfqtRequestOpts = { ...constants.DEFAULT_RFQT_REQUEST_OPTS, ...options };
|
||||
assertTakerAddressOrThrow(_opts.takerAddress);
|
||||
|
||||
const axiosResponsesIfDefined: Array<
|
||||
undefined | AxiosResponse<RfqtIndicativeQuoteResponse>
|
||||
> = await Promise.all(
|
||||
this._rfqtMakerEndpoints.map(async rfqtMakerEndpoint => {
|
||||
try {
|
||||
return await Axios.get<RfqtIndicativeQuoteResponse>(`${rfqtMakerEndpoint}/price`, {
|
||||
headers: { '0x-api-key': options.apiKey },
|
||||
params: {
|
||||
takerAddress: options.takerAddress,
|
||||
...inferQueryParams(marketOperation, makerAssetData, takerAssetData, assetFillAmount),
|
||||
},
|
||||
timeout: options.makerEndpointMaxResponseTimeMs,
|
||||
});
|
||||
} catch (err) {
|
||||
this._warningLogger(
|
||||
`Failed to get RFQ-T indicative quote from market maker endpoint ${rfqtMakerEndpoint} for API key ${
|
||||
options.apiKey
|
||||
} for taker address ${options.takerAddress}`,
|
||||
);
|
||||
this._warningLogger(err);
|
||||
return undefined;
|
||||
Object.keys(this._rfqtAssetOfferings).map(async url => {
|
||||
if (this._makerSupportsPair(url, makerAssetData, takerAssetData)) {
|
||||
try {
|
||||
const timeBeforeAwait = Date.now();
|
||||
const response = await Axios.get<RfqtIndicativeQuoteResponse>(`${url}/price`, {
|
||||
headers: { '0x-api-key': options.apiKey },
|
||||
params: {
|
||||
takerAddress: options.takerAddress,
|
||||
...inferQueryParams(marketOperation, makerAssetData, takerAssetData, assetFillAmount),
|
||||
},
|
||||
timeout: options.makerEndpointMaxResponseTimeMs,
|
||||
});
|
||||
this._infoLogger({
|
||||
rfqtIndicativeQuoteMakerResponseTime: {
|
||||
makerEndpoint: url,
|
||||
responseTimeMs: Date.now() - timeBeforeAwait,
|
||||
},
|
||||
});
|
||||
return response;
|
||||
} catch (err) {
|
||||
this._warningLogger(
|
||||
`Failed to get RFQ-T indicative quote from market maker endpoint ${url} for API key ${
|
||||
options.apiKey
|
||||
} for taker address ${options.takerAddress}`,
|
||||
);
|
||||
this._warningLogger(err);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -227,14 +262,25 @@ export class QuoteRequestor {
|
||||
return true;
|
||||
});
|
||||
|
||||
const responses = validResponsesWithStringInts.map(response => {
|
||||
const validResponses = validResponsesWithStringInts.map(response => {
|
||||
return {
|
||||
...response,
|
||||
makerAssetAmount: new BigNumber(response.makerAssetAmount),
|
||||
takerAssetAmount: new BigNumber(response.takerAssetAmount),
|
||||
expirationTimeSeconds: new BigNumber(response.expirationTimeSeconds),
|
||||
};
|
||||
});
|
||||
|
||||
const responses = validResponses.filter(response => {
|
||||
if (this._isExpirationTooSoon(response.expirationTimeSeconds)) {
|
||||
this._warningLogger(
|
||||
`Expiry too soon in RFQ-T indicative quote, filtering out: ${JSON.stringify(response)}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return responses;
|
||||
}
|
||||
|
||||
@@ -251,9 +297,38 @@ export class QuoteRequestor {
|
||||
const hasValidTakerAssetData =
|
||||
response.takerAssetData !== undefined &&
|
||||
this._schemaValidator.isValid(response.takerAssetData, schemas.hexSchema);
|
||||
if (hasValidMakerAssetAmount && hasValidTakerAssetAmount && hasValidMakerAssetData && hasValidTakerAssetData) {
|
||||
const hasValidExpirationTimeSeconds =
|
||||
response.expirationTimeSeconds !== undefined &&
|
||||
this._schemaValidator.isValid(response.expirationTimeSeconds, schemas.wholeNumberSchema);
|
||||
if (
|
||||
hasValidMakerAssetAmount &&
|
||||
hasValidTakerAssetAmount &&
|
||||
hasValidMakerAssetData &&
|
||||
hasValidTakerAssetData &&
|
||||
hasValidExpirationTimeSeconds
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private _makerSupportsPair(makerUrl: string, makerAssetData: string, takerAssetData: string): boolean {
|
||||
const makerTokenAddress = getTokenAddressOrThrow(makerAssetData);
|
||||
const takerTokenAddress = getTokenAddressOrThrow(takerAssetData);
|
||||
for (const assetPair of this._rfqtAssetOfferings[makerUrl]) {
|
||||
if (
|
||||
(assetPair[0] === makerTokenAddress && assetPair[1] === takerTokenAddress) ||
|
||||
(assetPair[0] === takerTokenAddress && assetPair[1] === makerTokenAddress)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private _isExpirationTooSoon(expirationTimeSeconds: BigNumber): boolean {
|
||||
const expirationTimeMs = expirationTimeSeconds.times(constants.ONE_SECOND_MS);
|
||||
const currentTimeMs = new BigNumber(Date.now());
|
||||
return expirationTimeMs.isLessThan(currentTimeMs.plus(this._expiryBufferMs));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { BigNumber } from '@0x/utils';
|
||||
import * as chai from 'chai';
|
||||
import 'mocha';
|
||||
|
||||
import { constants } from '../src/constants';
|
||||
import { MarketOperation, MockedRfqtFirmQuoteResponse, MockedRfqtIndicativeQuoteResponse } from '../src/types';
|
||||
import { QuoteRequestor } from '../src/utils/quote_requestor';
|
||||
import { rfqtMocker } from '../src/utils/rfqt_mocker';
|
||||
@@ -15,6 +16,12 @@ import { testOrderFactory } from './utils/test_order_factory';
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
function makeThreeMinuteExpiry(): BigNumber {
|
||||
const expiry = new Date(Date.now());
|
||||
expiry.setMinutes(expiry.getMinutes() + 3);
|
||||
return new BigNumber(Math.round(expiry.valueOf() / constants.ONE_SECOND_MS));
|
||||
}
|
||||
|
||||
describe('QuoteRequestor', async () => {
|
||||
const [makerToken, takerToken, otherToken1] = tokenUtils.getDummyERC20TokenAddresses();
|
||||
const makerAssetData = assetDataUtils.encodeERC20AssetData(makerToken);
|
||||
@@ -39,7 +46,9 @@ describe('QuoteRequestor', async () => {
|
||||
const successfulOrder1 = testOrderFactory.generateTestSignedOrder({
|
||||
makerAssetData,
|
||||
takerAssetData,
|
||||
takerAddress,
|
||||
feeRecipientAddress: '0x0000000000000000000000000000000000000001',
|
||||
expirationTimeSeconds: makeThreeMinuteExpiry(),
|
||||
});
|
||||
mockedRequests.push({
|
||||
endpoint: 'https://1337.0.0.1',
|
||||
@@ -67,6 +76,7 @@ describe('QuoteRequestor', async () => {
|
||||
// A successful response code and valid order, but for wrong maker asset data
|
||||
const wrongMakerAssetDataOrder = testOrderFactory.generateTestSignedOrder({
|
||||
makerAssetData: assetDataUtils.encodeERC20AssetData(otherToken1),
|
||||
expirationTimeSeconds: makeThreeMinuteExpiry(),
|
||||
takerAssetData,
|
||||
});
|
||||
mockedRequests.push({
|
||||
@@ -79,6 +89,7 @@ describe('QuoteRequestor', async () => {
|
||||
// A successful response code and valid order, but for wrong taker asset data
|
||||
const wrongTakerAssetDataOrder = testOrderFactory.generateTestSignedOrder({
|
||||
makerAssetData,
|
||||
expirationTimeSeconds: makeThreeMinuteExpiry(),
|
||||
takerAssetData: assetDataUtils.encodeERC20AssetData(otherToken1),
|
||||
});
|
||||
mockedRequests.push({
|
||||
@@ -92,6 +103,7 @@ describe('QuoteRequestor', async () => {
|
||||
const unsignedOrder = testOrderFactory.generateTestSignedOrder({
|
||||
makerAssetData,
|
||||
takerAssetData,
|
||||
expirationTimeSeconds: makeThreeMinuteExpiry(),
|
||||
feeRecipientAddress: '0x0000000000000000000000000000000000000002',
|
||||
});
|
||||
delete unsignedOrder.signature;
|
||||
@@ -102,9 +114,29 @@ describe('QuoteRequestor', async () => {
|
||||
responseData: unsignedOrder,
|
||||
responseCode: StatusCodes.Success,
|
||||
});
|
||||
// A successful response code and good order but for the wrong takerAddress
|
||||
const orderWithNullTaker = testOrderFactory.generateTestSignedOrder({
|
||||
makerAssetData,
|
||||
takerAssetData,
|
||||
expirationTimeSeconds: makeThreeMinuteExpiry(),
|
||||
takerAddress: constants.NULL_ADDRESS,
|
||||
feeRecipientAddress: '0x0000000000000000000000000000000000000002',
|
||||
});
|
||||
mockedRequests.push({
|
||||
endpoint: 'https://425.0.0.1',
|
||||
requestApiKey: apiKey,
|
||||
requestParams: expectedParams,
|
||||
responseData: orderWithNullTaker,
|
||||
responseCode: StatusCodes.Success,
|
||||
});
|
||||
|
||||
// Another Successful response
|
||||
const successfulOrder2 = testOrderFactory.generateTestSignedOrder({ makerAssetData, takerAssetData });
|
||||
const successfulOrder2 = testOrderFactory.generateTestSignedOrder({
|
||||
makerAssetData,
|
||||
takerAssetData,
|
||||
takerAddress,
|
||||
expirationTimeSeconds: makeThreeMinuteExpiry(),
|
||||
});
|
||||
mockedRequests.push({
|
||||
endpoint: 'https://37.0.0.1',
|
||||
requestApiKey: apiKey,
|
||||
@@ -114,15 +146,18 @@ describe('QuoteRequestor', async () => {
|
||||
});
|
||||
|
||||
return rfqtMocker.withMockedRfqtFirmQuotes(mockedRequests, async () => {
|
||||
const qr = new QuoteRequestor([
|
||||
'https://1337.0.0.1',
|
||||
'https://420.0.0.1',
|
||||
'https://421.0.0.1',
|
||||
'https://422.0.0.1',
|
||||
'https://423.0.0.1',
|
||||
'https://424.0.0.1',
|
||||
'https://37.0.0.1',
|
||||
]);
|
||||
const qr = new QuoteRequestor({
|
||||
'https://1337.0.0.1': [[makerToken, takerToken]],
|
||||
'https://420.0.0.1': [[makerToken, takerToken]],
|
||||
'https://421.0.0.1': [[makerToken, takerToken]],
|
||||
'https://422.0.0.1': [[makerToken, takerToken]],
|
||||
'https://423.0.0.1': [[makerToken, takerToken]],
|
||||
'https://424.0.0.1': [[makerToken, takerToken]],
|
||||
'https://425.0.0.1': [[makerToken, takerToken]],
|
||||
'https://426.0.0.1': [] /* Shouldn't ping an RFQ-T
|
||||
provider when they don't support the requested asset pair. */,
|
||||
'https://37.0.0.1': [[makerToken, takerToken]],
|
||||
});
|
||||
const resp = await qr.requestRfqtFirmQuotesAsync(
|
||||
makerAssetData,
|
||||
takerAssetData,
|
||||
@@ -159,6 +194,7 @@ describe('QuoteRequestor', async () => {
|
||||
takerAssetData,
|
||||
makerAssetAmount: new BigNumber(expectedParams.sellAmount),
|
||||
takerAssetAmount: new BigNumber(expectedParams.sellAmount),
|
||||
expirationTimeSeconds: makeThreeMinuteExpiry(),
|
||||
};
|
||||
mockedRequests.push({
|
||||
endpoint: 'https://1337.0.0.1',
|
||||
@@ -209,15 +245,15 @@ describe('QuoteRequestor', async () => {
|
||||
});
|
||||
|
||||
return rfqtMocker.withMockedRfqtIndicativeQuotes(mockedRequests, async () => {
|
||||
const qr = new QuoteRequestor([
|
||||
'https://1337.0.0.1',
|
||||
'https://420.0.0.1',
|
||||
'https://421.0.0.1',
|
||||
'https://422.0.0.1',
|
||||
'https://423.0.0.1',
|
||||
'https://424.0.0.1',
|
||||
'https://37.0.0.1',
|
||||
]);
|
||||
const qr = new QuoteRequestor({
|
||||
'https://1337.0.0.1': [[makerToken, takerToken]],
|
||||
'https://420.0.0.1': [[makerToken, takerToken]],
|
||||
'https://421.0.0.1': [[makerToken, takerToken]],
|
||||
'https://422.0.0.1': [[makerToken, takerToken]],
|
||||
'https://423.0.0.1': [[makerToken, takerToken]],
|
||||
'https://424.0.0.1': [[makerToken, takerToken]],
|
||||
'https://37.0.0.1': [[makerToken, takerToken]],
|
||||
});
|
||||
const resp = await qr.requestRfqtIndicativeQuotesAsync(
|
||||
makerAssetData,
|
||||
takerAssetData,
|
||||
@@ -252,6 +288,7 @@ describe('QuoteRequestor', async () => {
|
||||
takerAssetData,
|
||||
makerAssetAmount: new BigNumber(expectedParams.buyAmount),
|
||||
takerAssetAmount: new BigNumber(expectedParams.buyAmount),
|
||||
expirationTimeSeconds: makeThreeMinuteExpiry(),
|
||||
};
|
||||
mockedRequests.push({
|
||||
endpoint: 'https://1337.0.0.1',
|
||||
@@ -262,7 +299,7 @@ describe('QuoteRequestor', async () => {
|
||||
});
|
||||
|
||||
return rfqtMocker.withMockedRfqtIndicativeQuotes(mockedRequests, async () => {
|
||||
const qr = new QuoteRequestor(['https://1337.0.0.1']);
|
||||
const qr = new QuoteRequestor({ 'https://1337.0.0.1': [[makerToken, takerToken]] });
|
||||
const resp = await qr.requestRfqtIndicativeQuotesAsync(
|
||||
makerAssetData,
|
||||
takerAssetData,
|
||||
|
||||
Reference in New Issue
Block a user