Skip to content

Commit 39f8d0d

Browse files
committed
Implement layer 1 gas fee flows
1 parent dfc5925 commit 39f8d0d

7 files changed

Lines changed: 279 additions & 23 deletions

File tree

packages/transaction-controller/src/TransactionController.ts

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import { projectLogger as log } from './logger';
5858
import type {
5959
Events,
6060
DappSuggestedGasFees,
61+
Layer1GasFeeFlow,
6162
SavedGasFees,
6263
SecurityProviderRequest,
6364
SendFlowHistoryEntry,
@@ -80,6 +81,7 @@ import {
8081
addInitialHistorySnapshot,
8182
updateTransactionHistory,
8283
} from './utils/history';
84+
import { getLayer1GasFeeFlow } from './utils/layer1-gas-fee-flow';
8385
import {
8486
getAndFormatTransactionsForNonceTracker,
8587
getNextNonce,
@@ -360,6 +362,8 @@ export class TransactionController extends BaseControllerV1<
360362
chainId?: string,
361363
) => NonceTrackerTransaction[];
362364

365+
private readonly layer1GasFeeFlows: Layer1GasFeeFlow[];
366+
363367
private readonly messagingSystem: TransactionControllerMessenger;
364368

365369
readonly #incomingTransactionOptions: IncomingTransactionOptions;
@@ -578,6 +582,7 @@ export class TransactionController extends BaseControllerV1<
578582
});
579583

580584
this.gasFeeFlows = this.#getGasFeeFlows();
585+
this.layer1GasFeeFlows = this.#getLayer1GasFeeFlows();
581586

582587
const gasFeePoller = new GasFeePoller({
583588
// Default gas fee polling is not yet supported by the clients
@@ -589,6 +594,7 @@ export class TransactionController extends BaseControllerV1<
589594
}),
590595
getGasFeeControllerEstimates: this.getGasFeeEstimates,
591596
getTransactions: () => this.state.transactions,
597+
layer1GasFeeFlows: this.layer1GasFeeFlows,
592598
onStateChange: (listener) => {
593599
this.subscribe(listener);
594600
},
@@ -758,6 +764,7 @@ export class TransactionController extends BaseControllerV1<
758764
networkClientId,
759765
};
760766

767+
await this.#updateTransactionLayer1GasFee(ethQuery, transactionMeta);
761768
await this.updateGasProperties(transactionMeta);
762769

763770
// Checks if a transaction already exists with a given actionId
@@ -1622,15 +1629,18 @@ export class TransactionController extends BaseControllerV1<
16221629
) as TransactionParams;
16231630

16241631
const updatedTransaction = merge(transactionMeta, editableParams);
1632+
const ethQuery = this.#multichainTrackingHelper.getEthQuery({
1633+
networkClientId: transactionMeta.networkClientId,
1634+
chainId: transactionMeta.chainId,
1635+
});
16251636
const { type } = await determineTransactionType(
16261637
updatedTransaction.txParams,
1627-
this.#multichainTrackingHelper.getEthQuery({
1628-
networkClientId: transactionMeta.networkClientId,
1629-
chainId: transactionMeta.chainId,
1630-
}),
1638+
ethQuery,
16311639
);
16321640
updatedTransaction.type = type;
16331641

1642+
await this.#updateTransactionLayer1GasFee(ethQuery, updatedTransaction);
1643+
16341644
this.updateTransaction(
16351645
updatedTransaction,
16361646
`Update Editable Params for ${txId}`,
@@ -2009,22 +2019,21 @@ export class TransactionController extends BaseControllerV1<
20092019
).configuration.type === NetworkClientType.Custom
20102020
: this.getNetworkState().providerConfig.type === NetworkType.rpc;
20112021

2022+
const ethQuery = this.#multichainTrackingHelper.getEthQuery({
2023+
networkClientId,
2024+
chainId,
2025+
});
2026+
20122027
await updateGas({
2013-
ethQuery: this.#multichainTrackingHelper.getEthQuery({
2014-
networkClientId,
2015-
chainId,
2016-
}),
2028+
ethQuery,
20172029
chainId,
20182030
isCustomNetwork,
20192031
txMeta: transactionMeta,
20202032
});
20212033

20222034
await updateGasFees({
20232035
eip1559: isEIP1559Compatible,
2024-
ethQuery: this.#multichainTrackingHelper.getEthQuery({
2025-
networkClientId,
2026-
chainId,
2027-
}),
2036+
ethQuery,
20282037
gasFeeFlows: this.gasFeeFlows,
20292038
getGasFeeEstimates: this.getGasFeeEstimates,
20302039
getSavedGasFees: this.getSavedGasFees.bind(this),
@@ -3072,10 +3081,41 @@ export class TransactionController extends BaseControllerV1<
30723081
);
30733082
}
30743083

