Merge pull request #2574 from 0xProject/rfqt-follow-ups

asset-swapper: RFQ-T follow ups
This commit is contained in:
F. Eugene Aumson
2020-05-07 17:44:02 -04:00
committed by GitHub
9 changed files with 250 additions and 88 deletions

View File

@@ -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",

View File

@@ -46,7 +46,8 @@ const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = {
samplerGasLimit: 250e6,
rfqt: {
takerApiKeyWhitelist: [],
makerEndpoints: [],
makerAssetOfferings: {},
skipBuyRequests: false,
},
};

View File

@@ -45,6 +45,7 @@ export {
MarketOperation,
MarketSellSwapQuote,
MockedRfqtFirmQuoteResponse,
RfqtMakerAssetOfferings,
RfqtRequestOpts,
SwapQuote,
SwapQuoteConsumerBase,

View File

@@ -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)
);
}
}

View File

@@ -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;
};
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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));
}
}

View File

@@ -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,