Compare commits

...

3 Commits

Author SHA1 Message Date
Github Actions
6ce4458a5d Publish
- @0x/asset-swapper@16.27.2
2021-09-14 17:13:46 +00:00
Github Actions
fad6e65c07 Updated CHANGELOGS & MD docs 2021-09-14 17:13:42 +00:00
Daniel Pyrathon
840c85373e fix: Refactor integrator ID and add Prometheus metrics (#322)
* Refactor integrator ID and add Prometheus metrics

* Update packages/asset-swapper/src/swap_quoter.ts

Co-authored-by: David Walsh <5778036+rhinodavid@users.noreply.github.com>

* Update packages/asset-swapper/src/swap_quoter.ts

Co-authored-by: David Walsh <5778036+rhinodavid@users.noreply.github.com>

* Update packages/asset-swapper/src/swap_quoter.ts

Co-authored-by: David Walsh <5778036+rhinodavid@users.noreply.github.com>

* Added documentation and fixed some minor requests

* Added more metrics

* more docs

* lint fix

* added new Integrator ID addition

* refactor tests

Co-authored-by: David Walsh <5778036+rhinodavid@users.noreply.github.com>
2021-09-14 12:45:41 -04:00
11 changed files with 162 additions and 33 deletions

View File

@@ -1,4 +1,13 @@
[ [
{
"timestamp": 1631639620,
"version": "16.27.2",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{ {
"version": "16.27.1", "version": "16.27.1",
"changes": [ "changes": [

View File

@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG CHANGELOG
## v16.27.2 - _September 14, 2021_
* Dependencies updated
## v16.27.1 - _September 8, 2021_ ## v16.27.1 - _September 8, 2021_
* Fix ApproximateBuys sampler to terminate if the buy amount is not met (#319) * Fix ApproximateBuys sampler to terminate if the buy amount is not met (#319)

View File

@@ -1,6 +1,6 @@
{ {
"name": "@0x/asset-swapper", "name": "@0x/asset-swapper",
"version": "16.27.1", "version": "16.27.2",
"engines": { "engines": {
"node": ">=6.12" "node": ">=6.12"
}, },

View File

@@ -50,7 +50,7 @@ const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = {
samplerGasLimit: 500e6, samplerGasLimit: 500e6,
ethGasStationUrl: ETH_GAS_STATION_API_URL, ethGasStationUrl: ETH_GAS_STATION_API_URL,
rfqt: { rfqt: {
takerApiKeyWhitelist: [], integratorsWhitelist: [],
makerAssetOfferings: {}, makerAssetOfferings: {},
txOriginBlacklist: new Set(), txOriginBlacklist: new Set(),
}, },

View File

@@ -88,6 +88,7 @@ export {
ExchangeProxyContractOpts, ExchangeProxyContractOpts,
ExchangeProxyRefundReceiver, ExchangeProxyRefundReceiver,
GetExtensionContractTypeOpts, GetExtensionContractTypeOpts,
Integrator,
LogFunction, LogFunction,
MarketBuySwapQuote, MarketBuySwapQuote,
MarketOperation, MarketOperation,

View File

@@ -75,6 +75,7 @@ export class SwapQuoter {
private readonly _marketOperationUtils: MarketOperationUtils; private readonly _marketOperationUtils: MarketOperationUtils;
private readonly _rfqtOptions?: SwapQuoterRfqOpts; private readonly _rfqtOptions?: SwapQuoterRfqOpts;
private readonly _quoteRequestorHttpClient: AxiosInstance; private readonly _quoteRequestorHttpClient: AxiosInstance;
private readonly _integratorIdsSet: Set<string>;
/** /**
* Instantiates a new SwapQuoter instance * Instantiates a new SwapQuoter instance
@@ -164,6 +165,9 @@ export class SwapQuoter {
httpsAgent: new HttpsAgent({ keepAlive: true, timeout: KEEP_ALIVE_TTL }), httpsAgent: new HttpsAgent({ keepAlive: true, timeout: KEEP_ALIVE_TTL }),
...(rfqt ? rfqt.axiosInstanceOpts : {}), ...(rfqt ? rfqt.axiosInstanceOpts : {}),
}); });
const integratorIds = this._rfqtOptions?.integratorsWhitelist.map(integrator => integrator.integratorId) || [];
this._integratorIdsSet = new Set(integratorIds);
} }
public async getBatchMarketBuySwapQuoteAsync( public async getBatchMarketBuySwapQuoteAsync(
@@ -414,12 +418,11 @@ export class SwapQuoter {
return isOpenOrder && !willOrderExpire && isFeeTypeAllowed; return isOpenOrder && !willOrderExpire && isFeeTypeAllowed;
}; // tslint:disable-line:semicolon }; // tslint:disable-line:semicolon
private _isApiKeyWhitelisted(apiKey: string | undefined): boolean { private _isIntegratorIdWhitelisted(integratorId: string | undefined): boolean {
if (!apiKey) { if (!integratorId) {
return false; return false;
} }
const whitelistedApiKeys = this._rfqtOptions ? this._rfqtOptions.takerApiKeyWhitelist : []; return this._integratorIdsSet.has(integratorId);
return whitelistedApiKeys.includes(apiKey);
} }
private _isTxOriginBlacklisted(txOrigin: string | undefined): boolean { private _isTxOriginBlacklisted(txOrigin: string | undefined): boolean {
@@ -438,19 +441,19 @@ export class SwapQuoter {
return rfqt; return rfqt;
} }
// tslint:disable-next-line: boolean-naming // tslint:disable-next-line: boolean-naming
const { apiKey, nativeExclusivelyRFQ, intentOnFilling, txOrigin } = rfqt; const { integrator, nativeExclusivelyRFQ, intentOnFilling, txOrigin } = rfqt;
// If RFQ-T is enabled and `nativeExclusivelyRFQ` is set, then `ERC20BridgeSource.Native` should // If RFQ-T is enabled and `nativeExclusivelyRFQ` is set, then `ERC20BridgeSource.Native` should
// never be excluded. // never be excluded.
if (nativeExclusivelyRFQ === true && !sourceFilters.isAllowed(ERC20BridgeSource.Native)) { if (nativeExclusivelyRFQ === true && !sourceFilters.isAllowed(ERC20BridgeSource.Native)) {
throw new Error('Native liquidity cannot be excluded if "rfqt.nativeExclusivelyRFQ" is set'); throw new Error('Native liquidity cannot be excluded if "rfqt.nativeExclusivelyRFQ" is set');
} }
// If an API key was provided, but the key is not whitelisted, raise a warning and disable RFQ // If an integrator ID was provided, but the ID is not whitelisted, raise a warning and disable RFQ
if (!this._isApiKeyWhitelisted(apiKey)) { if (!this._isIntegratorIdWhitelisted(integrator.integratorId)) {
if (this._rfqtOptions && this._rfqtOptions.warningLogger) { if (this._rfqtOptions && this._rfqtOptions.warningLogger) {
this._rfqtOptions.warningLogger( this._rfqtOptions.warningLogger(
{ {
apiKey, ...integrator,
}, },
'Attempt at using an RFQ API key that is not whitelisted. Disabling RFQ for the request lifetime.', 'Attempt at using an RFQ API key that is not whitelisted. Disabling RFQ for the request lifetime.',
); );
@@ -474,7 +477,7 @@ export class SwapQuoter {
// Otherwise check other RFQ options // Otherwise check other RFQ options
if ( if (
intentOnFilling && // The requestor is asking for a firm quote intentOnFilling && // The requestor is asking for a firm quote
this._isApiKeyWhitelisted(apiKey) && // A valid API key was provided this._isIntegratorIdWhitelisted(integrator.integratorId) && // A valid API key was provided
sourceFilters.isAllowed(ERC20BridgeSource.Native) // Native liquidity is not excluded sourceFilters.isAllowed(ERC20BridgeSource.Native) // Native liquidity is not excluded
) { ) {
if (!txOrigin || txOrigin === constants.NULL_ADDRESS) { if (!txOrigin || txOrigin === constants.NULL_ADDRESS) {

View File

@@ -243,7 +243,7 @@ export interface RfqmRequestOptions extends RfqRequestOpts {
export interface RfqRequestOpts { export interface RfqRequestOpts {
takerAddress: string; takerAddress: string;
txOrigin: string; txOrigin: string;
apiKey: string; integrator: Integrator;
apiKeyWhitelist?: string[]; apiKeyWhitelist?: string[];
intentOnFilling: boolean; intentOnFilling: boolean;
isIndicative?: boolean; isIndicative?: boolean;
@@ -294,8 +294,13 @@ export interface RfqFirmQuoteValidator {
getRfqtTakerFillableAmountsAsync(quotes: RfqOrder[]): Promise<BigNumber[]>; getRfqtTakerFillableAmountsAsync(quotes: RfqOrder[]): Promise<BigNumber[]>;
} }
export interface Integrator {
integratorId: string;
label: string;
}
export interface SwapQuoterRfqOpts { export interface SwapQuoterRfqOpts {
takerApiKeyWhitelist: string[]; integratorsWhitelist: Integrator[];
makerAssetOfferings: RfqMakerAssetOfferings; makerAssetOfferings: RfqMakerAssetOfferings;
txOriginBlacklist: Set<string>; txOriginBlacklist: Set<string>;
altRfqCreds?: { altRfqCreds?: {

View File

@@ -14,6 +14,7 @@ import { constants } from '../constants';
import { import {
AltQuoteModel, AltQuoteModel,
AltRfqMakerAssetOfferings, AltRfqMakerAssetOfferings,
Integrator,
LogFunction, LogFunction,
MarketOperation, MarketOperation,
RfqMakerAssetOfferings, RfqMakerAssetOfferings,
@@ -61,6 +62,31 @@ export interface MetricsProxy {
* @param expirationTimeSeconds the expiration time in seconds * @param expirationTimeSeconds the expiration time in seconds
*/ */
incrementFillRatioWarningCounter(isLastLook: boolean, maker: string): void; incrementFillRatioWarningCounter(isLastLook: boolean, maker: string): void;
/**
* Logs the outcome of a network (HTTP) interaction with a market maker.
*
* @param interaction.isLastLook true if the request is RFQM
* @param interaction.integrator the integrator that is requesting the RFQ quote
* @param interaction.url the URL of the market maker
* @param interaction.quoteType indicative or firm quote
* @param interaction.statusCode the statusCode returned by a market maker
* @param interaction.latencyMs the latency of the HTTP request (in ms)
* @param interaction.included if a firm quote that was returned got included in the next step of processing.
* NOTE: this does not mean that the request returned a valid fillable order. It just
* means that the network response was successful.
*/
logRfqMakerNetworkInteraction(interaction: {
isLastLook: boolean;
integrator: Integrator;
url: string;
quoteType: 'firm' | 'indicative';
statusCode: number | undefined;
latencyMs: number;
included: boolean;
sellTokenAddress: string;
buyTokenAddress: string;
}): void;
} }
/** /**
@@ -453,7 +479,20 @@ export class QuoteRequestor {
// filter out requests to skip // filter out requests to skip
const isBlacklisted = rfqMakerBlacklist.isMakerBlacklisted(typedMakerUrl.url); const isBlacklisted = rfqMakerBlacklist.isMakerBlacklisted(typedMakerUrl.url);
const partialLogEntry = { url: typedMakerUrl.url, quoteType, requestParams, isBlacklisted }; const partialLogEntry = { url: typedMakerUrl.url, quoteType, requestParams, isBlacklisted };
const { isLastLook, integrator } = options;
const { sellTokenAddress, buyTokenAddress } = requestParams;
if (isBlacklisted) { if (isBlacklisted) {
this._metrics?.logRfqMakerNetworkInteraction({
isLastLook: false,
url: typedMakerUrl.url,
quoteType,
statusCode: undefined,
sellTokenAddress,
buyTokenAddress,
latencyMs: 0,
included: false,
integrator,
});
this._infoLogger({ rfqtMakerInteraction: { ...partialLogEntry } }); this._infoLogger({ rfqtMakerInteraction: { ...partialLogEntry } });
return; return;
} else if ( } else if (
@@ -472,18 +511,32 @@ export class QuoteRequestor {
try { try {
if (typedMakerUrl.pairType === RfqPairType.Standard) { if (typedMakerUrl.pairType === RfqPairType.Standard) {
const response = await this._quoteRequestorHttpClient.get(`${typedMakerUrl.url}/${quotePath}`, { const response = await this._quoteRequestorHttpClient.get(`${typedMakerUrl.url}/${quotePath}`, {
headers: { '0x-api-key': options.apiKey }, headers: {
'0x-api-key': options.integrator.integratorId,
'0x-integrator-id': options.integrator.integratorId,
},
params: requestParams, params: requestParams,
timeout: timeoutMs, timeout: timeoutMs,
cancelToken: cancelTokenSource.token, cancelToken: cancelTokenSource.token,
}); });
const latencyMs = Date.now() - timeBeforeAwait; const latencyMs = Date.now() - timeBeforeAwait;
this._metrics?.logRfqMakerNetworkInteraction({
isLastLook: isLastLook || false,
url: typedMakerUrl.url,
quoteType,
statusCode: response.status,
sellTokenAddress,
buyTokenAddress,
latencyMs,
included: true,
integrator,
});
this._infoLogger({ this._infoLogger({
rfqtMakerInteraction: { rfqtMakerInteraction: {
...partialLogEntry, ...partialLogEntry,
response: { response: {
included: true, included: true,
apiKey: options.apiKey, apiKey: options.integrator.integratorId,
takerAddress: requestParams.takerAddress, takerAddress: requestParams.takerAddress,
txOrigin: requestParams.txOrigin, txOrigin: requestParams.txOrigin,
statusCode: response.status, statusCode: response.status,
@@ -501,7 +554,7 @@ export class QuoteRequestor {
typedMakerUrl.url, typedMakerUrl.url,
this._altRfqCreds.altRfqApiKey, this._altRfqCreds.altRfqApiKey,
this._altRfqCreds.altRfqProfile, this._altRfqCreds.altRfqProfile,
options.apiKey, options.integrator.integratorId,
quoteType === 'firm' ? AltQuoteModel.Firm : AltQuoteModel.Indicative, quoteType === 'firm' ? AltQuoteModel.Firm : AltQuoteModel.Indicative,
makerToken, makerToken,
takerToken, takerToken,
@@ -514,12 +567,23 @@ export class QuoteRequestor {
); );
const latencyMs = Date.now() - timeBeforeAwait; const latencyMs = Date.now() - timeBeforeAwait;
this._metrics?.logRfqMakerNetworkInteraction({
isLastLook: isLastLook || false,
url: typedMakerUrl.url,
quoteType,
statusCode: quote.status,
sellTokenAddress,
buyTokenAddress,
latencyMs,
included: true,
integrator,
});
this._infoLogger({ this._infoLogger({
rfqtMakerInteraction: { rfqtMakerInteraction: {
...partialLogEntry, ...partialLogEntry,
response: { response: {
included: true, included: true,
apiKey: options.apiKey, apiKey: options.integrator.integratorId,
takerAddress: requestParams.takerAddress, takerAddress: requestParams.takerAddress,
txOrigin: requestParams.txOrigin, txOrigin: requestParams.txOrigin,
statusCode: quote.status, statusCode: quote.status,
@@ -533,12 +597,23 @@ export class QuoteRequestor {
} catch (err) { } catch (err) {
// log error if any // log error if any
const latencyMs = Date.now() - timeBeforeAwait; const latencyMs = Date.now() - timeBeforeAwait;
this._metrics?.logRfqMakerNetworkInteraction({
isLastLook: isLastLook || false,
url: typedMakerUrl.url,
quoteType,
statusCode: err.response?.status,
sellTokenAddress,
buyTokenAddress,
latencyMs,
included: false,
integrator,
});
this._infoLogger({ this._infoLogger({
rfqtMakerInteraction: { rfqtMakerInteraction: {
...partialLogEntry, ...partialLogEntry,
response: { response: {
included: false, included: false,
apiKey: options.apiKey, apiKey: options.integrator.integratorId,
takerAddress: requestParams.takerAddress, takerAddress: requestParams.takerAddress,
txOrigin: requestParams.txOrigin, txOrigin: requestParams.txOrigin,
statusCode: err.response ? err.response.status : undefined, statusCode: err.response ? err.response.status : undefined,
@@ -549,7 +624,7 @@ export class QuoteRequestor {
rfqMakerBlacklist.logTimeoutOrLackThereof(typedMakerUrl.url, latencyMs >= timeoutMs); rfqMakerBlacklist.logTimeoutOrLackThereof(typedMakerUrl.url, latencyMs >= timeoutMs);
this._warningLogger( this._warningLogger(
convertIfAxiosError(err), convertIfAxiosError(err),
`Failed to get RFQ-T ${quoteType} quote from market maker endpoint ${typedMakerUrl.url} for API key ${options.apiKey} for taker address ${options.takerAddress} and tx origin ${options.txOrigin}`, `Failed to get RFQ-T ${quoteType} quote from market maker endpoint ${typedMakerUrl.url} for integrator ${options.integrator.integratorId} (${options.integrator.label}) for taker address ${options.takerAddress} and tx origin ${options.txOrigin}`,
); );
return; return;
} }

View File

@@ -16,7 +16,7 @@ import * as _ from 'lodash';
import * as TypeMoq from 'typemoq'; import * as TypeMoq from 'typemoq';
import { MarketOperation, QuoteRequestor, RfqRequestOpts, SignedNativeOrder } from '../src'; import { MarketOperation, QuoteRequestor, RfqRequestOpts, SignedNativeOrder } from '../src';
import { NativeOrderWithFillableAmounts } from '../src/types'; import { Integrator, NativeOrderWithFillableAmounts } from '../src/types';
import { MarketOperationUtils } from '../src/utils/market_operation_utils/'; import { MarketOperationUtils } from '../src/utils/market_operation_utils/';
import { import {
BUY_SOURCE_FILTER_BY_CHAIN_ID, BUY_SOURCE_FILTER_BY_CHAIN_ID,
@@ -62,6 +62,10 @@ const SELL_SOURCES = SELL_SOURCE_FILTER_BY_CHAIN_ID[ChainId.Mainnet].sources;
const TOKEN_ADJACENCY_GRAPH: TokenAdjacencyGraph = { default: [] }; const TOKEN_ADJACENCY_GRAPH: TokenAdjacencyGraph = { default: [] };
const SIGNATURE = { v: 1, r: NULL_BYTES, s: NULL_BYTES, signatureType: SignatureType.EthSign }; const SIGNATURE = { v: 1, r: NULL_BYTES, s: NULL_BYTES, signatureType: SignatureType.EthSign };
const FOO_INTEGRATOR: Integrator = {
integratorId: 'foo',
label: 'foo',
};
/** /**
* gets the orders required for a market sell operation by (potentially) merging native orders with * gets the orders required for a market sell operation by (potentially) merging native orders with
@@ -745,7 +749,7 @@ describe('MarketOperationUtils tests', () => {
feeSchedule, feeSchedule,
rfqt: { rfqt: {
isIndicative: false, isIndicative: false,
apiKey: 'foo', integrator: FOO_INTEGRATOR,
takerAddress: randomAddress(), takerAddress: randomAddress(),
txOrigin: randomAddress(), txOrigin: randomAddress(),
intentOnFilling: true, intentOnFilling: true,
@@ -790,7 +794,7 @@ describe('MarketOperationUtils tests', () => {
...DEFAULT_OPTS, ...DEFAULT_OPTS,
rfqt: { rfqt: {
isIndicative: false, isIndicative: false,
apiKey: 'foo', integrator: FOO_INTEGRATOR,
takerAddress: randomAddress(), takerAddress: randomAddress(),
intentOnFilling: true, intentOnFilling: true,
txOrigin: randomAddress(), txOrigin: randomAddress(),
@@ -837,7 +841,7 @@ describe('MarketOperationUtils tests', () => {
...DEFAULT_OPTS, ...DEFAULT_OPTS,
rfqt: { rfqt: {
isIndicative: true, isIndicative: true,
apiKey: 'foo', integrator: FOO_INTEGRATOR,
takerAddress: randomAddress(), takerAddress: randomAddress(),
txOrigin: randomAddress(), txOrigin: randomAddress(),
intentOnFilling: true, intentOnFilling: true,
@@ -896,7 +900,10 @@ describe('MarketOperationUtils tests', () => {
...DEFAULT_OPTS, ...DEFAULT_OPTS,
rfqt: { rfqt: {
isIndicative: false, isIndicative: false,
apiKey: 'foo', integrator: {
integratorId: 'foo',
label: 'foo',
},
takerAddress: randomAddress(), takerAddress: randomAddress(),
intentOnFilling: true, intentOnFilling: true,
txOrigin: randomAddress(), txOrigin: randomAddress(),
@@ -954,7 +961,7 @@ describe('MarketOperationUtils tests', () => {
...DEFAULT_OPTS, ...DEFAULT_OPTS,
rfqt: { rfqt: {
isIndicative: false, isIndicative: false,
apiKey: 'foo', integrator: FOO_INTEGRATOR,
takerAddress: randomAddress(), takerAddress: randomAddress(),
txOrigin: randomAddress(), txOrigin: randomAddress(),
intentOnFilling: true, intentOnFilling: true,

View File

@@ -240,7 +240,10 @@ describe('QuoteRequestor', async () => {
MarketOperation.Sell, MarketOperation.Sell,
undefined, undefined,
{ {
apiKey, integrator: {
integratorId: apiKey,
label: 'foo',
},
takerAddress, takerAddress,
txOrigin: takerAddress, txOrigin: takerAddress,
intentOnFilling: true, intentOnFilling: true,
@@ -435,7 +438,10 @@ describe('QuoteRequestor', async () => {
MarketOperation.Sell, MarketOperation.Sell,
undefined, undefined,
{ {
apiKey, integrator: {
integratorId: apiKey,
label: 'foo',
},
takerAddress, takerAddress,
txOrigin: takerAddress, txOrigin: takerAddress,
intentOnFilling: true, intentOnFilling: true,
@@ -551,7 +557,10 @@ describe('QuoteRequestor', async () => {
MarketOperation.Sell, MarketOperation.Sell,
undefined, undefined,
{ {
apiKey, integrator: {
integratorId: apiKey,
label: 'foo',
},
takerAddress, takerAddress,
txOrigin: takerAddress, txOrigin: takerAddress,
intentOnFilling: true, intentOnFilling: true,
@@ -675,7 +684,10 @@ describe('QuoteRequestor', async () => {
MarketOperation.Sell, MarketOperation.Sell,
undefined, undefined,
{ {
apiKey, integrator: {
integratorId: apiKey,
label: 'foo',
},
takerAddress, takerAddress,
txOrigin: takerAddress, txOrigin: takerAddress,
intentOnFilling: true, intentOnFilling: true,
@@ -762,7 +774,10 @@ describe('QuoteRequestor', async () => {
MarketOperation.Sell, MarketOperation.Sell,
undefined, undefined,
{ {
apiKey, integrator: {
integratorId: apiKey,
label: 'foo',
},
takerAddress, takerAddress,
txOrigin: takerAddress, txOrigin: takerAddress,
intentOnFilling: true, intentOnFilling: true,
@@ -823,7 +838,10 @@ describe('QuoteRequestor', async () => {
MarketOperation.Buy, MarketOperation.Buy,
undefined, undefined,
{ {
apiKey, integrator: {
integratorId: apiKey,
label: 'foo',
},
takerAddress, takerAddress,
txOrigin: takerAddress, txOrigin: takerAddress,
intentOnFilling: true, intentOnFilling: true,
@@ -1088,7 +1106,10 @@ describe('QuoteRequestor', async () => {
altScenario.requestedOperation, altScenario.requestedOperation,
undefined, undefined,
{ {
apiKey, integrator: {
integratorId: apiKey,
label: 'foo',
},
takerAddress, takerAddress,
txOrigin, txOrigin,
intentOnFilling: true, intentOnFilling: true,

View File

@@ -48,7 +48,11 @@ export const testHelpers = {
// Mock out Standard RFQ-T/M 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,
'0x-integrator-id': requestApiKey,
};
if (mockedResponse.callback !== undefined) { if (mockedResponse.callback !== undefined) {
mockedAxios mockedAxios
.onGet(`${endpoint}/${quoteType}`, { params: requestParams }, requestHeaders) .onGet(`${endpoint}/${quoteType}`, { params: requestParams }, requestHeaders)