Files
protocol/packages/asset-swapper/src/quote_consumers/quote_consumer_utils.ts
mzhu25 1849b1bb9a Update ExchangeProxySwapQuoteConsumer for MultiplexV2 (#282)
* Update ExchangeProxySwapQuoteConsumer for MultiplexV2

* Move TransformERC20FeatureContract instance into private readonly
2021-09-29 16:02:13 -07:00

171 lines
5.5 KiB
TypeScript

import { FillQuoteTransformerData, FillQuoteTransformerOrderType } from '@0x/protocol-utils';
import { ExchangeProxyContractOpts, MarketBuySwapQuote, MarketOperation, SwapQuote } from '../types';
import {
createBridgeDataForBridgeOrder,
getErc20BridgeSourceToBridgeSource,
} from '../utils/market_operation_utils/orders';
import {
ERC20BridgeSource,
NativeLimitOrderFillData,
NativeRfqOrderFillData,
OptimizedMarketBridgeOrder,
OptimizedMarketOrder,
OptimizedMarketOrderBase,
} from '../utils/market_operation_utils/types';
const MULTIPLEX_BATCH_FILL_SOURCES = [
ERC20BridgeSource.UniswapV2,
ERC20BridgeSource.SushiSwap,
ERC20BridgeSource.LiquidityProvider,
ERC20BridgeSource.Native,
ERC20BridgeSource.UniswapV3,
];
/**
* Returns true iff a quote can be filled via `MultiplexFeature.batchFill`.
*/
export function isMultiplexBatchFillCompatible(quote: SwapQuote, opts: ExchangeProxyContractOpts): boolean {
if (requiresTransformERC20(opts)) {
return false;
}
if (quote.isTwoHop) {
return false;
}
if (quote.orders.map(o => o.type).includes(FillQuoteTransformerOrderType.Limit)) {
return false;
}
// Use Multiplex if the non-fallback sources are a subset of
// {UniswapV2, Sushiswap, RFQ, PLP, UniswapV3}
const nonFallbackSources = Object.keys(quote.sourceBreakdown);
return nonFallbackSources.every(source => MULTIPLEX_BATCH_FILL_SOURCES.includes(source as ERC20BridgeSource));
}
const MULTIPLEX_MULTIHOP_FILL_SOURCES = [
ERC20BridgeSource.UniswapV2,
ERC20BridgeSource.SushiSwap,
ERC20BridgeSource.LiquidityProvider,
ERC20BridgeSource.UniswapV3,
];
/**
* Returns true iff a quote can be filled via `MultiplexFeature.multiHopFill`.
*/
export function isMultiplexMultiHopFillCompatible(quote: SwapQuote, opts: ExchangeProxyContractOpts): boolean {
if (requiresTransformERC20(opts)) {
return false;
}
if (!quote.isTwoHop) {
return false;
}
const [firstHopOrder, secondHopOrder] = quote.orders;
return (
MULTIPLEX_MULTIHOP_FILL_SOURCES.includes(firstHopOrder.source) &&
MULTIPLEX_MULTIHOP_FILL_SOURCES.includes(secondHopOrder.source)
);
}
/**
* Returns true iff a quote can be filled via a VIP feature.
*/
export function isDirectSwapCompatible(
quote: SwapQuote,
opts: ExchangeProxyContractOpts,
directSources: ERC20BridgeSource[],
): boolean {
if (requiresTransformERC20(opts)) {
return false;
}
// Must be a single order.
if (quote.orders.length !== 1) {
return false;
}
const order = quote.orders[0];
if (!directSources.includes(order.source)) {
return false;
}
return true;
}
/**
* Whether a quote is a market buy or not.
*/
export function isBuyQuote(quote: SwapQuote): quote is MarketBuySwapQuote {
return quote.type === MarketOperation.Buy;
}
function isOptimizedBridgeOrder(x: OptimizedMarketOrder): x is OptimizedMarketBridgeOrder {
return x.type === FillQuoteTransformerOrderType.Bridge;
}
function isOptimizedLimitOrder(x: OptimizedMarketOrder): x is OptimizedMarketOrderBase<NativeLimitOrderFillData> {
return x.type === FillQuoteTransformerOrderType.Limit;
}
function isOptimizedRfqOrder(x: OptimizedMarketOrder): x is OptimizedMarketOrderBase<NativeRfqOrderFillData> {
return x.type === FillQuoteTransformerOrderType.Rfq;
}
/**
* Converts the given `OptimizedMarketOrder`s into bridge, limit, and RFQ orders for
* FillQuoteTransformer.
*/
export function getFQTTransformerDataFromOptimizedOrders(
orders: OptimizedMarketOrder[],
): Pick<FillQuoteTransformerData, 'bridgeOrders' | 'limitOrders' | 'rfqOrders' | 'fillSequence'> {
const fqtData: Pick<FillQuoteTransformerData, 'bridgeOrders' | 'limitOrders' | 'rfqOrders' | 'fillSequence'> = {
bridgeOrders: [],
limitOrders: [],
rfqOrders: [],
fillSequence: [],
};
for (const order of orders) {
if (isOptimizedBridgeOrder(order)) {
fqtData.bridgeOrders.push({
bridgeData: createBridgeDataForBridgeOrder(order),
makerTokenAmount: order.makerAmount,
takerTokenAmount: order.takerAmount,
source: getErc20BridgeSourceToBridgeSource(order.source),
});
} else if (isOptimizedLimitOrder(order)) {
fqtData.limitOrders.push({
order: order.fillData.order,
signature: order.fillData.signature,
maxTakerTokenFillAmount: order.takerAmount,
});
} else if (isOptimizedRfqOrder(order)) {
fqtData.rfqOrders.push({
order: order.fillData.order,
signature: order.fillData.signature,
maxTakerTokenFillAmount: order.takerAmount,
});
} else {
// Should never happen
throw new Error('Unknown Order type');
}
fqtData.fillSequence.push(order.type);
}
return fqtData;
}
/**
* Returns true if swap quote must go through `tranformERC20`.
*/
export function requiresTransformERC20(opts: ExchangeProxyContractOpts): boolean {
// Is a mtx.
if (opts.isMetaTransaction) {
return true;
}
// Has an affiliate fee.
if (!opts.affiliateFee.buyTokenFeeAmount.eq(0) || !opts.affiliateFee.sellTokenFeeAmount.eq(0)) {
return true;
}
// VIP does not support selling the entire balance
if (opts.shouldSellEntireBalance) {
return true;
}
return false;
}