feat: Extended Quote Report
* Extended Quote report for indicative quote * feat: Only save 'full' quotes on quote report * Unify extended quote report
This commit is contained in:
parent
10b0d7f363
commit
b7adc5a889
@ -1,5 +1,14 @@
|
||||
[
|
||||
{
|
||||
"version": "16.32.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Extended Quote Report",
|
||||
"pr": 361
|
||||
}
|
||||
],
|
||||
"timestamp": 1636480845
|
||||
},
|
||||
"timestamp": 1635903615,
|
||||
"version": "16.31.0",
|
||||
"changes": [
|
||||
|
@ -162,14 +162,20 @@ export {
|
||||
export { ProtocolFeeUtils } from './utils/protocol_fee_utils';
|
||||
export {
|
||||
BridgeQuoteReportEntry,
|
||||
jsonifyFillData,
|
||||
MultiHopQuoteReportEntry,
|
||||
NativeLimitOrderQuoteReportEntry,
|
||||
NativeRfqOrderQuoteReportEntry,
|
||||
QuoteReport,
|
||||
QuoteReportEntry,
|
||||
ExtendedQuoteReport,
|
||||
ExtendedQuoteReportSources,
|
||||
ExtendedQuoteReportEntry,
|
||||
ExtendedQuoteReportIndexedEntry,
|
||||
ExtendedQuoteReportIndexedEntryOutbound,
|
||||
PriceComparisonsReport,
|
||||
} from './utils/quote_report_generator';
|
||||
export { QuoteRequestor } from './utils/quote_requestor';
|
||||
export { QuoteRequestor, V4RFQIndicativeQuoteMM } from './utils/quote_requestor';
|
||||
export { ERC20BridgeSamplerContract, BalanceCheckerContract, FakeTakerContract } from './wrappers';
|
||||
import { ERC20BridgeSource } from './utils/market_operation_utils/types';
|
||||
export type Native = ERC20BridgeSource.Native;
|
||||
|
@ -505,6 +505,7 @@ function createSwapQuote(
|
||||
const {
|
||||
optimizedOrders,
|
||||
quoteReport,
|
||||
extendedQuoteReportSources,
|
||||
sourceFlags,
|
||||
takerAmountPerEth,
|
||||
makerAmountPerEth,
|
||||
@ -532,6 +533,7 @@ function createSwapQuote(
|
||||
takerAmountPerEth,
|
||||
makerAmountPerEth,
|
||||
quoteReport,
|
||||
extendedQuoteReportSources,
|
||||
isTwoHop,
|
||||
priceComparisonsReport,
|
||||
};
|
||||
|
@ -19,7 +19,7 @@ import {
|
||||
OptimizedMarketOrder,
|
||||
TokenAdjacencyGraph,
|
||||
} from './utils/market_operation_utils/types';
|
||||
import { PriceComparisonsReport, QuoteReport } from './utils/quote_report_generator';
|
||||
import { ExtendedQuoteReportSources, PriceComparisonsReport, QuoteReport } from './utils/quote_report_generator';
|
||||
import { MetricsProxy } from './utils/quote_requestor';
|
||||
|
||||
/**
|
||||
@ -171,6 +171,7 @@ export interface SwapQuoteBase {
|
||||
worstCaseQuoteInfo: SwapQuoteInfo;
|
||||
sourceBreakdown: SwapQuoteOrdersBreakdown;
|
||||
quoteReport?: QuoteReport;
|
||||
extendedQuoteReportSources?: ExtendedQuoteReportSources;
|
||||
priceComparisonsReport?: PriceComparisonsReport;
|
||||
isTwoHop: boolean;
|
||||
makerTokenDecimals: number;
|
||||
|
@ -18,12 +18,15 @@ import {
|
||||
|
||||
import {
|
||||
dexSampleToReportSource,
|
||||
ExtendedQuoteReportSources,
|
||||
generateExtendedQuoteReportSources,
|
||||
generateQuoteReport,
|
||||
multiHopSampleToReportSource,
|
||||
nativeOrderToReportEntry,
|
||||
PriceComparisonsReport,
|
||||
QuoteReport,
|
||||
} from './../quote_report_generator';
|
||||
|
||||
import { getComparisonPrices } from './comparison_price';
|
||||
import {
|
||||
BUY_SOURCE_FILTER_BY_CHAIN_ID,
|
||||
@ -78,6 +81,25 @@ export class MarketOperationUtils {
|
||||
return generateQuoteReport(side, quotes.nativeOrders, liquidityDelivered, comparisonPrice, quoteRequestor);
|
||||
}
|
||||
|
||||
private static _computeExtendedQuoteReportSources(
|
||||
quoteRequestor: QuoteRequestor | undefined,
|
||||
marketSideLiquidity: MarketSideLiquidity,
|
||||
amount: BigNumber,
|
||||
optimizerResult: OptimizerResult,
|
||||
comparisonPrice?: BigNumber | undefined,
|
||||
): ExtendedQuoteReportSources {
|
||||
const { side, quotes } = marketSideLiquidity;
|
||||
const { liquidityDelivered } = optimizerResult;
|
||||
return generateExtendedQuoteReportSources(
|
||||
side,
|
||||
quotes,
|
||||
liquidityDelivered,
|
||||
amount,
|
||||
comparisonPrice,
|
||||
quoteRequestor,
|
||||
);
|
||||
}
|
||||
|
||||
private static _computePriceComparisonsReport(
|
||||
quoteRequestor: QuoteRequestor | undefined,
|
||||
marketSideLiquidity: MarketSideLiquidity,
|
||||
@ -702,6 +724,16 @@ export class MarketOperationUtils {
|
||||
);
|
||||
}
|
||||
|
||||
// Always compute the Extended Quote Report
|
||||
let extendedQuoteReportSources: ExtendedQuoteReportSources | undefined;
|
||||
extendedQuoteReportSources = MarketOperationUtils._computeExtendedQuoteReportSources(
|
||||
_opts.rfqt ? _opts.rfqt.quoteRequestor : undefined,
|
||||
marketSideLiquidity,
|
||||
amount,
|
||||
optimizerResult,
|
||||
wholeOrderPrice,
|
||||
);
|
||||
|
||||
let priceComparisonsReport: PriceComparisonsReport | undefined;
|
||||
if (_opts.shouldIncludePriceComparisonsReport) {
|
||||
priceComparisonsReport = MarketOperationUtils._computePriceComparisonsReport(
|
||||
@ -710,7 +742,7 @@ export class MarketOperationUtils {
|
||||
wholeOrderPrice,
|
||||
);
|
||||
}
|
||||
return { ...optimizerResult, quoteReport, priceComparisonsReport };
|
||||
return { ...optimizerResult, quoteReport, extendedQuoteReportSources, priceComparisonsReport };
|
||||
}
|
||||
|
||||
private async _refreshPoolCacheIfRequiredAsync(takerToken: string, makerToken: string): Promise<void> {
|
||||
|
@ -3,13 +3,12 @@ import {
|
||||
FillQuoteTransformerOrderType,
|
||||
FillQuoteTransformerRfqOrderInfo,
|
||||
} from '@0x/protocol-utils';
|
||||
import { V4RFQIndicativeQuote } from '@0x/quote-server';
|
||||
import { MarketOperation } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { NativeOrderWithFillableAmounts, RfqFirmQuoteValidator, RfqRequestOpts } from '../../types';
|
||||
import { QuoteRequestor } from '../../utils/quote_requestor';
|
||||
import { PriceComparisonsReport, QuoteReport } from '../quote_report_generator';
|
||||
import { QuoteRequestor, V4RFQIndicativeQuoteMM } from '../../utils/quote_requestor';
|
||||
import { ExtendedQuoteReportSources, PriceComparisonsReport, QuoteReport } from '../quote_report_generator';
|
||||
|
||||
import { CollapsedPath } from './path';
|
||||
import { SourceFilters } from './source_filters';
|
||||
@ -491,6 +490,7 @@ export interface OptimizerResult {
|
||||
|
||||
export interface OptimizerResultWithReport extends OptimizerResult {
|
||||
quoteReport?: QuoteReport;
|
||||
extendedQuoteReportSources?: ExtendedQuoteReportSources;
|
||||
priceComparisonsReport?: PriceComparisonsReport;
|
||||
}
|
||||
|
||||
@ -519,7 +519,7 @@ export interface MarketSideLiquidity {
|
||||
|
||||
export interface RawQuotes {
|
||||
nativeOrders: NativeOrderWithFillableAmounts[];
|
||||
rfqtIndicativeQuotes: V4RFQIndicativeQuote[];
|
||||
rfqtIndicativeQuotes: V4RFQIndicativeQuoteMM[];
|
||||
twoHopQuotes: Array<DexSample<MultiHopFillData>>;
|
||||
dexQuotes: Array<Array<DexSample<FillData>>>;
|
||||
}
|
||||
|
@ -14,8 +14,9 @@ import {
|
||||
NativeFillData,
|
||||
NativeLimitOrderFillData,
|
||||
NativeRfqOrderFillData,
|
||||
RawQuotes,
|
||||
} from './market_operation_utils/types';
|
||||
import { QuoteRequestor } from './quote_requestor';
|
||||
import { QuoteRequestor, V4RFQIndicativeQuoteMM } from './quote_requestor';
|
||||
|
||||
export interface QuoteReportEntryBase {
|
||||
liquiditySource: ERC20BridgeSource;
|
||||
@ -36,30 +37,77 @@ export interface NativeLimitOrderQuoteReportEntry extends QuoteReportEntryBase {
|
||||
liquiditySource: ERC20BridgeSource.Native;
|
||||
fillData: NativeFillData;
|
||||
fillableTakerAmount: BigNumber;
|
||||
isRfqt: false;
|
||||
isRFQ: false;
|
||||
}
|
||||
|
||||
export interface NativeRfqOrderQuoteReportEntry extends QuoteReportEntryBase {
|
||||
liquiditySource: ERC20BridgeSource.Native;
|
||||
fillData: NativeFillData;
|
||||
fillableTakerAmount: BigNumber;
|
||||
isRfqt: true;
|
||||
isRFQ: true;
|
||||
nativeOrder: RfqOrderFields;
|
||||
makerUri: string;
|
||||
comparisonPrice?: number;
|
||||
}
|
||||
|
||||
export interface IndicativeRfqOrderQuoteReportEntry extends QuoteReportEntryBase {
|
||||
liquiditySource: ERC20BridgeSource.Native;
|
||||
fillableTakerAmount: BigNumber;
|
||||
isRFQ: true;
|
||||
makerUri?: string;
|
||||
comparisonPrice?: number;
|
||||
}
|
||||
|
||||
export type QuoteReportEntry =
|
||||
| BridgeQuoteReportEntry
|
||||
| MultiHopQuoteReportEntry
|
||||
| NativeLimitOrderQuoteReportEntry
|
||||
| NativeRfqOrderQuoteReportEntry;
|
||||
|
||||
export type ExtendedQuoteReportEntry =
|
||||
| BridgeQuoteReportEntry
|
||||
| MultiHopQuoteReportEntry
|
||||
| NativeLimitOrderQuoteReportEntry
|
||||
| NativeRfqOrderQuoteReportEntry
|
||||
| IndicativeRfqOrderQuoteReportEntry;
|
||||
|
||||
export type ExtendedQuoteReportIndexedEntry = ExtendedQuoteReportEntry & {
|
||||
quoteEntryIndex: number;
|
||||
isDelivered: boolean;
|
||||
};
|
||||
|
||||
export type ExtendedQuoteReportIndexedEntryOutbound = Omit<ExtendedQuoteReportIndexedEntry, 'fillData'> & {
|
||||
fillData?: string;
|
||||
};
|
||||
|
||||
export interface QuoteReport {
|
||||
sourcesConsidered: QuoteReportEntry[];
|
||||
sourcesDelivered: QuoteReportEntry[];
|
||||
}
|
||||
|
||||
export interface ExtendedQuoteReportSources {
|
||||
sourcesConsidered: ExtendedQuoteReportIndexedEntry[];
|
||||
sourcesDelivered: ExtendedQuoteReportIndexedEntry[] | undefined;
|
||||
}
|
||||
|
||||
export interface ExtendedQuoteReport {
|
||||
quoteId?: string;
|
||||
taker?: string;
|
||||
timestamp: number;
|
||||
firmQuoteReport: boolean;
|
||||
submissionBy: 'taker' | 'metaTxn' | 'rfqm';
|
||||
buyAmount?: string;
|
||||
sellAmount?: string;
|
||||
buyTokenAddress: string;
|
||||
sellTokenAddress: string;
|
||||
integratorId?: string;
|
||||
slippageBips?: number;
|
||||
zeroExTransactionHash?: string;
|
||||
decodedUniqueId?: string;
|
||||
sourcesConsidered: ExtendedQuoteReportIndexedEntryOutbound[];
|
||||
sourcesDelivered: ExtendedQuoteReportIndexedEntryOutbound[] | undefined;
|
||||
}
|
||||
|
||||
export interface PriceComparisonsReport {
|
||||
dexSources: BridgeQuoteReportEntry[];
|
||||
multiHopSources: MultiHopQuoteReportEntry[];
|
||||
@ -80,7 +128,7 @@ export function generateQuoteReport(
|
||||
const nativeOrderSourcesConsidered = nativeOrders.map(order =>
|
||||
nativeOrderToReportEntry(order.type, order as any, order.fillableTakerAmount, comparisonPrice, quoteRequestor),
|
||||
);
|
||||
const sourcesConsidered = [...nativeOrderSourcesConsidered.filter(order => order.isRfqt)];
|
||||
const sourcesConsidered = [...nativeOrderSourcesConsidered.filter(order => order.isRFQ)];
|
||||
|
||||
let sourcesDelivered;
|
||||
if (Array.isArray(liquidityDelivered)) {
|
||||
@ -116,6 +164,105 @@ export function generateQuoteReport(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a report of sources considered while computing the optimized
|
||||
* swap quote, the sources ultimately included in the computed quote. This
|
||||
* extende version incudes all considered quotes, not only native liquidity.
|
||||
*/
|
||||
export function generateExtendedQuoteReportSources(
|
||||
marketOperation: MarketOperation,
|
||||
quotes: RawQuotes,
|
||||
liquidityDelivered: ReadonlyArray<CollapsedFill> | DexSample<MultiHopFillData>,
|
||||
amount: BigNumber,
|
||||
comparisonPrice?: BigNumber | undefined,
|
||||
quoteRequestor?: QuoteRequestor,
|
||||
): ExtendedQuoteReportSources {
|
||||
const sourcesConsidered: ExtendedQuoteReportEntry[] = [];
|
||||
|
||||
// NativeOrders
|
||||
sourcesConsidered.push(
|
||||
...quotes.nativeOrders.map(order =>
|
||||
nativeOrderToReportEntry(
|
||||
order.type,
|
||||
order as any,
|
||||
order.fillableTakerAmount,
|
||||
comparisonPrice,
|
||||
quoteRequestor,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// IndicativeQuotes
|
||||
sourcesConsidered.push(
|
||||
...quotes.rfqtIndicativeQuotes.map(order => indicativeQuoteToReportEntry(order, comparisonPrice)),
|
||||
);
|
||||
|
||||
// MultiHop
|
||||
sourcesConsidered.push(...quotes.twoHopQuotes.map(quote => multiHopSampleToReportSource(quote, marketOperation)));
|
||||
|
||||
// Dex Quotes
|
||||
sourcesConsidered.push(
|
||||
..._.flatten(
|
||||
quotes.dexQuotes.map(dex =>
|
||||
dex
|
||||
.filter(quote => isDexSampleForTotalAmount(quote, marketOperation, amount))
|
||||
.map(quote => dexSampleToReportSource(quote, marketOperation)),
|
||||
),
|
||||
),
|
||||
);
|
||||
const sourcesConsideredIndexed = sourcesConsidered.map(
|
||||
(quote, index): ExtendedQuoteReportIndexedEntry => {
|
||||
return {
|
||||
...quote,
|
||||
quoteEntryIndex: index,
|
||||
isDelivered: false,
|
||||
};
|
||||
},
|
||||
);
|
||||
let sourcesDelivered;
|
||||
if (Array.isArray(liquidityDelivered)) {
|
||||
// create easy way to look up fillable amounts
|
||||
const nativeOrderSignaturesToFillableAmounts = _.fromPairs(
|
||||
quotes.nativeOrders.map(o => {
|
||||
return [_nativeDataToId(o), o.fillableTakerAmount];
|
||||
}),
|
||||
);
|
||||
// map sources delivered
|
||||
sourcesDelivered = liquidityDelivered.map(collapsedFill => {
|
||||
if (_isNativeOrderFromCollapsedFill(collapsedFill)) {
|
||||
return nativeOrderToReportEntry(
|
||||
collapsedFill.type,
|
||||
collapsedFill.fillData,
|
||||
nativeOrderSignaturesToFillableAmounts[_nativeDataToId(collapsedFill.fillData)],
|
||||
comparisonPrice,
|
||||
quoteRequestor,
|
||||
);
|
||||
} else {
|
||||
return dexSampleToReportSource(collapsedFill, marketOperation);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
sourcesDelivered = [
|
||||
// tslint:disable-next-line: no-unnecessary-type-assertion
|
||||
multiHopSampleToReportSource(liquidityDelivered as DexSample<MultiHopFillData>, marketOperation),
|
||||
];
|
||||
}
|
||||
const sourcesDeliveredIndexed = sourcesDelivered.map(
|
||||
(quote, index): ExtendedQuoteReportIndexedEntry => {
|
||||
return {
|
||||
...quote,
|
||||
quoteEntryIndex: index,
|
||||
isDelivered: false,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
sourcesConsidered: sourcesConsideredIndexed,
|
||||
sourcesDelivered: sourcesDeliveredIndexed,
|
||||
};
|
||||
}
|
||||
|
||||
function _nativeDataToId(data: { signature: Signature }): string {
|
||||
const { v, r, s } = data.signature;
|
||||
return `${v}${r}${s}`;
|
||||
@ -153,6 +300,22 @@ export function dexSampleToReportSource(ds: DexSample, marketOperation: MarketOp
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a DEX sample is the one that represents the whole amount requested by taker
|
||||
* NOTE: this is used for the QuoteReport to filter samples
|
||||
*/
|
||||
function isDexSampleForTotalAmount(ds: DexSample, marketOperation: MarketOperation, amount: BigNumber): boolean {
|
||||
// input and output map to different values
|
||||
// based on the market operation
|
||||
if (marketOperation === MarketOperation.Buy) {
|
||||
return ds.input === amount;
|
||||
} else if (marketOperation === MarketOperation.Sell) {
|
||||
return ds.output === amount;
|
||||
} else {
|
||||
throw new Error(`Unexpected marketOperation ${marketOperation}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a report sample for a MultiHop source
|
||||
* NOTE: this is used for the QuoteReport and quote price comparison data
|
||||
@ -208,17 +371,17 @@ export function nativeOrderToReportEntry(
|
||||
};
|
||||
|
||||
// if we find this is an rfqt order, label it as such and associate makerUri
|
||||
const isRfqt = type === FillQuoteTransformerOrderType.Rfq;
|
||||
const isRFQ = type === FillQuoteTransformerOrderType.Rfq;
|
||||
const rfqtMakerUri =
|
||||
isRfqt && quoteRequestor ? quoteRequestor.getMakerUriForSignature(fillData.signature) : undefined;
|
||||
isRFQ && quoteRequestor ? quoteRequestor.getMakerUriForSignature(fillData.signature) : undefined;
|
||||
|
||||
if (isRfqt) {
|
||||
if (isRFQ) {
|
||||
const nativeOrder = fillData.order as RfqOrderFields;
|
||||
// tslint:disable-next-line: no-object-literal-type-assertion
|
||||
return {
|
||||
liquiditySource: ERC20BridgeSource.Native,
|
||||
...nativeOrderBase,
|
||||
isRfqt: true,
|
||||
isRFQ: true,
|
||||
makerUri: rfqtMakerUri || '',
|
||||
...(comparisonPrice ? { comparisonPrice: comparisonPrice.toNumber() } : {}),
|
||||
nativeOrder,
|
||||
@ -229,8 +392,49 @@ export function nativeOrderToReportEntry(
|
||||
return {
|
||||
liquiditySource: ERC20BridgeSource.Native,
|
||||
...nativeOrderBase,
|
||||
isRfqt: false,
|
||||
isRFQ: false,
|
||||
fillData,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a report entry for an indicative RFQ Quote
|
||||
* NOTE: this is used for the QuoteReport and quote price comparison data
|
||||
*/
|
||||
export function indicativeQuoteToReportEntry(
|
||||
order: V4RFQIndicativeQuoteMM,
|
||||
comparisonPrice?: BigNumber | undefined,
|
||||
): IndicativeRfqOrderQuoteReportEntry {
|
||||
const nativeOrderBase = {
|
||||
makerAmount: order.makerAmount,
|
||||
takerAmount: order.takerAmount,
|
||||
fillableTakerAmount: order.takerAmount,
|
||||
};
|
||||
|
||||
// tslint:disable-next-line: no-object-literal-type-assertion
|
||||
return {
|
||||
liquiditySource: ERC20BridgeSource.Native,
|
||||
...nativeOrderBase,
|
||||
isRFQ: true,
|
||||
makerUri: order.makerUri,
|
||||
fillData: {},
|
||||
...(comparisonPrice ? { comparisonPrice: comparisonPrice.toNumber() } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* For the extended quote report, we output the filldata as JSON
|
||||
*/
|
||||
export function jsonifyFillData(source: ExtendedQuoteReportIndexedEntry): ExtendedQuoteReportIndexedEntryOutbound {
|
||||
return {
|
||||
...source,
|
||||
fillData: JSON.stringify(source.fillData, (key: string, value: any) => {
|
||||
if (key === '_samplerContract') {
|
||||
return {};
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
@ -39,6 +39,10 @@ interface RfqQuote<T> {
|
||||
makerUri: string;
|
||||
}
|
||||
|
||||
export interface V4RFQIndicativeQuoteMM extends V4RFQIndicativeQuote {
|
||||
makerUri: string;
|
||||
}
|
||||
|
||||
export interface MetricsProxy {
|
||||
/**
|
||||
* Increments a counter that is tracking valid Firm Quotes that are dropped due to low expiration.
|
||||
@ -343,7 +347,7 @@ export class QuoteRequestor {
|
||||
marketOperation: MarketOperation,
|
||||
comparisonPrice: BigNumber | undefined,
|
||||
options: RfqmRequestOptions,
|
||||
): Promise<V4RFQIndicativeQuote[]> {
|
||||
): Promise<V4RFQIndicativeQuoteMM[]> {
|
||||
const _opts: RfqRequestOpts = {
|
||||
...constants.DEFAULT_RFQT_REQUEST_OPTS,
|
||||
...options,
|
||||
@ -367,7 +371,7 @@ export class QuoteRequestor {
|
||||
marketOperation: MarketOperation,
|
||||
comparisonPrice: BigNumber | undefined,
|
||||
options: RfqRequestOpts,
|
||||
): Promise<V4RFQIndicativeQuote[]> {
|
||||
): Promise<V4RFQIndicativeQuoteMM[]> {
|
||||
const _opts: RfqRequestOpts = { ...constants.DEFAULT_RFQT_REQUEST_OPTS, ...options };
|
||||
// Originally a takerAddress was required for indicative quotes, but
|
||||
// now we've eliminated that requirement. @0x/quote-server, however,
|
||||
@ -398,8 +402,8 @@ export class QuoteRequestor {
|
||||
return this._orderSignatureToMakerUri[nativeDataToId({ signature })];
|
||||
}
|
||||
|
||||
private _isValidRfqtIndicativeQuoteResponse(response: V4RFQIndicativeQuote): boolean {
|
||||
const requiredKeys: Array<keyof V4RFQIndicativeQuote> = [
|
||||
private _isValidRfqtIndicativeQuoteResponse(response: V4RFQIndicativeQuoteMM): boolean {
|
||||
const requiredKeys: Array<keyof V4RFQIndicativeQuoteMM> = [
|
||||
'makerAmount',
|
||||
'takerAmount',
|
||||
'makerToken',
|
||||
@ -545,7 +549,10 @@ export class QuoteRequestor {
|
||||
},
|
||||
});
|
||||
rfqMakerBlacklist.logTimeoutOrLackThereof(typedMakerUrl.url, latencyMs >= timeoutMs);
|
||||
return { response: response.data, makerUri: typedMakerUrl.url };
|
||||
return {
|
||||
response: { ...response.data, makerUri: typedMakerUrl.url },
|
||||
makerUri: typedMakerUrl.url,
|
||||
};
|
||||
} else {
|
||||
if (this._altRfqCreds === undefined) {
|
||||
throw new Error(`don't have credentials for alt MM`);
|
||||
@ -694,7 +701,6 @@ export class QuoteRequestor {
|
||||
} else {
|
||||
const secondsRemaining = msRemainingUntilExpiration.div(ONE_SECOND_MS);
|
||||
this._metrics?.measureExpirationForValidOrder(isLastLook, order.maker, secondsRemaining);
|
||||
|
||||
const takerAmount = new BigNumber(order.takerAmount);
|
||||
const fillRatio = takerAmount.div(assetFillAmount);
|
||||
if (fillRatio.lt(1) && fillRatio.gte(FILL_RATIO_WARNING_LEVEL)) {
|
||||
@ -744,9 +750,9 @@ export class QuoteRequestor {
|
||||
comparisonPrice: BigNumber | undefined,
|
||||
options: RfqRequestOpts,
|
||||
assetOfferings: RfqMakerAssetOfferings,
|
||||
): Promise<V4RFQIndicativeQuote[]> {
|
||||
): Promise<V4RFQIndicativeQuoteMM[]> {
|
||||
// fetch quotes
|
||||
const rawQuotes = await this._getQuotesAsync<V4RFQIndicativeQuote>(
|
||||
const rawQuotes = await this._getQuotesAsync<V4RFQIndicativeQuoteMM>(
|
||||
makerToken,
|
||||
takerToken,
|
||||
assetFillAmount,
|
||||
@ -758,7 +764,7 @@ export class QuoteRequestor {
|
||||
);
|
||||
|
||||
// validate
|
||||
const validationFunction = (o: V4RFQIndicativeQuote) => this._isValidRfqtIndicativeQuoteResponse(o);
|
||||
const validationFunction = (o: V4RFQIndicativeQuoteMM) => this._isValidRfqtIndicativeQuoteResponse(o);
|
||||
const validQuotes = rawQuotes.filter(result => {
|
||||
const order = result.response;
|
||||
if (!validationFunction(order)) {
|
||||
|
@ -159,7 +159,11 @@ describe('MarketOperationUtils tests', () => {
|
||||
} else {
|
||||
requestor
|
||||
.setup(r => r.requestRfqtIndicativeQuotesAsync(...args))
|
||||
.returns(async () => results.map(r => r.order))
|
||||
.returns(async () =>
|
||||
results.map(r => {
|
||||
return { ...r.order, makerUri: 'https://foo.bar/' };
|
||||
}),
|
||||
)
|
||||
.verifiable(verifiable);
|
||||
}
|
||||
return requestor;
|
||||
|
@ -155,7 +155,7 @@ describe('generateQuoteReport', async () => {
|
||||
makerAmount: rfqtOrder1.order.makerAmount,
|
||||
takerAmount: rfqtOrder1.order.takerAmount,
|
||||
fillableTakerAmount: rfqtOrder1.fillableTakerAmount,
|
||||
isRfqt: true,
|
||||
isRFQ: true,
|
||||
makerUri: 'https://rfqt1.provider.club',
|
||||
nativeOrder: rfqtOrder1.order,
|
||||
fillData: {
|
||||
@ -167,7 +167,7 @@ describe('generateQuoteReport', async () => {
|
||||
makerAmount: rfqtOrder2.order.makerAmount,
|
||||
takerAmount: rfqtOrder2.order.takerAmount,
|
||||
fillableTakerAmount: rfqtOrder2.fillableTakerAmount,
|
||||
isRfqt: true,
|
||||
isRFQ: true,
|
||||
makerUri: 'https://rfqt2.provider.club',
|
||||
nativeOrder: rfqtOrder2.order,
|
||||
fillData: {
|
||||
@ -179,7 +179,7 @@ describe('generateQuoteReport', async () => {
|
||||
makerAmount: orderbookOrder2.order.makerAmount,
|
||||
takerAmount: orderbookOrder2.order.takerAmount,
|
||||
fillableTakerAmount: orderbookOrder2.fillableTakerAmount,
|
||||
isRfqt: false,
|
||||
isRFQ: false,
|
||||
fillData: {
|
||||
order: orderbookOrder2.order,
|
||||
} as NativeLimitOrderFillData,
|
||||
@ -263,7 +263,7 @@ describe('generateQuoteReport', async () => {
|
||||
makerAmount: orderbookOrder1.order.makerAmount,
|
||||
takerAmount: orderbookOrder1.order.takerAmount,
|
||||
fillableTakerAmount: orderbookOrder1.fillableTakerAmount,
|
||||
isRfqt: false,
|
||||
isRFQ: false,
|
||||
fillData: {
|
||||
order: orderbookOrder1.order,
|
||||
} as NativeLimitOrderFillData,
|
||||
|
@ -494,15 +494,18 @@ describe('QuoteRequestor', async () => {
|
||||
expiry: makeThreeMinuteExpiry(),
|
||||
};
|
||||
|
||||
const goodMMUri1 = 'https://1337.0.0.1';
|
||||
const goodMMUri2 = 'https://37.0.0.1';
|
||||
|
||||
mockedRequests.push({
|
||||
...mockedDefaults,
|
||||
endpoint: 'https://1337.0.0.1',
|
||||
endpoint: goodMMUri1,
|
||||
responseData: successfulQuote1,
|
||||
});
|
||||
// [GOOD] Another Successful response
|
||||
mockedRequests.push({
|
||||
...mockedDefaults,
|
||||
endpoint: 'https://37.0.0.1',
|
||||
endpoint: goodMMUri2,
|
||||
responseData: successfulQuote1,
|
||||
});
|
||||
|
||||
@ -532,6 +535,16 @@ describe('QuoteRequestor', async () => {
|
||||
responseData: { ...successfulQuote1, takerToken: otherToken1 },
|
||||
});
|
||||
|
||||
const assetOfferings: { [k: string]: [[string, string]] } = {
|
||||
'https://420.0.0.1': [[makerToken, takerToken]],
|
||||
'https://421.0.0.1': [[makerToken, takerToken]],
|
||||
'https://422.0.0.1': [[makerToken, takerToken]],
|
||||
'https://423.0.0.1': [[makerToken, takerToken]],
|
||||
'https://424.0.0.1': [[makerToken, takerToken]],
|
||||
};
|
||||
assetOfferings[goodMMUri1] = [[makerToken, takerToken]];
|
||||
assetOfferings[goodMMUri2] = [[makerToken, takerToken]];
|
||||
|
||||
return testHelpers.withMockedRfqQuotes(
|
||||
mockedRequests,
|
||||
[],
|
||||
@ -539,15 +552,7 @@ describe('QuoteRequestor', async () => {
|
||||
async () => {
|
||||
const qr = new QuoteRequestor(
|
||||
{}, // No RFQ-T asset offerings
|
||||
{
|
||||
'https://1337.0.0.1': [[makerToken, takerToken]],
|
||||
'https://37.0.0.1': [[makerToken, takerToken]],
|
||||
'https://420.0.0.1': [[makerToken, takerToken]],
|
||||
'https://421.0.0.1': [[makerToken, takerToken]],
|
||||
'https://422.0.0.1': [[makerToken, takerToken]],
|
||||
'https://423.0.0.1': [[makerToken, takerToken]],
|
||||
'https://424.0.0.1': [[makerToken, takerToken]],
|
||||
},
|
||||
assetOfferings,
|
||||
quoteRequestorHttpClient,
|
||||
);
|
||||
const resp = await qr.requestRfqmIndicativeQuotesAsync(
|
||||
@ -572,7 +577,12 @@ describe('QuoteRequestor', async () => {
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(resp.sort()).to.eql([successfulQuote1, successfulQuote1].sort());
|
||||
expect(resp.sort()).to.eql(
|
||||
[
|
||||
{ ...successfulQuote1, makerUri: goodMMUri1 },
|
||||
{ ...successfulQuote1, makerUri: goodMMUri2 },
|
||||
].sort(),
|
||||
);
|
||||
},
|
||||
quoteRequestorHttpClient,
|
||||
);
|
||||
@ -622,9 +632,12 @@ describe('QuoteRequestor', async () => {
|
||||
expiry: makeThreeMinuteExpiry(),
|
||||
};
|
||||
|
||||
const goodMMUri1 = 'https://1337.0.0.1';
|
||||
const goodMMUri2 = 'https://37.0.0.1';
|
||||
|
||||
mockedRequests.push({
|
||||
...mockedDefaults,
|
||||
endpoint: 'https://1337.0.0.1',
|
||||
endpoint: goodMMUri1,
|
||||
responseData: successfulQuote1,
|
||||
});
|
||||
// Test out a bad response code, ensure it doesnt cause throw
|
||||
@ -655,28 +668,26 @@ describe('QuoteRequestor', async () => {
|
||||
// Another Successful response
|
||||
mockedRequests.push({
|
||||
...mockedDefaults,
|
||||
endpoint: 'https://37.0.0.1',
|
||||
endpoint: goodMMUri2,
|
||||
responseData: successfulQuote1,
|
||||
});
|
||||
|
||||
const assetOfferings: { [k: string]: [[string, string]] } = {
|
||||
'https://420.0.0.1': [[makerToken, takerToken]],
|
||||
'https://421.0.0.1': [[makerToken, takerToken]],
|
||||
'https://422.0.0.1': [[makerToken, takerToken]],
|
||||
'https://423.0.0.1': [[makerToken, takerToken]],
|
||||
'https://424.0.0.1': [[makerToken, takerToken]],
|
||||
};
|
||||
assetOfferings[goodMMUri1] = [[makerToken, takerToken]];
|
||||
assetOfferings[goodMMUri2] = [[makerToken, takerToken]];
|
||||
|
||||
return testHelpers.withMockedRfqQuotes(
|
||||
mockedRequests,
|
||||
[],
|
||||
RfqQuoteEndpoint.Indicative,
|
||||
async () => {
|
||||
const qr = new QuoteRequestor(
|
||||
{
|
||||
'https://1337.0.0.1': [[makerToken, takerToken]],
|
||||
'https://420.0.0.1': [[makerToken, takerToken]],
|
||||
'https://421.0.0.1': [[makerToken, takerToken]],
|
||||
'https://422.0.0.1': [[makerToken, takerToken]],
|
||||
'https://423.0.0.1': [[makerToken, takerToken]],
|
||||
'https://424.0.0.1': [[makerToken, takerToken]],
|
||||
'https://37.0.0.1': [[makerToken, takerToken]],
|
||||
},
|
||||
{},
|
||||
quoteRequestorHttpClient,
|
||||
);
|
||||
const qr = new QuoteRequestor(assetOfferings, {}, quoteRequestorHttpClient);
|
||||
const resp = await qr.requestRfqtIndicativeQuotesAsync(
|
||||
makerToken,
|
||||
takerToken,
|
||||
@ -693,7 +704,12 @@ describe('QuoteRequestor', async () => {
|
||||
intentOnFilling: true,
|
||||
},
|
||||
);
|
||||
expect(resp.sort()).to.eql([successfulQuote1, successfulQuote1].sort());
|
||||
expect(resp.sort()).to.eql(
|
||||
[
|
||||
{ ...successfulQuote1, makerUri: goodMMUri1 },
|
||||
{ ...successfulQuote1, makerUri: goodMMUri2 },
|
||||
].sort(),
|
||||
);
|
||||
},
|
||||
quoteRequestorHttpClient,
|
||||
);
|
||||
@ -784,7 +800,7 @@ describe('QuoteRequestor', async () => {
|
||||
makerEndpointMaxResponseTimeMs: maxTimeoutMs,
|
||||
},
|
||||
);
|
||||
expect(resp.sort()).to.eql([successfulQuote1].sort()); // notice only one result, despite two requests made
|
||||
expect(resp.sort()).to.eql([{ ...successfulQuote1, makerUri: 'https://1337.0.0.1' }].sort()); // notice only one result, despite two requests made
|
||||
},
|
||||
quoteRequestorHttpClient,
|
||||
);
|
||||
@ -847,7 +863,7 @@ describe('QuoteRequestor', async () => {
|
||||
intentOnFilling: true,
|
||||
},
|
||||
);
|
||||
expect(resp.sort()).to.eql([successfulQuote1].sort());
|
||||
expect(resp.sort()).to.eql([{ ...successfulQuote1, makerUri: 'https://1337.0.0.1' }].sort());
|
||||
},
|
||||
quoteRequestorHttpClient,
|
||||
);
|
||||
|
@ -6402,6 +6402,7 @@ fake-merkle-patricia-tree@^1.0.1:
|
||||
fast-abi@^0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/fast-abi/-/fast-abi-0.0.2.tgz#da5f796fd7c7b0c966d916ee21daae3eca61c07c"
|
||||
integrity sha512-k/2s63SkFf6jU2LyF6oQC5/N+L90q6VD1wkp2NXo+DSHoTeOJD2Q6Egpcs+bTPODik0CHxjb7lORgsG+QCRq/Q==
|
||||
dependencies:
|
||||
"@mapbox/node-pre-gyp" "^1.0.4"
|
||||
neon-cli "^0.8.0"
|
||||
|
Loading…
x
Reference in New Issue
Block a user