Skip to content
This repository has been archived by the owner on Jul 9, 2021. It is now read-only.

asset-swapper: RFQ-T indicative quotes #2555

Merged
merged 19 commits into from
Apr 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/asset-swapper/CHANGELOG.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
{
"note": "Add support for RFQ-T, querying maker-hosted endpoints for quotes to be submitted by the taker",
"pr": 2541
},
{
"note": "Add support for indicative (non-committal) quotes via RFQ-T",
"pr": 2555
}
]
},
Expand Down
6 changes: 3 additions & 3 deletions packages/asset-swapper/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
ForwarderExtensionContractOpts,
OrderPrunerOpts,
OrderPrunerPermittedFeeTypes,
RfqtFirmQuoteRequestOpts,
RfqtRequestOpts,
SwapQuoteExecutionOpts,
SwapQuoteGetOutputOpts,
SwapQuoteRequestOpts,
Expand Down Expand Up @@ -66,7 +66,7 @@ const DEFAULT_SWAP_QUOTE_REQUEST_OPTS: SwapQuoteRequestOpts = {
...DEFAULT_GET_MARKET_ORDERS_OPTS,
};

const DEFAULT_RFQT_FIRM_QUOTE_REQUEST_OPTS: RfqtFirmQuoteRequestOpts = {
const DEFAULT_RFQT_REQUEST_OPTS: Partial<RfqtRequestOpts> = {
makerEndpointMaxResponseTimeMs: 1000,
};

Expand All @@ -86,7 +86,7 @@ export const constants = {
DEFAULT_FORWARDER_SWAP_QUOTE_EXECUTE_OPTS,
DEFAULT_SWAP_QUOTE_REQUEST_OPTS,
DEFAULT_PER_PAGE,
DEFAULT_RFQT_FIRM_QUOTE_REQUEST_OPTS,
DEFAULT_RFQT_REQUEST_OPTS,
NULL_ERC20_ASSET_DATA,
PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS,
MARKET_UTILS_AMOUNT_BUFFER_PERCENTAGE,
Expand Down
5 changes: 3 additions & 2 deletions packages/asset-swapper/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export {
MarketOperation,
MarketSellSwapQuote,
MockedRfqtFirmQuoteResponse,
RfqtFirmQuoteRequestOpts,
RfqtRequestOpts,
SwapQuote,
SwapQuoteConsumerBase,
SwapQuoteConsumerOpts,
Expand All @@ -64,8 +64,9 @@ export {
CollapsedFill,
NativeCollapsedFill,
OptimizedMarketOrder,
GetMarketOrdersRfqtOpts,
} from './utils/market_operation_utils/types';
export { affiliateFeeUtils } from './utils/affiliate_fee_utils';
export { ProtocolFeeUtils } from './utils/protocol_fee_utils';
export { QuoteRequestor } from './utils/quote_requestor';
export { QuoteRequestor, RfqtIndicativeQuoteResponse } from './utils/quote_requestor';
export { rfqtMocker } from './utils/rfqt_mocker';
29 changes: 21 additions & 8 deletions packages/asset-swapper/src/swap_quoter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as _ from 'lodash';

import { constants } from './constants';
import {
CalculateSwapQuoteOpts,
LiquidityForTakerMakerAssetDataPair,
MarketBuySwapQuote,
MarketOperation,
Expand Down Expand Up @@ -169,8 +170,7 @@ export class SwapQuoter {
this._devUtilsContract = new DevUtilsContract(this._contractAddresses.devUtils, provider);
this._protocolFeeUtils = new ProtocolFeeUtils(constants.PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS);
this._orderStateUtils = new OrderStateUtils(this._devUtilsContract);
this._quoteRequestor =
options.quoteRequestor || new QuoteRequestor(options.rfqt ? options.rfqt.makerEndpoints || [] : []);
this._quoteRequestor = new QuoteRequestor(options.rfqt ? options.rfqt.makerEndpoints || [] : []);
const sampler = new DexOrderSampler(
new IERC20BridgeSamplerContract(this._contractAddresses.erc20BridgeSampler, this.provider, {
gas: samplerGasLimit,
Expand Down Expand Up @@ -533,8 +533,8 @@ export class SwapQuoter {
if (
opts.rfqt &&
opts.rfqt.intentOnFilling &&
opts.apiKey &&
this._rfqtTakerApiKeyWhitelist.includes(opts.apiKey)
opts.rfqt.apiKey &&
this._rfqtTakerApiKeyWhitelist.includes(opts.rfqt.apiKey)
) {
if (!opts.rfqt.takerAddress || opts.rfqt.takerAddress === constants.NULL_ADDRESS) {
throw new Error('RFQ-T requests must specify a taker address');
Expand All @@ -545,8 +545,7 @@ export class SwapQuoter {
takerAssetData,
assetFillAmount,
marketOperation,
opts.apiKey,
opts.rfqt.takerAddress,
opts.rfqt,
),
);
}
Expand All @@ -566,23 +565,37 @@ export class SwapQuoter {

let swapQuote: SwapQuote;

const calcOpts: CalculateSwapQuoteOpts = opts;

if (calcOpts.rfqt !== undefined && this._shouldEnableIndicativeRfqt(calcOpts.rfqt)) {
calcOpts.rfqt.quoteRequestor = this._quoteRequestor;
}

if (marketOperation === MarketOperation.Buy) {
swapQuote = await this._swapQuoteCalculator.calculateMarketBuySwapQuoteAsync(
orders,
assetFillAmount,
gasPrice,
opts,
calcOpts,
);
} else {
swapQuote = await this._swapQuoteCalculator.calculateMarketSellSwapQuoteAsync(
orders,
assetFillAmount,
gasPrice,
opts,
calcOpts,
);
}

return swapQuote;
}
private _shouldEnableIndicativeRfqt(opts: CalculateSwapQuoteOpts['rfqt']): boolean {
return (
opts !== undefined &&
opts.isIndicative !== undefined &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont need this undefined check

opts.isIndicative &&
this._rfqtTakerApiKeyWhitelist.includes(opts.apiKey)
);
}
}
// tslint:disable-next-line: max-file-line-count
31 changes: 21 additions & 10 deletions packages/asset-swapper/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';

import { GetMarketOrdersOpts } from './utils/market_operation_utils/types';
import { QuoteRequestor } from './utils/quote_requestor';

/**
* expiryBufferMs: The number of seconds to add when calculating whether an order is expired or not. Defaults to 300s (5m).
Expand Down Expand Up @@ -188,16 +187,20 @@ export interface SwapQuoteOrdersBreakdown {
[source: string]: BigNumber;
}

export interface RfqtRequestOpts {
takerAddress: string;
apiKey: string;
intentOnFilling: boolean;
isIndicative?: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit but would prefer for this to be required

makerEndpointMaxResponseTimeMs?: number;
}

/**
* gasPrice: gas price to determine protocolFee amount, default to ethGasStation fast amount
*/
export interface SwapQuoteRequestOpts extends CalculateSwapQuoteOpts {
gasPrice?: BigNumber;
apiKey?: string;
rfqt?: {
takerAddress: string;
intentOnFilling: boolean;
};
rfqt?: RfqtRequestOpts;
}

/**
Expand All @@ -223,7 +226,6 @@ export interface SwapQuoterOpts extends OrderPrunerOpts {
takerApiKeyWhitelist: string[];
makerEndpoints: string[];
};
quoteRequestor?: QuoteRequestor;
}

/**
Expand Down Expand Up @@ -274,14 +276,23 @@ export enum OrderPrunerPermittedFeeTypes {
TakerDenominatedTakerFee = 'TAKER_DENOMINATED_TAKER_FEE',
}

export interface RfqtFirmQuoteRequestOpts {
makerEndpointMaxResponseTimeMs?: number;
/**
* Represents a mocked RFQT maker responses.
*/
export interface MockedRfqtFirmQuoteResponse {
endpoint: string;
requestApiKey: string;
requestParams: {
[key: string]: string | undefined;
};
responseData: any;
responseCode: number;
}

/**
* Represents a mocked RFQT maker responses.
*/
export interface MockedRfqtFirmQuoteResponse {
export interface MockedRfqtIndicativeQuoteResponse {
endpoint: string;
requestApiKey: string;
requestParams: {
Expand Down
73 changes: 59 additions & 14 deletions packages/asset-swapper/src/utils/market_operation_utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SignedOrder } from '@0x/types';
import { BigNumber, NULL_ADDRESS } from '@0x/utils';

import { MarketOperation } from '../../types';
import { RfqtIndicativeQuoteResponse } from '../quote_requestor';
import { difference } from '../utils';

import { BUY_SOURCES, DEFAULT_GET_MARKET_ORDERS_OPTS, FEE_QUOTE_SOURCES, ONE_ETHER, SELL_SOURCES } from './constants';
Expand All @@ -13,7 +14,12 @@ import {
getPathAdjustedSlippage,
getPathSize,
} from './fills';
import { createOrdersFromPath, createSignedOrdersWithFillableAmounts, getNativeOrderTokens } from './orders';
import {
createOrdersFromPath,
createSignedOrdersFromRfqtIndicativeQuotes,
createSignedOrdersWithFillableAmounts,
getNativeOrderTokens,
} from './orders';
import { findOptimalPath } from './path_optimizer';
import { DexOrderSampler, getSampleAmounts } from './sampler';
import {
Expand All @@ -26,6 +32,26 @@ import {
OrderDomain,
} from './types';

async function getRfqtIndicativeQuotesAsync(
makerAssetData: string,
takerAssetData: string,
marketOperation: MarketOperation,
assetFillAmount: BigNumber,
opts: Partial<GetMarketOrdersOpts>,
): Promise<RfqtIndicativeQuoteResponse[]> {
if (opts.rfqt && opts.rfqt.isIndicative === true && opts.rfqt.quoteRequestor) {
return opts.rfqt.quoteRequestor.requestRfqtIndicativeQuotesAsync(
makerAssetData,
takerAssetData,
assetFillAmount,
marketOperation,
opts.rfqt,
);
} else {
return Promise.resolve<RfqtIndicativeQuoteResponse[]>([]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we just return []; here?

Note: this is a nit, don't need to change

}
}

export class MarketOperationUtils {
private readonly _wethAddress: string;

Expand Down Expand Up @@ -57,12 +83,7 @@ export class MarketOperationUtils {
const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
const [makerToken, takerToken] = getNativeOrderTokens(nativeOrders[0]);
// Call the sampler contract.
const [
orderFillableAmounts,
liquidityProviderAddress,
ethToMakerAssetRate,
dexQuotes,
] = await this._sampler.executeAsync(
const samplerPromise = this._sampler.executeAsync(
// Get native order fillable amounts.
DexOrderSampler.ops.getOrderFillableTakerAmounts(nativeOrders),
// Get the custom liquidity provider from registry.
Expand Down Expand Up @@ -92,10 +113,22 @@ export class MarketOperationUtils {
this._liquidityProviderRegistry,
),
);
const rfqtPromise = getRfqtIndicativeQuotesAsync(
nativeOrders[0].makerAssetData,
nativeOrders[0].takerAssetData,
MarketOperation.Sell,
takerAmount,
_opts,
);
const [
[orderFillableAmounts, liquidityProviderAddress, ethToMakerAssetRate, dexQuotes],
rfqtIndicativeQuotes,
] = await Promise.all([samplerPromise, rfqtPromise]);
return this._generateOptimizedOrders({
orderFillableAmounts,
nativeOrders,
dexQuotes,
rfqtIndicativeQuotes,
liquidityProviderAddress,
inputToken: takerToken,
outputToken: makerToken,
Expand Down Expand Up @@ -130,12 +163,7 @@ export class MarketOperationUtils {
const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
const [makerToken, takerToken] = getNativeOrderTokens(nativeOrders[0]);
// Call the sampler contract.
const [
orderFillableAmounts,
liquidityProviderAddress,
ethToTakerAssetRate,
dexQuotes,
] = await this._sampler.executeAsync(
const samplerPromise = this._sampler.executeAsync(
// Get native order fillable amounts.
DexOrderSampler.ops.getOrderFillableMakerAmounts(nativeOrders),
// Get the custom liquidity provider from registry.
Expand Down Expand Up @@ -165,11 +193,23 @@ export class MarketOperationUtils {
this._liquidityProviderRegistry,
),
);
const rfqtPromise = getRfqtIndicativeQuotesAsync(
nativeOrders[0].makerAssetData,
nativeOrders[0].takerAssetData,
MarketOperation.Buy,
makerAmount,
_opts,
);
const [
[orderFillableAmounts, liquidityProviderAddress, ethToTakerAssetRate, dexQuotes],
rfqtIndicativeQuotes,
] = await Promise.all([samplerPromise, rfqtPromise]);

return this._generateOptimizedOrders({
orderFillableAmounts,
nativeOrders,
dexQuotes,
rfqtIndicativeQuotes,
liquidityProviderAddress,
inputToken: makerToken,
outputToken: takerToken,
Expand Down Expand Up @@ -246,6 +286,7 @@ export class MarketOperationUtils {
orderFillableAmounts,
nativeOrders,
dexQuotes,
rfqtIndicativeQuotes: [],
inputToken: makerToken,
outputToken: takerToken,
side: MarketOperation.Buy,
Expand Down Expand Up @@ -274,6 +315,7 @@ export class MarketOperationUtils {
nativeOrders: SignedOrder[];
orderFillableAmounts: BigNumber[];
dexQuotes: DexSample[][];
rfqtIndicativeQuotes: RfqtIndicativeQuoteResponse[];
runLimit?: number;
ethToOutputRate?: BigNumber;
bridgeSlippage?: number;
Expand All @@ -290,7 +332,10 @@ export class MarketOperationUtils {
const paths = createFillPaths({
side,
// Augment native orders with their fillable amounts.
orders: createSignedOrdersWithFillableAmounts(side, opts.nativeOrders, opts.orderFillableAmounts),
orders: [
...createSignedOrdersWithFillableAmounts(side, opts.nativeOrders, opts.orderFillableAmounts),
...createSignedOrdersFromRfqtIndicativeQuotes(opts.rfqtIndicativeQuotes),
feuGeneA marked this conversation as resolved.
Show resolved Hide resolved
],
dexQuotes: opts.dexQuotes,
targetInput: inputAmount,
ethToOutputRate: opts.ethToOutputRate,
Expand Down
30 changes: 30 additions & 0 deletions packages/asset-swapper/src/utils/market_operation_utils/orders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ERC20BridgeAssetData, SignedOrder } from '@0x/types';
import { AbiEncoder, BigNumber } from '@0x/utils';

import { MarketOperation, SignedOrderWithFillableAmounts } from '../../types';
import { RfqtIndicativeQuoteResponse } from '../quote_requestor';
import { getCurveInfo, isCurveSource } from '../source_utils';

import {
Expand Down Expand Up @@ -356,3 +357,32 @@ function createNativeOrder(fill: CollapsedFill): OptimizedMarketOrder {
...(fill as NativeCollapsedFill).nativeOrder,
};
}

export function createSignedOrdersFromRfqtIndicativeQuotes(
quotes: RfqtIndicativeQuoteResponse[],
): SignedOrderWithFillableAmounts[] {
return quotes.map(quote => {
return {
fillableMakerAssetAmount: quote.makerAssetAmount,
fillableTakerAssetAmount: quote.takerAssetAmount,
makerAssetAmount: quote.makerAssetAmount,
takerAssetAmount: quote.takerAssetAmount,
makerAssetData: quote.makerAssetData,
takerAssetData: quote.takerAssetData,
feuGeneA marked this conversation as resolved.
Show resolved Hide resolved
takerAddress: NULL_ADDRESS,
makerAddress: NULL_ADDRESS,
senderAddress: NULL_ADDRESS,
feeRecipientAddress: NULL_ADDRESS,
salt: ZERO_AMOUNT,
expirationTimeSeconds: ZERO_AMOUNT,
makerFeeAssetData: NULL_BYTES,
takerFeeAssetData: NULL_BYTES,
makerFee: ZERO_AMOUNT,
takerFee: ZERO_AMOUNT,
fillableTakerFeeAmount: ZERO_AMOUNT,
signature: WALLET_SIGNATURE,
chainId: 0,
exchangeAddress: NULL_ADDRESS,
};
});
}
Loading