diff --git a/packages/asset-swapper/src/index.ts b/packages/asset-swapper/src/index.ts index 89f7ee4626..adddb13bd6 100644 --- a/packages/asset-swapper/src/index.ts +++ b/packages/asset-swapper/src/index.ts @@ -116,6 +116,15 @@ export { SamplerMetrics, } from './types'; export { affiliateFeeUtils } from './utils/affiliate_fee_utils'; +export { + IRfqClient, + RfqClientV1Price, + RfqClientV1PriceRequest, + RfqClientV1PriceResponse, + RfqClientV1Quote, + RfqClientV1QuoteRequest, + RfqClientV1QuoteResponse, +} from './utils/irfq_client'; export { DEFAULT_TOKEN_ADJACENCY_GRAPH_BY_CHAIN_ID, DEFAULT_GAS_SCHEDULE, diff --git a/packages/asset-swapper/src/swap_quoter.ts b/packages/asset-swapper/src/swap_quoter.ts index bef9d30e9c..d3edd73b83 100644 --- a/packages/asset-swapper/src/swap_quoter.ts +++ b/packages/asset-swapper/src/swap_quoter.ts @@ -25,6 +25,7 @@ import { SwapQuoterRfqOpts, } from './types'; import { assert } from './utils/assert'; +import { IRfqClient } from './utils/irfq_client'; import { MarketOperationUtils } from './utils/market_operation_utils'; import { BancorService } from './utils/market_operation_utils/bancor_service'; import { SAMPLER_ADDRESS, SOURCE_FLAGS, ZERO_AMOUNT } from './utils/market_operation_utils/constants'; @@ -327,6 +328,7 @@ export class SwapQuoter { assetFillAmount: BigNumber, marketOperation: MarketOperation, options: Partial, + rfqClient: IRfqClient | undefined, ): Promise { assert.isETHAddressHex('makerToken', makerToken); assert.isETHAddressHex('takerToken', takerToken); @@ -381,6 +383,7 @@ export class SwapQuoter { this.expiryBufferMs, rfqtOptions?.metricsProxy, ); + calcOpts.rfqt.rfqClient = rfqClient; } const result: OptimizerResultWithReport = await this._marketOperationUtils.getOptimizerResultAsync( diff --git a/packages/asset-swapper/src/utils/alt_mm_implementation_utils.ts b/packages/asset-swapper/src/utils/alt_mm_implementation_utils.ts index e7a289e250..a8ca1998b3 100644 --- a/packages/asset-swapper/src/utils/alt_mm_implementation_utils.ts +++ b/packages/asset-swapper/src/utils/alt_mm_implementation_utils.ts @@ -17,7 +17,10 @@ import { const SUCCESS_CODE = 201; -function getAltMarketInfo( +/** + * Returns the AltOffering if it exists for a given pair + */ +export function getAltMarketInfo( offerings: AltOffering[], buyTokenAddress: string, sellTokenAddress: string, diff --git a/packages/asset-swapper/src/utils/irfq_client.ts b/packages/asset-swapper/src/utils/irfq_client.ts new file mode 100644 index 0000000000..d59cf5f874 --- /dev/null +++ b/packages/asset-swapper/src/utils/irfq_client.ts @@ -0,0 +1,59 @@ +import { RfqOrder, Signature } from '@0x/protocol-utils'; +import { BigNumber } from '@0x/utils'; + +import { AltRfqMakerAssetOfferings } from '../types'; + +export interface RfqClientV1PriceRequest { + altRfqAssetOfferings: AltRfqMakerAssetOfferings | undefined; + assetFillAmount: BigNumber; + chainId: number; + comparisonPrice: BigNumber | undefined; + integratorId: string; + intentOnFilling: boolean; + makerToken: string; + marketOperation: 'Sell' | 'Buy'; + takerAddress: string; + takerToken: string; + txOrigin: string; +} + +export interface RfqClientV1QuoteRequest extends RfqClientV1PriceRequest {} + +export interface RfqClientV1Price { + expiry: BigNumber; + kind: 'rfq' | 'otc'; + makerAmount: BigNumber; + makerToken: string; + makerUri: string; + takerAmount: BigNumber; + takerToken: string; +} + +export interface RfqClientV1PriceResponse { + prices: RfqClientV1Price[]; +} + +export interface RfqClientV1Quote { + makerUri: string; + order: RfqOrder; + signature: Signature; +} + +export interface RfqClientV1QuoteResponse { + quotes: RfqClientV1Quote[]; +} + +/** + * IRfqClient is an interface that defines how to connect with an Rfq system. + */ +export interface IRfqClient { + /** + * Fetches a list of "indicative quotes" or prices from a remote Rfq server + */ + getV1PricesAsync(request: RfqClientV1PriceRequest): Promise; + + /** + * Fetches a list of "firm quotes" or signed quotes from a remote Rfq server. + */ + getV1QuotesAsync(request: RfqClientV1QuoteRequest): Promise; +} 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 6c4a347526..8f2d084ec2 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/index.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/index.ts @@ -4,12 +4,15 @@ import * as _ from 'lodash'; import { DEFAULT_INFO_LOGGER, INVALID_SIGNATURE } from '../../constants'; import { + AltRfqMakerAssetOfferings, AssetSwapperContractAddresses, MarketOperation, NativeOrderWithFillableAmounts, SignedNativeOrder, } from '../../types'; -import { QuoteRequestor } from '../quote_requestor'; +import { getAltMarketInfo } from '../alt_mm_implementation_utils'; +import { QuoteRequestor, V4RFQIndicativeQuoteMM } from '../quote_requestor'; +import { toSignedNativeOrder } from '../rfq_client_mappers'; import { getNativeAdjustedFillableAmountsFromMakerAmount, getNativeAdjustedFillableAmountsFromTakerAmount, @@ -663,17 +666,49 @@ export class MarketOperationUtils { // Timing of RFQT lifecycle const timeStart = new Date().getTime(); const { makerToken, takerToken } = nativeOrders[0].order; + + // Filter Alt Rfq Maker Asset Offerings to the current pair + const filteredOfferings: AltRfqMakerAssetOfferings = {}; + if (rfqt.altRfqAssetOfferings) { + const endpoints = Object.keys(rfqt.altRfqAssetOfferings); + for (const endpoint of endpoints) { + // Get the current pair if being offered + const offering = getAltMarketInfo(rfqt.altRfqAssetOfferings[endpoint], makerToken, takerToken); + if (offering) { + filteredOfferings[endpoint] = [offering]; + } + } + } + if (rfqt.isIndicative) { // An indicative quote is being requested, and indicative quotes price-aware enabled // Make the RFQT request and then re-run the sampler if new orders come back. - const indicativeQuotes = await rfqt.quoteRequestor.requestRfqtIndicativeQuotesAsync( - makerToken, - takerToken, - amount, - side, - wholeOrderPrice, - rfqt, - ); + + const indicativeQuotes = + rfqt.rfqClient !== undefined + ? (( + await rfqt.rfqClient.getV1PricesAsync({ + altRfqAssetOfferings: filteredOfferings, + assetFillAmount: amount, + chainId: this._sampler.chainId, + comparisonPrice: wholeOrderPrice, + integratorId: rfqt.integrator.integratorId, + intentOnFilling: rfqt.intentOnFilling, + makerToken, + marketOperation: side, + takerAddress: rfqt.takerAddress, + takerToken, + txOrigin: rfqt.txOrigin, + }) + ).prices as V4RFQIndicativeQuoteMM[]) + : await rfqt.quoteRequestor.requestRfqtIndicativeQuotesAsync( + makerToken, + takerToken, + amount, + side, + wholeOrderPrice, + rfqt, + ); const deltaTime = new Date().getTime() - timeStart; DEFAULT_INFO_LOGGER({ rfqQuoteType: 'indicative', @@ -687,14 +722,31 @@ export class MarketOperationUtils { } else { // A firm quote is being requested, and firm quotes price-aware enabled. // Ensure that `intentOnFilling` is enabled and make the request. - const firmQuotes = await rfqt.quoteRequestor.requestRfqtFirmQuotesAsync( - makerToken, - takerToken, - amount, - side, - wholeOrderPrice, - rfqt, - ); + const firmQuotes = + rfqt.rfqClient !== undefined + ? ( + await rfqt.rfqClient.getV1QuotesAsync({ + altRfqAssetOfferings: filteredOfferings, + assetFillAmount: amount, + chainId: this._sampler.chainId, + comparisonPrice: wholeOrderPrice, + integratorId: rfqt.integrator.integratorId, + intentOnFilling: rfqt.intentOnFilling, + makerToken, + marketOperation: side, + takerAddress: rfqt.takerAddress, + takerToken, + txOrigin: rfqt.txOrigin, + }) + ).quotes.map(toSignedNativeOrder) + : await rfqt.quoteRequestor.requestRfqtFirmQuotesAsync( + makerToken, + takerToken, + amount, + side, + wholeOrderPrice, + rfqt, + ); const deltaTime = new Date().getTime() - timeStart; DEFAULT_INFO_LOGGER({ rfqQuoteType: 'firm', 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 55f607afbb..c8b294e9e1 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/types.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/types.ts @@ -8,6 +8,7 @@ import { BigNumber } from '@0x/utils'; import { NativeOrderWithFillableAmounts, RfqFirmQuoteValidator, RfqRequestOpts } from '../../types'; import { QuoteRequestor, V4RFQIndicativeQuoteMM } from '../../utils/quote_requestor'; +import { IRfqClient } from '../irfq_client'; import { ExtendedQuoteReportSources, PriceComparisonsReport, QuoteReport } from '../quote_report_generator'; import { SourceFilters } from './source_filters'; @@ -446,6 +447,7 @@ export type OptimizedMarketOrder = | OptimizedMarketOrderBase; export interface GetMarketOrdersRfqOpts extends RfqRequestOpts { + rfqClient?: IRfqClient; quoteRequestor?: QuoteRequestor; firmQuoteValidator?: RfqFirmQuoteValidator; } diff --git a/packages/asset-swapper/src/utils/rfq_client_mappers.ts b/packages/asset-swapper/src/utils/rfq_client_mappers.ts new file mode 100644 index 0000000000..6d2936c3b6 --- /dev/null +++ b/packages/asset-swapper/src/utils/rfq_client_mappers.ts @@ -0,0 +1,16 @@ +import { FillQuoteTransformerOrderType } from '@0x/protocol-utils'; + +import { SignedNativeOrder } from '../types'; + +import { RfqClientV1Quote } from './irfq_client'; + +/** + * Converts a RfqClientRfqOrderFirmQuote to a SignedNativeOrder + */ +export const toSignedNativeOrder = (quote: RfqClientV1Quote): SignedNativeOrder => { + return { + type: FillQuoteTransformerOrderType.Rfq, + order: quote.order, + signature: quote.signature, + }; +};