* improve logging for alt rfq request * clean up unsuccessful status code logic * Fix quote requestor tests * get rid of unnecessary promise handling * remove unused code * update changelog * changed warning message for no quote * appease prettier
		
			
				
	
	
		
			755 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			755 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { tokenUtils } from '@0x/dev-utils';
 | 
						|
import { FillQuoteTransformerOrderType, SignatureType } from '@0x/protocol-utils';
 | 
						|
import { TakerRequestQueryParams, V4RFQIndicativeQuote } from '@0x/quote-server';
 | 
						|
import { StatusCodes } from '@0x/types';
 | 
						|
import { BigNumber, logUtils } from '@0x/utils';
 | 
						|
import Axios from 'axios';
 | 
						|
import * as chai from 'chai';
 | 
						|
import { Agent as HttpAgent } from 'http';
 | 
						|
import { Agent as HttpsAgent } from 'https';
 | 
						|
import _ = require('lodash');
 | 
						|
import 'mocha';
 | 
						|
 | 
						|
import { constants, KEEP_ALIVE_TTL } from '../src/constants';
 | 
						|
import {
 | 
						|
    AltMockedRfqQuoteResponse,
 | 
						|
    AltQuoteModel,
 | 
						|
    AltQuoteRequestData,
 | 
						|
    AltQuoteSide,
 | 
						|
    AltRfqMakerAssetOfferings,
 | 
						|
    MarketOperation,
 | 
						|
    MockedRfqQuoteResponse,
 | 
						|
} from '../src/types';
 | 
						|
import { NULL_ADDRESS } from '../src/utils/market_operation_utils/constants';
 | 
						|
import { QuoteRequestor } from '../src/utils/quote_requestor';
 | 
						|
 | 
						|
import { chaiSetup } from './utils/chai_setup';
 | 
						|
import { RfqtQuoteEndpoint, testHelpers } from './utils/test_helpers';
 | 
						|
 | 
						|
const quoteRequestorHttpClient = Axios.create({
 | 
						|
    httpAgent: new HttpAgent({ keepAlive: true, timeout: KEEP_ALIVE_TTL }),
 | 
						|
    httpsAgent: new HttpsAgent({ keepAlive: true, timeout: KEEP_ALIVE_TTL }),
 | 
						|
});
 | 
						|
 | 
						|
chaiSetup.configure();
 | 
						|
const expect = chai.expect;
 | 
						|
const ALT_MM_API_KEY = 'averysecurekey';
 | 
						|
const ALT_PROFILE = 'acoolprofile';
 | 
						|
const ALT_RFQ_CREDS = {
 | 
						|
    altRfqApiKey: ALT_MM_API_KEY,
 | 
						|
    altRfqProfile: ALT_PROFILE,
 | 
						|
};
 | 
						|
 | 
						|
const CREATED_STATUS_CODE = 201;
 | 
						|
 | 
						|
function makeThreeMinuteExpiry(): BigNumber {
 | 
						|
    const expiry = new Date(Date.now());
 | 
						|
    expiry.setMinutes(expiry.getMinutes() + 3);
 | 
						|
    return new BigNumber(Math.round(expiry.valueOf() / constants.ONE_SECOND_MS));
 | 
						|
}
 | 
						|
 | 
						|
