fix: Exclusive API keys for select integrations (#317)

* Initial commit of changes

* Added unit tests for filtering process

* linting

* Update packages/asset-swapper/src/utils/quote_requestor.ts

Co-authored-by: phil-ociraptor <philipliao@gmail.com>

* lint and refactor based on feedback

Co-authored-by: phil-ociraptor <philipliao@gmail.com>
This commit is contained in:
Daniel Pyrathon 2021-09-07 14:26:40 -04:00 committed by GitHub
parent 7439871aa0
commit e838a6801b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 71 additions and 15 deletions

View File

@ -244,6 +244,7 @@ export interface RfqRequestOpts {
takerAddress: string; takerAddress: string;
txOrigin: string; txOrigin: string;
apiKey: string; apiKey: string;
apiKeyWhitelist?: string[];
intentOnFilling: boolean; intentOnFilling: boolean;
isIndicative?: boolean; isIndicative?: boolean;
makerEndpointMaxResponseTimeMs?: number; makerEndpointMaxResponseTimeMs?: number;

View File

@ -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<RfqmRequestOptions, 'apiKeyWhitelist' | 'altRfqAssetOfferings'>,
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 { public static getDurationUntilExpirationMs(expirationTimeSeconds: BigNumber): BigNumber {
const expirationTimeMs = expirationTimeSeconds.times(constants.ONE_SECOND_MS); const expirationTimeMs = expirationTimeSeconds.times(constants.ONE_SECOND_MS);
const currentTimeMs = new BigNumber(Date.now()); 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 = const timeoutMs =
options.makerEndpointMaxResponseTimeMs || options.makerEndpointMaxResponseTimeMs ||
constants.DEFAULT_RFQT_REQUEST_OPTS.makerEndpointMaxResponseTimeMs!; constants.DEFAULT_RFQT_REQUEST_OPTS.makerEndpointMaxResponseTimeMs!;
@ -427,6 +448,7 @@ export class QuoteRequestor {
cancelTokenSource.cancel('timeout via cancel token'); cancelTokenSource.cancel('timeout via cancel token');
}, timeoutMs + bufferMs); }, timeoutMs + bufferMs);
const typedMakerUrls = QuoteRequestor.getTypedMakerUrlsAndWhitelist(options, assetOfferings);
const quotePromises = typedMakerUrls.map(async typedMakerUrl => { const quotePromises = typedMakerUrls.map(async typedMakerUrl => {
// filter out requests to skip // filter out requests to skip
const isBlacklisted = rfqMakerBlacklist.isMakerBlacklisted(typedMakerUrl.url); const isBlacklisted = rfqMakerBlacklist.isMakerBlacklisted(typedMakerUrl.url);

View File

@ -834,6 +834,39 @@ describe('QuoteRequestor', async () => {
quoteRequestorHttpClient, 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 () => { it('should return successful alt indicative quotes', async () => {
const takerAddress = '0xd209925defc99488e3afff1174e48b4fa628302a'; const takerAddress = '0xd209925defc99488e3afff1174e48b4fa628302a';
const txOrigin = '0xf209925defc99488e3afff1174e48b4fa628302a'; const txOrigin = '0xf209925defc99488e3afff1174e48b4fa628302a';