asset-swapper: RFQ-T indicative quotes
These changes have been exercised via mocha tests in the 0x-api repo.
Not sure why I had to add GetMarketOrdersRfqtOpts to the package
exports.  `yarn test:generate_docs:circleci` said:
$ node ./packages/monorepo-scripts/lib/doc_generate.js --package @0x/asset-swapper
GENERATE_DOCS: Generating Typedoc JSON for @0x/asset-swapper...
GENERATE_DOCS: Generating Typedoc Markdown for @0x/asset-swapper...
GENERATE_DOCS: Modifying Markdown To Exclude Unexported Items...
Error: @0x/asset-swapper package needs to export:
GetMarketOrdersRfqtOpts
From it's index.ts. If any are from external dependencies, then add them to the EXTERNAL_TYPE_MAP.
    at DocGenerateUtils._lookForMissingReferenceExportsThrowIfExists (/root/repo/packages/monorepo-scripts/lib/utils/doc_generate_utils.js:288:19)
    at DocGenerateUtils.<anonymous> (/root/repo/packages/monorepo-scripts/lib/utils/doc_generate_utils.js:255:34)
    at step (/root/repo/packages/monorepo-scripts/lib/utils/doc_generate_utils.js:32:23)
    at Object.next (/root/repo/packages/monorepo-scripts/lib/utils/doc_generate_utils.js:13:53)
    at fulfilled (/root/repo/packages/monorepo-scripts/lib/utils/doc_generate_utils.js:4:58)
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:189:7)
			
			
This commit is contained in:
		| @@ -64,8 +64,9 @@ export { | ||||
|     CollapsedFill, | ||||
|     NativeCollapsedFill, | ||||
|     OptimizedMarketOrder, | ||||
|     GetMarketOrdersRfqtOpts, | ||||
| } from './utils/market_operation_utils/types'; | ||||
| export { affiliateFeeUtils } from './utils/affiliate_fee_utils'; | ||||
| export { ProtocolFeeUtils } from './utils/protocol_fee_utils'; | ||||
| export { QuoteRequestor } from './utils/quote_requestor'; | ||||
| export { QuoteRequestor, RfqtIndicativeQuoteResponse } from './utils/quote_requestor'; | ||||
| export { rfqtMocker } from './utils/rfqt_mocker'; | ||||
|   | ||||
| @@ -9,6 +9,7 @@ import * as _ from 'lodash'; | ||||
|  | ||||
| import { constants } from './constants'; | ||||
| import { | ||||
|     CalculateSwapQuoteOpts, | ||||
|     LiquidityForTakerMakerAssetDataPair, | ||||
|     MarketBuySwapQuote, | ||||
|     MarketOperation, | ||||
| @@ -24,7 +25,6 @@ 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'; | ||||
| @@ -565,19 +565,30 @@ export class SwapQuoter { | ||||
|  | ||||
|         let swapQuote: SwapQuote; | ||||
|  | ||||
|         const calcOpts: CalculateSwapQuoteOpts = opts; | ||||
|         if ( | ||||
|             // we should request indicative quotes: | ||||
|             calcOpts.rfqt && | ||||
|             !calcOpts.rfqt.intentOnFilling && | ||||
|             calcOpts.rfqt.apiKey && | ||||
|             this._rfqtTakerApiKeyWhitelist.includes(calcOpts.rfqt.apiKey) | ||||
|         ) { | ||||
|             calcOpts.rfqt.quoteRequestor = this._quoteRequestor; | ||||
|         } | ||||
|  | ||||
|         if (marketOperation === MarketOperation.Buy) { | ||||
|             swapQuote = await this._swapQuoteCalculator.calculateMarketBuySwapQuoteAsync( | ||||
|                 orders, | ||||
|                 assetFillAmount, | ||||
|                 gasPrice, | ||||
|                 opts, | ||||
|                 calcOpts, | ||||
|             ); | ||||
|         } else { | ||||
|             swapQuote = await this._swapQuoteCalculator.calculateMarketSellSwapQuoteAsync( | ||||
|                 orders, | ||||
|                 assetFillAmount, | ||||
|                 gasPrice, | ||||
|                 opts, | ||||
|                 calcOpts, | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -287,3 +287,16 @@ export interface MockedRfqtFirmQuoteResponse { | ||||
|     responseData: any; | ||||
|     responseCode: number; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Represents a mocked RFQT maker responses. | ||||
|  */ | ||||
| export interface MockedRfqtIndicativeQuoteResponse { | ||||
|     endpoint: string; | ||||
|     requestApiKey: string; | ||||
|     requestParams: { | ||||
|         [key: string]: string | undefined; | ||||
|     }; | ||||
|     responseData: any; | ||||
|     responseCode: number; | ||||
| } | ||||
|   | ||||
| @@ -3,6 +3,7 @@ import { SignedOrder } from '@0x/types'; | ||||
| import { BigNumber, NULL_ADDRESS } from '@0x/utils'; | ||||
|  | ||||
| import { MarketOperation } from '../../types'; | ||||
| import { RfqtIndicativeQuoteResponse } from '../quote_requestor'; | ||||
| import { difference } from '../utils'; | ||||
|  | ||||
| import { BUY_SOURCES, DEFAULT_GET_MARKET_ORDERS_OPTS, FEE_QUOTE_SOURCES, ONE_ETHER, SELL_SOURCES } from './constants'; | ||||
| @@ -13,7 +14,12 @@ import { | ||||
|     getPathAdjustedSlippage, | ||||
|     getPathSize, | ||||
| } from './fills'; | ||||
| import { createOrdersFromPath, createSignedOrdersWithFillableAmounts, getNativeOrderTokens } from './orders'; | ||||
| import { | ||||
|     createOrdersFromPath, | ||||
|     createSignedOrdersFromRfqtIndicativeQuotes, | ||||
|     createSignedOrdersWithFillableAmounts, | ||||
|     getNativeOrderTokens, | ||||
| } from './orders'; | ||||
| import { findOptimalPath } from './path_optimizer'; | ||||
| import { DexOrderSampler, getSampleAmounts } from './sampler'; | ||||
| import { | ||||
| @@ -57,12 +63,7 @@ export class MarketOperationUtils { | ||||
|         const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts }; | ||||
|         const [makerToken, takerToken] = getNativeOrderTokens(nativeOrders[0]); | ||||
|         // Call the sampler contract. | ||||
|         const [ | ||||
|             orderFillableAmounts, | ||||
|             liquidityProviderAddress, | ||||
|             ethToMakerAssetRate, | ||||
|             dexQuotes, | ||||
|         ] = await this._sampler.executeAsync( | ||||
|         const samplerPromise = this._sampler.executeAsync( | ||||
|             // Get native order fillable amounts. | ||||
|             DexOrderSampler.ops.getOrderFillableTakerAmounts(nativeOrders), | ||||
|             // Get the custom liquidity provider from registry. | ||||
| @@ -92,10 +93,25 @@ export class MarketOperationUtils { | ||||
|                 this._liquidityProviderRegistry, | ||||
|             ), | ||||
|         ); | ||||
|         const rfqtPromise = | ||||
|             _opts !== undefined && _opts.rfqt !== undefined && _opts.rfqt.quoteRequestor !== undefined | ||||
|                 ? _opts.rfqt.quoteRequestor.requestRfqtIndicativeQuotesAsync( | ||||
|                       nativeOrders[0].makerAssetData, | ||||
|                       nativeOrders[0].takerAssetData, | ||||
|                       takerAmount, | ||||
|                       MarketOperation.Sell, | ||||
|                       _opts.rfqt, | ||||
|                   ) | ||||
|                 : Promise.resolve<RfqtIndicativeQuoteResponse[]>([]); | ||||
|         const [ | ||||
|             [orderFillableAmounts, liquidityProviderAddress, ethToMakerAssetRate, dexQuotes], | ||||
|             rfqtIndicativeQuotes, | ||||
|         ] = await Promise.all([samplerPromise, rfqtPromise]); | ||||
|         return this._generateOptimizedOrders({ | ||||
|             orderFillableAmounts, | ||||
|             nativeOrders, | ||||
|             dexQuotes, | ||||
|             rfqtIndicativeQuotes, | ||||
|             liquidityProviderAddress, | ||||
|             inputToken: takerToken, | ||||
|             outputToken: makerToken, | ||||
| @@ -130,12 +146,7 @@ export class MarketOperationUtils { | ||||
|         const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts }; | ||||
|         const [makerToken, takerToken] = getNativeOrderTokens(nativeOrders[0]); | ||||
|         // Call the sampler contract. | ||||
|         const [ | ||||
|             orderFillableAmounts, | ||||
|             liquidityProviderAddress, | ||||
|             ethToTakerAssetRate, | ||||
|             dexQuotes, | ||||
|         ] = await this._sampler.executeAsync( | ||||
|         const samplerPromise = this._sampler.executeAsync( | ||||
|             // Get native order fillable amounts. | ||||
|             DexOrderSampler.ops.getOrderFillableMakerAmounts(nativeOrders), | ||||
|             // Get the custom liquidity provider from registry. | ||||
| @@ -165,11 +176,26 @@ export class MarketOperationUtils { | ||||
|                 this._liquidityProviderRegistry, | ||||
|             ), | ||||
|         ); | ||||
|         const rfqtPromise = | ||||
|             opts !== undefined && _opts.rfqt !== undefined && _opts.rfqt.quoteRequestor !== undefined | ||||
|                 ? _opts.rfqt.quoteRequestor.requestRfqtIndicativeQuotesAsync( | ||||
|                       nativeOrders[0].makerAssetData, | ||||
|                       nativeOrders[0].takerAssetData, | ||||
|                       makerAmount, | ||||
|                       MarketOperation.Buy, | ||||
|                       _opts.rfqt, | ||||
|                   ) | ||||
|                 : []; | ||||
|         const [ | ||||
|             [orderFillableAmounts, liquidityProviderAddress, ethToTakerAssetRate, dexQuotes], | ||||
|             rfqtIndicativeQuotes, | ||||
|         ] = await Promise.all([samplerPromise, rfqtPromise]); | ||||
|  | ||||
|         return this._generateOptimizedOrders({ | ||||
|             orderFillableAmounts, | ||||
|             nativeOrders, | ||||
|             dexQuotes, | ||||
|             rfqtIndicativeQuotes, | ||||
|             liquidityProviderAddress, | ||||
|             inputToken: makerToken, | ||||
|             outputToken: takerToken, | ||||
| @@ -246,6 +272,7 @@ export class MarketOperationUtils { | ||||
|                     orderFillableAmounts, | ||||
|                     nativeOrders, | ||||
|                     dexQuotes, | ||||
|                     rfqtIndicativeQuotes: [], | ||||
|                     inputToken: makerToken, | ||||
|                     outputToken: takerToken, | ||||
|                     side: MarketOperation.Buy, | ||||
| @@ -274,6 +301,7 @@ export class MarketOperationUtils { | ||||
|         nativeOrders: SignedOrder[]; | ||||
|         orderFillableAmounts: BigNumber[]; | ||||
|         dexQuotes: DexSample[][]; | ||||
|         rfqtIndicativeQuotes: RfqtIndicativeQuoteResponse[]; | ||||
|         runLimit?: number; | ||||
|         ethToOutputRate?: BigNumber; | ||||
|         bridgeSlippage?: number; | ||||
| @@ -290,7 +318,10 @@ export class MarketOperationUtils { | ||||
|         const paths = createFillPaths({ | ||||
|             side, | ||||
|             // Augment native orders with their fillable amounts. | ||||
|             orders: createSignedOrdersWithFillableAmounts(side, opts.nativeOrders, opts.orderFillableAmounts), | ||||
|             orders: [ | ||||
|                 ...createSignedOrdersWithFillableAmounts(side, opts.nativeOrders, opts.orderFillableAmounts), | ||||
|                 ...createSignedOrdersFromRfqtIndicativeQuotes(opts.rfqtIndicativeQuotes), | ||||
|             ], | ||||
|             dexQuotes: opts.dexQuotes, | ||||
|             targetInput: inputAmount, | ||||
|             ethToOutputRate: opts.ethToOutputRate, | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import { ERC20BridgeAssetData, SignedOrder } from '@0x/types'; | ||||
| import { AbiEncoder, BigNumber } from '@0x/utils'; | ||||
|  | ||||
| import { MarketOperation, SignedOrderWithFillableAmounts } from '../../types'; | ||||
| import { RfqtIndicativeQuoteResponse } from '../quote_requestor'; | ||||
|  | ||||
| import { | ||||
|     DEFAULT_CURVE_OPTS, | ||||
| @@ -358,3 +359,32 @@ function createNativeOrder(fill: CollapsedFill): OptimizedMarketOrder { | ||||
|         ...(fill as NativeCollapsedFill).nativeOrder, | ||||
|     }; | ||||
| } | ||||
|  | ||||
| export function createSignedOrdersFromRfqtIndicativeQuotes( | ||||
|     quotes: RfqtIndicativeQuoteResponse[], | ||||
| ): SignedOrderWithFillableAmounts[] { | ||||
|     return quotes.map(quote => { | ||||
|         return { | ||||
|             fillableMakerAssetAmount: quote.makerAssetAmount, | ||||
|             fillableTakerAssetAmount: quote.takerAssetAmount, | ||||
|             makerAssetAmount: quote.makerAssetAmount, | ||||
|             takerAssetAmount: quote.takerAssetAmount, | ||||
|             makerAssetData: quote.makerAssetData, | ||||
|             takerAssetData: quote.takerAssetData, | ||||
|             takerAddress: NULL_ADDRESS, | ||||
|             makerAddress: NULL_ADDRESS, | ||||
|             senderAddress: NULL_ADDRESS, | ||||
|             feeRecipientAddress: NULL_ADDRESS, | ||||
|             salt: ZERO_AMOUNT, // generatePseudoRandomSalt(), | ||||
|             expirationTimeSeconds: new BigNumber(Math.floor(Date.now() / ONE_SECOND_MS) + ONE_HOUR_IN_SECONDS), | ||||
|             makerFeeAssetData: NULL_BYTES, | ||||
|             takerFeeAssetData: NULL_BYTES, | ||||
|             makerFee: ZERO_AMOUNT, | ||||
|             takerFee: ZERO_AMOUNT, | ||||
|             fillableTakerFeeAmount: ZERO_AMOUNT, | ||||
|             signature: WALLET_SIGNATURE, | ||||
|             chainId: 0, // HACK !!!!!!!!! how can we get at this from this context? | ||||
|             exchangeAddress: NULL_ADDRESS, // HACK !!!!!!!!! how can we get at this from this context? | ||||
|         }; | ||||
|     }); | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,8 @@ | ||||
| import { IERC20BridgeSamplerContract } from '@0x/contract-wrappers'; | ||||
| import { BigNumber } from '@0x/utils'; | ||||
|  | ||||
| import { SignedOrderWithFillableAmounts } from '../../types'; | ||||
| import { RfqtRequestOpts, SignedOrderWithFillableAmounts } from '../../types'; | ||||
| import { QuoteRequestor, RfqtIndicativeQuoteResponse } from '../../utils/quote_requestor'; | ||||
|  | ||||
| /** | ||||
|  * Order domain keys: chainId and exchange | ||||
| @@ -34,6 +35,7 @@ export enum ERC20BridgeSource { | ||||
|     CurveUsdcDaiUsdtTusd = 'Curve_USDC_DAI_USDT_TUSD', | ||||
|     LiquidityProvider = 'LiquidityProvider', | ||||
|     CurveUsdcDaiUsdtBusd = 'Curve_USDC_DAI_USDT_BUSD', | ||||
|     Rfqt = 'Rfqt', | ||||
| } | ||||
|  | ||||
| // Internal `fillData` field for `Fill` objects. | ||||
| @@ -44,6 +46,10 @@ export interface NativeFillData extends FillData { | ||||
|     order: SignedOrderWithFillableAmounts; | ||||
| } | ||||
|  | ||||
| export interface RfqtFillData extends FillData { | ||||
|     quote: RfqtIndicativeQuoteResponse; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Represents an individual DEX sample from the sampler contract. | ||||
|  */ | ||||
| @@ -130,6 +136,10 @@ export interface OptimizedMarketOrder extends SignedOrderWithFillableAmounts { | ||||
|     fills: CollapsedFill[]; | ||||
| } | ||||
|  | ||||
| export interface GetMarketOrdersRfqtOpts extends RfqtRequestOpts { | ||||
|     quoteRequestor?: QuoteRequestor; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Options for `getMarketSellOrdersAsync()` and `getMarketBuyOrdersAsync()`. | ||||
|  */ | ||||
| @@ -183,6 +193,7 @@ export interface GetMarketOrdersOpts { | ||||
|      * sources. Defaults to `true`. | ||||
|      */ | ||||
|     allowFallback: boolean; | ||||
|     rfqt?: GetMarketOrdersRfqtOpts; | ||||
|     /** | ||||
|      * Whether to combine contiguous bridge orders into a single DexForwarderBridge | ||||
|      * order. Defaults to `true`. | ||||
|   | ||||
| @@ -12,6 +12,13 @@ import { MarketOperation, RfqtRequestOpts } from '../types'; | ||||
|  * Request quotes from RFQ-T providers | ||||
|  */ | ||||
|  | ||||
| export interface RfqtIndicativeQuoteResponse { | ||||
|     makerAssetData: string; | ||||
|     makerAssetAmount: BigNumber; | ||||
|     takerAssetData: string; | ||||
|     takerAssetAmount: BigNumber; | ||||
| } | ||||
|  | ||||
| function getTokenAddressOrThrow(assetData: string): string { | ||||
|     const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData); | ||||
|     if (decodedAssetData.hasOwnProperty('tokenAddress')) { | ||||
| @@ -141,4 +148,81 @@ export class QuoteRequestor { | ||||
|  | ||||
|         return orders; | ||||
|     } | ||||
|  | ||||
|     public async requestRfqtIndicativeQuotesAsync( | ||||
|         makerAssetData: string, | ||||
|         takerAssetData: string, | ||||
|         assetFillAmount: BigNumber, | ||||
|         marketOperation: MarketOperation, | ||||
|         options: RfqtRequestOpts, | ||||
|     ): Promise<RfqtIndicativeQuoteResponse[]> { | ||||
|         const _opts = _.merge({}, constants.DEFAULT_RFQT_REQUEST_OPTS, options); | ||||
|         assertTakerAddressOrThrow(_opts.takerAddress); | ||||
|  | ||||
|         const axiosResponsesIfDefined: Array< | ||||
|             undefined | AxiosResponse<RfqtIndicativeQuoteResponse> | ||||
|         > = await Promise.all( | ||||
|             this._rfqtMakerEndpoints.map(async rfqtMakerEndpoint => { | ||||
|                 try { | ||||
|                     return await Axios.get<RfqtIndicativeQuoteResponse>(`${rfqtMakerEndpoint}/price`, { | ||||
|                         headers: { '0x-api-key': options.apiKey }, | ||||
|                         params: { | ||||
|                             takerAddress: options.takerAddress, | ||||
|                             ...inferQueryParams(marketOperation, makerAssetData, takerAssetData, assetFillAmount), | ||||
|                         }, | ||||
|                         timeout: options.makerEndpointMaxResponseTimeMs, | ||||
|                     }); | ||||
|                 } catch (err) { | ||||
|                     logUtils.warn( | ||||
|                         `Failed to get RFQ-T quote from market maker endpoint ${rfqtMakerEndpoint} for API key ${ | ||||
|                             options.apiKey | ||||
|                         } for taker address ${options.takerAddress}`, | ||||
|                     ); | ||||
|                     logUtils.warn(err); | ||||
|                     return undefined; | ||||
|                 } | ||||
|             }), | ||||
|         ); | ||||
|  | ||||
|         const axiosResponses = axiosResponsesIfDefined.filter( | ||||
|             (respIfDefd): respIfDefd is AxiosResponse<RfqtIndicativeQuoteResponse> => respIfDefd !== undefined, | ||||
|         ); | ||||
|  | ||||
|         const responsesWithStringInts = axiosResponses.map(response => response.data); // not yet BigNumber | ||||
|  | ||||
|         const validResponsesWithStringInts = responsesWithStringInts.filter(response => { | ||||
|             if (this._isValidRfqtIndicativeQuoteResponse(response)) { | ||||
|                 return true; | ||||
|             } | ||||
|             logUtils.warn(`Invalid RFQ-T indicative quote received, filtering out: ${JSON.stringify(response)}`); | ||||
|             return false; | ||||
|         }); | ||||
|  | ||||
|         const responses = validResponsesWithStringInts.map(response => { | ||||
|             return { | ||||
|                 ...response, | ||||
|                 makerAssetAmount: new BigNumber(response.makerAssetAmount), | ||||
|                 takerAssetAmount: new BigNumber(response.takerAssetAmount), | ||||
|             }; | ||||
|         }); | ||||
|  | ||||
|         return responses; | ||||
|     } | ||||
|  | ||||
|     private _isValidRfqtIndicativeQuoteResponse(response: RfqtIndicativeQuoteResponse): boolean { | ||||
|         const hasValidMakerAssetAmount = this._schemaValidator.isValid( | ||||
|             response.makerAssetAmount, | ||||
|             schemas.wholeNumberSchema, | ||||
|         ); | ||||
|         const hasValidTakerAssetAmount = this._schemaValidator.isValid( | ||||
|             response.takerAssetAmount, | ||||
|             schemas.wholeNumberSchema, | ||||
|         ); | ||||
|         const hasValidMakerAssetData = this._schemaValidator.isValid(response.makerAssetData, schemas.hexSchema); | ||||
|         const hasValidTakerAssetData = this._schemaValidator.isValid(response.takerAssetData, schemas.hexSchema); | ||||
|         if (hasValidMakerAssetAmount && hasValidTakerAssetAmount && hasValidMakerAssetData && hasValidTakerAssetData) { | ||||
|             return true; | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -28,6 +28,27 @@ export const rfqtMocker = { | ||||
|                     .replyOnce(responseCode, responseData); | ||||
|             } | ||||
|  | ||||
|             await performFn(); | ||||
|         } finally { | ||||
|             // Ensure we always restore axios afterwards | ||||
|             mockedAxios.restore(); | ||||
|         } | ||||
|     }, | ||||
|     withMockedRfqtIndicativeQuotes: async ( | ||||
|         mockedResponses: MockedRfqtFirmQuoteResponse[], | ||||
|         performFn: () => Promise<void>, | ||||
|     ) => { | ||||
|         const mockedAxios = new AxiosMockAdapter(axios); | ||||
|         try { | ||||
|             // Mock out RFQT responses | ||||
|             for (const mockedResponse of mockedResponses) { | ||||
|                 const { endpoint, requestApiKey, requestParams, responseData, responseCode } = mockedResponse; | ||||
|                 const requestHeaders = { Accept: 'application/json, text/plain, */*', '0x-api-key': requestApiKey }; | ||||
|                 mockedAxios | ||||
|                     .onGet(`${endpoint}/price`, { params: requestParams }, requestHeaders) | ||||
|                     .replyOnce(responseCode, responseData); | ||||
|             } | ||||
|  | ||||
|             await performFn(); | ||||
|         } finally { | ||||
|             // Ensure we always restore axios afterwards | ||||
|   | ||||
| @@ -20,7 +20,7 @@ describe('QuoteRequestor', async () => { | ||||
|     const makerAssetData = assetDataUtils.encodeERC20AssetData(makerToken); | ||||
|     const takerAssetData = assetDataUtils.encodeERC20AssetData(takerToken); | ||||
|  | ||||
|     describe('requestRfqtFirmQuotesAsync', async () => { | ||||
|     describe('requestRfqtFirmQuotesAsync for firm quotes', async () => { | ||||
|         it('should return successful RFQT requests', async () => { | ||||
|             const takerAddress = '0xd209925defc99488e3afff1174e48b4fa628302a'; | ||||
|             const apiKey = 'my-ko0l-api-key'; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user