asset-s: skip RFQT makers not supporting reqd pair

Skip querying an RFQ-T maker for a quote when the requested asset pair
isn't supported by that maker.
This commit is contained in:
F. Eugene Aumson
2020-04-28 18:09:04 -04:00
parent 4cb08a61d5
commit 93bdaba8ee
6 changed files with 101 additions and 60 deletions

View File

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

View File

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

View File

@@ -171,7 +171,7 @@ export class SwapQuoter {
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.makerAssetOfferings || {} : {},
options.rfqt ? options.rfqt.warningLogger : undefined,
options.rfqt ? options.rfqt.infoLogger : undefined,
);

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,7 +232,7 @@ export interface SwapQuoterOpts extends OrderPrunerOpts {
liquidityProviderRegistryAddress?: string;
rfqt?: {
takerApiKeyWhitelist: string[];
makerEndpoints: string[];
makerAssetOfferings: RfqtMakerAssetOfferings;
warningLogger?: (s: string) => void;
infoLogger?: (s: string) => void;
};

View File

@@ -6,7 +6,7 @@ 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
@@ -85,7 +85,7 @@ export class QuoteRequestor {
private readonly _schemaValidator: SchemaValidator = new SchemaValidator();
constructor(
private readonly _rfqtMakerEndpoints: string[],
private readonly _rfqtAssetOfferings: RfqtMakerAssetOfferings,
private readonly _warningLogger: (s: string) => void = s => logUtils.warn(s),
private readonly _infoLogger: (s: string) => void = () => { return; },
) {}
@@ -104,25 +104,28 @@ export class QuoteRequestor {
// as a placeholder for failed requests.
const timeBeforeAwait = Date.now();
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 {
return await Axios.get<SignedOrder>(`${url}/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 ${url} for API key ${
_opts.apiKey
} for taker address ${_opts.takerAddress}`,
);
this._warningLogger(err);
return undefined;
}
}
return undefined;
}),
);
this._infoLogger(JSON.stringify({ aggregatedRfqtLatencyMs: Date.now() - timeBeforeAwait }));
@@ -184,25 +187,28 @@ export class QuoteRequestor {
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 {
return await Axios.get<RfqtIndicativeQuoteResponse>(`${url}/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 ${url} for API key ${
options.apiKey
} for taker address ${options.takerAddress}`,
);
this._warningLogger(err);
return undefined;
}
}
return undefined;
}),
);
this._infoLogger(JSON.stringify({ aggregatedRfqtLatencyMs: Date.now() - timeBeforeAwait }));
@@ -269,4 +275,18 @@ export class QuoteRequestor {
}
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;
}
}

View File

@@ -109,6 +109,17 @@ describe('QuoteRequestor', async () => {
responseCode: StatusCodes.Success,
});
// Shouldn't ping an RFQ-T provider when they don't support the requested asset pair.
// (see how QuoteRequestor constructor parameters below don't list
// any supported asset pairs for this maker.)
mockedRequests.push({
endpoint: 'https://426.0.0.1',
requestApiKey: apiKey,
requestParams: expectedParams,
responseData: successfulOrder1,
responseCode: StatusCodes.Success,
});
// Another Successful response
const successfulOrder2 = testOrderFactory.generateTestSignedOrder({ makerAssetData, takerAssetData });
mockedRequests.push({
@@ -120,15 +131,16 @@ 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://426.0.0.1': [],
'https://37.0.0.1': [[makerToken, takerToken]],
});
const resp = await qr.requestRfqtFirmQuotesAsync(
makerAssetData,
takerAssetData,
@@ -216,15 +228,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,
@@ -270,7 +282,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,