describe('QuoteRequestor', async () => {
 | 
						|
    const [makerToken, takerToken, otherToken1] = tokenUtils.getDummyERC20TokenAddresses();
 | 
						|
    const validSignature = { v: 28, r: '0x', s: '0x', signatureType: SignatureType.EthSign };
 | 
						|
 | 
						|
    const altRfqAssetOfferings: AltRfqMakerAssetOfferings = {
 | 
						|
        'https://132.0.0.1': [
 | 
						|
            {
 | 
						|
                id: 'XYZ-123',
 | 
						|
                baseAsset: makerToken,
 | 
						|
                quoteAsset: takerToken,
 | 
						|
                baseAssetDecimals: 2,
 | 
						|
                quoteAssetDecimals: 3,
 | 
						|
            },
 | 
						|
        ],
 | 
						|
    };
 | 
						|
 | 
						|
    describe('requestRfqtFirmQuotesAsync for firm quotes', async () => {
 | 
						|
        it('should return successful RFQT requests', async () => {
 | 
						|
            const takerAddress = '0xd209925defc99488e3afff1174e48b4fa628302a';
 | 
						|
            const txOrigin = takerAddress;
 | 
						|
            const apiKey = 'my-ko0l-api-key';
 | 
						|
 | 
						|
            // Set up RFQT 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,
 | 
						|
                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,
 | 
						|
                },
 | 
						|
            };
 | 
						|
 | 
						|
            // Successful response
 | 
						|
            mockedRequests.push({
 | 
						|
                ...mockedDefaults,
 | 
						|
                endpoint: 'https://1337.0.0.1',
 | 
						|
                responseData: {
 | 
						|
                    signedOrder: validSignedOrder,
 | 
						|
                },
 | 
						|
            });
 | 
						|
            // Another Successful response
 | 
						|
            mockedRequests.push({
 | 
						|
                ...mockedDefaults,
 | 
						|
                endpoint: 'https://37.0.0.1',
 | 
						|
                responseData: { signedOrder: validSignedOrder },
 | 
						|
            });
 | 
						|
            // 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,
 | 
						|
            });
 | 
						|
            // Test out a successful response code but a partial order
 | 
						|
            mockedRequests.push({
 | 
						|
                ...mockedDefaults,
 | 
						|
                endpoint: 'https://421.0.0.1',
 | 
						|
                responseData: { signedOrder: { makerToken: '123' } },
 | 
						|
            });
 | 
						|
            // A successful response code and invalid response data (encoding)
 | 
						|
            mockedRequests.push({
 | 
						|
                ...mockedDefaults,
 | 
						|
                endpoint: 'https://421.1.0.1',
 | 
						|
                responseData: 'this is not JSON!',
 | 
						|
            });
 | 
						|
            // 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' } },
 | 
						|
            });
 | 
						|
            // 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' } },
 | 
						|
            });
 | 
						|
            // A successful response code and good order but its unsigned
 | 
						|
            mockedRequests.push({
 | 
						|
                ...mockedDefaults,
 | 
						|
                endpoint: 'https://424.0.0.1',
 | 
						|
                responseData: { signedOrder: _.omit(validSignedOrder, ['signature']) },
 | 
						|
            });
 | 
						|
            // 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 } },
 | 
						|
            });
 | 
						|
            // 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.withMockedRfqtQuotes(
 | 
						|
                mockedRequests,
 | 
						|
                altMockedRequests,
 | 
						|
                RfqtQuoteEndpoint.Firm,
 | 
						|
                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://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-T 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.requestRfqtFirmQuotesAsync(
 | 
						|
                        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('requestRfqtIndicativeQuotesAsync for Indicative quotes', async () => {
 | 
						|
        it('should optionally accept a "comparisonPrice" parameter', async () => {
 | 
						|
            const response = QuoteRequestor.makeQueryParameters(
 | 
						|
                otherToken1, // tx origin
 | 
						|
                otherToken1, // taker
 | 
						|
                MarketOperation.Sell,
 | 
						|
                makerToken,
 | 
						|
                takerToken,
 | 
						|
                new BigNumber(1000),
 | 
						|
                new BigNumber(300.2),
 | 
						|
            );
 | 
						|
            expect(response.comparisonPrice).to.eql('300.2');
 | 
						|
        });
 | 
						|
        it('should return successful RFQT requests', async () => {
 | 
						|
            const takerAddress = '0xd209925defc99488e3afff1174e48b4fa628302a';
 | 
						|
            const apiKey = 'my-ko0l-api-key';
 | 
						|
 | 
						|
            // Set up RFQT responses
 | 
						|
            // tslint:disable-next-line:array-type
 | 
						|
            const mockedRequests: MockedRfqQuoteResponse[] = [];
 | 
						|
            const expectedParams: TakerRequestQueryParams = {
 | 
						|
                sellTokenAddress: takerToken,
 | 
						|
                buyTokenAddress: makerToken,
 | 
						|
                sellAmountBaseUnits: '10000',
 | 
						|
                comparisonPrice: undefined,
 | 
						|
                takerAddress,
 | 
						|
                txOrigin: takerAddress,
 | 
						|
                protocolVersion: '4',
 | 
						|
            };
 | 
						|
            const mockedDefaults = {
 | 
						|
                requestApiKey: apiKey,
 | 
						|
                requestParams: expectedParams,
 | 
						|
                responseCode: StatusCodes.Success,
 | 
						|
            };
 | 
						|
 | 
						|
            // 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,
 | 
						|
            });
 | 
						|
            // 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,
 | 
						|
            });
 | 
						|
            // Test out a successful response code but an invalid order
 | 
						|
            mockedRequests.push({
 | 
						|
                ...mockedDefaults,
 | 
						|
                endpoint: 'https://421.0.0.1',
 | 
						|
                responseData: { makerToken: '123' },
 | 
						|
            });
 | 
						|
            // 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 },
 | 
						|
            });
 | 
						|
            // 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 },
 | 
						|
            });
 | 
						|
            // Another Successful response
 | 
						|
            mockedRequests.push({
 | 
						|
                ...mockedDefaults,
 | 
						|
                endpoint: 'https://37.0.0.1',
 | 
						|
                responseData: successfulQuote1,
 | 
						|
            });
 | 
						|
 | 
						|
            return testHelpers.withMockedRfqtQuotes(
 | 
						|
                mockedRequests,
 | 
						|
                [],
 | 
						|
                RfqtQuoteEndpoint.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 resp = await qr.requestRfqtIndicativeQuotesAsync(
 | 
						|
                        makerToken,
 | 
						|
                        takerToken,
 | 
						|
                        new BigNumber(10000),
 | 
						|
                        MarketOperation.Sell,
 | 
						|
                        undefined,
 | 
						|
                        {
 | 
						|
                            apiKey,
 | 
						|
                            takerAddress,
 | 
						|
                            txOrigin: takerAddress,
 | 
						|
                            intentOnFilling: true,
 | 
						|
                        },
 | 
						|
                    );
 | 
						|
                    expect(resp.sort()).to.eql([successfulQuote1, successfulQuote1].sort());
 | 
						|
                },
 | 
						|
                quoteRequestorHttpClient,
 | 
						|
            );
 | 
						|
        });
 | 
						|
        it('should only return RFQT requests that meet the timeout', async () => {
 | 
						|
            const takerAddress = '0xd209925defc99488e3afff1174e48b4fa628302a';
 | 
						|
            const apiKey = 'my-ko0l-api-key';
 | 
						|
            const maxTimeoutMs = 10;
 | 
						|
            // tslint:disable-next-line:custom-no-magic-numbers
 | 
						|
            const exceedTimeoutMs = maxTimeoutMs + 50;
 | 
						|
 | 
						|
            // Set up RFQT responses
 | 
						|
            // tslint:disable-next-line:array-type
 | 
						|
            const mockedRequests: MockedRfqQuoteResponse[] = [];
 | 
						|
            const expectedParams: TakerRequestQueryParams = {
 | 
						|
                sellTokenAddress: takerToken,
 | 
						|
                buyTokenAddress: makerToken,
 | 
						|
                sellAmountBaseUnits: '10000',
 | 
						|
                comparisonPrice: undefined,
 | 
						|
                takerAddress,
 | 
						|
                txOrigin: takerAddress,
 | 
						|
                protocolVersion: '4',
 | 
						|
            };
 | 
						|
            const mockedDefaults = {
 | 
						|
                requestApiKey: apiKey,
 | 
						|
                requestParams: expectedParams,
 | 
						|
                responseCode: StatusCodes.Success,
 | 
						|
            };
 | 
						|
 | 
						|
            // Successful response
 | 
						|
            const successfulQuote1 = {
 | 
						|
                makerToken,
 | 
						|
                takerToken,
 | 
						|
                makerAmount: new BigNumber(expectedParams.sellAmountBaseUnits),
 | 
						|
                takerAmount: new BigNumber(expectedParams.sellAmountBaseUnits),
 | 
						|
                expiry: makeThreeMinuteExpiry(),
 | 
						|
            };
 | 
						|
 | 
						|
            // One good request
 | 
						|
            mockedRequests.push({
 | 
						|
                ...mockedDefaults,
 | 
						|
                endpoint: 'https://1337.0.0.1',
 | 
						|
                responseData: successfulQuote1,
 | 
						|
            });
 | 
						|
 | 
						|
            // One request that will timeout
 | 
						|
            mockedRequests.push({
 | 
						|
                ...mockedDefaults,
 | 
						|
                endpoint: 'https://420.0.0.1',
 | 
						|
                responseData: successfulQuote1,
 | 
						|
                callback: async () => {
 | 
						|
                    // tslint:disable-next-line:no-inferred-empty-object-type
 | 
						|
                    return new Promise((resolve, reject) => {
 | 
						|
                        setTimeout(() => {
 | 
						|
                            resolve([StatusCodes.Success, successfulQuote1]);
 | 
						|
                        }, exceedTimeoutMs);
 | 
						|
                    });
 | 
						|
                },
 | 
						|
            });
 | 
						|
 | 
						|
            return testHelpers.withMockedRfqtQuotes(
 | 
						|
                mockedRequests,
 | 
						|
                [],
 | 
						|
                RfqtQuoteEndpoint.Indicative,
 | 
						|
                async () => {
 | 
						|
                    const qr = new QuoteRequestor(
 | 
						|
                        {
 | 
						|
                            'https://1337.0.0.1': [[makerToken, takerToken]],
 | 
						|
                            'https://420.0.0.1': [[makerToken, takerToken]],
 | 
						|
                        },
 | 
						|
                        quoteRequestorHttpClient,
 | 
						|
                    );
 | 
						|
                    const resp = await qr.requestRfqtIndicativeQuotesAsync(
 | 
						|
                        makerToken,
 | 
						|
                        takerToken,
 | 
						|
                        new BigNumber(10000),
 | 
						|
                        MarketOperation.Sell,
 | 
						|
                        undefined,
 | 
						|
                        {
 | 
						|
                            apiKey,
 | 
						|
                            takerAddress,
 | 
						|
                            txOrigin: takerAddress,
 | 
						|
                            intentOnFilling: true,
 | 
						|
                            makerEndpointMaxResponseTimeMs: maxTimeoutMs,
 | 
						|
                        },
 | 
						|
                    );
 | 
						|
                    expect(resp.sort()).to.eql([successfulQuote1].sort()); // notice only one result, despite two requests made
 | 
						|
                },
 | 
						|
                quoteRequestorHttpClient,
 | 
						|
            );
 | 
						|
        });
 | 
						|
        it('should return successful RFQT indicative quote requests (Buy)', async () => {
 | 
						|
            const takerAddress = '0xd209925defc99488e3afff1174e48b4fa628302a';
 | 
						|
            const apiKey = 'my-ko0l-api-key';
 | 
						|
 | 
						|
            // Set up RFQT responses
 | 
						|
            // tslint:disable-next-line:array-type
 | 
						|
            const mockedRequests: MockedRfqQuoteResponse[] = [];
 | 
						|
            const expectedParams: TakerRequestQueryParams = {
 | 
						|
                sellTokenAddress: takerToken,
 | 
						|
                buyTokenAddress: makerToken,
 | 
						|
                buyAmountBaseUnits: '10000',
 | 
						|
                comparisonPrice: undefined,
 | 
						|
                takerAddress,
 | 
						|
                txOrigin: takerAddress,
 | 
						|
                protocolVersion: '4',
 | 
						|
            };
 | 
						|
            // Successful response
 | 
						|
            const successfulQuote1 = {
 | 
						|
                makerToken,
 | 
						|
                takerToken,
 | 
						|
                makerAmount: new BigNumber(expectedParams.buyAmountBaseUnits),
 | 
						|
                takerAmount: new BigNumber(expectedParams.buyAmountBaseUnits),
 | 
						|
                expiry: makeThreeMinuteExpiry(),
 | 
						|
            };
 | 
						|
            mockedRequests.push({
 | 
						|
                endpoint: 'https://1337.0.0.1',
 | 
						|
                requestApiKey: apiKey,
 | 
						|
                requestParams: expectedParams,
 | 
						|
                responseData: successfulQuote1,
 | 
						|
                responseCode: StatusCodes.Success,
 | 
						|
            });
 | 
						|
 | 
						|
            return testHelpers.withMockedRfqtQuotes(
 | 
						|
                mockedRequests,
 | 
						|
                [],
 | 
						|
                RfqtQuoteEndpoint.Indicative,
 | 
						|
                async () => {
 | 
						|
                    const qr = new QuoteRequestor(
 | 
						|
                        { 'https://1337.0.0.1': [[makerToken, takerToken]] },
 | 
						|
                        quoteRequestorHttpClient,
 | 
						|
                    );
 | 
						|
                    const resp = await qr.requestRfqtIndicativeQuotesAsync(
 | 
						|
                        makerToken,
 | 
						|
                        takerToken,
 | 
						|
                        new BigNumber(10000),
 | 
						|
                        MarketOperation.Buy,
 | 
						|
                        undefined,
 | 
						|
                        {
 | 
						|
                            apiKey,
 | 
						|
                            takerAddress,
 | 
						|
                            txOrigin: takerAddress,
 | 
						|
                            intentOnFilling: true,
 | 
						|
                        },
 | 
						|
                    );
 | 
						|
                    expect(resp.sort()).to.eql([successfulQuote1].sort());
 | 
						|
                },
 | 
						|
                quoteRequestorHttpClient,
 | 
						|
            );
 | 
						|
        });
 | 
						|
        it('should return successful alt indicative quotes', async () => {
 | 
						|
            const takerAddress = '0xd209925defc99488e3afff1174e48b4fa628302a';
 | 
						|
            const txOrigin = '0xf209925defc99488e3afff1174e48b4fa628302a';
 | 
						|
            const apiKey = 'my-ko0l-api-key';
 | 
						|
 | 
						|
            // base token has 2 decimals
 | 
						|
            // quote token has 3 decimals
 | 
						|
            const baseToken = makerToken;
 | 
						|
            const quoteToken = takerToken;
 | 
						|
 | 
						|
            // Set up RFQT responses
 | 
						|
            const altMockedRequests: AltMockedRfqQuoteResponse[] = [];
 | 
						|
            const altScenarios: Array<{
 | 
						|
                successfulQuote: V4RFQIndicativeQuote;
 | 
						|
                requestedMakerToken: string;
 | 
						|
                requestedTakerToken: string;
 | 
						|
                requestedAmount: BigNumber;
 | 
						|
                requestedOperation: MarketOperation;
 | 
						|
            }> = [];
 | 
						|
 | 
						|
            // SCENARIO 1
 | 
						|
            // buy, base asset specified
 | 
						|
            // requesting to buy 100 units (10000 base units) of the base token
 | 
						|
            // returning a price of 0.01, which should mean 10000 maker, 1000 taker amount
 | 
						|
            const buyAmountAltRequest: AltQuoteRequestData = {
 | 
						|
                market: 'XYZ-123',
 | 
						|
                model: AltQuoteModel.Indicative,
 | 
						|
                profile: ALT_PROFILE,
 | 
						|
                side: AltQuoteSide.Sell,
 | 
						|
                meta: {
 | 
						|
                    txOrigin,
 | 
						|
                    taker: takerAddress,
 | 
						|
                    client: apiKey,
 | 
						|
                },
 | 
						|
                amount: '100',
 | 
						|
            };
 | 
						|
            // Successful response
 | 
						|
            const buyAmountAltResponse = {
 | 
						|
                ...buyAmountAltRequest,
 | 
						|
                id: 'random_id',
 | 
						|
                // tslint:disable-next-line:custom-no-magic-numbers
 | 
						|
                price: new BigNumber(0.01).toString(),
 | 
						|
                status: 'live',
 | 
						|
            };
 | 
						|
            const successfulBuyAmountQuote: V4RFQIndicativeQuote = {
 | 
						|
                makerToken: baseToken,
 | 
						|
                takerToken: quoteToken,
 | 
						|
                makerAmount: new BigNumber(10000),
 | 
						|
                takerAmount: new BigNumber(1000),
 | 
						|
                expiry: new BigNumber(0),
 | 
						|
            };
 | 
						|
            altMockedRequests.push({
 | 
						|
                endpoint: 'https://132.0.0.1',
 | 
						|
                mmApiKey: ALT_MM_API_KEY,
 | 
						|
                responseCode: CREATED_STATUS_CODE,
 | 
						|
                requestData: buyAmountAltRequest,
 | 
						|
                responseData: buyAmountAltResponse,
 | 
						|
            });
 | 
						|
            altScenarios.push({
 | 
						|
                successfulQuote: successfulBuyAmountQuote,
 | 
						|
                requestedMakerToken: baseToken,
 | 
						|
                requestedTakerToken: quoteToken,
 | 
						|
                requestedAmount: new BigNumber(10000),
 | 
						|
                requestedOperation: MarketOperation.Buy,
 | 
						|
            });
 | 
						|
 | 
						|
            // SCENARIO 2
 | 
						|
            // alt buy, quote asset specified
 | 
						|
            // user is requesting to sell 1 unit of the quote token, or 1000 base units
 | 
						|
            // returning a price of 0.01, which should mean 10000 maker amount, 1000 taker amount
 | 
						|
            const buyValueAltRequest: AltQuoteRequestData = {
 | 
						|
                market: 'XYZ-123',
 | 
						|
                model: AltQuoteModel.Indicative,
 | 
						|
                profile: ALT_PROFILE,
 | 
						|
                side: AltQuoteSide.Sell,
 | 
						|
                meta: {
 | 
						|
                    txOrigin,
 | 
						|
                    taker: takerAddress,
 | 
						|
                    client: apiKey,
 | 
						|
                },
 | 
						|
                value: '1',
 | 
						|
            };
 | 
						|
            // Successful response
 | 
						|
            const buyValueAltResponse = {
 | 
						|
                ...buyValueAltRequest,
 | 
						|
                id: 'random_id',
 | 
						|
                // tslint:disable-next-line:custom-no-magic-numbers
 | 
						|
                price: new BigNumber(0.01).toString(),
 | 
						|
                status: 'live',
 | 
						|
            };
 | 
						|
            const successfulBuyValueQuote: V4RFQIndicativeQuote = {
 | 
						|
                makerToken: baseToken,
 | 
						|
                takerToken: quoteToken,
 | 
						|
                makerAmount: new BigNumber(10000),
 | 
						|
                takerAmount: new BigNumber(1000),
 | 
						|
                expiry: new BigNumber(0),
 | 
						|
            };
 | 
						|
            altMockedRequests.push({
 | 
						|
                endpoint: 'https://132.0.0.1',
 | 
						|
                mmApiKey: ALT_MM_API_KEY,
 | 
						|
                responseCode: CREATED_STATUS_CODE,
 | 
						|
                requestData: buyValueAltRequest,
 | 
						|
                responseData: buyValueAltResponse,
 | 
						|
            });
 | 
						|
            altScenarios.push({
 | 
						|
                successfulQuote: successfulBuyValueQuote,
 | 
						|
                requestedMakerToken: baseToken,
 | 
						|
                requestedTakerToken: quoteToken,
 | 
						|
                requestedAmount: new BigNumber(1000),
 | 
						|
                requestedOperation: MarketOperation.Sell,
 | 
						|
            });
 | 
						|
 | 
						|
            // SCENARIO 3
 | 
						|
            // alt sell, base asset specified
 | 
						|
            // user is requesting to sell 100 units (10000 base units) of the base token
 | 
						|
            // returning a price of 0.01, which should mean 10000 taker amount, 1000 maker amount
 | 
						|
            const sellAmountAltRequest: AltQuoteRequestData = {
 | 
						|
                market: 'XYZ-123',
 | 
						|
                model: AltQuoteModel.Indicative,
 | 
						|
                profile: ALT_PROFILE,
 | 
						|
                side: AltQuoteSide.Buy,
 | 
						|
                meta: {
 | 
						|
                    txOrigin,
 | 
						|
                    taker: takerAddress,
 | 
						|
                    client: apiKey,
 | 
						|
                },
 | 
						|
                amount: '100',
 | 
						|
            };
 | 
						|
            // Successful response
 | 
						|
            const sellAmountAltResponse = {
 | 
						|
                ...sellAmountAltRequest,
 | 
						|
                id: 'random_id',
 | 
						|
                // tslint:disable-next-line:custom-no-magic-numbers
 | 
						|
                price: new BigNumber(0.01).toString(),
 | 
						|
                status: 'live',
 | 
						|
            };
 | 
						|
            const successfulSellAmountQuote: V4RFQIndicativeQuote = {
 | 
						|
                makerToken: quoteToken,
 | 
						|
                takerToken: baseToken,
 | 
						|
                makerAmount: new BigNumber(1000),
 | 
						|
                takerAmount: new BigNumber(10000),
 | 
						|
                expiry: new BigNumber(0),
 | 
						|
            };
 | 
						|
            altMockedRequests.push({
 | 
						|
                endpoint: 'https://132.0.0.1',
 | 
						|
                mmApiKey: ALT_MM_API_KEY,
 | 
						|
                responseCode: CREATED_STATUS_CODE,
 | 
						|
                requestData: sellAmountAltRequest,
 | 
						|
                responseData: sellAmountAltResponse,
 | 
						|
            });
 | 
						|
            altScenarios.push({
 | 
						|
                successfulQuote: successfulSellAmountQuote,
 | 
						|
                requestedMakerToken: quoteToken,
 | 
						|
                requestedTakerToken: baseToken,
 | 
						|
                requestedAmount: new BigNumber(10000),
 | 
						|
                requestedOperation: MarketOperation.Sell,
 | 
						|
            });
 | 
						|
 | 
						|
            // SCENARIO 4
 | 
						|
            // alt sell, quote asset specified
 | 
						|
            // user is requesting to buy 1 unit (1000 base units) of the quote token
 | 
						|
            // returning a price of 0.01, which should mean 10000 taker amount, 1000 maker amount
 | 
						|
            const sellValueAltRequest: AltQuoteRequestData = {
 | 
						|
                market: 'XYZ-123',
 | 
						|
                model: AltQuoteModel.Indicative,
 | 
						|
                profile: ALT_PROFILE,
 | 
						|
                side: AltQuoteSide.Buy,
 | 
						|
                meta: {
 | 
						|
                    txOrigin,
 | 
						|
                    taker: takerAddress,
 | 
						|
                    client: apiKey,
 | 
						|
                },
 | 
						|
                value: '1',
 | 
						|
            };
 | 
						|
            // Successful response
 | 
						|
            const sellValueAltResponse = {
 | 
						|
                ...sellValueAltRequest,
 | 
						|
                id: 'random_id',
 | 
						|
                // tslint:disable-next-line:custom-no-magic-numbers
 | 
						|
                price: new BigNumber(0.01).toString(),
 | 
						|
                status: 'live',
 | 
						|
            };
 | 
						|
            const successfulSellValueQuote: V4RFQIndicativeQuote = {
 | 
						|
                makerToken: quoteToken,
 | 
						|
                takerToken: baseToken,
 | 
						|
                makerAmount: new BigNumber(1000),
 | 
						|
                takerAmount: new BigNumber(10000),
 | 
						|
                expiry: new BigNumber(0),
 | 
						|
            };
 | 
						|
            altMockedRequests.push({
 | 
						|
                endpoint: 'https://132.0.0.1',
 | 
						|
                mmApiKey: ALT_MM_API_KEY,
 | 
						|
                responseCode: CREATED_STATUS_CODE,
 | 
						|
                requestData: sellValueAltRequest,
 | 
						|
                responseData: sellValueAltResponse,
 | 
						|
            });
 | 
						|
            altScenarios.push({
 | 
						|
                successfulQuote: successfulSellValueQuote,
 | 
						|
                requestedMakerToken: quoteToken,
 | 
						|
                requestedTakerToken: baseToken,
 | 
						|
                requestedAmount: new BigNumber(1000),
 | 
						|
                requestedOperation: MarketOperation.Buy,
 | 
						|
            });
 | 
						|
 | 
						|
            let scenarioCounter = 1;
 | 
						|
            for (const altScenario of altScenarios) {
 | 
						|
                logUtils.log(`Alt MM indicative scenario ${scenarioCounter}`);
 | 
						|
                scenarioCounter += 1;
 | 
						|
                await testHelpers.withMockedRfqtQuotes(
 | 
						|
                    [],
 | 
						|
                    altMockedRequests,
 | 
						|
                    RfqtQuoteEndpoint.Indicative,
 | 
						|
                    async () => {
 | 
						|
                        const qr = new QuoteRequestor({}, quoteRequestorHttpClient, ALT_RFQ_CREDS);
 | 
						|
                        const resp = await qr.requestRfqtIndicativeQuotesAsync(
 | 
						|
                            altScenario.requestedMakerToken,
 | 
						|
                            altScenario.requestedTakerToken,
 | 
						|
                            altScenario.requestedAmount,
 | 
						|
                            altScenario.requestedOperation,
 | 
						|
                            undefined,
 | 
						|
                            {
 | 
						|
                                apiKey,
 | 
						|
                                takerAddress,
 | 
						|
                                txOrigin,
 | 
						|
                                intentOnFilling: true,
 | 
						|
                                altRfqAssetOfferings,
 | 
						|
                            },
 | 
						|
                        );
 | 
						|
                        // hack to get the expiry right, since it's dependent on the current timestamp
 | 
						|
                        const expected = { ...altScenario.successfulQuote, expiry: resp[0].expiry };
 | 
						|
                        expect(resp.sort()).to.eql([expected].sort());
 | 
						|
                    },
 | 
						|
                    quoteRequestorHttpClient,
 | 
						|
                );
 | 
						|
            }
 | 
						|
        });
 | 
						|
    });
 | 
						|
});
 |