3084+
async #updateTransactionLayer1GasFee(
3085+
ethQuery: EthQuery,
3086+
transactionMeta: TransactionMeta,
3087+
) {
3088+
const layer1GasFeeFlow = getLayer1GasFeeFlow(
3089+
transactionMeta,
3090+
this.layer1GasFeeFlows,
3091+
);
3092+
if (!layer1GasFeeFlow) {
3093+
log(
3094+
'Skipping update as no layer 1 gas fee flow found',
3095+
transactionMeta.id,
3096+
);
3097+
return;
3098+
}
3099+
3100+
try {
3101+
const { layer1Fee } = await layer1GasFeeFlow.getLayer1Fee({
3102+
ethQuery,
3103+
transactionMeta,
3104+
});
3105+
transactionMeta.layer1GasFee = layer1Fee;
3106+
} catch (error) {
3107+
log('Failed to get layer 1 gas fee', transactionMeta.id, error);
3108+
}
3109+
}
3110+
30753111
#getGasFeeFlows(): GasFeeFlow[] {
30763112
return [new LineaGasFeeFlow(), new DefaultGasFeeFlow()];
30773113
}
30783114

3115+
#getLayer1GasFeeFlows(): Layer1GasFeeFlow[] {
3116+
return [];
3117+
}
3118+
30793119
#updateTransactionInternal(
30803120
transactionMeta: TransactionMeta,
30813121
{ note, skipHistory }: { note?: string; skipHistory?: boolean },

