Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 55 additions & 10 deletions src/gas/GasFeeController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import {
GasFeeStateChange,
LegacyGasPriceEstimate,
} from './GasFeeController';
import { EXTERNAL_GAS_PRICES_API_URL } from './gas-util';

const GAS_FEE_API = 'https://mock-gas-server.herokuapp.com/';
const TEST_GAS_FEE_API = 'https://mock-gas-server.herokuapp.com/<chain_id>';
const TEST_LEGACY_FEE_API = 'https://test/<chain_id>';

const name = 'GasFeeController';

Expand All @@ -30,8 +30,9 @@ function getRestrictedMessenger() {

describe('GasFeeController', () => {
let gasFeeController: GasFeeController;
let getIsMainnet: jest.Mock<boolean>;
let getCurrentNetworkLegacyGasAPICompatibility: jest.Mock<boolean>;
let getIsEIP1559Compatible: jest.Mock<Promise<boolean>>;
let getChainId: jest.Mock<`0x${string}` | `${number}` | number>;

beforeAll(() => {
nock.disableNetConnect();
Expand All @@ -42,11 +43,14 @@ describe('GasFeeController', () => {
});

beforeEach(() => {
getIsMainnet = jest.fn().mockImplementation(() => false);
getChainId = jest.fn().mockImplementation(() => '0x1');
getCurrentNetworkLegacyGasAPICompatibility = jest
.fn()
.mockImplementation(() => false);
getIsEIP1559Compatible = jest
.fn()
.mockImplementation(() => Promise.resolve(true));
nock(GAS_FEE_API)
nock(TEST_GAS_FEE_API.replace('<chain_id>', '1'))
.get(/.+/u)
.reply(200, {
low: {
Expand All @@ -71,7 +75,7 @@ describe('GasFeeController', () => {
})
.persist();

nock(EXTERNAL_GAS_PRICES_API_URL)
nock(TEST_LEGACY_FEE_API.replace('<chain_id>', '0x1'))
.get(/.+/u)
.reply(200, {
SafeGasPrice: '22',
Expand All @@ -84,8 +88,11 @@ describe('GasFeeController', () => {
interval: 10000,
messenger: getRestrictedMessenger(),
getProvider: () => stub(),
getChainId,
legacyAPIEndpoint: TEST_LEGACY_FEE_API,
EIP1559APIEndpoint: TEST_GAS_FEE_API,
onNetworkStateChange: () => stub(),
getIsMainnet,
getCurrentNetworkLegacyGasAPICompatibility,
getCurrentNetworkEIP1559Compatibility: getIsEIP1559Compatible, // change this for networkController.state.properties.isEIP1559Compatible ???
});
});
Expand Down Expand Up @@ -113,9 +120,47 @@ describe('GasFeeController', () => {
);
});

describe('when on mainnet before london', () => {
describe('when on any network supporting legacy gas estimation api', () => {
it('should _fetchGasFeeEstimateData', async () => {
getIsMainnet.mockImplementation(() => true);
getCurrentNetworkLegacyGasAPICompatibility.mockImplementation(() => true);
getIsEIP1559Compatible.mockImplementation(() => Promise.resolve(false));
expect(gasFeeController.state.gasFeeEstimates).toStrictEqual({});
const estimates = await gasFeeController._fetchGasFeeEstimateData();
expect(estimates).toHaveProperty('gasFeeEstimates');
expect(
(gasFeeController.state.gasFeeEstimates as LegacyGasPriceEstimate).high,
).toBe('30');
});
});

describe('getChainId', () => {
it('should work with a number input', async () => {
getChainId.mockImplementation(() => 1);
getCurrentNetworkLegacyGasAPICompatibility.mockImplementation(() => true);
getIsEIP1559Compatible.mockImplementation(() => Promise.resolve(false));
expect(gasFeeController.state.gasFeeEstimates).toStrictEqual({});
const estimates = await gasFeeController._fetchGasFeeEstimateData();
expect(estimates).toHaveProperty('gasFeeEstimates');
expect(
(gasFeeController.state.gasFeeEstimates as LegacyGasPriceEstimate).high,
).toBe('30');
});

it('should work with a hexstring input', async () => {
getChainId.mockImplementation(() => '0x1');
getCurrentNetworkLegacyGasAPICompatibility.mockImplementation(() => true);
getIsEIP1559Compatible.mockImplementation(() => Promise.resolve(false));
expect(gasFeeController.state.gasFeeEstimates).toStrictEqual({});
const estimates = await gasFeeController._fetchGasFeeEstimateData();
expect(estimates).toHaveProperty('gasFeeEstimates');
expect(
(gasFeeController.state.gasFeeEstimates as LegacyGasPriceEstimate).high,
).toBe('30');
});

it('should work with a numeric string input', async () => {
getChainId.mockImplementation(() => '1');
getCurrentNetworkLegacyGasAPICompatibility.mockImplementation(() => true);
getIsEIP1559Compatible.mockImplementation(() => Promise.resolve(false));
expect(gasFeeController.state.gasFeeEstimates).toStrictEqual({});
const estimates = await gasFeeController._fetchGasFeeEstimateData();
Expand All @@ -128,7 +173,7 @@ describe('GasFeeController', () => {

describe('when on any network supporting EIP-1559', () => {
it('should _fetchGasFeeEstimateData', async () => {
getIsMainnet.mockImplementation(() => true);
getCurrentNetworkLegacyGasAPICompatibility.mockImplementation(() => true);
expect(gasFeeController.state.gasFeeEstimates).toStrictEqual({});
const estimates = await gasFeeController._fetchGasFeeEstimateData();
expect(estimates).toHaveProperty('gasFeeEstimates');
Expand Down
44 changes: 36 additions & 8 deletions src/gas/GasFeeController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Patch } from 'immer';

import EthQuery from 'eth-query';
import { v1 as random } from 'uuid';
import { isHexString } from 'ethereumjs-util';
import { BaseController } from '../BaseControllerV2';
import { safelyExecute } from '../util';
import type { RestrictedControllerMessenger } from '../ControllerMessenger';
Expand All @@ -16,6 +17,9 @@ import {
calculateTimeEstimate,
} from './gas-util';

const GAS_FEE_API = 'https://mock-gas-server.herokuapp.com/';
export const LEGACY_GAS_PRICES_API_URL = `https://api.metaswap.codefi.network/gasPrices`;

export type unknownString = 'unknown';

// Fee Market describes the way gas is set after the london hardfork, and was
Expand Down Expand Up @@ -197,6 +201,10 @@ export class GasFeeController extends BaseController<typeof name, GasFeeState> {

private pollTokens: Set<string>;

private legacyAPIEndpoint: string;

private EIP1559APIEndpoint: string;

private fetchGasEstimates;

private fetchEthGasPriceEstimate;
Expand All @@ -205,9 +213,11 @@ export class GasFeeController extends BaseController<typeof name, GasFeeState> {

private getCurrentNetworkEIP1559Compatibility;

private getCurrentNetworkLegacyGasAPICompatibility;

private getCurrentAccountEIP1559Compatibility;

private getIsMainnet;
private getChainId;

private ethQuery: any;

Expand All @@ -224,9 +234,12 @@ export class GasFeeController extends BaseController<typeof name, GasFeeState> {
fetchLegacyGasPriceEstimates = defaultFetchLegacyGasPriceEstimates,
getCurrentNetworkEIP1559Compatibility,
getCurrentAccountEIP1559Compatibility,
getIsMainnet,
getChainId,
getCurrentNetworkLegacyGasAPICompatibility,
getProvider,
onNetworkStateChange,
legacyAPIEndpoint = LEGACY_GAS_PRICES_API_URL,
EIP1559APIEndpoint = GAS_FEE_API,
}: {
interval?: number;
messenger: RestrictedControllerMessenger<
Expand All @@ -241,10 +254,13 @@ export class GasFeeController extends BaseController<typeof name, GasFeeState> {
fetchEthGasPriceEstimate?: typeof defaultFetchEthGasPriceEstimate;
fetchLegacyGasPriceEstimates?: typeof defaultFetchLegacyGasPriceEstimates;
getCurrentNetworkEIP1559Compatibility: () => Promise<boolean>;
getCurrentNetworkLegacyGasAPICompatibility: () => boolean;
getCurrentAccountEIP1559Compatibility?: () => boolean;
getIsMainnet: () => boolean;
getChainId: () => `0x${string}` | `${number}` | number;
getProvider: () => NetworkController['provider'];
onNetworkStateChange: (listener: (state: NetworkState) => void) => void;
legacyAPIEndpoint?: string;
EIP1559APIEndpoint?: string;
}) {
super({
name,
Expand All @@ -258,8 +274,11 @@ export class GasFeeController extends BaseController<typeof name, GasFeeState> {
this.fetchLegacyGasPriceEstimates = fetchLegacyGasPriceEstimates;
this.pollTokens = new Set();
this.getCurrentNetworkEIP1559Compatibility = getCurrentNetworkEIP1559Compatibility;
this.getCurrentNetworkLegacyGasAPICompatibility = getCurrentNetworkLegacyGasAPICompatibility;
this.getCurrentAccountEIP1559Compatibility = getCurrentAccountEIP1559Compatibility;
this.getIsMainnet = getIsMainnet;
this.EIP1559APIEndpoint = EIP1559APIEndpoint;
this.legacyAPIEndpoint = legacyAPIEndpoint;
this.getChainId = getChainId;

const provider = getProvider();
this.ethQuery = new EthQuery(provider);
Expand Down Expand Up @@ -294,7 +313,12 @@ export class GasFeeController extends BaseController<typeof name, GasFeeState> {
*/
async _fetchGasFeeEstimateData(): Promise<GasFeeState | undefined> {
let isEIP1559Compatible;
const isMainnet = this.getIsMainnet();
const isLegacyGasAPICompatible = this.getCurrentNetworkLegacyGasAPICompatibility();

let chainId = this.getChainId();
if (typeof chainId === 'string' && isHexString(chainId)) {
chainId = parseInt(chainId, 16);
}
try {
isEIP1559Compatible = await this.getEIP1559Compatibility();
} catch (e) {
Expand All @@ -310,7 +334,9 @@ export class GasFeeController extends BaseController<typeof name, GasFeeState> {

try {
if (isEIP1559Compatible) {
const estimates = await this.fetchGasEstimates();
const estimates = await this.fetchGasEstimates(
this.EIP1559APIEndpoint.replace('<chain_id>', `${chainId}`),
);
const {
suggestedMaxPriorityFeePerGas,
suggestedMaxFeePerGas,
Expand All @@ -324,8 +350,10 @@ export class GasFeeController extends BaseController<typeof name, GasFeeState> {
estimatedGasFeeTimeBounds,
gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,
};
} else if (isMainnet) {
const estimates = await this.fetchLegacyGasPriceEstimates();
} else if (isLegacyGasAPICompatible) {
const estimates = await this.fetchLegacyGasPriceEstimates(
this.legacyAPIEndpoint.replace('<chain_id>', `${chainId}`),
);
newState = {
gasFeeEstimates: estimates,
estimatedGasFeeTimeBounds: {},
Expand Down
11 changes: 5 additions & 6 deletions src/gas/gas-util.test.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import nock from 'nock';
import {
EXTERNAL_GAS_PRICES_API_URL,
fetchLegacyGasPriceEstimates,
} from './gas-util';
import { fetchLegacyGasPriceEstimates } from './gas-util';

describe('gas utils', () => {
describe('fetchLegacyGasPriceEstimates', () => {
it('should fetch external gasPrices and return high/medium/low', async () => {
const scope = nock(EXTERNAL_GAS_PRICES_API_URL)
const scope = nock('https://not-a-real-url/')
.get(/.+/u)
.reply(200, {
SafeGasPrice: '22',
ProposeGasPrice: '25',
FastGasPrice: '30',
})
.persist();
const result = await fetchLegacyGasPriceEstimates();
const result = await fetchLegacyGasPriceEstimates(
'https://not-a-real-url/',
);
expect(result).toMatchObject({
high: '30',
medium: '25',
Expand Down
15 changes: 7 additions & 8 deletions src/gas/gas-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,19 @@ import {
LegacyGasPriceEstimate,
} from './GasFeeController';

const GAS_FEE_API = 'https://mock-gas-server.herokuapp.com/';
export const EXTERNAL_GAS_PRICES_API_URL = `https://api.metaswap.codefi.network/gasPrices`;

export async function fetchGasEstimates(): Promise<GasFeeEstimates> {
return await handleFetch(GAS_FEE_API);
export async function fetchGasEstimates(url: string): Promise<GasFeeEstimates> {
return await handleFetch(url);
}

/**
* Hit the legacy MetaSwaps gasPrices estimate api and return the low, medium
* high values from that API.
*/
export async function fetchLegacyGasPriceEstimates(): Promise<LegacyGasPriceEstimate> {
const result = await handleFetch(EXTERNAL_GAS_PRICES_API_URL, {
referrer: EXTERNAL_GAS_PRICES_API_URL,
export async function fetchLegacyGasPriceEstimates(
url: string,
): Promise<LegacyGasPriceEstimate> {
const result = await handleFetch(url, {
referrer: url,
referrerPolicy: 'no-referrer-when-downgrade',
method: 'GET',
mode: 'cors',
Expand Down