From 7bc9701c813308bdf8f12a59b08e7b5848a2176e Mon Sep 17 00:00:00 2001 From: Daniel Pyrathon Date: Mon, 28 Sep 2020 13:15:44 -0700 Subject: [PATCH] initial implementation --- packages/asset-swapper/src/swap_quoter.ts | 29 ++------ .../src/utils/market_operation_utils/index.ts | 67 +++++++++++++++---- .../src/utils/market_operation_utils/types.ts | 12 ++++ 3 files changed, 74 insertions(+), 34 deletions(-) diff --git a/packages/asset-swapper/src/swap_quoter.ts b/packages/asset-swapper/src/swap_quoter.ts index 7b99bc55b4..43b9a59894 100644 --- a/packages/asset-swapper/src/swap_quoter.ts +++ b/packages/asset-swapper/src/swap_quoter.ts @@ -683,33 +683,18 @@ export class SwapQuoter { this.expiryBufferMs, ); - if ( - opts.rfqt && // This is an RFQT-enabled API request - opts.rfqt.intentOnFilling && // The requestor is asking for a firm quote - opts.rfqt.apiKey && - this._isApiKeyWhitelisted(opts.rfqt.apiKey) && // A valid API key was provided - sourceFilters.isAllowed(ERC20BridgeSource.Native) // Native liquidity is not excluded - ) { - if (!opts.rfqt.takerAddress || opts.rfqt.takerAddress === constants.NULL_ADDRESS) { - throw new Error('RFQ-T requests must specify a taker address'); + // If an API key was provided, but the key is not whitelisted, raise a warning and disable RFQ + if (opts.rfqt && opts.rfqt.apiKey && !this._isApiKeyWhitelisted(opts.rfqt.apiKey)) { + if (rfqtOptions && rfqtOptions.warningLogger) { + rfqtOptions.warningLogger({ + apiKey: opts.rfqt.apiKey, + }, 'Attempt at using an RFQ API key that is not whitelisted. Disabling RFQ for the request lifetime.'); } - orderBatchPromises.push( - quoteRequestor - .requestRfqtFirmQuotesAsync( - makerAssetData, - takerAssetData, - assetFillAmount, - marketOperation, - opts.rfqt, - ) - .then(firmQuotes => firmQuotes.map(quote => quote.signedOrder)), - ); + opts.rfqt = undefined; } const orderBatches: SignedOrder[][] = await Promise.all(orderBatchPromises); - const unsortedOrders: SignedOrder[] = orderBatches.reduce((_orders, batch) => _orders.concat(...batch), []); - const orders = sortingUtils.sortOrders(unsortedOrders); // if no native orders, pass in a dummy order for the sampler to have required metadata for sampling diff --git a/packages/asset-swapper/src/utils/market_operation_utils/index.ts b/packages/asset-swapper/src/utils/market_operation_utils/index.ts index c922496400..830f1603ed 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/index.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/index.ts @@ -232,6 +232,7 @@ export class MarketOperationUtils { ethToInputRate: ethToTakerAssetRate, rfqtIndicativeQuotes, twoHopQuotes, + quoteSourceFilters, }; } @@ -345,6 +346,7 @@ export class MarketOperationUtils { ethToInputRate: ethToMakerAssetRate, rfqtIndicativeQuotes, twoHopQuotes, + quoteSourceFilters, }; } @@ -362,15 +364,63 @@ export class MarketOperationUtils { opts?: Partial, ): Promise { const defaultOpts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts }; - const marketSideLiquidity = await this.getMarketSellLiquidityAsync(nativeOrders, takerAmount, defaultOpts); - const optimizerResult = await this._generateOptimizedOrdersAsync(marketSideLiquidity, { + const optimizerOpts: GenerateOptimizedOrdersOpts = { bridgeSlippage: defaultOpts.bridgeSlippage, maxFallbackSlippage: defaultOpts.maxFallbackSlippage, excludedSources: defaultOpts.excludedSources, feeSchedule: defaultOpts.feeSchedule, allowFallback: defaultOpts.allowFallback, shouldBatchBridgeOrders: defaultOpts.shouldBatchBridgeOrders, - }); + }; + + // Compute an optimized path for on-chain DEX and open-orderbook. This should not include RFQ liquidity. + const marketSideLiquidity = await this.getMarketSellLiquidityAsync(nativeOrders, takerAmount, defaultOpts); + let optimizerResult = await this._generateOptimizedOrdersAsync(marketSideLiquidity, optimizerOpts); + + // If RFQ liquidity is enabled, make a request to check RFQ liquidity + const { rfqt } = defaultOpts; + if (rfqt && rfqt.quoteRequestor && marketSideLiquidity.quoteSourceFilters.isAllowed(ERC20BridgeSource.Native)) { + + // If we are making an indicative quote, make the RFQT request and then re-run the sampler if new orders come back. + if (rfqt.isIndicative) { + const indicativeQuotes = await getRfqtIndicativeQuotesAsync( + nativeOrders[0].makerAssetData, + nativeOrders[0].takerAssetData, + MarketOperation.Sell, + takerAmount, + defaultOpts, + ); + if (indicativeQuotes.length > 0) { + optimizerResult = await this._generateOptimizedOrdersAsync({ + ...marketSideLiquidity, + rfqtIndicativeQuotes: indicativeQuotes, + }, optimizerOpts); + } + } else { + // A firm quote is being requested. Ensure that `intentOnFilling` is enabled. + if (rfqt.intentOnFilling) { + + // Extra validation happens when requesting a firm quote, such as ensuring that the takerAddress + // is indeed valid. + if (!rfqt.takerAddress || rfqt.takerAddress === NULL_ADDRESS) { + throw new Error('RFQ-T requests must specify a taker address'); + } + const firmQuotes = await rfqt.quoteRequestor.requestRfqtFirmQuotesAsync( + nativeOrders[0].makerAssetData, + nativeOrders[0].takerAssetData, + takerAmount, + MarketOperation.Sell, + rfqt, + ); + if (firmQuotes.length > 0) { + optimizerResult = await this._generateOptimizedOrdersAsync({ + ...marketSideLiquidity, + nativeOrders: marketSideLiquidity.nativeOrders.concat(firmQuotes.map(quote => quote.signedOrder)), + }, optimizerOpts); + } + } + } + } // Compute Quote Report and return the results. let quoteReport: QuoteReport | undefined; @@ -494,6 +544,7 @@ export class MarketOperationUtils { inputToken: makerToken, outputToken: takerToken, twoHopQuotes: [], + quoteSourceFilters, }, { bridgeSlippage: _opts.bridgeSlippage, @@ -516,15 +567,7 @@ export class MarketOperationUtils { private async _generateOptimizedOrdersAsync( marketSideLiquidity: MarketSideLiquidity, - opts: { - runLimit?: number; - bridgeSlippage?: number; - maxFallbackSlippage?: number; - excludedSources?: ERC20BridgeSource[]; - feeSchedule?: FeeSchedule; - allowFallback?: boolean; - shouldBatchBridgeOrders?: boolean; - }, + opts: GenerateOptimizedOrdersOpts, ): Promise { const { inputToken, diff --git a/packages/asset-swapper/src/utils/market_operation_utils/types.ts b/packages/asset-swapper/src/utils/market_operation_utils/types.ts index 5e7ea158fd..c80a722342 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/types.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/types.ts @@ -5,6 +5,7 @@ import { BigNumber } from '@0x/utils'; import { RfqtRequestOpts, SignedOrderWithFillableAmounts } from '../../types'; import { QuoteRequestor } from '../../utils/quote_requestor'; import { QuoteReport } from '../quote_report_generator'; +import { SourceFilters } from './source_filters'; /** * Order domain keys: chainId and exchange @@ -348,8 +349,19 @@ export interface MarketSideLiquidity { ethToInputRate: BigNumber; rfqtIndicativeQuotes: RFQTIndicativeQuote[]; twoHopQuotes: Array>; + quoteSourceFilters: SourceFilters; } export interface TokenAdjacencyGraph { [token: string]: string[]; } + +export interface GenerateOptimizedOrdersOpts { + runLimit?: number; + bridgeSlippage?: number; + maxFallbackSlippage?: number; + excludedSources?: ERC20BridgeSource[]; + feeSchedule?: FeeSchedule; + allowFallback?: boolean; + shouldBatchBridgeOrders?: boolean; +}