asset-swapper: Restructure RFQ-T request options

There's one subtlety here: apiKey has been moved to be within the rfqt
namespace, after talking to Fabio and discovering that he only needs to
re-use the API key in 0x API, not in asset-swapper.
This commit is contained in:
F. Eugene Aumson
2020-04-14 17:19:02 -04:00
parent 110e1afa8e
commit 957e3eb93c
6 changed files with 50 additions and 36 deletions

View File

@@ -5,7 +5,7 @@ import {
ForwarderExtensionContractOpts,
OrderPrunerOpts,
OrderPrunerPermittedFeeTypes,
RfqtFirmQuoteRequestOpts,
RfqtRequestOpts,
SwapQuoteExecutionOpts,
SwapQuoteGetOutputOpts,
SwapQuoteRequestOpts,
@@ -66,7 +66,7 @@ const DEFAULT_SWAP_QUOTE_REQUEST_OPTS: SwapQuoteRequestOpts = {
...DEFAULT_GET_MARKET_ORDERS_OPTS,
};
const DEFAULT_RFQT_FIRM_QUOTE_REQUEST_OPTS: RfqtFirmQuoteRequestOpts = {
const DEFAULT_RFQT_REQUEST_OPTS: Partial<RfqtRequestOpts> = {
makerEndpointMaxResponseTimeMs: 1000,
};
@@ -86,7 +86,7 @@ export const constants = {
DEFAULT_FORWARDER_SWAP_QUOTE_EXECUTE_OPTS,
DEFAULT_SWAP_QUOTE_REQUEST_OPTS,
DEFAULT_PER_PAGE,
DEFAULT_RFQT_FIRM_QUOTE_REQUEST_OPTS,
DEFAULT_RFQT_REQUEST_OPTS,
NULL_ERC20_ASSET_DATA,
PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS,
MARKET_UTILS_AMOUNT_BUFFER_PERCENTAGE,

View File

@@ -45,7 +45,7 @@ export {
MarketOperation,
MarketSellSwapQuote,
MockedRfqtFirmQuoteResponse,
RfqtFirmQuoteRequestOpts,
RfqtRequestOpts,
SwapQuote,
SwapQuoteConsumerBase,
SwapQuoteConsumerOpts,

View File

@@ -24,6 +24,7 @@ import { calculateLiquidity } from './utils/calculate_liquidity';
import { MarketOperationUtils } from './utils/market_operation_utils';
import { createDummyOrderForSampler } from './utils/market_operation_utils/orders';
import { DexOrderSampler } from './utils/market_operation_utils/sampler';
import { GetMarketOrdersOpts } from './utils/market_operation_utils/types';
import { orderPrunerUtils } from './utils/order_prune_utils';
import { OrderStateUtils } from './utils/order_state_utils';
import { ProtocolFeeUtils } from './utils/protocol_fee_utils';
@@ -533,8 +534,8 @@ export class SwapQuoter {
if (
opts.rfqt &&
opts.rfqt.intentOnFilling &&
opts.apiKey &&
this._rfqtTakerApiKeyWhitelist.includes(opts.apiKey)
opts.rfqt.apiKey &&
this._rfqtTakerApiKeyWhitelist.includes(opts.rfqt.apiKey)
) {
if (!opts.rfqt.takerAddress || opts.rfqt.takerAddress === constants.NULL_ADDRESS) {
throw new Error('RFQ-T requests must specify a taker address');
@@ -545,8 +546,7 @@ export class SwapQuoter {
takerAssetData,
assetFillAmount,
marketOperation,
opts.apiKey,
opts.rfqt.takerAddress,
opts.rfqt,
),
);
}

View File

@@ -188,16 +188,19 @@ export interface SwapQuoteOrdersBreakdown {
[source: string]: BigNumber;
}
export interface RfqtRequestOpts {
takerAddress: string;
apiKey: string;
intentOnFilling?: boolean;
makerEndpointMaxResponseTimeMs?: number;
}
/**
* gasPrice: gas price to determine protocolFee amount, default to ethGasStation fast amount
*/
export interface SwapQuoteRequestOpts extends CalculateSwapQuoteOpts {
gasPrice?: BigNumber;
apiKey?: string;
rfqt?: {
takerAddress: string;
intentOnFilling: boolean;
};
rfqt?: RfqtRequestOpts;
}
/**
@@ -274,10 +277,6 @@ export enum OrderPrunerPermittedFeeTypes {
TakerDenominatedTakerFee = 'TAKER_DENOMINATED_TAKER_FEE',
}
export interface RfqtFirmQuoteRequestOpts {
makerEndpointMaxResponseTimeMs?: number;
}
/**
* Represents a mocked RFQT maker responses.
*/

View File

@@ -6,7 +6,7 @@ import Axios, { AxiosResponse } from 'axios';
import * as _ from 'lodash';
import { constants } from '../constants';
import { MarketOperation, RfqtFirmQuoteRequestOpts } from '../types';
import { MarketOperation, RfqtRequestOpts } from '../types';
/**
* Request quotes from RFQ-T providers
@@ -27,6 +27,17 @@ function getTokenAddressOrThrow(assetData: string): string {
throw new Error(`Decoded asset data (${JSON.stringify(decodedAssetData)}) does not contain a token address`);
}
function assertTakerAddressOrThrow(takerAddress: string | undefined): void {
if (
takerAddress === undefined ||
takerAddress === '' ||
takerAddress === '0x' ||
takerAddress === constants.NULL_ADDRESS
) {
throw new Error('RFQ-T requires the presence of a taker address');
}
}
export class QuoteRequestor {
private readonly _rfqtMakerEndpoints: string[];
private readonly _schemaValidator: SchemaValidator = new SchemaValidator();
@@ -40,11 +51,10 @@ export class QuoteRequestor {
takerAssetData: string,
assetFillAmount: BigNumber,
marketOperation: MarketOperation,
takerApiKey: string,
takerAddress: string,
options?: Partial<RfqtFirmQuoteRequestOpts>,
options?: Partial<RfqtRequestOpts>,
): Promise<SignedOrder[]> {
const { makerEndpointMaxResponseTimeMs } = _.merge({}, constants.DEFAULT_RFQT_FIRM_QUOTE_REQUEST_OPTS, options);
const _opts = _.merge({}, constants.DEFAULT_RFQT_REQUEST_OPTS, options);
assertTakerAddressOrThrow(_opts.takerAddress);
const buyToken = getTokenAddressOrThrow(makerAssetData);
const sellToken = getTokenAddressOrThrow(takerAssetData);
@@ -55,20 +65,22 @@ export class QuoteRequestor {
this._rfqtMakerEndpoints.map(async rfqtMakerEndpoint => {
try {
return await Axios.get<SignedOrder>(`${rfqtMakerEndpoint}/quote`, {
headers: { '0x-api-key': takerApiKey },
headers: { '0x-api-key': _opts.apiKey },
params: {
sellToken,
buyToken,
buyAmount: marketOperation === MarketOperation.Buy ? assetFillAmount.toString() : undefined,
sellAmount:
marketOperation === MarketOperation.Sell ? assetFillAmount.toString() : undefined,
takerAddress,
takerAddress: _opts.takerAddress,
},
timeout: makerEndpointMaxResponseTimeMs,
timeout: _opts.makerEndpointMaxResponseTimeMs,
});
} catch (err) {
logUtils.warn(
`Failed to get RFQ-T quote from market maker endpoint ${rfqtMakerEndpoint} for API key ${takerApiKey} for taker address ${takerAddress}`,
`Failed to get RFQ-T quote from market maker endpoint ${rfqtMakerEndpoint} for API key ${
_opts.apiKey
} for taker address ${_opts.takerAddress}`,
);
logUtils.warn(err);
return undefined;

View File

@@ -23,7 +23,7 @@ describe('QuoteRequestor', async () => {
describe('requestRfqtFirmQuotesAsync', async () => {
it('should return successful RFQT requests', async () => {
const takerAddress = '0xd209925defc99488e3afff1174e48b4fa628302a';
const takerApiKey = 'my-ko0l-api-key';
const apiKey = 'my-ko0l-api-key';
// Set up RFQT responses
// tslint:disable-next-line:array-type
@@ -43,7 +43,7 @@ describe('QuoteRequestor', async () => {
});
mockedRequests.push({
endpoint: 'https://1337.0.0.1',
requestApiKey: takerApiKey,
requestApiKey: apiKey,
requestParams: expectedParams,
responseData: successfulOrder1,
responseCode: StatusCodes.Success,
@@ -51,7 +51,7 @@ describe('QuoteRequestor', async () => {
// Test out a bad response code, ensure it doesnt cause throw
mockedRequests.push({
endpoint: 'https://420.0.0.1',
requestApiKey: takerApiKey,
requestApiKey: apiKey,
requestParams: expectedParams,
responseData: { error: 'bad request' },
responseCode: StatusCodes.InternalError,
@@ -59,7 +59,7 @@ describe('QuoteRequestor', async () => {
// Test out a successful response code but an invalid order
mockedRequests.push({
endpoint: 'https://421.0.0.1',
requestApiKey: takerApiKey,
requestApiKey: apiKey,
requestParams: expectedParams,
responseData: { makerAssetData: '123' },
responseCode: StatusCodes.Success,
@@ -71,7 +71,7 @@ describe('QuoteRequestor', async () => {
});
mockedRequests.push({
endpoint: 'https://422.0.0.1',
requestApiKey: takerApiKey,
requestApiKey: apiKey,
requestParams: expectedParams,
responseData: wrongMakerAssetDataOrder,
responseCode: StatusCodes.Success,
@@ -83,7 +83,7 @@ describe('QuoteRequestor', async () => {
});
mockedRequests.push({
endpoint: 'https://423.0.0.1',
requestApiKey: takerApiKey,
requestApiKey: apiKey,
requestParams: expectedParams,
responseData: wrongTakerAssetDataOrder,
responseCode: StatusCodes.Success,
@@ -97,7 +97,7 @@ describe('QuoteRequestor', async () => {
delete unsignedOrder.signature;
mockedRequests.push({
endpoint: 'https://424.0.0.1',
requestApiKey: takerApiKey,
requestApiKey: apiKey,
requestParams: expectedParams,
responseData: unsignedOrder,
responseCode: StatusCodes.Success,
@@ -107,7 +107,7 @@ describe('QuoteRequestor', async () => {
const successfulOrder2 = testOrderFactory.generateTestSignedOrder({ makerAssetData, takerAssetData });
mockedRequests.push({
endpoint: 'https://37.0.0.1',
requestApiKey: takerApiKey,
requestApiKey: apiKey,
requestParams: expectedParams,
responseData: successfulOrder2,
responseCode: StatusCodes.Success,
@@ -128,8 +128,11 @@ describe('QuoteRequestor', async () => {
takerAssetData,
new BigNumber(10000),
MarketOperation.Sell,
takerApiKey,
takerAddress,
{
apiKey,
takerAddress,
intentOnFilling: true,
},
);
expect(resp.sort()).to.eql([successfulOrder1, successfulOrder2].sort());
});