Merge pull request #2642 from 0xProject/only-rfqt
Add the `nativeExclusivelyRFQT` argument.
This commit is contained in:
commit
9a16f5736e
@ -47,7 +47,6 @@ const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = {
|
|||||||
rfqt: {
|
rfqt: {
|
||||||
takerApiKeyWhitelist: [],
|
takerApiKeyWhitelist: [],
|
||||||
makerAssetOfferings: {},
|
makerAssetOfferings: {},
|
||||||
skipBuyRequests: false,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -48,7 +48,6 @@ export class SwapQuoter {
|
|||||||
private readonly _orderStateUtils: OrderStateUtils;
|
private readonly _orderStateUtils: OrderStateUtils;
|
||||||
private readonly _quoteRequestor: QuoteRequestor;
|
private readonly _quoteRequestor: QuoteRequestor;
|
||||||
private readonly _rfqtTakerApiKeyWhitelist: string[];
|
private readonly _rfqtTakerApiKeyWhitelist: string[];
|
||||||
private readonly _rfqtSkipBuyRequests: boolean;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new SwapQuoter instance given existing liquidity in the form of orders and feeOrders.
|
* Instantiates a new SwapQuoter instance given existing liquidity in the form of orders and feeOrders.
|
||||||
@ -170,10 +169,6 @@ export class SwapQuoter {
|
|||||||
this.expiryBufferMs = expiryBufferMs;
|
this.expiryBufferMs = expiryBufferMs;
|
||||||
this.permittedOrderFeeTypes = permittedOrderFeeTypes;
|
this.permittedOrderFeeTypes = permittedOrderFeeTypes;
|
||||||
this._rfqtTakerApiKeyWhitelist = rfqt ? rfqt.takerApiKeyWhitelist || [] : [];
|
this._rfqtTakerApiKeyWhitelist = rfqt ? rfqt.takerApiKeyWhitelist || [] : [];
|
||||||
this._rfqtSkipBuyRequests =
|
|
||||||
rfqt && rfqt.skipBuyRequests !== undefined
|
|
||||||
? rfqt.skipBuyRequests
|
|
||||||
: (r => r !== undefined && r.skipBuyRequests === true)(constants.DEFAULT_SWAP_QUOTER_OPTS.rfqt);
|
|
||||||
this._contractAddresses = options.contractAddresses || getContractAddressesForChainOrThrow(chainId);
|
this._contractAddresses = options.contractAddresses || getContractAddressesForChainOrThrow(chainId);
|
||||||
this._devUtilsContract = new DevUtilsContract(this._contractAddresses.devUtils, provider);
|
this._devUtilsContract = new DevUtilsContract(this._contractAddresses.devUtils, provider);
|
||||||
this._protocolFeeUtils = ProtocolFeeUtils.getInstance(
|
this._protocolFeeUtils = ProtocolFeeUtils.getInstance(
|
||||||
@ -561,20 +556,31 @@ export class SwapQuoter {
|
|||||||
} else {
|
} else {
|
||||||
gasPrice = await this.getGasPriceEstimationOrThrowAsync();
|
gasPrice = await this.getGasPriceEstimationOrThrowAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If RFQT is enabled and `nativeExclusivelyRFQT` is set, then `ERC20BridgeSource.Native` should
|
||||||
|
// never be excluded.
|
||||||
|
if (
|
||||||
|
opts.rfqt &&
|
||||||
|
opts.rfqt.nativeExclusivelyRFQT === true &&
|
||||||
|
opts.excludedSources.includes(ERC20BridgeSource.Native)
|
||||||
|
) {
|
||||||
|
throw new Error('Native liquidity cannot be excluded if "rfqt.nativeExclusivelyRFQT" is set');
|
||||||
|
}
|
||||||
|
|
||||||
// get batches of orders from different sources, awaiting sources in parallel
|
// get batches of orders from different sources, awaiting sources in parallel
|
||||||
const orderBatchPromises: Array<Promise<SignedOrder[]>> = [];
|
const orderBatchPromises: Array<Promise<SignedOrder[]>> = [];
|
||||||
orderBatchPromises.push(
|
orderBatchPromises.push(
|
||||||
// Don't fetch from the DB if Native has been excluded
|
// Don't fetch Open Orderbook orders from the DB if Native has been excluded, or if `nativeExclusivelyRFQT` has been set.
|
||||||
opts.excludedSources.includes(ERC20BridgeSource.Native)
|
opts.excludedSources.includes(ERC20BridgeSource.Native) ||
|
||||||
|
(opts.rfqt && opts.rfqt.nativeExclusivelyRFQT === true)
|
||||||
? Promise.resolve([])
|
? Promise.resolve([])
|
||||||
: this._getSignedOrdersAsync(makerAssetData, takerAssetData),
|
: this._getSignedOrdersAsync(makerAssetData, takerAssetData),
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
opts.rfqt &&
|
opts.rfqt && // This is an RFQT-enabled API request
|
||||||
opts.rfqt.intentOnFilling &&
|
opts.rfqt.intentOnFilling && // The requestor is asking for a firm quote
|
||||||
opts.rfqt.apiKey &&
|
!opts.excludedSources.includes(ERC20BridgeSource.Native) && // Native liquidity is not excluded
|
||||||
this._rfqtTakerApiKeyWhitelist.includes(opts.rfqt.apiKey) &&
|
this._rfqtTakerApiKeyWhitelist.includes(opts.rfqt.apiKey) // A valid API key was provided
|
||||||
!(marketOperation === MarketOperation.Buy && this._rfqtSkipBuyRequests)
|
|
||||||
) {
|
) {
|
||||||
if (!opts.rfqt.takerAddress || opts.rfqt.takerAddress === constants.NULL_ADDRESS) {
|
if (!opts.rfqt.takerAddress || opts.rfqt.takerAddress === constants.NULL_ADDRESS) {
|
||||||
throw new Error('RFQ-T requests must specify a taker address');
|
throw new Error('RFQ-T requests must specify a taker address');
|
||||||
@ -636,8 +642,7 @@ export class SwapQuoter {
|
|||||||
opts !== undefined &&
|
opts !== undefined &&
|
||||||
opts.isIndicative !== undefined &&
|
opts.isIndicative !== undefined &&
|
||||||
opts.isIndicative &&
|
opts.isIndicative &&
|
||||||
this._rfqtTakerApiKeyWhitelist.includes(opts.apiKey) &&
|
this._rfqtTakerApiKeyWhitelist.includes(opts.apiKey)
|
||||||
!(op === MarketOperation.Buy && this._rfqtSkipBuyRequests)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -199,12 +199,18 @@ export interface SwapQuoteOrdersBreakdown {
|
|||||||
[source: string]: BigNumber;
|
[source: string]: BigNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* nativeExclusivelyRFQT: if set to `true`, Swap quote will exclude Open Orderbook liquidity.
|
||||||
|
* If set to `true` and `ERC20BridgeSource.Native` is part of the `excludedSources`
|
||||||
|
* array in `SwapQuoteRequestOpts`, an Error will be raised.
|
||||||
|
*/
|
||||||
export interface RfqtRequestOpts {
|
export interface RfqtRequestOpts {
|
||||||
takerAddress: string;
|
takerAddress: string;
|
||||||
apiKey: string;
|
apiKey: string;
|
||||||
intentOnFilling: boolean;
|
intentOnFilling: boolean;
|
||||||
isIndicative?: boolean;
|
isIndicative?: boolean;
|
||||||
makerEndpointMaxResponseTimeMs?: number;
|
makerEndpointMaxResponseTimeMs?: number;
|
||||||
|
nativeExclusivelyRFQT?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -249,7 +255,6 @@ export interface SwapQuoterOpts extends OrderPrunerOpts {
|
|||||||
rfqt?: {
|
rfqt?: {
|
||||||
takerApiKeyWhitelist: string[];
|
takerApiKeyWhitelist: string[];
|
||||||
makerAssetOfferings: RfqtMakerAssetOfferings;
|
makerAssetOfferings: RfqtMakerAssetOfferings;
|
||||||
skipBuyRequests?: boolean;
|
|
||||||
warningLogger?: LogFunction;
|
warningLogger?: LogFunction;
|
||||||
infoLogger?: LogFunction;
|
infoLogger?: LogFunction;
|
||||||
};
|
};
|
||||||
|
@ -26,14 +26,23 @@ import {
|
|||||||
OrderDomain,
|
OrderDomain,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
async function getRfqtIndicativeQuotesAsync(
|
/**
|
||||||
|
* Returns a indicative quotes or an empty array if RFQT is not enabled or requested
|
||||||
|
* @param makerAssetData the maker asset data
|
||||||
|
* @param takerAssetData the taker asset data
|
||||||
|
* @param marketOperation Buy or Sell
|
||||||
|
* @param assetFillAmount the amount to fill, in base units
|
||||||
|
* @param opts market request options
|
||||||
|
*/
|
||||||
|
export async function getRfqtIndicativeQuotesAsync(
|
||||||
makerAssetData: string,
|
makerAssetData: string,
|
||||||
takerAssetData: string,
|
takerAssetData: string,
|
||||||
marketOperation: MarketOperation,
|
marketOperation: MarketOperation,
|
||||||
assetFillAmount: BigNumber,
|
assetFillAmount: BigNumber,
|
||||||
opts: Partial<GetMarketOrdersOpts>,
|
opts: Partial<GetMarketOrdersOpts>,
|
||||||
): Promise<RFQTIndicativeQuote[]> {
|
): Promise<RFQTIndicativeQuote[]> {
|
||||||
if (opts.rfqt && opts.rfqt.isIndicative === true && opts.rfqt.quoteRequestor) {
|
const hasExcludedNativeLiquidity = opts.excludedSources && opts.excludedSources.includes(ERC20BridgeSource.Native);
|
||||||
|
if (!hasExcludedNativeLiquidity && opts.rfqt && opts.rfqt.isIndicative === true && opts.rfqt.quoteRequestor) {
|
||||||
return opts.rfqt.quoteRequestor.requestRfqtIndicativeQuotesAsync(
|
return opts.rfqt.quoteRequestor.requestRfqtIndicativeQuotesAsync(
|
||||||
makerAssetData,
|
makerAssetData,
|
||||||
takerAssetData,
|
takerAssetData,
|
||||||
|
@ -13,14 +13,20 @@ import { assetDataUtils, generatePseudoRandomSalt } from '@0x/order-utils';
|
|||||||
import { AssetProxyId, ERC20BridgeAssetData, SignedOrder } from '@0x/types';
|
import { AssetProxyId, ERC20BridgeAssetData, SignedOrder } from '@0x/types';
|
||||||
import { BigNumber, fromTokenUnitAmount, hexUtils, NULL_ADDRESS } from '@0x/utils';
|
import { BigNumber, fromTokenUnitAmount, hexUtils, NULL_ADDRESS } from '@0x/utils';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
import * as TypeMoq from 'typemoq';
|
||||||
|
|
||||||
import { MarketOperation, SignedOrderWithFillableAmounts } from '../src';
|
import { MarketOperation, QuoteRequestor, RfqtRequestOpts, SignedOrderWithFillableAmounts } from '../src';
|
||||||
import { MarketOperationUtils } from '../src/utils/market_operation_utils/';
|
import { getRfqtIndicativeQuotesAsync, MarketOperationUtils } from '../src/utils/market_operation_utils/';
|
||||||
import { BUY_SOURCES, POSITIVE_INF, SELL_SOURCES, ZERO_AMOUNT } from '../src/utils/market_operation_utils/constants';
|
import { BUY_SOURCES, POSITIVE_INF, SELL_SOURCES, ZERO_AMOUNT } from '../src/utils/market_operation_utils/constants';
|
||||||
import { createFillPaths } from '../src/utils/market_operation_utils/fills';
|
import { createFillPaths } from '../src/utils/market_operation_utils/fills';
|
||||||
import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler';
|
import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler';
|
||||||
import { DexSample, ERC20BridgeSource, FillData, NativeFillData } from '../src/utils/market_operation_utils/types';
|
import { DexSample, ERC20BridgeSource, FillData, NativeFillData } from '../src/utils/market_operation_utils/types';
|
||||||
|
|
||||||
|
const MAKER_TOKEN = randomAddress();
|
||||||
|
const TAKER_TOKEN = randomAddress();
|
||||||
|
const MAKER_ASSET_DATA = assetDataUtils.encodeERC20AssetData(MAKER_TOKEN);
|
||||||
|
const TAKER_ASSET_DATA = assetDataUtils.encodeERC20AssetData(TAKER_TOKEN);
|
||||||
|
|
||||||
// tslint:disable: custom-no-magic-numbers promise-function-async
|
// tslint:disable: custom-no-magic-numbers promise-function-async
|
||||||
describe('MarketOperationUtils tests', () => {
|
describe('MarketOperationUtils tests', () => {
|
||||||
const CHAIN_ID = 1;
|
const CHAIN_ID = 1;
|
||||||
@ -30,11 +36,6 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
const UNISWAP_BRIDGE_ADDRESS = contractAddresses.uniswapBridge;
|
const UNISWAP_BRIDGE_ADDRESS = contractAddresses.uniswapBridge;
|
||||||
const UNISWAP_V2_BRIDGE_ADDRESS = contractAddresses.uniswapV2Bridge;
|
const UNISWAP_V2_BRIDGE_ADDRESS = contractAddresses.uniswapV2Bridge;
|
||||||
const CURVE_BRIDGE_ADDRESS = contractAddresses.curveBridge;
|
const CURVE_BRIDGE_ADDRESS = contractAddresses.curveBridge;
|
||||||
|
|
||||||
const MAKER_TOKEN = randomAddress();
|
|
||||||
const TAKER_TOKEN = randomAddress();
|
|
||||||
const MAKER_ASSET_DATA = assetDataUtils.encodeERC20AssetData(MAKER_TOKEN);
|
|
||||||
const TAKER_ASSET_DATA = assetDataUtils.encodeERC20AssetData(TAKER_TOKEN);
|
|
||||||
let originalSamplerOperations: any;
|
let originalSamplerOperations: any;
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
@ -344,6 +345,68 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
},
|
},
|
||||||
} as any) as DexOrderSampler;
|
} as any) as DexOrderSampler;
|
||||||
|
|
||||||
|
describe('getRfqtIndicativeQuotesAsync', () => {
|
||||||
|
const partialRfqt: RfqtRequestOpts = {
|
||||||
|
apiKey: 'foo',
|
||||||
|
takerAddress: NULL_ADDRESS,
|
||||||
|
isIndicative: true,
|
||||||
|
intentOnFilling: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
it('returns an empty array if native liquidity is excluded from the salad', async () => {
|
||||||
|
const requestor = TypeMoq.Mock.ofType(QuoteRequestor, TypeMoq.MockBehavior.Strict);
|
||||||
|
const result = await getRfqtIndicativeQuotesAsync(
|
||||||
|
MAKER_ASSET_DATA,
|
||||||
|
TAKER_ASSET_DATA,
|
||||||
|
MarketOperation.Sell,
|
||||||
|
new BigNumber('100e18'),
|
||||||
|
{
|
||||||
|
rfqt: { quoteRequestor: requestor.object, ...partialRfqt },
|
||||||
|
excludedSources: [ERC20BridgeSource.Native],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(result.length).to.eql(0);
|
||||||
|
requestor.verify(
|
||||||
|
r =>
|
||||||
|
r.requestRfqtIndicativeQuotesAsync(
|
||||||
|
TypeMoq.It.isAny(),
|
||||||
|
TypeMoq.It.isAny(),
|
||||||
|
TypeMoq.It.isAny(),
|
||||||
|
TypeMoq.It.isAny(),
|
||||||
|
TypeMoq.It.isAny(),
|
||||||
|
),
|
||||||
|
TypeMoq.Times.never(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls RFQT if Native source is not excluded', async () => {
|
||||||
|
const requestor = TypeMoq.Mock.ofType(QuoteRequestor, TypeMoq.MockBehavior.Loose);
|
||||||
|
requestor
|
||||||
|
.setup(r =>
|
||||||
|
r.requestRfqtIndicativeQuotesAsync(
|
||||||
|
TypeMoq.It.isAny(),
|
||||||
|
TypeMoq.It.isAny(),
|
||||||
|
TypeMoq.It.isAny(),
|
||||||
|
TypeMoq.It.isAny(),
|
||||||
|
TypeMoq.It.isAny(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.returns(() => Promise.resolve([]))
|
||||||
|
.verifiable(TypeMoq.Times.once());
|
||||||
|
await getRfqtIndicativeQuotesAsync(
|
||||||
|
MAKER_ASSET_DATA,
|
||||||
|
TAKER_ASSET_DATA,
|
||||||
|
MarketOperation.Sell,
|
||||||
|
new BigNumber('100e18'),
|
||||||
|
{
|
||||||
|
rfqt: { quoteRequestor: requestor.object, ...partialRfqt },
|
||||||
|
excludedSources: [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
requestor.verifyAll();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('MarketOperationUtils', () => {
|
describe('MarketOperationUtils', () => {
|
||||||
let marketOperationUtils: MarketOperationUtils;
|
let marketOperationUtils: MarketOperationUtils;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user