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:
@@ -46,7 +46,7 @@ const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = {
|
||||
samplerGasLimit: 250e6,
|
||||
rfqt: {
|
||||
takerApiKeyWhitelist: [],
|
||||
makerEndpoints: [],
|
||||
makerAssetOfferings: {},
|
||||
},
|
||||
};
|
||||
|
||||
|
@@ -45,6 +45,7 @@ export {
|
||||
MarketOperation,
|
||||
MarketSellSwapQuote,
|
||||
MockedRfqtFirmQuoteResponse,
|
||||
RfqtMakerAssetOfferings,
|
||||
RfqtRequestOpts,
|
||||
SwapQuote,
|
||||
SwapQuoteConsumerBase,
|
||||
|
@@ -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,
|
||||
);
|
||||
|
@@ -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;
|
||||
};
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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,
|
||||
|
Reference in New Issue
Block a user