Refactor integrator ID and add Prometheus metrics
This commit is contained in:
@@ -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(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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 _isIntegrationKeyWhitelisted(apiKey: string | undefined): boolean {
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const whitelistedApiKeys = this._rfqtOptions ? this._rfqtOptions.takerApiKeyWhitelist : [];
|
return this._integratorIdsSet.has(apiKey);
|
||||||
return whitelistedApiKeys.includes(apiKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _isTxOriginBlacklisted(txOrigin: string | undefined): boolean {
|
private _isTxOriginBlacklisted(txOrigin: string | undefined): boolean {
|
||||||
@@ -438,7 +441,7 @@ 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)) {
|
||||||
@@ -446,11 +449,11 @@ export class SwapQuoter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If an API key was provided, but the key is not whitelisted, raise a warning and disable RFQ
|
// If an API key was provided, but the key is not whitelisted, raise a warning and disable RFQ
|
||||||
if (!this._isApiKeyWhitelisted(apiKey)) {
|
if (!this._isIntegrationKeyWhitelisted(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._isIntegrationKeyWhitelisted(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) {
|
||||||
|
|||||||
@@ -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?: {
|
||||||
|
|||||||
@@ -61,6 +61,20 @@ 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;
|
||||||
|
|
||||||
|
|
||||||
|
logRfqMakerInteraction(interaction: {
|
||||||
|
isLastLook: boolean,
|
||||||
|
integrator: {
|
||||||
|
integratorId: string;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
url: string;
|
||||||
|
quoteType: 'firm' | 'indicative';
|
||||||
|
statusCode: number;
|
||||||
|
latencyMs: number;
|
||||||
|
included: boolean;
|
||||||
|
}): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -472,18 +486,28 @@ 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 },
|
||||||
params: requestParams,
|
params: requestParams,
|
||||||
timeout: timeoutMs,
|
timeout: timeoutMs,
|
||||||
cancelToken: cancelTokenSource.token,
|
cancelToken: cancelTokenSource.token,
|
||||||
});
|
});
|
||||||
const latencyMs = Date.now() - timeBeforeAwait;
|
const latencyMs = Date.now() - timeBeforeAwait;
|
||||||
|
const {isLastLook, integrator} = options;
|
||||||
|
this._metrics?.logRfqMakerInteraction({
|
||||||
|
isLastLook: isLastLook || false,
|
||||||
|
url: typedMakerUrl.url,
|
||||||
|
quoteType,
|
||||||
|
statusCode: response.status,
|
||||||
|
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 +525,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,
|
||||||
@@ -519,7 +543,7 @@ export class QuoteRequestor {
|
|||||||
...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,
|
||||||
@@ -538,7 +562,7 @@ export class QuoteRequestor {
|
|||||||
...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 +573,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 API key ${options.integrator.integratorId} (${options.integrator.label}) for taker address ${options.takerAddress} and tx origin ${options.txOrigin}`,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user