[MKR-3] Prepare Asset Swapper for RFQM (#187)
* Prepare QuoteRequestor for RFQM * Add unit tests for Quote Requestor changes * Fix lint errors
This commit is contained in:
parent
7bf009fbf6
commit
70ddab0231
@ -66,7 +66,7 @@
|
|||||||
"@0x/dev-utils": "^4.2.1",
|
"@0x/dev-utils": "^4.2.1",
|
||||||
"@0x/json-schemas": "^5.4.1",
|
"@0x/json-schemas": "^5.4.1",
|
||||||
"@0x/protocol-utils": "^1.3.1",
|
"@0x/protocol-utils": "^1.3.1",
|
||||||
"@0x/quote-server": "^4.0.1",
|
"@0x/quote-server": "^5.0.0",
|
||||||
"@0x/types": "^3.3.1",
|
"@0x/types": "^3.3.1",
|
||||||
"@0x/typescript-typings": "^5.1.6",
|
"@0x/typescript-typings": "^5.1.6",
|
||||||
"@0x/utils": "^6.2.0",
|
"@0x/utils": "^6.2.0",
|
||||||
|
@ -358,6 +358,7 @@ export class SwapQuoter {
|
|||||||
if (calcOpts.rfqt !== undefined) {
|
if (calcOpts.rfqt !== undefined) {
|
||||||
calcOpts.rfqt.quoteRequestor = new QuoteRequestor(
|
calcOpts.rfqt.quoteRequestor = new QuoteRequestor(
|
||||||
rfqtOptions ? rfqtOptions.makerAssetOfferings || {} : {},
|
rfqtOptions ? rfqtOptions.makerAssetOfferings || {} : {},
|
||||||
|
{},
|
||||||
this._quoteRequestorHttpClient,
|
this._quoteRequestorHttpClient,
|
||||||
rfqtOptions ? rfqtOptions.altRfqCreds : undefined,
|
rfqtOptions ? rfqtOptions.altRfqCreds : undefined,
|
||||||
rfqtOptions ? rfqtOptions.warningLogger : undefined,
|
rfqtOptions ? rfqtOptions.warningLogger : undefined,
|
||||||
|
@ -240,6 +240,7 @@ export interface RfqRequestOpts {
|
|||||||
makerEndpointMaxResponseTimeMs?: number;
|
makerEndpointMaxResponseTimeMs?: number;
|
||||||
nativeExclusivelyRFQ?: boolean;
|
nativeExclusivelyRFQ?: boolean;
|
||||||
altRfqAssetOfferings?: AltRfqMakerAssetOfferings;
|
altRfqAssetOfferings?: AltRfqMakerAssetOfferings;
|
||||||
|
isLastLook?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -257,7 +258,6 @@ export interface SwapQuoteRequestOpts extends GetMarketOrdersOpts {
|
|||||||
export interface RfqMakerAssetOfferings {
|
export interface RfqMakerAssetOfferings {
|
||||||
[endpoint: string]: Array<[string, string]>;
|
[endpoint: string]: Array<[string, string]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AltOffering {
|
export interface AltOffering {
|
||||||
id: string;
|
id: string;
|
||||||
baseAsset: string;
|
baseAsset: string;
|
||||||
|
@ -83,6 +83,7 @@ export class QuoteRequestor {
|
|||||||
sellTokenAddress: string, // taker token
|
sellTokenAddress: string, // taker token
|
||||||
assetFillAmount: BigNumber,
|
assetFillAmount: BigNumber,
|
||||||
comparisonPrice?: BigNumber,
|
comparisonPrice?: BigNumber,
|
||||||
|
isLastLook: boolean = false,
|
||||||
): TakerRequestQueryParams {
|
): TakerRequestQueryParams {
|
||||||
const { buyAmountBaseUnits, sellAmountBaseUnits } =
|
const { buyAmountBaseUnits, sellAmountBaseUnits } =
|
||||||
marketOperation === MarketOperation.Buy
|
marketOperation === MarketOperation.Buy
|
||||||
@ -97,13 +98,20 @@ export class QuoteRequestor {
|
|||||||
|
|
||||||
const requestParamsWithBigNumbers: Pick<
|
const requestParamsWithBigNumbers: Pick<
|
||||||
TakerRequestQueryParams,
|
TakerRequestQueryParams,
|
||||||
'buyTokenAddress' | 'sellTokenAddress' | 'txOrigin' | 'comparisonPrice' | 'protocolVersion' | 'takerAddress'
|
| 'txOrigin'
|
||||||
|
| 'takerAddress'
|
||||||
|
| 'buyTokenAddress'
|
||||||
|
| 'sellTokenAddress'
|
||||||
|
| 'comparisonPrice'
|
||||||
|
| 'isLastLook'
|
||||||
|
| 'protocolVersion'
|
||||||
> = {
|
> = {
|
||||||
txOrigin,
|
txOrigin,
|
||||||
takerAddress,
|
takerAddress,
|
||||||
comparisonPrice: comparisonPrice === undefined ? undefined : comparisonPrice.toString(),
|
|
||||||
buyTokenAddress,
|
buyTokenAddress,
|
||||||
sellTokenAddress,
|
sellTokenAddress,
|
||||||
|
comparisonPrice: comparisonPrice === undefined ? undefined : comparisonPrice.toString(),
|
||||||
|
isLastLook: isLastLook.toString(),
|
||||||
protocolVersion: '4',
|
protocolVersion: '4',
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -124,8 +132,38 @@ export class QuoteRequestor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static _makerSupportsPair(
|
||||||
|
typedMakerUrl: TypedMakerUrl,
|
||||||
|
makerToken: string,
|
||||||
|
takerToken: string,
|
||||||
|
altMakerAssetOfferings: AltRfqMakerAssetOfferings | undefined,
|
||||||
|
assetOfferings: RfqMakerAssetOfferings | undefined,
|
||||||
|
): boolean {
|
||||||
|
if (typedMakerUrl.pairType === RfqPairType.Standard && assetOfferings) {
|
||||||
|
for (const assetPair of assetOfferings[typedMakerUrl.url]) {
|
||||||
|
if (
|
||||||
|
(assetPair[0] === makerToken && assetPair[1] === takerToken) ||
|
||||||
|
(assetPair[0] === takerToken && assetPair[1] === makerToken)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (typedMakerUrl.pairType === RfqPairType.Alt && altMakerAssetOfferings) {
|
||||||
|
for (const altAssetPair of altMakerAssetOfferings[typedMakerUrl.url]) {
|
||||||
|
if (
|
||||||
|
(altAssetPair.baseAsset === makerToken && altAssetPair.quoteAsset === takerToken) ||
|
||||||
|
(altAssetPair.baseAsset === takerToken && altAssetPair.quoteAsset === makerToken)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _rfqtAssetOfferings: RfqMakerAssetOfferings,
|
private readonly _rfqtAssetOfferings: RfqMakerAssetOfferings,
|
||||||
|
private readonly _rfqmAssetOfferings: RfqMakerAssetOfferings,
|
||||||
private readonly _quoteRequestorHttpClient: AxiosInstance,
|
private readonly _quoteRequestorHttpClient: AxiosInstance,
|
||||||
private readonly _altRfqCreds?: { altRfqApiKey: string; altRfqProfile: string },
|
private readonly _altRfqCreds?: { altRfqApiKey: string; altRfqProfile: string },
|
||||||
private readonly _warningLogger: LogFunction = constants.DEFAULT_WARNING_LOGGER,
|
private readonly _warningLogger: LogFunction = constants.DEFAULT_WARNING_LOGGER,
|
||||||
@ -135,6 +173,31 @@ export class QuoteRequestor {
|
|||||||
rfqMakerBlacklist.infoLogger = this._infoLogger;
|
rfqMakerBlacklist.infoLogger = this._infoLogger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async requestRfqmFirmQuotesAsync(
|
||||||
|
makerToken: string, // maker token
|
||||||
|
takerToken: string, // taker token
|
||||||
|
assetFillAmount: BigNumber,
|
||||||
|
marketOperation: MarketOperation,
|
||||||
|
comparisonPrice: BigNumber | undefined,
|
||||||
|
options: RfqRequestOpts,
|
||||||
|
): Promise<SignedNativeOrder[]> {
|
||||||
|
const _opts: RfqRequestOpts = {
|
||||||
|
...constants.DEFAULT_RFQT_REQUEST_OPTS,
|
||||||
|
...options,
|
||||||
|
isLastLook: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
return this._fetchAndValidateFirmQuotesAsync(
|
||||||
|
makerToken,
|
||||||
|
takerToken,
|
||||||
|
assetFillAmount,
|
||||||
|
marketOperation,
|
||||||
|
comparisonPrice,
|
||||||
|
_opts,
|
||||||
|
this._rfqmAssetOfferings,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public async requestRfqtFirmQuotesAsync(
|
public async requestRfqtFirmQuotesAsync(
|
||||||
makerToken: string, // maker token
|
makerToken: string, // maker token
|
||||||
takerToken: string, // taker token
|
takerToken: string, // taker token
|
||||||
@ -148,72 +211,40 @@ export class QuoteRequestor {
|
|||||||
throw new Error('RFQ-T firm quotes require the presence of a tx origin');
|
throw new Error('RFQ-T firm quotes require the presence of a tx origin');
|
||||||
}
|
}
|
||||||
|
|
||||||
const quotesRaw = await this._getQuotesAsync<V4RFQFirmQuote>(
|
return this._fetchAndValidateFirmQuotesAsync(
|
||||||
makerToken,
|
makerToken,
|
||||||
takerToken,
|
takerToken,
|
||||||
assetFillAmount,
|
assetFillAmount,
|
||||||
marketOperation,
|
marketOperation,
|
||||||
comparisonPrice,
|
comparisonPrice,
|
||||||
_opts,
|
_opts,
|
||||||
'firm',
|
this._rfqtAssetOfferings,
|
||||||
);
|
);
|
||||||
const quotes = quotesRaw.map(result => ({ ...result, response: result.response.signedOrder }));
|
}
|
||||||
|
|
||||||
// validate
|
public async requestRfqmIndicativeQuotesAsync(
|
||||||
const validationFunction = (o: V4SignedRfqOrder) => {
|
makerToken: string,
|
||||||
try {
|
takerToken: string,
|
||||||
// Handle the validate throwing, i.e if it isn't an object or json response
|
assetFillAmount: BigNumber,
|
||||||
return this._schemaValidator.isValid(o, schemas.v4RfqSignedOrderSchema);
|
marketOperation: MarketOperation,
|
||||||
} catch (e) {
|
comparisonPrice: BigNumber | undefined,
|
||||||
return false;
|
options: RfqRequestOpts,
|
||||||
}
|
): Promise<V4RFQIndicativeQuote[]> {
|
||||||
|
const _opts: RfqRequestOpts = {
|
||||||
|
...constants.DEFAULT_RFQT_REQUEST_OPTS,
|
||||||
|
...options,
|
||||||
|
isLastLook: true,
|
||||||
};
|
};
|
||||||
const validQuotes = quotes.filter(result => {
|
|
||||||
const order = result.response;
|
return this._fetchAndValidateIndicativeQuotesAsync(
|
||||||
if (!validationFunction(order)) {
|
makerToken,
|
||||||
this._warningLogger(result, 'Invalid RFQ-T firm quote received, filtering out');
|
takerToken,
|
||||||
return false;
|
assetFillAmount,
|
||||||
}
|
marketOperation,
|
||||||
if (
|
comparisonPrice,
|
||||||
!hasExpectedAddresses([
|
_opts,
|
||||||
[makerToken, order.makerToken],
|
this._rfqmAssetOfferings,
|
||||||
[takerToken, order.takerToken],
|
|
||||||
[_opts.takerAddress, order.taker],
|
|
||||||
[_opts.txOrigin, order.txOrigin],
|
|
||||||
])
|
|
||||||
) {
|
|
||||||
this._warningLogger(
|
|
||||||
order,
|
|
||||||
'Unexpected token, tx origin or taker address in RFQ-T order, filtering out',
|
|
||||||
);
|
);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (this._isExpirationTooSoon(new BigNumber(order.expiry))) {
|
|
||||||
this._warningLogger(order, 'Expiry too soon in RFQ-T firm quote, filtering out');
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Save the maker URI for later and return just the order
|
|
||||||
const rfqQuotes = validQuotes.map(result => {
|
|
||||||
const { signature, ...rest } = result.response;
|
|
||||||
const order: SignedNativeOrder = {
|
|
||||||
order: {
|
|
||||||
...rest,
|
|
||||||
makerAmount: new BigNumber(result.response.makerAmount),
|
|
||||||
takerAmount: new BigNumber(result.response.takerAmount),
|
|
||||||
expiry: new BigNumber(result.response.expiry),
|
|
||||||
salt: new BigNumber(result.response.salt),
|
|
||||||
},
|
|
||||||
type: FillQuoteTransformerOrderType.Rfq,
|
|
||||||
signature,
|
|
||||||
};
|
|
||||||
this._orderSignatureToMakerUri[nativeDataToId(result.response)] = result.makerUri;
|
|
||||||
return order;
|
|
||||||
});
|
|
||||||
return rfqQuotes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async requestRfqtIndicativeQuotesAsync(
|
public async requestRfqtIndicativeQuotesAsync(
|
||||||
@ -236,42 +267,15 @@ export class QuoteRequestor {
|
|||||||
if (!_opts.txOrigin) {
|
if (!_opts.txOrigin) {
|
||||||
_opts.txOrigin = constants.NULL_ADDRESS;
|
_opts.txOrigin = constants.NULL_ADDRESS;
|
||||||
}
|
}
|
||||||
const rawQuotes = await this._getQuotesAsync<V4RFQIndicativeQuote>(
|
return this._fetchAndValidateIndicativeQuotesAsync(
|
||||||
makerToken,
|
makerToken,
|
||||||
takerToken,
|
takerToken,
|
||||||
assetFillAmount,
|
assetFillAmount,
|
||||||
marketOperation,
|
marketOperation,
|
||||||
comparisonPrice,
|
comparisonPrice,
|
||||||
_opts,
|
_opts,
|
||||||
'indicative',
|
this._rfqtAssetOfferings,
|
||||||
);
|
);
|
||||||
|
|
||||||
// validate
|
|
||||||
const validationFunction = (o: V4RFQIndicativeQuote) => this._isValidRfqtIndicativeQuoteResponse(o);
|
|
||||||
const validQuotes = rawQuotes.filter(result => {
|
|
||||||
const order = result.response;
|
|
||||||
if (!validationFunction(order)) {
|
|
||||||
this._warningLogger(result, 'Invalid RFQ-T indicative quote received, filtering out');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!hasExpectedAddresses([[makerToken, order.makerToken], [takerToken, order.takerToken]])) {
|
|
||||||
this._warningLogger(order, 'Unexpected token or taker address in RFQ-T order, filtering out');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (this._isExpirationTooSoon(new BigNumber(order.expiry))) {
|
|
||||||
this._warningLogger(order, 'Expiry too soon in RFQ-T indicative quote, filtering out');
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const quotes = validQuotes.map(r => r.response);
|
|
||||||
quotes.forEach(q => {
|
|
||||||
q.makerAmount = new BigNumber(q.makerAmount);
|
|
||||||
q.takerAmount = new BigNumber(q.takerAmount);
|
|
||||||
q.expiry = new BigNumber(q.expiry);
|
|
||||||
});
|
|
||||||
return quotes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -313,34 +317,6 @@ export class QuoteRequestor {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _makerSupportsPair(
|
|
||||||
typedMakerUrl: TypedMakerUrl,
|
|
||||||
makerToken: string,
|
|
||||||
takerToken: string,
|
|
||||||
altMakerAssetOfferings: AltRfqMakerAssetOfferings | undefined,
|
|
||||||
): boolean {
|
|
||||||
if (typedMakerUrl.pairType === RfqPairType.Standard) {
|
|
||||||
for (const assetPair of this._rfqtAssetOfferings[typedMakerUrl.url]) {
|
|
||||||
if (
|
|
||||||
(assetPair[0] === makerToken && assetPair[1] === takerToken) ||
|
|
||||||
(assetPair[0] === takerToken && assetPair[1] === makerToken)
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (typedMakerUrl.pairType === RfqPairType.Alt && altMakerAssetOfferings) {
|
|
||||||
for (const altAssetPair of altMakerAssetOfferings[typedMakerUrl.url]) {
|
|
||||||
if (
|
|
||||||
(altAssetPair.baseAsset === makerToken && altAssetPair.quoteAsset === takerToken) ||
|
|
||||||
(altAssetPair.baseAsset === takerToken && altAssetPair.quoteAsset === makerToken)
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _isExpirationTooSoon(expirationTimeSeconds: BigNumber): boolean {
|
private _isExpirationTooSoon(expirationTimeSeconds: BigNumber): boolean {
|
||||||
const expirationTimeMs = expirationTimeSeconds.times(constants.ONE_SECOND_MS);
|
const expirationTimeMs = expirationTimeSeconds.times(constants.ONE_SECOND_MS);
|
||||||
const currentTimeMs = new BigNumber(Date.now());
|
const currentTimeMs = new BigNumber(Date.now());
|
||||||
@ -355,6 +331,7 @@ export class QuoteRequestor {
|
|||||||
comparisonPrice: BigNumber | undefined,
|
comparisonPrice: BigNumber | undefined,
|
||||||
options: RfqRequestOpts,
|
options: RfqRequestOpts,
|
||||||
quoteType: 'firm' | 'indicative',
|
quoteType: 'firm' | 'indicative',
|
||||||
|
assetOfferings: RfqMakerAssetOfferings,
|
||||||
): Promise<Array<RfqQuote<ResponseT>>> {
|
): Promise<Array<RfqQuote<ResponseT>>> {
|
||||||
const requestParams = QuoteRequestor.makeQueryParameters(
|
const requestParams = QuoteRequestor.makeQueryParameters(
|
||||||
options.txOrigin,
|
options.txOrigin,
|
||||||
@ -364,6 +341,7 @@ export class QuoteRequestor {
|
|||||||
takerToken,
|
takerToken,
|
||||||
assetFillAmount,
|
assetFillAmount,
|
||||||
comparisonPrice,
|
comparisonPrice,
|
||||||
|
options.isLastLook,
|
||||||
);
|
);
|
||||||
|
|
||||||
const quotePath = (() => {
|
const quotePath = (() => {
|
||||||
@ -377,7 +355,7 @@ export class QuoteRequestor {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const standardUrls = Object.keys(this._rfqtAssetOfferings).map(
|
const standardUrls = Object.keys(assetOfferings).map(
|
||||||
(mm: string): TypedMakerUrl => {
|
(mm: string): TypedMakerUrl => {
|
||||||
return { pairType: RfqPairType.Standard, url: mm };
|
return { pairType: RfqPairType.Standard, url: mm };
|
||||||
},
|
},
|
||||||
@ -410,7 +388,15 @@ export class QuoteRequestor {
|
|||||||
if (isBlacklisted) {
|
if (isBlacklisted) {
|
||||||
this._infoLogger({ rfqtMakerInteraction: { ...partialLogEntry } });
|
this._infoLogger({ rfqtMakerInteraction: { ...partialLogEntry } });
|
||||||
return;
|
return;
|
||||||
} else if (!this._makerSupportsPair(typedMakerUrl, makerToken, takerToken, options.altRfqAssetOfferings)) {
|
} else if (
|
||||||
|
!QuoteRequestor._makerSupportsPair(
|
||||||
|
typedMakerUrl,
|
||||||
|
makerToken,
|
||||||
|
takerToken,
|
||||||
|
options.altRfqAssetOfferings,
|
||||||
|
assetOfferings,
|
||||||
|
)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
// make request to MM
|
// make request to MM
|
||||||
@ -509,4 +495,130 @@ export class QuoteRequestor {
|
|||||||
const results = (await Promise.all(quotePromises)).filter(x => x !== undefined);
|
const results = (await Promise.all(quotePromises)).filter(x => x !== undefined);
|
||||||
return results as Array<RfqQuote<ResponseT>>;
|
return results as Array<RfqQuote<ResponseT>>;
|
||||||
}
|
}
|
||||||
|
private async _fetchAndValidateFirmQuotesAsync(
|
||||||
|
makerToken: string,
|
||||||
|
takerToken: string,
|
||||||
|
assetFillAmount: BigNumber,
|
||||||
|
marketOperation: MarketOperation,
|
||||||
|
comparisonPrice: BigNumber | undefined,
|
||||||
|
options: RfqRequestOpts,
|
||||||
|
assetOfferings: RfqMakerAssetOfferings,
|
||||||
|
): Promise<SignedNativeOrder[]> {
|
||||||
|
const quotesRaw = await this._getQuotesAsync<V4RFQFirmQuote>(
|
||||||
|
makerToken,
|
||||||
|
takerToken,
|
||||||
|
assetFillAmount,
|
||||||
|
marketOperation,
|
||||||
|
comparisonPrice,
|
||||||
|
options,
|
||||||
|
'firm',
|
||||||
|
assetOfferings,
|
||||||
|
);
|
||||||
|
const quotes = quotesRaw.map(result => ({ ...result, response: result.response.signedOrder }));
|
||||||
|
|
||||||
|
// validate
|
||||||
|
const validationFunction = (o: V4SignedRfqOrder) => {
|
||||||
|
try {
|
||||||
|
// Handle the validate throwing, i.e if it isn't an object or json response
|
||||||
|
return this._schemaValidator.isValid(o, schemas.v4RfqSignedOrderSchema);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const validQuotes = quotes.filter(result => {
|
||||||
|
const order = result.response;
|
||||||
|
if (!validationFunction(order)) {
|
||||||
|
this._warningLogger(result, 'Invalid RFQ-T firm quote received, filtering out');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!hasExpectedAddresses([
|
||||||
|
[makerToken, order.makerToken],
|
||||||
|
[takerToken, order.takerToken],
|
||||||
|
[options.takerAddress, order.taker],
|
||||||
|
[options.txOrigin, order.txOrigin],
|
||||||
|
])
|
||||||
|
) {
|
||||||
|
this._warningLogger(
|
||||||
|
order,
|
||||||
|
'Unexpected token, tx origin or taker address in RFQ-T order, filtering out',
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this._isExpirationTooSoon(new BigNumber(order.expiry))) {
|
||||||
|
this._warningLogger(order, 'Expiry too soon in RFQ-T firm quote, filtering out');
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save the maker URI for later and return just the order
|
||||||
|
const rfqQuotes = validQuotes.map(result => {
|
||||||
|
const { signature, ...rest } = result.response;
|
||||||
|
const order: SignedNativeOrder = {
|
||||||
|
order: {
|
||||||
|
...rest,
|
||||||
|
makerAmount: new BigNumber(result.response.makerAmount),
|
||||||
|
takerAmount: new BigNumber(result.response.takerAmount),
|
||||||
|
expiry: new BigNumber(result.response.expiry),
|
||||||
|
salt: new BigNumber(result.response.salt),
|
||||||
|
},
|
||||||
|
type: FillQuoteTransformerOrderType.Rfq,
|
||||||
|
signature,
|
||||||
|
};
|
||||||
|
this._orderSignatureToMakerUri[nativeDataToId(result.response)] = result.makerUri;
|
||||||
|
return order;
|
||||||
|
});
|
||||||
|
return rfqQuotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _fetchAndValidateIndicativeQuotesAsync(
|
||||||
|
makerToken: string,
|
||||||
|
takerToken: string,
|
||||||
|
assetFillAmount: BigNumber,
|
||||||
|
marketOperation: MarketOperation,
|
||||||
|
comparisonPrice: BigNumber | undefined,
|
||||||
|
options: RfqRequestOpts,
|
||||||
|
assetOfferings: RfqMakerAssetOfferings,
|
||||||
|
): Promise<V4RFQIndicativeQuote[]> {
|
||||||
|
// fetch quotes
|
||||||
|
const rawQuotes = await this._getQuotesAsync<V4RFQIndicativeQuote>(
|
||||||
|
makerToken,
|
||||||
|
takerToken,
|
||||||
|
assetFillAmount,
|
||||||
|
marketOperation,
|
||||||
|
comparisonPrice,
|
||||||
|
options,
|
||||||
|
'indicative',
|
||||||
|
assetOfferings,
|
||||||
|
);
|
||||||
|
|
||||||
|
// validate
|
||||||
|
const validationFunction = (o: V4RFQIndicativeQuote) => this._isValidRfqtIndicativeQuoteResponse(o);
|
||||||
|
const validQuotes = rawQuotes.filter(result => {
|
||||||
|
const order = result.response;
|
||||||
|
if (!validationFunction(order)) {
|
||||||
|
this._warningLogger(result, 'Invalid RFQ indicative quote received, filtering out');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!hasExpectedAddresses([[makerToken, order.makerToken], [takerToken, order.takerToken]])) {
|
||||||
|
this._warningLogger(order, 'Unexpected token or taker address in RFQ order, filtering out');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this._isExpirationTooSoon(new BigNumber(order.expiry))) {
|
||||||
|
this._warningLogger(order, 'Expiry too soon in RFQ indicative quote, filtering out');
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const quotes = validQuotes.map(r => r.response);
|
||||||
|
quotes.forEach(q => {
|
||||||
|
q.makerAmount = new BigNumber(q.makerAmount);
|
||||||
|
q.takerAmount = new BigNumber(q.takerAmount);
|
||||||
|
q.expiry = new BigNumber(q.expiry);
|
||||||
|
});
|
||||||
|
return quotes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ import { NULL_ADDRESS } from '../src/utils/market_operation_utils/constants';
|
|||||||
import { QuoteRequestor } from '../src/utils/quote_requestor';
|
import { QuoteRequestor } from '../src/utils/quote_requestor';
|
||||||
|
|
||||||
import { chaiSetup } from './utils/chai_setup';
|
import { chaiSetup } from './utils/chai_setup';
|
||||||
import { RfqtQuoteEndpoint, testHelpers } from './utils/test_helpers';
|
import { RfqQuoteEndpoint, testHelpers } from './utils/test_helpers';
|
||||||
|
|
||||||
const quoteRequestorHttpClient = Axios.create({
|
const quoteRequestorHttpClient = Axios.create({
|
||||||
httpAgent: new HttpAgent({ keepAlive: true, timeout: KEEP_ALIVE_TTL }),
|
httpAgent: new HttpAgent({ keepAlive: true, timeout: KEEP_ALIVE_TTL }),
|
||||||
@ -64,6 +64,196 @@ describe('QuoteRequestor', async () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
describe('requestRfqmFirmQuotesAsync for firm quotes', async () => {
|
||||||
|
it('should return successful RFQM requests', async () => {
|
||||||
|
const takerAddress = '0xd209925defc99488e3afff1174e48b4fa628302a';
|
||||||
|
const txOrigin = takerAddress;
|
||||||
|
const apiKey = 'my-ko0l-api-key';
|
||||||
|
|
||||||
|
// Set up RFQM responses
|
||||||
|
// tslint:disable-next-line:array-type
|
||||||
|
const mockedRequests: MockedRfqQuoteResponse[] = [];
|
||||||
|
const altMockedRequests: AltMockedRfqQuoteResponse[] = [];
|
||||||
|
|
||||||
|
const expectedParams: TakerRequestQueryParams = {
|
||||||
|
sellTokenAddress: takerToken,
|
||||||
|
buyTokenAddress: makerToken,
|
||||||
|
sellAmountBaseUnits: '10000',
|
||||||
|
comparisonPrice: undefined,
|
||||||
|
takerAddress,
|
||||||
|
txOrigin,
|
||||||
|
isLastLook: 'true', // the major difference between RFQ-T and RFQ-M
|
||||||
|
protocolVersion: '4',
|
||||||
|
};
|
||||||
|
const mockedDefaults = {
|
||||||
|
requestApiKey: apiKey,
|
||||||
|
requestParams: expectedParams,
|
||||||
|
responseCode: StatusCodes.Success,
|
||||||
|
};
|
||||||
|
const validSignedOrder = {
|
||||||
|
makerToken,
|
||||||
|
takerToken,
|
||||||
|
makerAmount: new BigNumber('1000'),
|
||||||
|
takerAmount: new BigNumber('1000'),
|
||||||
|
maker: takerAddress,
|
||||||
|
taker: takerAddress,
|
||||||
|
pool: '0x',
|
||||||
|
salt: '0',
|
||||||
|
chainId: 1,
|
||||||
|
verifyingContract: takerAddress,
|
||||||
|
txOrigin,
|
||||||
|
expiry: makeThreeMinuteExpiry(),
|
||||||
|
signature: validSignature,
|
||||||
|
};
|
||||||
|
// request is to sell 10000 units of the base token
|
||||||
|
// 10 units at 3 decimals
|
||||||
|
const altFirmRequestData = {
|
||||||
|
market: 'XYZ-123',
|
||||||
|
model: AltQuoteModel.Firm,
|
||||||
|
profile: ALT_PROFILE,
|
||||||
|
side: AltQuoteSide.Sell,
|
||||||
|
meta: {
|
||||||
|
txOrigin,
|
||||||
|
taker: takerAddress,
|
||||||
|
client: apiKey,
|
||||||
|
},
|
||||||
|
value: '10',
|
||||||
|
};
|
||||||
|
const altFirmResponse = {
|
||||||
|
...altFirmRequestData,
|
||||||
|
id: 'random_id',
|
||||||
|
// tslint:disable-next-line:custom-no-magic-numbers
|
||||||
|
price: new BigNumber(10 / 100).toString(),
|
||||||
|
status: 'active',
|
||||||
|
data: {
|
||||||
|
'0xv4order': validSignedOrder,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// [GOOD] Successful response
|
||||||
|
mockedRequests.push({
|
||||||
|
...mockedDefaults,
|
||||||
|
endpoint: 'https://1337.0.0.1',
|
||||||
|
responseData: {
|
||||||
|
signedOrder: validSignedOrder,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// [GOOD] Another Successful response
|
||||||
|
mockedRequests.push({
|
||||||
|
...mockedDefaults,
|
||||||
|
endpoint: 'https://37.0.0.1',
|
||||||
|
responseData: { signedOrder: validSignedOrder },
|
||||||
|
});
|
||||||
|
// [BAD] Test out a bad response code, ensure it doesnt cause throw
|
||||||
|
mockedRequests.push({
|
||||||
|
...mockedDefaults,
|
||||||
|
endpoint: 'https://420.0.0.1',
|
||||||
|
responseData: { error: 'bad request' },
|
||||||
|
responseCode: StatusCodes.InternalError,
|
||||||
|
});
|
||||||
|
// [BAD] Test out a successful response code but a partial order
|
||||||
|
mockedRequests.push({
|
||||||
|
...mockedDefaults,
|
||||||
|
endpoint: 'https://421.0.0.1',
|
||||||
|
responseData: { signedOrder: { makerToken: '123' } },
|
||||||
|
});
|
||||||
|
// [BAD] A successful response code and invalid response data (encoding)
|
||||||
|
mockedRequests.push({
|
||||||
|
...mockedDefaults,
|
||||||
|
endpoint: 'https://421.1.0.1',
|
||||||
|
responseData: 'this is not JSON!',
|
||||||
|
});
|
||||||
|
// [BAD] A successful response code and valid order, but for wrong maker asset data
|
||||||
|
mockedRequests.push({
|
||||||
|
...mockedDefaults,
|
||||||
|
endpoint: 'https://422.0.0.1',
|
||||||
|
responseData: { signedOrder: { ...validSignedOrder, makerToken: '0x1234' } },
|
||||||
|
});
|
||||||
|
// [BAD] A successful response code and valid order, but for wrong taker asset data
|
||||||
|
mockedRequests.push({
|
||||||
|
...mockedDefaults,
|
||||||
|
endpoint: 'https://423.0.0.1',
|
||||||
|
responseData: { signedOrder: { ...validSignedOrder, takerToken: '0x1234' } },
|
||||||
|
});
|
||||||
|
// [BAD] A successful response code and good order but its unsigned
|
||||||
|
mockedRequests.push({
|
||||||
|
...mockedDefaults,
|
||||||
|
endpoint: 'https://424.0.0.1',
|
||||||
|
responseData: { signedOrder: _.omit(validSignedOrder, ['signature']) },
|
||||||
|
});
|
||||||
|
// [BAD] A successful response code and good order but for the wrong txOrigin
|
||||||
|
mockedRequests.push({
|
||||||
|
...mockedDefaults,
|
||||||
|
endpoint: 'https://425.0.0.1',
|
||||||
|
responseData: { signedOrder: { ...validSignedOrder, txOrigin: NULL_ADDRESS } },
|
||||||
|
});
|
||||||
|
// [GOOD] A successful response code and order from an alt RFQ implementation
|
||||||
|
altMockedRequests.push({
|
||||||
|
endpoint: 'https://132.0.0.1',
|
||||||
|
mmApiKey: ALT_MM_API_KEY,
|
||||||
|
responseCode: CREATED_STATUS_CODE,
|
||||||
|
requestData: altFirmRequestData,
|
||||||
|
responseData: altFirmResponse,
|
||||||
|
});
|
||||||
|
|
||||||
|
const normalizedSuccessfulOrder = {
|
||||||
|
order: {
|
||||||
|
..._.omit(validSignedOrder, ['signature']),
|
||||||
|
makerAmount: new BigNumber(validSignedOrder.makerAmount),
|
||||||
|
takerAmount: new BigNumber(validSignedOrder.takerAmount),
|
||||||
|
expiry: new BigNumber(validSignedOrder.expiry),
|
||||||
|
salt: new BigNumber(validSignedOrder.salt),
|
||||||
|
},
|
||||||
|
signature: validSignedOrder.signature,
|
||||||
|
type: FillQuoteTransformerOrderType.Rfq,
|
||||||
|
};
|
||||||
|
|
||||||
|
return testHelpers.withMockedRfqQuotes(
|
||||||
|
mockedRequests,
|
||||||
|
altMockedRequests,
|
||||||
|
RfqQuoteEndpoint.Firm,
|
||||||
|
async () => {
|
||||||
|
const qr = new QuoteRequestor(
|
||||||
|
{}, // No RFQ-T asset offerings
|
||||||
|
{
|
||||||
|
'https://1337.0.0.1': [[makerToken, takerToken]],
|
||||||
|
'https://420.0.0.1': [[makerToken, takerToken]],
|
||||||
|
'https://421.0.0.1': [[makerToken, takerToken]],
|
||||||
|
'https://421.1.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://425.0.0.1': [[makerToken, takerToken]],
|
||||||
|
'https://426.0.0.1': [] /* Shouldn't ping an RFQ provider when they don't support the requested asset pair. */,
|
||||||
|
'https://37.0.0.1': [[makerToken, takerToken]],
|
||||||
|
},
|
||||||
|
quoteRequestorHttpClient,
|
||||||
|
ALT_RFQ_CREDS,
|
||||||
|
);
|
||||||
|
const resp = await qr.requestRfqmFirmQuotesAsync(
|
||||||
|
makerToken,
|
||||||
|
takerToken,
|
||||||
|
new BigNumber(10000),
|
||||||
|
MarketOperation.Sell,
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
apiKey,
|
||||||
|
takerAddress,
|
||||||
|
txOrigin: takerAddress,
|
||||||
|
intentOnFilling: true,
|
||||||
|
altRfqAssetOfferings,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(resp).to.deep.eq([
|
||||||
|
normalizedSuccessfulOrder,
|
||||||
|
normalizedSuccessfulOrder,
|
||||||
|
normalizedSuccessfulOrder,
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
quoteRequestorHttpClient,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('requestRfqtFirmQuotesAsync for firm quotes', async () => {
|
describe('requestRfqtFirmQuotesAsync for firm quotes', async () => {
|
||||||
it('should return successful RFQT requests', async () => {
|
it('should return successful RFQT requests', async () => {
|
||||||
const takerAddress = '0xd209925defc99488e3afff1174e48b4fa628302a';
|
const takerAddress = '0xd209925defc99488e3afff1174e48b4fa628302a';
|
||||||
@ -82,6 +272,7 @@ describe('QuoteRequestor', async () => {
|
|||||||
comparisonPrice: undefined,
|
comparisonPrice: undefined,
|
||||||
takerAddress,
|
takerAddress,
|
||||||
txOrigin,
|
txOrigin,
|
||||||
|
isLastLook: 'false',
|
||||||
protocolVersion: '4',
|
protocolVersion: '4',
|
||||||
};
|
};
|
||||||
const mockedDefaults = {
|
const mockedDefaults = {
|
||||||
@ -207,10 +398,10 @@ describe('QuoteRequestor', async () => {
|
|||||||
type: FillQuoteTransformerOrderType.Rfq,
|
type: FillQuoteTransformerOrderType.Rfq,
|
||||||
};
|
};
|
||||||
|
|
||||||
return testHelpers.withMockedRfqtQuotes(
|
return testHelpers.withMockedRfqQuotes(
|
||||||
mockedRequests,
|
mockedRequests,
|
||||||
altMockedRequests,
|
altMockedRequests,
|
||||||
RfqtQuoteEndpoint.Firm,
|
RfqQuoteEndpoint.Firm,
|
||||||
async () => {
|
async () => {
|
||||||
const qr = new QuoteRequestor(
|
const qr = new QuoteRequestor(
|
||||||
{
|
{
|
||||||
@ -225,6 +416,7 @@ describe('QuoteRequestor', async () => {
|
|||||||
'https://426.0.0.1': [] /* Shouldn't ping an RFQ-T provider when they don't support the requested asset pair. */,
|
'https://426.0.0.1': [] /* Shouldn't ping an RFQ-T provider when they don't support the requested asset pair. */,
|
||||||
'https://37.0.0.1': [[makerToken, takerToken]],
|
'https://37.0.0.1': [[makerToken, takerToken]],
|
||||||
},
|
},
|
||||||
|
{},
|
||||||
quoteRequestorHttpClient,
|
quoteRequestorHttpClient,
|
||||||
ALT_RFQ_CREDS,
|
ALT_RFQ_CREDS,
|
||||||
);
|
);
|
||||||
@ -252,6 +444,114 @@ describe('QuoteRequestor', async () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('requestRfqmIndicativeQuotesAsync for Indicative quotes', async () => {
|
||||||
|
it('should return successful RFQM requests', async () => {
|
||||||
|
const takerAddress = '0xd209925defc99488e3afff1174e48b4fa628302a';
|
||||||
|
const apiKey = 'my-ko0l-api-key';
|
||||||
|
|
||||||
|
// Set up RFQ responses
|
||||||
|
// tslint:disable-next-line:array-type
|
||||||
|
const mockedRequests: MockedRfqQuoteResponse[] = [];
|
||||||
|
const expectedParams: TakerRequestQueryParams = {
|
||||||
|
sellTokenAddress: takerToken,
|
||||||
|
buyTokenAddress: makerToken,
|
||||||
|
sellAmountBaseUnits: '10000',
|
||||||
|
comparisonPrice: undefined,
|
||||||
|
takerAddress,
|
||||||
|
txOrigin: takerAddress,
|
||||||
|
isLastLook: 'true', // the major difference between RFQ-T and RFQ-M
|
||||||
|
protocolVersion: '4',
|
||||||
|
};
|
||||||
|
const mockedDefaults = {
|
||||||
|
requestApiKey: apiKey,
|
||||||
|
requestParams: expectedParams,
|
||||||
|
responseCode: StatusCodes.Success,
|
||||||
|
};
|
||||||
|
|
||||||
|
// [GOOD] Successful response
|
||||||
|
const successfulQuote1 = {
|
||||||
|
makerToken,
|
||||||
|
takerToken,
|
||||||
|
makerAmount: new BigNumber(expectedParams.sellAmountBaseUnits),
|
||||||
|
takerAmount: new BigNumber(expectedParams.sellAmountBaseUnits),
|
||||||
|
expiry: makeThreeMinuteExpiry(),
|
||||||
|
};
|
||||||
|
|
||||||
|
mockedRequests.push({
|
||||||
|
...mockedDefaults,
|
||||||
|
endpoint: 'https://1337.0.0.1',
|
||||||
|
responseData: successfulQuote1,
|
||||||
|
});
|
||||||
|
// [GOOD] Another Successful response
|
||||||
|
mockedRequests.push({
|
||||||
|
...mockedDefaults,
|
||||||
|
endpoint: 'https://37.0.0.1',
|
||||||
|
responseData: successfulQuote1,
|
||||||
|
});
|
||||||
|
|
||||||
|
// [BAD] Test out a bad response code, ensure it doesnt cause throw
|
||||||
|
mockedRequests.push({
|
||||||
|
...mockedDefaults,
|
||||||
|
endpoint: 'https://420.0.0.1',
|
||||||
|
responseData: { error: 'bad request' },
|
||||||
|
responseCode: StatusCodes.InternalError,
|
||||||
|
});
|
||||||
|
// [BAD] Test out a successful response code but an invalid order
|
||||||
|
mockedRequests.push({
|
||||||
|
...mockedDefaults,
|
||||||
|
endpoint: 'https://421.0.0.1',
|
||||||
|
responseData: { makerToken: '123' },
|
||||||
|
});
|
||||||
|
// [BAD] A successful response code and valid response data, but for wrong maker asset data
|
||||||
|
mockedRequests.push({
|
||||||
|
...mockedDefaults,
|
||||||
|
endpoint: 'https://422.0.0.1',
|
||||||
|
responseData: { ...successfulQuote1, makerToken: otherToken1 },
|
||||||
|
});
|
||||||
|
// [BAD] A successful response code and valid response data, but for wrong taker asset data
|
||||||
|
mockedRequests.push({
|
||||||
|
...mockedDefaults,
|
||||||
|
endpoint: 'https://423.0.0.1',
|
||||||
|
responseData: { ...successfulQuote1, takerToken: otherToken1 },
|
||||||
|
});
|
||||||
|
|
||||||
|
return testHelpers.withMockedRfqQuotes(
|
||||||
|
mockedRequests,
|
||||||
|
[],
|
||||||
|
RfqQuoteEndpoint.Indicative,
|
||||||
|
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]],
|
||||||
|
},
|
||||||
|
quoteRequestorHttpClient,
|
||||||
|
);
|
||||||
|
const resp = await qr.requestRfqmIndicativeQuotesAsync(
|
||||||
|
makerToken,
|
||||||
|
takerToken,
|
||||||
|
new BigNumber(10000),
|
||||||
|
MarketOperation.Sell,
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
apiKey,
|
||||||
|
takerAddress,
|
||||||
|
txOrigin: takerAddress,
|
||||||
|
intentOnFilling: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(resp.sort()).to.eql([successfulQuote1, successfulQuote1].sort());
|
||||||
|
},
|
||||||
|
quoteRequestorHttpClient,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('requestRfqtIndicativeQuotesAsync for Indicative quotes', async () => {
|
describe('requestRfqtIndicativeQuotesAsync for Indicative quotes', async () => {
|
||||||
it('should optionally accept a "comparisonPrice" parameter', async () => {
|
it('should optionally accept a "comparisonPrice" parameter', async () => {
|
||||||
const response = QuoteRequestor.makeQueryParameters(
|
const response = QuoteRequestor.makeQueryParameters(
|
||||||
@ -279,6 +579,7 @@ describe('QuoteRequestor', async () => {
|
|||||||
comparisonPrice: undefined,
|
comparisonPrice: undefined,
|
||||||
takerAddress,
|
takerAddress,
|
||||||
txOrigin: takerAddress,
|
txOrigin: takerAddress,
|
||||||
|
isLastLook: 'false',
|
||||||
protocolVersion: '4',
|
protocolVersion: '4',
|
||||||
};
|
};
|
||||||
const mockedDefaults = {
|
const mockedDefaults = {
|
||||||
@ -333,10 +634,10 @@ describe('QuoteRequestor', async () => {
|
|||||||
responseData: successfulQuote1,
|
responseData: successfulQuote1,
|
||||||
});
|
});
|
||||||
|
|
||||||
return testHelpers.withMockedRfqtQuotes(
|
return testHelpers.withMockedRfqQuotes(
|
||||||
mockedRequests,
|
mockedRequests,
|
||||||
[],
|
[],
|
||||||
RfqtQuoteEndpoint.Indicative,
|
RfqQuoteEndpoint.Indicative,
|
||||||
async () => {
|
async () => {
|
||||||
const qr = new QuoteRequestor(
|
const qr = new QuoteRequestor(
|
||||||
{
|
{
|
||||||
@ -348,6 +649,7 @@ describe('QuoteRequestor', async () => {
|
|||||||
'https://424.0.0.1': [[makerToken, takerToken]],
|
'https://424.0.0.1': [[makerToken, takerToken]],
|
||||||
'https://37.0.0.1': [[makerToken, takerToken]],
|
'https://37.0.0.1': [[makerToken, takerToken]],
|
||||||
},
|
},
|
||||||
|
{},
|
||||||
quoteRequestorHttpClient,
|
quoteRequestorHttpClient,
|
||||||
);
|
);
|
||||||
const resp = await qr.requestRfqtIndicativeQuotesAsync(
|
const resp = await qr.requestRfqtIndicativeQuotesAsync(
|
||||||
@ -386,6 +688,7 @@ describe('QuoteRequestor', async () => {
|
|||||||
takerAddress,
|
takerAddress,
|
||||||
txOrigin: takerAddress,
|
txOrigin: takerAddress,
|
||||||
protocolVersion: '4',
|
protocolVersion: '4',
|
||||||
|
isLastLook: 'false',
|
||||||
};
|
};
|
||||||
const mockedDefaults = {
|
const mockedDefaults = {
|
||||||
requestApiKey: apiKey,
|
requestApiKey: apiKey,
|
||||||
@ -424,16 +727,17 @@ describe('QuoteRequestor', async () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return testHelpers.withMockedRfqtQuotes(
|
return testHelpers.withMockedRfqQuotes(
|
||||||
mockedRequests,
|
mockedRequests,
|
||||||
[],
|
[],
|
||||||
RfqtQuoteEndpoint.Indicative,
|
RfqQuoteEndpoint.Indicative,
|
||||||
async () => {
|
async () => {
|
||||||
const qr = new QuoteRequestor(
|
const qr = new QuoteRequestor(
|
||||||
{
|
{
|
||||||
'https://1337.0.0.1': [[makerToken, takerToken]],
|
'https://1337.0.0.1': [[makerToken, takerToken]],
|
||||||
'https://420.0.0.1': [[makerToken, takerToken]],
|
'https://420.0.0.1': [[makerToken, takerToken]],
|
||||||
},
|
},
|
||||||
|
{},
|
||||||
quoteRequestorHttpClient,
|
quoteRequestorHttpClient,
|
||||||
);
|
);
|
||||||
const resp = await qr.requestRfqtIndicativeQuotesAsync(
|
const resp = await qr.requestRfqtIndicativeQuotesAsync(
|
||||||
@ -469,6 +773,7 @@ describe('QuoteRequestor', async () => {
|
|||||||
comparisonPrice: undefined,
|
comparisonPrice: undefined,
|
||||||
takerAddress,
|
takerAddress,
|
||||||
txOrigin: takerAddress,
|
txOrigin: takerAddress,
|
||||||
|
isLastLook: 'false',
|
||||||
protocolVersion: '4',
|
protocolVersion: '4',
|
||||||
};
|
};
|
||||||
// Successful response
|
// Successful response
|
||||||
@ -487,13 +792,14 @@ describe('QuoteRequestor', async () => {
|
|||||||
responseCode: StatusCodes.Success,
|
responseCode: StatusCodes.Success,
|
||||||
});
|
});
|
||||||
|
|
||||||
return testHelpers.withMockedRfqtQuotes(
|
return testHelpers.withMockedRfqQuotes(
|
||||||
mockedRequests,
|
mockedRequests,
|
||||||
[],
|
[],
|
||||||
RfqtQuoteEndpoint.Indicative,
|
RfqQuoteEndpoint.Indicative,
|
||||||
async () => {
|
async () => {
|
||||||
const qr = new QuoteRequestor(
|
const qr = new QuoteRequestor(
|
||||||
{ 'https://1337.0.0.1': [[makerToken, takerToken]] },
|
{ 'https://1337.0.0.1': [[makerToken, takerToken]] },
|
||||||
|
{},
|
||||||
quoteRequestorHttpClient,
|
quoteRequestorHttpClient,
|
||||||
);
|
);
|
||||||
const resp = await qr.requestRfqtIndicativeQuotesAsync(
|
const resp = await qr.requestRfqtIndicativeQuotesAsync(
|
||||||
@ -722,12 +1028,12 @@ describe('QuoteRequestor', async () => {
|
|||||||
for (const altScenario of altScenarios) {
|
for (const altScenario of altScenarios) {
|
||||||
logUtils.log(`Alt MM indicative scenario ${scenarioCounter}`);
|
logUtils.log(`Alt MM indicative scenario ${scenarioCounter}`);
|
||||||
scenarioCounter += 1;
|
scenarioCounter += 1;
|
||||||
await testHelpers.withMockedRfqtQuotes(
|
await testHelpers.withMockedRfqQuotes(
|
||||||
[],
|
[],
|
||||||
altMockedRequests,
|
altMockedRequests,
|
||||||
RfqtQuoteEndpoint.Indicative,
|
RfqQuoteEndpoint.Indicative,
|
||||||
async () => {
|
async () => {
|
||||||
const qr = new QuoteRequestor({}, quoteRequestorHttpClient, ALT_RFQ_CREDS);
|
const qr = new QuoteRequestor({}, {}, quoteRequestorHttpClient, ALT_RFQ_CREDS);
|
||||||
const resp = await qr.requestRfqtIndicativeQuotesAsync(
|
const resp = await qr.requestRfqtIndicativeQuotesAsync(
|
||||||
altScenario.requestedMakerToken,
|
altScenario.requestedMakerToken,
|
||||||
altScenario.requestedTakerToken,
|
altScenario.requestedTakerToken,
|
||||||
|
@ -6,7 +6,7 @@ import * as _ from 'lodash';
|
|||||||
import { InsufficientAssetLiquidityError } from '../../src/errors';
|
import { InsufficientAssetLiquidityError } from '../../src/errors';
|
||||||
import { AltMockedRfqQuoteResponse, MockedRfqQuoteResponse } from '../../src/types';
|
import { AltMockedRfqQuoteResponse, MockedRfqQuoteResponse } from '../../src/types';
|
||||||
|
|
||||||
export enum RfqtQuoteEndpoint {
|
export enum RfqQuoteEndpoint {
|
||||||
Indicative = 'price',
|
Indicative = 'price',
|
||||||
Firm = 'quote',
|
Firm = 'quote',
|
||||||
}
|
}
|
||||||
@ -34,18 +34,18 @@ export const testHelpers = {
|
|||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* A helper utility for testing which mocks out
|
* A helper utility for testing which mocks out
|
||||||
* requests to RFQ-t providers
|
* requests to RFQ-T/M providers
|
||||||
*/
|
*/
|
||||||
withMockedRfqtQuotes: async (
|
withMockedRfqQuotes: async (
|
||||||
standardMockedResponses: MockedRfqQuoteResponse[],
|
standardMockedResponses: MockedRfqQuoteResponse[],
|
||||||
altMockedResponses: AltMockedRfqQuoteResponse[],
|
altMockedResponses: AltMockedRfqQuoteResponse[],
|
||||||
quoteType: RfqtQuoteEndpoint,
|
quoteType: RfqQuoteEndpoint,
|
||||||
afterResponseCallback: () => Promise<void>,
|
afterResponseCallback: () => Promise<void>,
|
||||||
axiosClient: AxiosInstance = axios,
|
axiosClient: AxiosInstance = axios,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const mockedAxios = new AxiosMockAdapter(axiosClient, { onNoMatch: 'throwException' });
|
const mockedAxios = new AxiosMockAdapter(axiosClient, { onNoMatch: 'throwException' });
|
||||||
try {
|
try {
|
||||||
// Mock out Standard RFQT responses
|
// Mock out Standard RFQ-T/M responses
|
||||||
for (const mockedResponse of standardMockedResponses) {
|
for (const mockedResponse of standardMockedResponses) {
|
||||||
const { endpoint, requestApiKey, requestParams, responseData, responseCode } = mockedResponse;
|
const { endpoint, requestApiKey, requestParams, responseData, responseCode } = mockedResponse;
|
||||||
const requestHeaders = { Accept: 'application/json, text/plain, */*', '0x-api-key': requestApiKey };
|
const requestHeaders = { Accept: 'application/json, text/plain, */*', '0x-api-key': requestApiKey };
|
||||||
@ -59,7 +59,7 @@ export const testHelpers = {
|
|||||||
.replyOnce(responseCode, responseData);
|
.replyOnce(responseCode, responseData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Mock out Alt RFQT responses
|
// Mock out Alt RFQ-T/M responses
|
||||||
for (const mockedResponse of altMockedResponses) {
|
for (const mockedResponse of altMockedResponses) {
|
||||||
const { endpoint, mmApiKey, requestData, responseData, responseCode } = mockedResponse;
|
const { endpoint, mmApiKey, requestData, responseData, responseCode } = mockedResponse;
|
||||||
const requestHeaders = {
|
const requestHeaders = {
|
||||||
|
@ -830,9 +830,10 @@
|
|||||||
typedoc "~0.16.11"
|
typedoc "~0.16.11"
|
||||||
yargs "^10.0.3"
|
yargs "^10.0.3"
|
||||||
|
|
||||||
"@0x/quote-server@^4.0.1":
|
"@0x/quote-server@^5.0.0":
|
||||||
version "4.0.1"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@0x/quote-server/-/quote-server-4.0.1.tgz#05947589bfa7905d274ac3c726cb9918b93b0f9e"
|
resolved "https://registry.yarnpkg.com/@0x/quote-server/-/quote-server-5.0.0.tgz#15554099bdfdf71e2910430860257d622f24f703"
|
||||||
|
integrity sha512-U14C60RVnILL8n5DwuInG98MnhXbBbiEi8M2ymFGnHO+AjucGfm28BM6/GD59ftiqZmFSkOvBRU94QJ3mSsCQw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@0x/json-schemas" "^5.0.7"
|
"@0x/json-schemas" "^5.0.7"
|
||||||
"@0x/order-utils" "^10.2.4"
|
"@0x/order-utils" "^10.2.4"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user