diff --git a/packages/gas-fee-controller/package.json b/packages/gas-fee-controller/package.json index a4342d6ae7a..6492fe5dc79 100644 --- a/packages/gas-fee-controller/package.json +++ b/packages/gas-fee-controller/package.json @@ -60,7 +60,6 @@ "deepmerge": "^4.2.2", "jest": "^27.5.1", "jest-when": "^3.4.2", - "nock": "^13.3.1", "sinon": "^9.2.4", "ts-jest": "^27.1.4", "typedoc": "^0.24.8", diff --git a/packages/gas-fee-controller/src/GasFeeController.test.ts b/packages/gas-fee-controller/src/GasFeeController.test.ts index d198f4b983a..b7dd918b120 100644 --- a/packages/gas-fee-controller/src/GasFeeController.test.ts +++ b/packages/gas-fee-controller/src/GasFeeController.test.ts @@ -25,7 +25,11 @@ import { fetchEthGasPriceEstimate, calculateTimeEstimate, } from './gas-util'; -import { GAS_ESTIMATE_TYPES, GasFeeController } from './GasFeeController'; +import { + GAS_API_BASE_URL, + GAS_ESTIMATE_TYPES, + GasFeeController, +} from './GasFeeController'; import type { GasFeeState, GasFeeStateChange, @@ -218,21 +222,19 @@ describe('GasFeeController', () => { * GasFeeController. * @param options.getCurrentNetworkLegacyGasAPICompatibility - Sets * getCurrentNetworkLegacyGasAPICompatibility on the GasFeeController. - * @param options.legacyAPIEndpoint - Sets legacyAPIEndpoint on the GasFeeController. - * @param options.EIP1559APIEndpoint - Sets EIP1559APIEndpoint on the GasFeeController. * @param options.clientId - Sets clientId on the GasFeeController. * @param options.networkControllerState - State object to initialize * NetworkController with. * @param options.interval - The polling interval. * @param options.state - The initial GasFeeController state + * @param options.infuraAPIKey - The Infura API key. */ async function setupGasFeeController({ getIsEIP1559Compatible = jest.fn().mockResolvedValue(true), getCurrentNetworkLegacyGasAPICompatibility = jest .fn() .mockReturnValue(false), - legacyAPIEndpoint = 'http://legacy.endpoint/', - EIP1559APIEndpoint = 'http://eip-1559.endpoint/', + infuraAPIKey = 'INFURA_API_KEY', clientId, getChainId, networkControllerState = {}, @@ -242,12 +244,11 @@ describe('GasFeeController', () => { getChainId?: jest.Mock; getIsEIP1559Compatible?: jest.Mock>; getCurrentNetworkLegacyGasAPICompatibility?: jest.Mock; - legacyAPIEndpoint?: string; - EIP1559APIEndpoint?: string; clientId?: string; networkControllerState?: Partial; state?: GasFeeState; interval?: number; + infuraAPIKey?: string; } = {}) { const controllerMessenger = getControllerMessenger(); networkController = await setupNetworkController({ @@ -262,11 +263,10 @@ describe('GasFeeController', () => { messenger, getCurrentNetworkLegacyGasAPICompatibility, getCurrentNetworkEIP1559Compatibility: getIsEIP1559Compatible, // change this for networkDetails.state.networkDetails.isEIP1559Compatible ??? - legacyAPIEndpoint, - EIP1559APIEndpoint, state, clientId, interval, + infuraAPIKey, }); } @@ -319,8 +319,6 @@ describe('GasFeeController', () => { getCurrentNetworkLegacyGasAPICompatibility: jest .fn() .mockReturnValue(true), - legacyAPIEndpoint: 'https://some-legacy-endpoint/', - EIP1559APIEndpoint: 'https://some-eip-1559-endpoint/', networkControllerState: { providerConfig: { type: NetworkType.rpc, @@ -338,15 +336,15 @@ describe('GasFeeController', () => { isEIP1559Compatible: false, isLegacyGasAPICompatible: true, fetchGasEstimates, - fetchGasEstimatesUrl: 'https://some-eip-1559-endpoint/1337', + fetchGasEstimatesUrl: `${GAS_API_BASE_URL}/networks/1337/suggestedGasFees`, fetchGasEstimatesViaEthFeeHistory, fetchLegacyGasPriceEstimates, - fetchLegacyGasPriceEstimatesUrl: - 'https://some-legacy-endpoint/1337', + fetchLegacyGasPriceEstimatesUrl: `${GAS_API_BASE_URL}/networks/1337/gasPrices`, fetchEthGasPriceEstimate, calculateTimeEstimate, clientId: '99999', ethQuery: expect.any(EthQuery), + infuraAPIKey: expect.any(String), }); }); @@ -375,8 +373,6 @@ describe('GasFeeController', () => { getCurrentNetworkLegacyGasAPICompatibility: jest .fn() .mockReturnValue(true), - legacyAPIEndpoint: 'https://some-legacy-endpoint/', - EIP1559APIEndpoint: 'https://some-eip-1559-endpoint/', networkControllerState: { providerConfig: { type: NetworkType.rpc, @@ -396,15 +392,15 @@ describe('GasFeeController', () => { isEIP1559Compatible: false, isLegacyGasAPICompatible: true, fetchGasEstimates, - fetchGasEstimatesUrl: 'https://some-eip-1559-endpoint/1337', + fetchGasEstimatesUrl: `${GAS_API_BASE_URL}/networks/1337/suggestedGasFees`, fetchGasEstimatesViaEthFeeHistory, fetchLegacyGasPriceEstimates, - fetchLegacyGasPriceEstimatesUrl: - 'https://some-legacy-endpoint/1337', + fetchLegacyGasPriceEstimatesUrl: `${GAS_API_BASE_URL}/networks/1337/gasPrices`, fetchEthGasPriceEstimate, calculateTimeEstimate, clientId: '99999', ethQuery: expect.any(EthQuery), + infuraAPIKey: expect.any(String), }); }); @@ -686,8 +682,6 @@ describe('GasFeeController', () => { it('should call determineGasFeeCalculations correctly', async () => { await setupGasFeeController({ ...defaultConstructorOptions, - legacyAPIEndpoint: 'https://some-legacy-endpoint/', - EIP1559APIEndpoint: 'https://some-eip-1559-endpoint/', networkControllerState: { providerConfig: { type: NetworkType.rpc, @@ -705,14 +699,15 @@ describe('GasFeeController', () => { isEIP1559Compatible: false, isLegacyGasAPICompatible: true, fetchGasEstimates, - fetchGasEstimatesUrl: 'https://some-eip-1559-endpoint/1337', + fetchGasEstimatesUrl: `${GAS_API_BASE_URL}/networks/1337/suggestedGasFees`, fetchGasEstimatesViaEthFeeHistory, fetchLegacyGasPriceEstimates, - fetchLegacyGasPriceEstimatesUrl: 'https://some-legacy-endpoint/1337', + fetchLegacyGasPriceEstimatesUrl: `${GAS_API_BASE_URL}/networks/1337/gasPrices`, fetchEthGasPriceEstimate, calculateTimeEstimate, clientId: '99999', ethQuery: expect.any(EthQuery), + infuraAPIKey: expect.any(String), }); }); @@ -737,7 +732,6 @@ describe('GasFeeController', () => { it('should call determineGasFeeCalculations correctly when getChainId returns a number input', async () => { await setupGasFeeController({ ...defaultConstructorOptions, - legacyAPIEndpoint: 'http://legacy.endpoint/', getChainId: jest.fn().mockReturnValue(1), }); @@ -745,7 +739,7 @@ describe('GasFeeController', () => { expect(mockedDetermineGasFeeCalculations).toHaveBeenCalledWith( expect.objectContaining({ - fetchLegacyGasPriceEstimatesUrl: 'http://legacy.endpoint/1', + fetchLegacyGasPriceEstimatesUrl: `${GAS_API_BASE_URL}/networks/1/gasPrices`, }), ); }); @@ -753,7 +747,6 @@ describe('GasFeeController', () => { it('should call determineGasFeeCalculations correctly when getChainId returns a hexstring input', async () => { await setupGasFeeController({ ...defaultConstructorOptions, - legacyAPIEndpoint: 'http://legacy.endpoint/', getChainId: jest.fn().mockReturnValue('0x1'), }); @@ -761,7 +754,7 @@ describe('GasFeeController', () => { expect(mockedDetermineGasFeeCalculations).toHaveBeenCalledWith( expect.objectContaining({ - fetchLegacyGasPriceEstimatesUrl: 'http://legacy.endpoint/1', + fetchLegacyGasPriceEstimatesUrl: `${GAS_API_BASE_URL}/networks/1/gasPrices`, }), ); }); @@ -769,7 +762,6 @@ describe('GasFeeController', () => { it('should call determineGasFeeCalculations correctly when getChainId returns a numeric string input', async () => { await setupGasFeeController({ ...defaultConstructorOptions, - legacyAPIEndpoint: 'http://legacy.endpoint/', getChainId: jest.fn().mockReturnValue('1'), }); @@ -777,7 +769,7 @@ describe('GasFeeController', () => { expect(mockedDetermineGasFeeCalculations).toHaveBeenCalledWith( expect.objectContaining({ - fetchLegacyGasPriceEstimatesUrl: 'http://legacy.endpoint/1', + fetchLegacyGasPriceEstimatesUrl: `${GAS_API_BASE_URL}/networks/1/gasPrices`, }), ); }); @@ -798,8 +790,6 @@ describe('GasFeeController', () => { it('should call determineGasFeeCalculations correctly', async () => { await setupGasFeeController({ ...defaultConstructorOptions, - legacyAPIEndpoint: 'https://some-legacy-endpoint/', - EIP1559APIEndpoint: 'https://some-eip-1559-endpoint/', networkControllerState: { providerConfig: { type: NetworkType.rpc, @@ -817,14 +807,15 @@ describe('GasFeeController', () => { isEIP1559Compatible: true, isLegacyGasAPICompatible: false, fetchGasEstimates, - fetchGasEstimatesUrl: 'https://some-eip-1559-endpoint/1337', + fetchGasEstimatesUrl: `${GAS_API_BASE_URL}/networks/1337/suggestedGasFees`, fetchGasEstimatesViaEthFeeHistory, fetchLegacyGasPriceEstimates, - fetchLegacyGasPriceEstimatesUrl: 'https://some-legacy-endpoint/1337', + fetchLegacyGasPriceEstimatesUrl: `${GAS_API_BASE_URL}/networks/1337/gasPrices`, fetchEthGasPriceEstimate, calculateTimeEstimate, clientId: '99999', ethQuery: expect.any(EthQuery), + infuraAPIKey: expect.any(String), }); }); @@ -849,7 +840,6 @@ describe('GasFeeController', () => { it('should call determineGasFeeCalculations with a URL that contains the chain ID', async () => { await setupGasFeeController({ ...defaultConstructorOptions, - EIP1559APIEndpoint: 'http://eip-1559.endpoint/', getChainId: jest.fn().mockReturnValue('0x1'), }); @@ -857,7 +847,7 @@ describe('GasFeeController', () => { expect(mockedDetermineGasFeeCalculations).toHaveBeenCalledWith( expect.objectContaining({ - fetchGasEstimatesUrl: 'http://eip-1559.endpoint/1', + fetchGasEstimatesUrl: `${GAS_API_BASE_URL}/networks/1/suggestedGasFees`, }), ); }); @@ -899,8 +889,6 @@ describe('GasFeeController', () => { it('should call determineGasFeeCalculations correctly', async () => { await setupGasFeeController({ ...defaultConstructorOptions, - legacyAPIEndpoint: 'https://some-legacy-endpoint/', - EIP1559APIEndpoint: 'https://some-eip-1559-endpoint/', clientId: '99999', }); @@ -912,16 +900,19 @@ describe('GasFeeController', () => { isEIP1559Compatible: true, isLegacyGasAPICompatible: false, fetchGasEstimates, - fetchGasEstimatesUrl: 'https://some-eip-1559-endpoint/5', + fetchGasEstimatesUrl: `${GAS_API_BASE_URL}/networks/${convertHexToDecimal( + ChainId.goerli, + )}/suggestedGasFees`, fetchGasEstimatesViaEthFeeHistory, fetchLegacyGasPriceEstimates, - fetchLegacyGasPriceEstimatesUrl: `https://some-legacy-endpoint/${convertHexToDecimal( + fetchLegacyGasPriceEstimatesUrl: `${GAS_API_BASE_URL}/networks/${convertHexToDecimal( ChainId.goerli, - )}`, + )}/gasPrices`, fetchEthGasPriceEstimate, calculateTimeEstimate, clientId: '99999', ethQuery: expect.any(EthQuery), + infuraAPIKey: expect.any(String), }); }); @@ -931,10 +922,6 @@ describe('GasFeeController', () => { await gasFeeController.fetchGasFeeEstimates({ networkClientId: 'goerli', }); - console.log( - 'gasFeeController.state.gasFeeEstimatesByChainId: ', - gasFeeController.state.gasFeeEstimatesByChainId, - ); expect( gasFeeController.state.gasFeeEstimatesByChainId?.[ChainId.goerli], @@ -954,7 +941,6 @@ describe('GasFeeController', () => { it('should call determineGasFeeCalculations with a URL that contains the chain ID', async () => { await setupGasFeeController({ ...defaultConstructorOptions, - EIP1559APIEndpoint: 'http://eip-1559.endpoint/', }); await gasFeeController.fetchGasFeeEstimates({ @@ -963,9 +949,9 @@ describe('GasFeeController', () => { expect(mockedDetermineGasFeeCalculations).toHaveBeenCalledWith( expect.objectContaining({ - fetchGasEstimatesUrl: `http://eip-1559.endpoint/${convertHexToDecimal( + fetchGasEstimatesUrl: `${GAS_API_BASE_URL}/networks/${convertHexToDecimal( ChainId.sepolia, - )}`, + )}/suggestedGasFees`, }), ); }); @@ -980,8 +966,6 @@ describe('GasFeeController', () => { getCurrentNetworkLegacyGasAPICompatibility: jest .fn() .mockReturnValue(true), - legacyAPIEndpoint: 'https://some-legacy-endpoint/', - EIP1559APIEndpoint: 'https://some-eip-1559-endpoint/', networkControllerState: { networksMetadata: { goerli: { @@ -1007,9 +991,9 @@ describe('GasFeeController', () => { expect(mockedDetermineGasFeeCalculations).toHaveBeenNthCalledWith( 1, expect.objectContaining({ - fetchGasEstimatesUrl: `https://some-eip-1559-endpoint/${convertHexToDecimal( + fetchGasEstimatesUrl: `${GAS_API_BASE_URL}/networks/${convertHexToDecimal( ChainId.goerli, - )}`, + )}/suggestedGasFees`, }), ); await clock.tickAsync(pollingInterval / 2); @@ -1018,9 +1002,9 @@ describe('GasFeeController', () => { expect(mockedDetermineGasFeeCalculations).toHaveBeenNthCalledWith( 2, expect.objectContaining({ - fetchGasEstimatesUrl: `https://some-eip-1559-endpoint/${convertHexToDecimal( + fetchGasEstimatesUrl: `${GAS_API_BASE_URL}/networks/${convertHexToDecimal( ChainId.goerli, - )}`, + )}/suggestedGasFees`, }), ); expect( @@ -1031,9 +1015,9 @@ describe('GasFeeController', () => { await clock.tickAsync(pollingInterval); expect(mockedDetermineGasFeeCalculations).toHaveBeenCalledWith( expect.objectContaining({ - fetchGasEstimatesUrl: `https://some-eip-1559-endpoint/${convertHexToDecimal( + fetchGasEstimatesUrl: `${GAS_API_BASE_URL}/networks/${convertHexToDecimal( ChainId.sepolia, - )}`, + )}/suggestedGasFees`, }), ); }); diff --git a/packages/gas-fee-controller/src/GasFeeController.ts b/packages/gas-fee-controller/src/GasFeeController.ts index ecb397e6078..411449827ad 100644 --- a/packages/gas-fee-controller/src/GasFeeController.ts +++ b/packages/gas-fee-controller/src/GasFeeController.ts @@ -25,13 +25,13 @@ import { v1 as random } from 'uuid'; import determineGasFeeCalculations from './determineGasFeeCalculations'; import fetchGasEstimatesViaEthFeeHistory from './fetchGasEstimatesViaEthFeeHistory'; import { + calculateTimeEstimate, fetchGasEstimates, fetchLegacyGasPriceEstimates, fetchEthGasPriceEstimate, - calculateTimeEstimate, } from './gas-util'; -export const LEGACY_GAS_PRICES_API_URL = `https://api.metaswap.codefi.network/gasPrices`; +export const GAS_API_BASE_URL = 'https://gas.api.infura.io'; export type unknownString = 'unknown'; @@ -274,6 +274,8 @@ export class GasFeeController extends StaticIntervalPollingController< private readonly getCurrentAccountEIP1559Compatibility; + private readonly infuraAPIKey: string; + private currentChainId; private ethQuery?: EthQuery; @@ -299,11 +301,9 @@ export class GasFeeController extends StaticIntervalPollingController< * @param options.getProvider - Returns a network provider for the current network. * @param options.onNetworkDidChange - A function for registering an event handler for the * network state change event. - * @param options.legacyAPIEndpoint - The legacy gas price API URL. This option is primarily for - * testing purposes. - * @param options.EIP1559APIEndpoint - The EIP-1559 gas price API URL. * @param options.clientId - The client ID used to identify to the gas estimation API who is * asking for estimates. + * @param options.infuraAPIKey - The Infura API key used for infura API requests. */ constructor({ interval = 15000, @@ -315,9 +315,8 @@ export class GasFeeController extends StaticIntervalPollingController< getCurrentNetworkLegacyGasAPICompatibility, getProvider, onNetworkDidChange, - legacyAPIEndpoint = LEGACY_GAS_PRICES_API_URL, - EIP1559APIEndpoint, clientId, + infuraAPIKey, }: { interval?: number; messenger: GasFeeMessenger; @@ -328,9 +327,8 @@ export class GasFeeController extends StaticIntervalPollingController< getChainId?: () => Hex; getProvider: () => ProviderProxy; onNetworkDidChange?: (listener: (state: NetworkState) => void) => void; - legacyAPIEndpoint?: string; - EIP1559APIEndpoint: string; clientId?: string; + infuraAPIKey: string; }) { super({ name, @@ -348,9 +346,10 @@ export class GasFeeController extends StaticIntervalPollingController< this.getCurrentAccountEIP1559Compatibility = getCurrentAccountEIP1559Compatibility; this.#getProvider = getProvider; - this.EIP1559APIEndpoint = EIP1559APIEndpoint; - this.legacyAPIEndpoint = legacyAPIEndpoint; + this.EIP1559APIEndpoint = `${GAS_API_BASE_URL}/networks//suggestedGasFees`; + this.legacyAPIEndpoint = `${GAS_API_BASE_URL}/networks//gasPrices`; this.clientId = clientId; + this.infuraAPIKey = infuraAPIKey; this.ethQuery = new EthQuery(this.#getProvider()); @@ -473,6 +472,7 @@ export class GasFeeController extends StaticIntervalPollingController< calculateTimeEstimate, clientId: this.clientId, ethQuery, + infuraAPIKey: this.infuraAPIKey, }); if (shouldUpdateState) { diff --git a/packages/gas-fee-controller/src/determineGasFeeCalculations.test.ts b/packages/gas-fee-controller/src/determineGasFeeCalculations.test.ts index 1bff2582a7f..4d21cccbaca 100644 --- a/packages/gas-fee-controller/src/determineGasFeeCalculations.test.ts +++ b/packages/gas-fee-controller/src/determineGasFeeCalculations.test.ts @@ -40,6 +40,8 @@ const mockedFetchGasEstimatesViaEthFeeHistory = Parameters >; +const INFURA_API_KEY_MOCK = 'test'; + /** * Builds mock data for the `fetchGasEstimates` function. All of the data here is filled in to make * the gas fee estimation code function in a way that represents a reasonably happy path; it does @@ -132,6 +134,7 @@ describe('determineGasFeeCalculations', () => { calculateTimeEstimate: mockedCalculateTimeEstimate, clientId: 'some-client-id', ethQuery: {}, + infuraAPIKey: INFURA_API_KEY_MOCK, }; describe('when isEIP1559Compatible is true', () => { diff --git a/packages/gas-fee-controller/src/determineGasFeeCalculations.ts b/packages/gas-fee-controller/src/determineGasFeeCalculations.ts index f62ee4a7e28..96496dc678c 100644 --- a/packages/gas-fee-controller/src/determineGasFeeCalculations.ts +++ b/packages/gas-fee-controller/src/determineGasFeeCalculations.ts @@ -31,6 +31,7 @@ import { GAS_ESTIMATE_TYPES } from './GasFeeController'; * @param args.calculateTimeEstimate - A function that determine time estimate bounds. * @param args.clientId - An identifier that an API can use to know who is asking for estimates. * @param args.ethQuery - An EthQuery instance we can use to talk to Ethereum directly. + * @param args.infuraAPIKey - Infura API key to use for requests to Infura. * @returns The gas fee calculations. */ export default async function determineGasFeeCalculations({ @@ -45,11 +46,13 @@ export default async function determineGasFeeCalculations({ calculateTimeEstimate, clientId, ethQuery, + infuraAPIKey, }: { isEIP1559Compatible: boolean; isLegacyGasAPICompatible: boolean; fetchGasEstimates: ( url: string, + infuraAPIKey: string, clientId?: string, ) => Promise; fetchGasEstimatesUrl: string; @@ -60,6 +63,7 @@ export default async function determineGasFeeCalculations({ ) => Promise; fetchLegacyGasPriceEstimates: ( url: string, + infuraAPIKey: string, clientId?: string, ) => Promise; fetchLegacyGasPriceEstimatesUrl: string; @@ -75,12 +79,17 @@ export default async function determineGasFeeCalculations({ // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any ethQuery: any; + infuraAPIKey: string; }): Promise { try { if (isEIP1559Compatible) { let estimates: GasFeeEstimates; try { - estimates = await fetchGasEstimates(fetchGasEstimatesUrl, clientId); + estimates = await fetchGasEstimates( + fetchGasEstimatesUrl, + infuraAPIKey, + clientId, + ); } catch { estimates = await fetchGasEstimatesViaEthFeeHistory(ethQuery); } @@ -99,6 +108,7 @@ export default async function determineGasFeeCalculations({ } else if (isLegacyGasAPICompatible) { const estimates = await fetchLegacyGasPriceEstimates( fetchLegacyGasPriceEstimatesUrl, + infuraAPIKey, clientId, ); return { diff --git a/packages/gas-fee-controller/src/gas-util.test.ts b/packages/gas-fee-controller/src/gas-util.test.ts index baea4369289..b1e5647be84 100644 --- a/packages/gas-fee-controller/src/gas-util.test.ts +++ b/packages/gas-fee-controller/src/gas-util.test.ts @@ -1,4 +1,4 @@ -import nock from 'nock'; +import { handleFetch } from '@metamask/controller-utils'; import { fetchLegacyGasPriceEstimates, @@ -8,6 +8,14 @@ import { } from './gas-util'; import type { GasFeeEstimates } from './GasFeeController'; +jest.mock('@metamask/controller-utils', () => { + return { + ...jest.requireActual('@metamask/controller-utils'), + handleFetch: jest.fn(), + }; +}); +const handleFetchMock = jest.mocked(handleFetch); + const mockEIP1559ApiResponses: GasFeeEstimates[] = [ { low: { @@ -65,27 +73,49 @@ const mockEIP1559ApiResponses: GasFeeEstimates[] = [ }, ]; +const INFURA_API_KEY_MOCK = 'test'; +const INFURA_AUTH_TOKEN_MOCK = 'dGVzdDo='; +const INFURA_GAS_API_URL_MOCK = 'https://gas.api.infura.io'; + describe('gas utils', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + describe('fetchGasEstimates', () => { it('should fetch external gasFeeEstimates when data is valid', async () => { - const scope = nock('https://not-a-real-url/') - .get(/.+/u) - .reply(200, mockEIP1559ApiResponses[0]) - .persist(); - const result = await fetchGasEstimates('https://not-a-real-url/'); + handleFetchMock.mockResolvedValue(mockEIP1559ApiResponses[0]); + const result = await fetchGasEstimates( + INFURA_GAS_API_URL_MOCK, + INFURA_API_KEY_MOCK, + ); + + expect(handleFetchMock).toHaveBeenCalledTimes(1); + expect(handleFetchMock).toHaveBeenCalledWith(INFURA_GAS_API_URL_MOCK, { + headers: expect.objectContaining({ + Authorization: `Basic ${INFURA_AUTH_TOKEN_MOCK}`, + }), + }); expect(result).toMatchObject(mockEIP1559ApiResponses[0]); - scope.done(); }); it('should fetch external gasFeeEstimates with client id header when clientId arg is added', async () => { - const scope = nock('https://not-a-real-url/') - .matchHeader('x-client-id', 'test') - .get(/.+/u) - .reply(200, mockEIP1559ApiResponses[0]) - .persist(); - const result = await fetchGasEstimates('https://not-a-real-url/', 'test'); + const clientIdMock = 'test'; + handleFetchMock.mockResolvedValue(mockEIP1559ApiResponses[0]); + const result = await fetchGasEstimates( + INFURA_GAS_API_URL_MOCK, + INFURA_API_KEY_MOCK, + clientIdMock, + ); + + expect(handleFetchMock).toHaveBeenCalledTimes(1); + expect(handleFetchMock).toHaveBeenCalledWith(INFURA_GAS_API_URL_MOCK, { + headers: expect.objectContaining({ + Authorization: `Basic ${INFURA_AUTH_TOKEN_MOCK}`, + 'X-Client-Id': clientIdMock, + }), + }); expect(result).toMatchObject(mockEIP1559ApiResponses[0]); - scope.done(); }); it('should fetch and normalize external gasFeeEstimates when data is has an invalid number of decimals', async () => { @@ -111,57 +141,73 @@ describe('gas utils', () => { estimatedBaseFee: '32.000000017', }; - const scope = nock('https://not-a-real-url/') - .get(/.+/u) - .reply(200, mockEIP1559ApiResponses[1]) - .persist(); - const result = await fetchGasEstimates('https://not-a-real-url/'); + handleFetchMock.mockResolvedValue(mockEIP1559ApiResponses[1]); + const result = await fetchGasEstimates( + INFURA_GAS_API_URL_MOCK, + INFURA_API_KEY_MOCK, + ); expect(result).toMatchObject(expectedResult); - scope.done(); }); }); describe('fetchLegacyGasPriceEstimates', () => { it('should fetch external gasPrices and return high/medium/low', async () => { - const scope = nock('https://not-a-real-url/') - .get(/.+/u) - .reply(200, { - SafeGasPrice: '22', - ProposeGasPrice: '25', - FastGasPrice: '30', - }) - .persist(); + handleFetchMock.mockResolvedValue({ + SafeGasPrice: '22', + ProposeGasPrice: '25', + FastGasPrice: '30', + }); const result = await fetchLegacyGasPriceEstimates( - 'https://not-a-real-url/', + INFURA_GAS_API_URL_MOCK, + INFURA_API_KEY_MOCK, + ); + + expect(handleFetchMock).toHaveBeenCalledTimes(1); + expect(handleFetchMock).toHaveBeenCalledWith( + INFURA_GAS_API_URL_MOCK, + + expect.objectContaining({ + headers: expect.objectContaining({ + Authorization: `Basic ${INFURA_AUTH_TOKEN_MOCK}`, + }), + }), ); expect(result).toMatchObject({ high: '30', medium: '25', low: '22', }); - scope.done(); }); it('should fetch external gasPrices with client id header when clientId arg is passed', async () => { - const scope = nock('https://not-a-real-url/') - .matchHeader('x-client-id', 'test') - .get(/.+/u) - .reply(200, { - SafeGasPrice: '22', - ProposeGasPrice: '25', - FastGasPrice: '30', - }) - .persist(); + const clientIdMock = 'test'; + handleFetchMock.mockResolvedValue({ + SafeGasPrice: '22', + ProposeGasPrice: '25', + FastGasPrice: '30', + }); const result = await fetchLegacyGasPriceEstimates( - 'https://not-a-real-url/', - 'test', + INFURA_GAS_API_URL_MOCK, + INFURA_API_KEY_MOCK, + clientIdMock, + ); + + expect(handleFetchMock).toHaveBeenCalledTimes(1); + expect(handleFetchMock).toHaveBeenCalledWith( + INFURA_GAS_API_URL_MOCK, + + expect.objectContaining({ + headers: expect.objectContaining({ + Authorization: `Basic ${INFURA_AUTH_TOKEN_MOCK}`, + 'X-Client-Id': clientIdMock, + }), + }), ); expect(result).toMatchObject({ high: '30', medium: '25', low: '22', }); - scope.done(); }); }); diff --git a/packages/gas-fee-controller/src/gas-util.ts b/packages/gas-fee-controller/src/gas-util.ts index 17a242ac8be..8a7c863f2f1 100644 --- a/packages/gas-fee-controller/src/gas-util.ts +++ b/packages/gas-fee-controller/src/gas-util.ts @@ -33,17 +33,19 @@ export function normalizeGWEIDecimalNumbers(n: string | number) { * Fetch gas estimates from the given URL. * * @param url - The gas estimate URL. + * @param infuraAPIKey - The Infura API key used for infura API requests. * @param clientId - The client ID used to identify to the API who is asking for estimates. * @returns The gas estimates. */ export async function fetchGasEstimates( url: string, + infuraAPIKey: string, clientId?: string, ): Promise { - const estimates = await handleFetch( - url, - clientId ? { headers: makeClientIdHeader(clientId) } : undefined, - ); + const infuraAuthToken = buildInfuraAuthToken(infuraAPIKey); + const estimates = await handleFetch(url, { + headers: getHeaders(infuraAuthToken, clientId), + }); return { low: { ...estimates.low, @@ -87,22 +89,22 @@ export async function fetchGasEstimates( * high values from that API. * * @param url - The URL to fetch gas price estimates from. + * @param infuraAPIKey - The Infura API key used for infura API requests. * @param clientId - The client ID used to identify to the API who is asking for estimates. * @returns The gas price estimates. */ export async function fetchLegacyGasPriceEstimates( url: string, + infuraAPIKey: string, clientId?: string, ): Promise { + const infuraAuthToken = buildInfuraAuthToken(infuraAPIKey); const result = await handleFetch(url, { referrer: url, referrerPolicy: 'no-referrer-when-downgrade', method: 'GET', mode: 'cors', - headers: { - 'Content-Type': 'application/json', - ...(clientId && makeClientIdHeader(clientId)), - }, + headers: getHeaders(infuraAuthToken, clientId), }); return { low: result.SafeGasPrice, @@ -191,3 +193,30 @@ export function calculateTimeEstimate( upperTimeBound, }; } + +/** + * Build an infura auth token from the given API key and secret. + * + * @param infuraAPIKey - The Infura API key. + * @returns The base64 encoded auth token. + */ +function buildInfuraAuthToken(infuraAPIKey: string) { + // We intentionally leave the password empty, as Infura does not require one + return Buffer.from(`${infuraAPIKey}:`).toString('base64'); +} + +/** + * Get the headers for a request to the gas fee API. + * + * @param infuraAuthToken - The Infura auth token to use for the request. + * @param clientId - The client ID used to identify to the API who is asking for estimates. + * @returns The headers for the request. + */ +function getHeaders(infuraAuthToken: string, clientId?: string) { + return { + 'Content-Type': 'application/json', + Authorization: `Basic ${infuraAuthToken}`, + // Only add the clientId header if clientId is a non-empty string + ...(clientId?.trim() ? makeClientIdHeader(clientId) : {}), + }; +} diff --git a/yarn.lock b/yarn.lock index ce624094de4..bc68a7a12fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2342,7 +2342,6 @@ __metadata: deepmerge: ^4.2.2 jest: ^27.5.1 jest-when: ^3.4.2 - nock: ^13.3.1 sinon: ^9.2.4 ts-jest: ^27.1.4 typedoc: ^0.24.8