packages/transaction-controller/src/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
export const CHAIN_IDS = {
22
MAINNET: '0x1',
33
GOERLI: '0x5',
4+
BASE: '0x2105',
5+
BASE_TESTNET: '0x14a33',
46
BSC: '0x38',
57
BSC_TESTNET: '0x61',
68
OPTIMISM: '0xa',
79
OPTIMISM_TESTNET: '0x1a4',
10+
OPBNB: '0xcc',
11+
OPBNB_TESTNET: '0x15eb',
812
POLYGON: '0x89',
913
POLYGON_TESTNET: '0x13881',
1014
AVALANCHE: '0xa86a',

packages/transaction-controller/src/helpers/GasFeePoller.test.ts

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import type EthQuery from '@metamask/eth-query';
22
import type { Hex } from '@metamask/utils';
33

44
import { flushPromises } from '../../../../tests/helpers';
5-
import type { GasFeeFlowResponse } from '../types';
5+
import type {
6+
GasFeeFlowResponse,
7+
Layer1GasFeeFlow,
8+
Layer1GasFeeFlowResponse,
9+
} from '../types';
610
import {
711
TransactionStatus,
812
type GasFeeFlow,
@@ -38,6 +42,10 @@ const GAS_FEE_FLOW_RESPONSE_MOCK: GasFeeFlowResponse = {
3842
},
3943
};
4044

45+
const LAYER_1_GAS_FEE_FLOW_RESPONSE_MOCK: Layer1GasFeeFlowResponse = {
46+
layer1Fee: '0x123',
47+
};
48+
4149
/**
4250
* Creates a mock GasFeeFlow.
4351
* @returns The mock GasFeeFlow.
@@ -49,9 +57,21 @@ function createGasFeeFlowMock(): jest.Mocked<GasFeeFlow> {
4957
};
5058
}
5159

60+
/**
61+
* Creates a mock Layer1GasFeeFlow.
62+
* @returns The mock GasFeeFlow.
63+
*/
64+
function createLayer1GasFeeFlowMock(): jest.Mocked<Layer1GasFeeFlow> {
65+
return {
66+
matchesTransaction: jest.fn(),
67+
getLayer1Fee: jest.fn(),
68+
};
69+
}
70+
5271
describe('GasFeePoller', () => {
5372
let constructorOptions: ConstructorParameters<typeof GasFeePoller>[0];
5473
let gasFeeFlowMock: jest.Mocked<GasFeeFlow>;
74+
let layer1GasFeeFlowMock: jest.Mocked<Layer1GasFeeFlow>;
5575
let triggerOnStateChange: () => void;
5676
let getTransactionsMock: jest.MockedFunction<() => TransactionMeta[]>;
5777

@@ -63,6 +83,12 @@ describe('GasFeePoller', () => {
6383
gasFeeFlowMock.matchesTransaction.mockReturnValue(true);
6484
gasFeeFlowMock.getGasFees.mockResolvedValue(GAS_FEE_FLOW_RESPONSE_MOCK);
6585

86+
layer1GasFeeFlowMock = createLayer1GasFeeFlowMock();
87+
layer1GasFeeFlowMock.matchesTransaction.mockReturnValue(true);
88+
layer1GasFeeFlowMock.getLayer1Fee.mockResolvedValue(
89+
LAYER_1_GAS_FEE_FLOW_RESPONSE_MOCK,
90+
);
91+
6692
getTransactionsMock = jest.fn();
6793
getTransactionsMock.mockReturnValue([TRANSACTION_META_MOCK]);
6894

@@ -71,6 +97,7 @@ describe('GasFeePoller', () => {
7197
getEthQuery: () => ({} as EthQuery),
7298
getGasFeeControllerEstimates: jest.fn(),
7399
getTransactions: getTransactionsMock,
100+
layer1GasFeeFlows: [layer1GasFeeFlowMock],
74101
onStateChange: (listener: () => void) => {
75102
triggerOnStateChange = listener;
76103
},
@@ -88,11 +115,21 @@ describe('GasFeePoller', () => {
88115
triggerOnStateChange();
89116
await flushPromises();
90117

91-
expect(listener).toHaveBeenCalledTimes(1);
92-
expect(listener).toHaveBeenCalledWith({
93-
...TRANSACTION_META_MOCK,
94-
gasFeeEstimates: GAS_FEE_FLOW_RESPONSE_MOCK.estimates,
95-
});
118+
expect(listener).toHaveBeenCalledTimes(2);
119+
expect(listener.mock.calls).toMatchObject([
120+
[
121+
{
122+
...TRANSACTION_META_MOCK,
123+
gasFeeEstimates: GAS_FEE_FLOW_RESPONSE_MOCK.estimates,
124+
},
125+
],
126+
[
127+
{
128+
...TRANSACTION_META_MOCK,
129+
layer1GasFee: LAYER_1_GAS_FEE_FLOW_RESPONSE_MOCK.layer1Fee,
130+
},
131+
],
132+
]);
96133
});
97134

98135
it('calls gas fee flow', async () => {
@@ -113,6 +150,22 @@ describe('GasFeePoller', () => {
113150
});
114151
});
115152

153+
it('calls layer 1 gas fee flow', async () => {
154+
const listener = jest.fn();
155+
156+
const gasFeePoller = new GasFeePoller(constructorOptions);
157+
gasFeePoller.hub.on('transaction-updated', listener);
158+
159+
triggerOnStateChange();
160+
await flushPromises();
161+
162+
expect(layer1GasFeeFlowMock.getLayer1Fee).toHaveBeenCalledTimes(1);
163+
expect(layer1GasFeeFlowMock.getLayer1Fee).toHaveBeenCalledWith({
164+
ethQuery: expect.any(Object),
165+
transactionMeta: TRANSACTION_META_MOCK,
166+
});
167+
});
168+
116169
it('creates polling timeout', async () => {
117170
new GasFeePoller(constructorOptions);
118171

@@ -174,10 +227,11 @@ describe('GasFeePoller', () => {
174227
expect(listener).toHaveBeenCalledTimes(0);
175228
});
176229

177-
it('no gas fee flow matches transaction', async () => {
230+
it('no fee flow matches transaction', async () => {
178231
const listener = jest.fn();
179232

180233
gasFeeFlowMock.matchesTransaction.mockReturnValue(false);
234+
layer1GasFeeFlowMock.matchesTransaction.mockReturnValue(false);
181235

182236
const gasFeePoller = new GasFeePoller(constructorOptions);
183237
gasFeePoller.hub.on('transaction-updated', listener);
@@ -188,10 +242,13 @@ describe('GasFeePoller', () => {
188242
expect(listener).toHaveBeenCalledTimes(0);
189243
});
190244

191-
it('gas fee flow throws', async () => {
245+
it('fee flows throws', async () => {
192246
const listener = jest.fn();
193247

194248
gasFeeFlowMock.getGasFees.mockRejectedValue(new Error('TestError'));
249+
layer1GasFeeFlowMock.getLayer1Fee.mockRejectedValue(
250+
new Error('TestError'),
251+
);
195252

196253
const gasFeePoller = new GasFeePoller(constructorOptions);
197254
gasFeePoller.hub.on('transaction-updated', listener);

0 commit comments

Comments
 (0)