diff --git a/lib/config/ChainConfigManager.ts b/lib/config/ChainConfigManager.ts index d176b1f0..304f9de8 100644 --- a/lib/config/ChainConfigManager.ts +++ b/lib/config/ChainConfigManager.ts @@ -9,7 +9,7 @@ type IntentOverrides = { }; export type DutchOverrides = IntentOverrides & { - skipRFQ?: boolean; + forceOpenOrders?: boolean; priceImprovementBps?: number; }; @@ -76,6 +76,9 @@ export abstract class ChainConfigManager { [ChainId.ARBITRUM_ONE]: { routingTypes: { [RoutingType.CLASSIC]: {}, + [RoutingType.DUTCH_V2]: { + deadlineBufferSecs: 60 + }, }, alarmEnabled: true, }, diff --git a/lib/entities/context/DutchQuoteContext.ts b/lib/entities/context/DutchQuoteContext.ts index a4ce739c..f95f1966 100644 --- a/lib/entities/context/DutchQuoteContext.ts +++ b/lib/entities/context/DutchQuoteContext.ts @@ -137,7 +137,7 @@ export class DutchQuoteContext implements QuoteContext { } const quoteConfig = ChainConfigManager.getQuoteConfig(quote.chainId, this.request.routingType); - if (quoteConfig.skipRFQ) { + if (quoteConfig.forceOpenOrders || this.request.config.forceOpenOrders) { this.log.info('RFQ Orders are disabled in config'); return null; } @@ -202,8 +202,13 @@ export class DutchQuoteContext implements QuoteContext { const chainId = classicQuote.request.info.tokenInChainId; const quoteConfig = ChainConfigManager.getQuoteConfig(chainId, this.request.routingType); // if the useSyntheticQuotes override is not set by client or server and we're not skipping RFQ, return null - // if we are skipping RFQ, we need a synthetic quote - if (!this.request.config.useSyntheticQuotes && !syntheticStatus.syntheticEnabled && !quoteConfig.skipRFQ) { + // if we are forcing Open Orders, we need a synthetic quote + if ( + !this.request.config.useSyntheticQuotes && + !this.request.config.forceOpenOrders && + !syntheticStatus.syntheticEnabled && + !quoteConfig.forceOpenOrders + ) { this.log.info('Synthetic not enabled, skipping synthetic'); return null; } diff --git a/lib/entities/quote/DutchQuote.ts b/lib/entities/quote/DutchQuote.ts index ea80d8e2..3011bcd0 100644 --- a/lib/entities/quote/DutchQuote.ts +++ b/lib/entities/quote/DutchQuote.ts @@ -85,7 +85,7 @@ export abstract class DutchQuote implements IQuote public readonly createdAt: string; public derived: DutchQuoteDerived; public routingType: RoutingType.DUTCH_LIMIT | RoutingType.DUTCH_V2; - public abstract readonly defaultDeadlienBufferInSecs: number; + public abstract readonly defaultDeadlineBufferInSecs: number; // Add 1bps price improvmement to favor Dutch public static defaultPriceImprovementBps = 1; @@ -166,11 +166,8 @@ export abstract class DutchQuote implements IQuote // The number of seconds from endTime that the order should expire public get deadlineBufferSecs(): number { - if (this.request.config.deadlineBufferSecs !== undefined) { - return this.request.config.deadlineBufferSecs; - } const quoteConfig = ChainConfigManager.getQuoteConfig(this.chainId, this.request.routingType); - return quoteConfig.deadlineBufferSecs ?? this.defaultDeadlienBufferInSecs; + return this.request.config.deadlineBufferSecs ?? quoteConfig.deadlineBufferSecs ?? this.defaultDeadlineBufferInSecs; } public get portionAmountOutStart(): BigNumber { diff --git a/lib/entities/quote/DutchQuoteFactory.ts b/lib/entities/quote/DutchQuoteFactory.ts index d510049b..b5845eeb 100644 --- a/lib/entities/quote/DutchQuoteFactory.ts +++ b/lib/entities/quote/DutchQuoteFactory.ts @@ -63,7 +63,7 @@ export class DutchQuoteFactory { const priceImprovedStartAmounts = DutchQuote.applyPriceImprovement( { amountIn: quote.amountInGasAdjusted, amountOut: quote.amountOutGasAdjusted }, request.info.type, - quoteConfig.priceImprovementBps + request.config.priceImprovementBps ?? quoteConfig.priceImprovementBps ); const startAmounts = DutchQuote.applyPreSwapGasAdjustment(priceImprovedStartAmounts, quote); diff --git a/lib/entities/quote/DutchV1Quote.ts b/lib/entities/quote/DutchV1Quote.ts index 73ac7cc4..939bf9b0 100644 --- a/lib/entities/quote/DutchV1Quote.ts +++ b/lib/entities/quote/DutchV1Quote.ts @@ -16,7 +16,7 @@ import { generateRandomNonce } from '../../util/nonce'; export class DutchV1Quote extends DutchQuote implements IQuote { public routingType: RoutingType.DUTCH_LIMIT = RoutingType.DUTCH_LIMIT; - public readonly defaultDeadlienBufferInSecs: number = DEFAULT_DEADLINE_BUFFER_SECS; + public readonly defaultDeadlineBufferInSecs: number = DEFAULT_DEADLINE_BUFFER_SECS; public toJSON(): DutchQuoteDataJSON { return { diff --git a/lib/entities/quote/DutchV2Quote.ts b/lib/entities/quote/DutchV2Quote.ts index 886ad134..2f03edbd 100644 --- a/lib/entities/quote/DutchV2Quote.ts +++ b/lib/entities/quote/DutchV2Quote.ts @@ -32,7 +32,7 @@ export type DutchV2QuoteDataJSON = SharedOrderQuoteDataJSON & { export class DutchV2Quote extends DutchQuote implements IQuote { public readonly routingType: RoutingType.DUTCH_V2 = RoutingType.DUTCH_V2; - public readonly defaultDeadlienBufferInSecs: number = DEFAULT_V2_DEADLINE_BUFFER_SECS; + public readonly defaultDeadlineBufferInSecs: number = DEFAULT_V2_DEADLINE_BUFFER_SECS; public toJSON(): DutchV2QuoteDataJSON { return { diff --git a/lib/entities/request/DutchV1Request.ts b/lib/entities/request/DutchV1Request.ts index db2f3377..6fc37c48 100644 --- a/lib/entities/request/DutchV1Request.ts +++ b/lib/entities/request/DutchV1Request.ts @@ -15,8 +15,12 @@ export interface DutchConfig { startTimeBufferSecs?: number; auctionPeriodSecs?: number; deadlineBufferSecs?: number; + // Setting true will include an Open Order in the quote comparison useSyntheticQuotes: boolean; gasAdjustmentBps?: number; + // Setting true will force an Open Order and skip RFQ + forceOpenOrders?: boolean; + priceImprovementBps?: number; } export interface DutchQuoteRequestInfo extends QuoteRequestInfo { @@ -32,6 +36,8 @@ export interface DutchConfigJSON { deadlineBufferSecs?: number; useSyntheticQuotes?: boolean; gasAdjustmentBps?: number; + forceOpenOrders?: boolean; + priceImprovementBps?: number; } export class DutchV1Request implements QuoteRequest { @@ -52,6 +58,8 @@ export class DutchV1Request implements QuoteRequest { deadlineBufferSecs: body.deadlineBufferSecs, useSyntheticQuotes: body.useSyntheticQuotes ?? false, gasAdjustmentBps: body.gasAdjustmentBps, + forceOpenOrders: body.forceOpenOrders, + priceImprovementBps: body.priceImprovementBps, } ); } diff --git a/lib/entities/request/DutchV2Request.ts b/lib/entities/request/DutchV2Request.ts index d8af8b90..8dab10b9 100644 --- a/lib/entities/request/DutchV2Request.ts +++ b/lib/entities/request/DutchV2Request.ts @@ -6,8 +6,12 @@ import { DutchQuoteRequestInfo } from './DutchV1Request'; export interface DutchV2Config { swapper: string; deadlineBufferSecs?: number; + // Setting true will include an Open Order in the quote comparison useSyntheticQuotes: boolean; gasAdjustmentBps?: number; + // Setting true will force an Open Order and skip RFQ + forceOpenOrders?: boolean; + priceImprovementBps?: number; } export interface DutchV2ConfigJSON extends Omit { @@ -30,6 +34,8 @@ export class DutchV2Request implements QuoteRequest { deadlineBufferSecs: body.deadlineBufferSecs, useSyntheticQuotes: body.useSyntheticQuotes ?? false, gasAdjustmentBps: body.gasAdjustmentBps, + forceOpenOrders: body.forceOpenOrders, + priceImprovementBps: body.priceImprovementBps, } ); } diff --git a/lib/providers/quoters/RfqQuoter.ts b/lib/providers/quoters/RfqQuoter.ts index 7f97f139..a3408666 100644 --- a/lib/providers/quoters/RfqQuoter.ts +++ b/lib/providers/quoters/RfqQuoter.ts @@ -3,6 +3,7 @@ import { ID_TO_CHAIN_ID, WRAPPED_NATIVE_CURRENCY } from '@uniswap/smart-order-ro import { BigNumber } from 'ethers'; import axios from './helpers'; +import { ChainConfigManager } from '../../config/ChainConfigManager'; import { BPS, frontendAndUraEnablePortion, NATIVE_ADDRESS, RoutingType } from '../../constants'; import { DutchQuoteRequest, Quote } from '../../entities'; import { DutchQuoteFactory } from '../../entities/quote/DutchQuoteFactory'; @@ -19,6 +20,11 @@ export class RfqQuoter implements Quoter { constructor(private rfqUrl: string, private serviceUrl: string, private paramApiKey: string) {} async quote(request: DutchQuoteRequest): Promise { + // Skip RFQ for forced Open Orders + const quoteConfig = ChainConfigManager.getQuoteConfig(request.info.tokenInChainId, request.routingType); + if (quoteConfig.forceOpenOrders || request.config.forceOpenOrders) { + return null; + } const swapper = request.config.swapper; const now = Date.now(); const portionEnabled = frontendAndUraEnablePortion(request.info.sendPortionEnabled); diff --git a/lib/util/validator.ts b/lib/util/validator.ts index 2256aa58..0366f379 100644 --- a/lib/util/validator.ts +++ b/lib/util/validator.ts @@ -111,6 +111,8 @@ export class FieldValidator { deadlineBufferSecs: FieldValidator.positiveNumber.optional(), useSyntheticQuotes: Joi.boolean().optional(), gasAdjustmentBps: FieldValidator.bps.optional(), + forceOpenOrders: Joi.boolean().optional(), + priceImprovementBps: FieldValidator.bps.optional(), }); // extends a classic request config, but requires a gasToken and has optional parameters for the fee auction @@ -131,5 +133,7 @@ export class FieldValidator { deadlineBufferSecs: FieldValidator.positiveNumber.optional(), useSyntheticQuotes: Joi.boolean().optional(), gasAdjustmentBps: FieldValidator.bps.optional(), + forceOpenOrders: Joi.boolean().optional(), + priceImprovementBps: FieldValidator.bps.optional(), }); } diff --git a/test/integ/base.test.ts b/test/integ/base.test.ts index 2abd7532..5609782c 100644 --- a/test/integ/base.test.ts +++ b/test/integ/base.test.ts @@ -1,14 +1,7 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { ZERO } from '@uniswap/router-sdk'; import { Currency, CurrencyAmount, Fraction, Percent } from '@uniswap/sdk-core'; -import { - DAI_MAINNET, - parseAmount, - USDC_MAINNET, - USDT_MAINNET, - WBTC_MAINNET, - WETH9, -} from '@uniswap/smart-order-router'; +import { DAI_MAINNET, parseAmount, USDC_MAINNET, USDT_MAINNET, WBTC_MAINNET, WETH9 } from '@uniswap/smart-order-router'; import { fail } from 'assert'; import axiosStatic, { AxiosRequestConfig, AxiosResponse } from 'axios'; import axiosRetry from 'axios-retry'; diff --git a/test/integ/quote-relay.test.ts b/test/integ/quote-relay.test.ts index c32b5856..7bdf9024 100644 --- a/test/integ/quote-relay.test.ts +++ b/test/integ/quote-relay.test.ts @@ -114,7 +114,7 @@ describe.skip('relayQuote', function () { expect(order.info.universalRouterCalldata).to.not.be.undefined; }); - it('missing gasToken in request config' , async () => { + it('missing gasToken in request config', async () => { const quoteReq: QuoteRequestBodyJSON = { requestId: 'id', tokenIn: USDC_MAINNET.address, @@ -140,7 +140,7 @@ describe.skip('relayQuote', function () { errorCode: 'VALIDATION_ERROR', }, }); - }) + }); }); }); } diff --git a/test/unit/lib/config/ChainConfigManager.test.ts b/test/unit/lib/config/ChainConfigManager.test.ts index d69ae5f8..cd3d3597 100644 --- a/test/unit/lib/config/ChainConfigManager.test.ts +++ b/test/unit/lib/config/ChainConfigManager.test.ts @@ -322,7 +322,7 @@ describe('ChainConfigManager', () => { expect(req).toBeDefined(); }); - it('skipRFQ prevents RFQ from running', async () => { + it('forceOpenOrders prevents RFQ from running', async () => { const routingTypes = [RoutingType.DUTCH_LIMIT, RoutingType.DUTCH_V2]; for (const routingType of routingTypes) { // First show that RFQ is used by default @@ -392,7 +392,7 @@ describe('ChainConfigManager', () => { routingTypes: { [RoutingType.CLASSIC]: {}, [routingType]: { - skipRFQ: true, + forceOpenOrders: true, }, }, alarmEnabled: false, @@ -414,7 +414,7 @@ describe('ChainConfigManager', () => { } }); - it('skipRFQ forces synthetic quote', async () => { + it('forceOpenOrders forces synthetic quote', async () => { const routingTypes = [RoutingType.DUTCH_LIMIT, RoutingType.DUTCH_V2]; for (const routingType of routingTypes) { setChainConfigManager( @@ -423,7 +423,7 @@ describe('ChainConfigManager', () => { routingTypes: { [RoutingType.CLASSIC]: {}, [routingType]: { - skipRFQ: true, + forceOpenOrders: true, }, }, alarmEnabled: false, @@ -434,7 +434,7 @@ describe('ChainConfigManager', () => { [RoutingType.DUTCH_V2]: [RoutingType.CLASSIC], } ); - // Setting useSyntheticQuotes: false should be overridden by skipRFQ + // Setting useSyntheticQuotes: false should be overridden by forceOpenOrders let req, context, rfqQuote; switch (routingType) { case RoutingType.DUTCH_LIMIT: { @@ -490,7 +490,7 @@ describe('ChainConfigManager', () => { [RoutingType.CLASSIC]: {}, [routingType]: { priceImprovementBps: 1, - skipRFQ: true, + forceOpenOrders: true, }, }, alarmEnabled: false, @@ -554,7 +554,7 @@ describe('ChainConfigManager', () => { [RoutingType.CLASSIC]: {}, [routingType]: { priceImprovementBps: 10, - skipRFQ: true, + forceOpenOrders: true, }, }, alarmEnabled: false, @@ -585,7 +585,7 @@ describe('ChainConfigManager', () => { routingTypes: { [RoutingType.CLASSIC]: {}, [RoutingType.DUTCH_LIMIT]: { - skipRFQ: true, + forceOpenOrders: true, }, }, alarmEnabled: false, @@ -624,6 +624,83 @@ describe('ChainConfigManager', () => { expect(quote.amountOutStart).toEqual(expectedOut); }); + it('Client specified BPS override is given priority when set', async () => { + const clientBps = 100; + const serverBps = 10; + const routingTypes = [RoutingType.DUTCH_LIMIT, RoutingType.DUTCH_V2]; + for (const routingType of routingTypes) { + setChainConfigManager( + { + [ChainId.MAINNET]: { + routingTypes: { + [RoutingType.CLASSIC]: {}, + [routingType]: { + priceImprovementBps: serverBps, + forceOpenOrders: true, + }, + }, + alarmEnabled: false, + }, + }, + { + [RoutingType.DUTCH_LIMIT]: [RoutingType.CLASSIC], + [RoutingType.DUTCH_V2]: [RoutingType.CLASSIC], + } + ); + + let req, context, rfqQuote; + switch (routingType) { + case RoutingType.DUTCH_LIMIT: { + req = makeDutchRequest( + { tokenInChainId: ChainId.MAINNET, tokenOutChainId: ChainId.MAINNET }, + { useSyntheticQuotes: true, deadlineBufferSecs: undefined, priceImprovementBps: clientBps } + ); + context = new DutchQuoteContext(logger, req, makeProviders(false)); + rfqQuote = createDutchQuoteWithRequest( + { amountOut: AMOUNT_LARGE, tokenIn: NATIVE_ADDRESS, tokenOut: NATIVE_ADDRESS, chainId: ChainId.MAINNET }, + req + ); + break; + } + case RoutingType.DUTCH_V2: { + req = makeDutchV2Request( + { tokenInChainId: ChainId.MAINNET, tokenOutChainId: ChainId.MAINNET }, + { useSyntheticQuotes: true, deadlineBufferSecs: undefined, priceImprovementBps: clientBps } + ); + context = new DutchQuoteContext(logger, req, makeProviders(false)); + rfqQuote = createDutchV2QuoteWithRequest( + { amountOut: AMOUNT_LARGE, tokenIn: NATIVE_ADDRESS, tokenOut: NATIVE_ADDRESS, chainId: ChainId.MAINNET }, + req + ); + break; + } + default: + throw new Error('Unknown routing type'); + } + + const classicQuote = createClassicQuote( + { quote: AMOUNT_LARGE, quoteGasAdjusted: AMOUNT_LARGE_GAS_ADJUSTED }, + { type: 'EXACT_INPUT', tokenInChainId: ChainId.MAINNET, tokenOutChainId: ChainId.MAINNET } + ); + + const quote = await context.resolve({ + [req.key()]: rfqQuote, + [context.classicKey]: classicQuote, + }); + if (quote?.routingType != routingType) { + throw new Error(`Unexpected routing type in quote ${quote?.routingType}`); + } + expect(quote.quoteType).toEqual(QuoteType.SYNTHETIC); + // Client BPS is used + expect(quote?.amountOut.toString()).toEqual( + BigNumber.from(AMOUNT_LARGE_GAS_ADJUSTED) + .mul(BPS + clientBps) + .div(BPS) + .toString() + ); + } + }); + it('stdAuctionPeriodSecs is used when set', async () => { setChainConfigManager( { @@ -743,7 +820,7 @@ describe('ChainConfigManager', () => { [RoutingType.CLASSIC]: {}, [routingType]: { deadlineBufferSecs: 9999, - skipRFQ: true, + forceOpenOrders: true, }, }, alarmEnabled: false, @@ -811,5 +888,86 @@ describe('ChainConfigManager', () => { expect(quote.deadlineBufferSecs).toEqual(9999); } }); + + it('Client specified deadlineBufferSecs is given priority when set', async () => { + const clientDeadline = 999; + const serverDeadline = 9999; + const routingTypes = [RoutingType.DUTCH_LIMIT, RoutingType.DUTCH_V2, RoutingType.RELAY]; + for (const routingType of routingTypes) { + setChainConfigManager( + { + [ChainId.MAINNET]: { + routingTypes: { + [RoutingType.CLASSIC]: {}, + [routingType]: { + deadlineBufferSecs: serverDeadline, + forceOpenOrders: true, + }, + }, + alarmEnabled: false, + }, + }, + { + [RoutingType.DUTCH_LIMIT]: [RoutingType.CLASSIC], + [RoutingType.DUTCH_V2]: [RoutingType.CLASSIC], + } + ); + + let req, context, rfqQuote; + switch (routingType) { + case RoutingType.DUTCH_LIMIT: { + req = makeDutchRequest( + { tokenInChainId: ChainId.MAINNET, tokenOutChainId: ChainId.MAINNET }, + { useSyntheticQuotes: true, deadlineBufferSecs: clientDeadline } + ); + context = new DutchQuoteContext(logger, req, makeProviders(false)); + rfqQuote = createDutchQuoteWithRequest( + { amountOut: AMOUNT_LARGE, tokenIn: NATIVE_ADDRESS, tokenOut: NATIVE_ADDRESS, chainId: ChainId.MAINNET }, + req + ); + break; + } + case RoutingType.DUTCH_V2: { + req = makeDutchV2Request( + { tokenInChainId: ChainId.MAINNET, tokenOutChainId: ChainId.MAINNET }, + { useSyntheticQuotes: true, deadlineBufferSecs: clientDeadline } + ); + context = new DutchQuoteContext(logger, req, makeProviders(false)); + rfqQuote = createDutchV2QuoteWithRequest( + { amountOut: AMOUNT_LARGE, tokenIn: NATIVE_ADDRESS, tokenOut: NATIVE_ADDRESS, chainId: ChainId.MAINNET }, + req + ); + break; + } + case RoutingType.RELAY: { + req = makeRelayRequest( + { tokenInChainId: ChainId.MAINNET, tokenOutChainId: ChainId.MAINNET }, + { deadlineBufferSecs: clientDeadline } + ); + context = new RelayQuoteContext(logger, req, makeProviders(false)); + rfqQuote = createRelayQuoteWithRequest( + { amountOut: AMOUNT_LARGE, tokenIn: NATIVE_ADDRESS, tokenOut: NATIVE_ADDRESS, chainId: ChainId.MAINNET }, + req + ); + break; + } + default: + throw new Error('Unknown routing type'); + } + + const classicQuote = createClassicQuote( + { quote: AMOUNT_LARGE, quoteGasAdjusted: AMOUNT_LARGE_GAS_ADJUSTED }, + { type: 'EXACT_INPUT', tokenInChainId: ChainId.MAINNET, tokenOutChainId: ChainId.MAINNET } + ); + const quote = await context.resolve({ + [req.key()]: rfqQuote, + [context.classicKey]: classicQuote, + }); + if (quote?.routingType != routingType) { + throw new Error(`Unexpected routing type in quote ${quote?.routingType}`); + } + expect(quote.deadlineBufferSecs).toEqual(clientDeadline); + } + }); }); }); diff --git a/test/unit/lib/entities/context/DutchQuoteContext.test.ts b/test/unit/lib/entities/context/DutchQuoteContext.test.ts index bd871f44..7bcd004b 100644 --- a/test/unit/lib/entities/context/DutchQuoteContext.test.ts +++ b/test/unit/lib/entities/context/DutchQuoteContext.test.ts @@ -335,6 +335,82 @@ describe('DutchQuoteContext', () => { expect((quote?.toJSON() as DutchQuoteDataJSON).orderInfo.exclusiveFiller).toEqual(filler); }); + it('uses synthetic if worse with forceOpenOrders=true and switch=false', async () => { + // Show that the test first will prefer RFQ + let request = makeDutchRequest({}, { forceOpenOrders: false }); + let context = new DutchQuoteContext(logger, request, makeProviders(false)); + const filler = '0x1111111111111111111111111111111111111111'; + const rfqQuote = createDutchQuote({ amountOut: '10000000000', filler }, 'EXACT_INPUT'); + expect(rfqQuote.filler).toEqual(filler); + const classicQuote = createClassicQuote( + { quote: '5000000000', quoteGasAdjusted: '4999500000' }, + { type: 'EXACT_INPUT' } + ); + context.dependencies(); + + let quote = await context.resolve({ + [context.requestKey]: rfqQuote, + [context.classicKey]: classicQuote, + [context.routeToNativeKey]: classicQuote, + }); + expect(quote?.routingType).toEqual(RoutingType.DUTCH_LIMIT); + expect((quote?.toJSON() as DutchQuoteDataJSON).orderInfo.exclusiveFiller).toEqual(filler); + expect(quote?.amountOut.toString()).toEqual('10000000000'); + + // Setting forceOpenOrders will skip the RFQ + request = makeDutchRequest({}, { forceOpenOrders: true }); + context = new DutchQuoteContext(logger, request, makeProviders(false)); + context.dependencies(); + + quote = await context.resolve({ + [context.requestKey]: rfqQuote, + [context.classicKey]: classicQuote, + [context.routeToNativeKey]: classicQuote, + }); + expect(quote?.routingType).toEqual(RoutingType.DUTCH_LIMIT); + expect((quote?.toJSON() as DutchQuoteDataJSON).orderInfo.exclusiveFiller).toEqual( + '0x0000000000000000000000000000000000000000' + ); + // Synthetic starts at quoteGasAdjusted + 1bp + expect(quote?.amountOut.toString()).toEqual( + BigNumber.from(4999500000) + .mul(BPS + DutchQuote.defaultPriceImprovementBps) + .div(BPS) + .toString() + ); + }); + + it('uses priceImprovementBps when present in request', async () => { + const bpsImprovement = 10; + const request = makeDutchRequest({}, { forceOpenOrders: true, priceImprovementBps: bpsImprovement }); + const context = new DutchQuoteContext(logger, request, makeProviders(false)); + const filler = '0x1111111111111111111111111111111111111111'; + const rfqQuote = createDutchQuote({ amountOut: '10000000000', filler }, 'EXACT_INPUT'); + expect(rfqQuote.filler).toEqual(filler); + const classicQuote = createClassicQuote( + { quote: '5000000000', quoteGasAdjusted: '4999500000' }, + { type: 'EXACT_INPUT' } + ); + context.dependencies(); + + const quote = await context.resolve({ + [context.requestKey]: rfqQuote, + [context.classicKey]: classicQuote, + [context.routeToNativeKey]: classicQuote, + }); + + expect(quote?.routingType).toEqual(RoutingType.DUTCH_LIMIT); + expect((quote?.toJSON() as DutchQuoteDataJSON).orderInfo.exclusiveFiller).toEqual( + '0x0000000000000000000000000000000000000000' + ); + expect(quote?.amountOut.toString()).toEqual( + BigNumber.from(4999500000) + .mul(BPS + bpsImprovement) + .div(BPS) + .toString() + ); + }); + it('uses synthetic if rfq quote is at least 300% better than classic; EXACT_IN', async () => { const context = new DutchQuoteContext(logger, QUOTE_REQUEST_DL, makeProviders(false)); const filler = '0x1111111111111111111111111111111111111111';