diff --git a/packages/asset-swapper/src/types.ts b/packages/asset-swapper/src/types.ts index c32371c172..f85e69fbec 100644 --- a/packages/asset-swapper/src/types.ts +++ b/packages/asset-swapper/src/types.ts @@ -244,6 +244,7 @@ export interface RfqRequestOpts { takerAddress: string; txOrigin: string; apiKey: string; + apiKeyWhitelist?: string[]; intentOnFilling: boolean; isIndicative?: boolean; makerEndpointMaxResponseTimeMs?: number; diff --git a/packages/asset-swapper/src/utils/quote_requestor.ts b/packages/asset-swapper/src/utils/quote_requestor.ts index b2e9f1123b..3cc39bfaed 100644 --- a/packages/asset-swapper/src/utils/quote_requestor.ts +++ b/packages/asset-swapper/src/utils/quote_requestor.ts @@ -178,6 +178,42 @@ export class QuoteRequestor { } } + /** + * Gets both standard RFQ makers and "alternative" RFQ makers and combines them together + * in a single configuration map. If an integration key whitelist is present, it will be used + * to filter a specific makers. + * + * @param options the RfqmRequestOptions passed in + * @param assetOfferings the RFQM or RFQT maker offerings + * @returns a list of TypedMakerUrl instances + */ + public static getTypedMakerUrlsAndWhitelist( + options: Pick, + assetOfferings: RfqMakerAssetOfferings, + ): TypedMakerUrl[] { + const standardUrls = Object.keys(assetOfferings).map( + (mm: string): TypedMakerUrl => { + return { pairType: RfqPairType.Standard, url: mm }; + }, + ); + const altUrls = options.altRfqAssetOfferings + ? Object.keys(options.altRfqAssetOfferings).map( + (mm: string): TypedMakerUrl => { + return { pairType: RfqPairType.Alt, url: mm }; + }, + ) + : []; + + let typedMakerUrls = standardUrls.concat(altUrls); + + // If there is a whitelist, only allow approved maker URLs + if (options.apiKeyWhitelist !== undefined) { + const whitelist = new Set(options.apiKeyWhitelist.map(key => key.toLowerCase())); + typedMakerUrls = typedMakerUrls.filter(makerUrl => whitelist.has(makerUrl.url.toLowerCase())); + } + return typedMakerUrls; + } + public static getDurationUntilExpirationMs(expirationTimeSeconds: BigNumber): BigNumber { const expirationTimeMs = expirationTimeSeconds.times(constants.ONE_SECOND_MS); const currentTimeMs = new BigNumber(Date.now()); @@ -401,21 +437,6 @@ export class QuoteRequestor { } })(); - const standardUrls = Object.keys(assetOfferings).map( - (mm: string): TypedMakerUrl => { - return { pairType: RfqPairType.Standard, url: mm }; - }, - ); - const altUrls = options.altRfqAssetOfferings - ? Object.keys(options.altRfqAssetOfferings).map( - (mm: string): TypedMakerUrl => { - return { pairType: RfqPairType.Alt, url: mm }; - }, - ) - : []; - - const typedMakerUrls = standardUrls.concat(altUrls); - const timeoutMs = options.makerEndpointMaxResponseTimeMs || constants.DEFAULT_RFQT_REQUEST_OPTS.makerEndpointMaxResponseTimeMs!; @@ -427,6 +448,7 @@ export class QuoteRequestor { cancelTokenSource.cancel('timeout via cancel token'); }, timeoutMs + bufferMs); + const typedMakerUrls = QuoteRequestor.getTypedMakerUrlsAndWhitelist(options, assetOfferings); const quotePromises = typedMakerUrls.map(async typedMakerUrl => { // filter out requests to skip const isBlacklisted = rfqMakerBlacklist.isMakerBlacklisted(typedMakerUrl.url); diff --git a/packages/asset-swapper/test/quote_requestor_test.ts b/packages/asset-swapper/test/quote_requestor_test.ts index f473eb42d8..12e8bae71d 100644 --- a/packages/asset-swapper/test/quote_requestor_test.ts +++ b/packages/asset-swapper/test/quote_requestor_test.ts @@ -834,6 +834,39 @@ describe('QuoteRequestor', async () => { quoteRequestorHttpClient, ); }); + it('should be able to handle and filter RFQ offerings', () => { + const tests: Array<[string[] | undefined, string[]]> = [ + [['https://top.maker'], []], + [undefined, ['https://foo.bar/', 'https://lorem.ipsum/']], + [['https://lorem.ipsum/'], ['https://lorem.ipsum/']], + ]; + for (const test of tests) { + const [apiKeyWhitelist, results] = test; + const response = QuoteRequestor.getTypedMakerUrlsAndWhitelist( + { + apiKeyWhitelist, + altRfqAssetOfferings: {}, + }, + { + 'https://foo.bar/': [ + [ + '0xA6cD4cb8c62aCDe44739E3Ed0F1d13E0e31f2d94', + '0xF45107c0200a04A8aB9C600cc52A3C89AE5D0489', + ], + ], + 'https://lorem.ipsum/': [ + [ + '0xA6cD4cb8c62aCDe44739E3Ed0F1d13E0e31f2d94', + '0xF45107c0200a04A8aB9C600cc52A3C89AE5D0489', + ], + ], + }, + ); + const typedUrls = response.map(typed => typed.url); + expect(typedUrls).to.eql(results); + } + }); + it('should return successful alt indicative quotes', async () => { const takerAddress = '0xd209925defc99488e3afff1174e48b4fa628302a'; const txOrigin = '0xf209925defc99488e3afff1174e48b4fa628302a';