Skip to content

Extends GasFeePoller to update gas properties for unapproved transaction batches #5950

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 66 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
60076b1
add transaction batch approval type
vinistevam May 13, 2025
a8c3d14
add transactionBatches into the state
vinistevam May 13, 2025
423a43e
populate `transactionBatches` when process a batch transaction using …
vinistevam May 13, 2025
2cc016a
change logs and lint
vinistevam May 14, 2025
0f88df1
Merge branch 'main' into feat/add-batch-transaction-approval-type
vinistevam May 14, 2025
fe47147
fix: push types
vinistevam May 14, 2025
955cf57
Add `trimBatchTransactionsForState` and fix unit tests
pedronfigueiredo May 14, 2025
7c803e5
use 'transaction_batch' for now util releases utils controller
vinistevam May 14, 2025
1047465
Merge branch 'main' into feat/add-batch-transaction-approval-type
vinistevam May 14, 2025
e62592d
wip
pedronfigueiredo May 14, 2025
18b66e6
Update packages/transaction-controller/CHANGELOG.md
vinistevam May 15, 2025
f024fe5
fix: applied suggestions
vinistevam May 15, 2025
0b658c2
Merge branch 'feat/add-batch-transaction-approval-type' of github.com…
vinistevam May 15, 2025
4dbad19
remove time property
vinistevam May 15, 2025
74d94de
Merge branch 'main' into feat/add-batch-transaction-approval-type
vinistevam May 15, 2025
0614743
fix changelog
vinistevam May 15, 2025
d23d089
Merge branch 'main' into feat/add-batch-transaction-approval-type
vinistevam May 16, 2025
9f3ac61
Merge branch 'main' into feat/add-batch-transaction-approval-type
vinistevam May 20, 2025
ccbc815
fix: unit test after merge
vinistevam May 20, 2025
e281412
fix: add more coverage
vinistevam May 20, 2025
b9e97ea
feat: add simulate gas for batch transactions
vinistevam May 22, 2025
31bffc5
fix: add update and clean for transactionBatches
vinistevam May 22, 2025
6bc5880
fix unit test and changelog
vinistevam May 23, 2025
3e3c8d2
Merge branch 'main' into feat/add-batch-transaction-approval-type
vinistevam May 23, 2025
053ab21
Merge branch 'feat/add-batch-transaction-approval-type' into feat/add…
vinistevam May 23, 2025
559651c
fix: changelog
vinistevam May 23, 2025
b5ba681
Merge branch 'feat/add-batch-transaction-approval-type' into feat/add…
vinistevam May 23, 2025
01941aa
fix: add coverage to gas
vinistevam May 23, 2025
6aac6e7
fix: applied suggestions
vinistevam May 23, 2025
1e6520d
Merge branch 'feat/add-batch-transaction-approval-type' into feat/add…
vinistevam May 23, 2025
097124d
chore: clean up and changelogs
vinistevam May 23, 2025
9f4d990
Merge branch 'main' into feat/add-estimate-gas-batch-transaction
vinistevam May 27, 2025
3289a46
Update packages/transaction-controller/CHANGELOG.md
vinistevam May 28, 2025
0c494ab
fix: applied suggestions
vinistevam May 28, 2025
d317878
fix: lint
vinistevam May 28, 2025
b08cf24
feat: add estimate gas fee for batch
vinistevam May 30, 2025
c675525
fix e2e tests
vinistevam Jun 2, 2025
7fdb18b
logs and tests
vinistevam Jun 2, 2025
8c0b8a4
Merge branch 'main' into feat/add-estimate-gas-batch-transaction
vinistevam Jun 2, 2025
85235e2
applied suggestions
vinistevam Jun 2, 2025
c0ef3d7
Merge branch 'feat/add-estimate-gas-batch-transaction' into feat/add-…
vinistevam Jun 2, 2025
f7aabfc
add changelog
vinistevam Jun 2, 2025
1a99004
Merge branch 'main' into feat/add-estimate-gas-fee-batch-tx
vinistevam Jun 3, 2025
eb76770
fix lint
vinistevam Jun 3, 2025
ea99442
Merge branch 'main' into feat/add-estimate-gas-fee-batch-tx
vinistevam Jun 3, 2025
014c0d1
Merge branch 'main' into feat/add-estimate-gas-fee-batch-tx
vinistevam Jun 5, 2025
00adbd0
change log fix
vinistevam Jun 5, 2025
b1615f1
apply suggestions
vinistevam Jun 5, 2025
0fa9230
Merge branch 'main' into feat/add-estimate-gas-fee-batch-tx
vinistevam Jun 5, 2025
494ba93
Merge branch 'main' into feat/add-estimate-gas-fee-batch-tx
vinistevam Jun 6, 2025
935bf4a
fix: unit tests
vinistevam Jun 6, 2025
7cf5191
Merge branch 'main' into feat/add-estimate-gas-fee-batch-tx
vinistevam Jun 6, 2025
316f3d8
Merge branch 'main' into feat/add-estimate-gas-fee-batch-tx
vinistevam Jun 6, 2025
b850676
Merge branch 'main' into feat/add-estimate-gas-fee-batch-tx
vinistevam Jun 7, 2025
dd6fefd
fix: fix changelog
vinistevam Jun 9, 2025
a820020
Extending gasFeePoller to also track unapproved tx batches
vinistevam Jun 10, 2025
458704d
Merge branch 'feat/add-estimate-gas-fee-batch-tx' into feat/extend-ga…
vinistevam Jun 10, 2025
1df0224
add unit test and clean up
vinistevam Jun 10, 2025
ea8d243
fix lint
vinistevam Jun 10, 2025
6dac2ca
Merge branch 'main' into feat/extend-gas-fee-poller-batch
vinistevam Jun 11, 2025
27ae144
Merge branch 'main' into feat/extend-gas-fee-poller-batch
vinistevam Jun 13, 2025
564206e
Merge branch 'main' into feat/extend-gas-fee-poller-batch
vinistevam Jun 18, 2025
3a0a16b
fix: applied suggestions
vinistevam Jun 20, 2025
2f81246
Merge branch 'main' into feat/extend-gas-fee-poller-batch
vinistevam Jun 20, 2025
377c825
Merge branch 'main' into feat/extend-gas-fee-poller-batch
vinistevam Jun 20, 2025
eb0a5d3
fix changelog
vinistevam Jun 20, 2025
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
1 change: 1 addition & 0 deletions packages/transaction-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Query only latest page of transactions from accounts API ([#5983](https://github.com/MetaMask/core/pull/5983))
- Remove incoming transactions when calling `wipeTransactions` ([#5986](https://github.com/MetaMask/core/pull/5986))
- Poll immediately when calling `startIncomingTransactionPolling` ([#5986](https://github.com/MetaMask/core/pull/5986))
- Extend `GasFeePoller` to support gas updates for unapproved `transactionBatches`, emitting `transaction-batch-updated` with `gasFeeEstimates`. ([#5950](https://github.com/MetaMask/core/pull/5950))

## [58.0.0]

Expand Down
37 changes: 37 additions & 0 deletions packages/transaction-controller/src/TransactionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,7 @@ export class TransactionController extends BaseController<
getGasFeeControllerEstimates: this.#getGasFeeEstimates,
getProvider: (networkClientId) => this.#getProvider({ networkClientId }),
getTransactions: () => this.state.transactions,
getTransactionBatches: () => this.state.transactionBatches,
layer1GasFeeFlows: this.#layer1GasFeeFlows,
messenger: this.messagingSystem,
onStateChange: (listener) => {
Expand All @@ -955,6 +956,11 @@ export class TransactionController extends BaseController<
this.#onGasFeePollerTransactionUpdate.bind(this),
);

gasFeePoller.hub.on(
'transaction-batch-updated',
this.#onGasFeePollerTransactionBatchUpdate.bind(this),
);

this.#methodDataHelper = new MethodDataHelper({
getProvider: (networkClientId) => this.#getProvider({ networkClientId }),
getState: () => this.state.methodData,
Expand Down Expand Up @@ -4226,6 +4232,37 @@ export class TransactionController extends BaseController<
);
}

#onGasFeePollerTransactionBatchUpdate({
transactionBatchId,
gasFeeEstimates,
}: {
transactionBatchId: Hex;
gasFeeEstimates?: GasFeeEstimates;
}) {
this.#updateTransactionBatch(transactionBatchId, (batch) => {
batch.gasFeeEstimates = gasFeeEstimates;
return batch;
});
}

#updateTransactionBatch(
batchId: string,
callback: (batch: TransactionBatchMeta) => TransactionBatchMeta | void,
): void {
this.update((state) => {
const index = state.transactionBatches.findIndex((b) => b.id === batchId);

if (index === -1) {
throw new Error(`Cannot update batch, ID not found - ${batchId}`);
}

const batch = state.transactionBatches[index];
const updated = callback(batch);

state.transactionBatches[index] = updated ?? batch;
});
}

#getSelectedAccount() {
return this.messagingSystem.call('AccountsController:getSelectedAccount');
}
Expand Down
184 changes: 183 additions & 1 deletion packages/transaction-controller/src/helpers/GasFeePoller.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import EthQuery from '@metamask/eth-query';
import type { Provider } from '@metamask/network-controller';
import type { Hex } from '@metamask/utils';

Expand All @@ -7,8 +8,13 @@ import {
updateTransactionGasEstimates,
} from './GasFeePoller';
import { flushPromises } from '../../../../tests/helpers';
import { DefaultGasFeeFlow } from '../gas-flows/DefaultGasFeeFlow';
import type { TransactionControllerMessenger } from '../TransactionController';
import type { GasFeeFlowResponse, Layer1GasFeeFlow } from '../types';
import type {
GasFeeFlowResponse,
Layer1GasFeeFlow,
TransactionBatchMeta,
} from '../types';
import {
GasFeeEstimateLevel,
GasFeeEstimateType,
Expand Down Expand Up @@ -44,6 +50,22 @@ const TRANSACTION_META_MOCK: TransactionMeta = {
},
};

const TRANSACTION_BATCH_META_MOCK: TransactionBatchMeta = {
id: 'batch1',
chainId: CHAIN_ID_MOCK,
networkClientId: NETWORK_CLIENT_ID_MOCK,
status: TransactionStatus.unapproved,
transactions: [
{
gas: '0x5208',
},
{
gas: '0x5208',
},
],
from: '0x123',
};

const FEE_MARKET_GAS_FEE_ESTIMATES_MOCK = {
type: GasFeeEstimateType.FeeMarket,
[GasFeeEstimateLevel.Low]: {
Expand Down Expand Up @@ -93,6 +115,9 @@ describe('GasFeePoller', () => {
let gasFeeFlowMock: jest.Mocked<GasFeeFlow>;
let triggerOnStateChange: () => void;
let getTransactionsMock: jest.MockedFunction<() => TransactionMeta[]>;
let getTransactionBatchesMock: jest.MockedFunction<
() => TransactionBatchMeta[]
>;
const getTransactionLayer1GasFeeMock = jest.mocked(
getTransactionLayer1GasFee,
);
Expand All @@ -113,13 +138,19 @@ describe('GasFeePoller', () => {
getTransactionsMock = jest.fn();
getTransactionsMock.mockReturnValue([{ ...TRANSACTION_META_MOCK }]);

getTransactionBatchesMock = jest.fn();
getTransactionBatchesMock.mockReturnValue([
{ ...TRANSACTION_BATCH_META_MOCK },
]);

getTransactionLayer1GasFeeMock.mockResolvedValue(LAYER1_GAS_FEE_MOCK);

constructorOptions = {
findNetworkClientIdByChainId: findNetworkClientIdByChainIdMock,
gasFeeFlows: [gasFeeFlowMock],
getGasFeeControllerEstimates: getGasFeeControllerEstimatesMock,
getTransactions: getTransactionsMock,
getTransactionBatches: getTransactionBatchesMock,
layer1GasFeeFlows: layer1GasFeeFlowsMock,
messenger: messengerMock,
onStateChange: (listener: () => void) => {
Expand Down Expand Up @@ -212,6 +243,7 @@ describe('GasFeePoller', () => {

getTransactionsMock.mockReturnValueOnce([{ ...TRANSACTION_META_MOCK }]);
getTransactionsMock.mockReturnValueOnce([]);
getTransactionBatchesMock.mockReturnValue([]);

const gasFeePoller = new GasFeePoller(constructorOptions);
gasFeePoller.hub.on('transaction-updated', listener);
Expand Down Expand Up @@ -254,6 +286,155 @@ describe('GasFeePoller', () => {
triggerOnStateChange();
await flushPromises();

expect(getGasFeeControllerEstimatesMock).toHaveBeenCalledTimes(4);
expect(getGasFeeControllerEstimatesMock).toHaveBeenCalledWith({
networkClientId: 'networkClientId1',
});
expect(getGasFeeControllerEstimatesMock).toHaveBeenCalledWith({
networkClientId: 'networkClientId2',
});
expect(getGasFeeControllerEstimatesMock).toHaveBeenCalledWith({
networkClientId: 'networkClientId4',
});
expect(getGasFeeControllerEstimatesMock).toHaveBeenCalledWith({
networkClientId: NETWORK_CLIENT_ID_MOCK,
});
});
});
});

describe('if unapproved transaction batches', () => {
let getGasFeesMock: jest.Mock;
beforeEach(() => {
getGasFeesMock = jest.fn().mockResolvedValue({
estimates: FEE_MARKET_GAS_FEE_ESTIMATES_MOCK,
});
jest
.spyOn(DefaultGasFeeFlow.prototype, 'getGasFees')
.mockImplementation(getGasFeesMock);
});

it('emits batch updated event', async () => {
const listener = jest.fn();
getTransactionsMock.mockReturnValue([]);
const gasFeePoller = new GasFeePoller(constructorOptions);
gasFeePoller.hub.on('transaction-batch-updated', listener);

triggerOnStateChange();
await flushPromises();

expect(listener).toHaveBeenCalledTimes(1);
expect(listener).toHaveBeenCalledWith({
transactionBatchId: TRANSACTION_BATCH_META_MOCK.id,
gasFeeEstimates: GAS_FEE_FLOW_RESPONSE_MOCK.estimates,
gasFeeEstimatesLoaded: true,
});
});

it('calls gas fee flow for batches', async () => {
getGasFeeControllerEstimatesMock.mockResolvedValue({});

new GasFeePoller(constructorOptions);

triggerOnStateChange();
await flushPromises();

expect(gasFeeFlowMock.getGasFees).toHaveBeenCalledTimes(1);
expect(gasFeeFlowMock.getGasFees).toHaveBeenCalledWith({
ethQuery: expect.any(EthQuery),
gasFeeControllerData: expect.any(Object),
messenger: expect.any(Function),
transactionMeta: {
id: '1',
chainId: TRANSACTION_BATCH_META_MOCK.chainId,
networkClientId: TRANSACTION_BATCH_META_MOCK.networkClientId,
status: TRANSACTION_BATCH_META_MOCK.status,
time: expect.any(Number),
txParams: {
from: TRANSACTION_BATCH_META_MOCK.from,
type: TransactionEnvelopeType.feeMarket,
},
},
});
});

it('creates polling timeout for batches', async () => {
new GasFeePoller(constructorOptions);

triggerOnStateChange();
await flushPromises();

expect(jest.getTimerCount()).toBe(1);

jest.runOnlyPendingTimers();
await flushPromises();

expect(gasFeeFlowMock.getGasFees).toHaveBeenCalledTimes(2);
});

it('does not create additional polling timeout on subsequent state changes', async () => {
new GasFeePoller(constructorOptions);

triggerOnStateChange();
await flushPromises();

triggerOnStateChange();
await flushPromises();

expect(jest.getTimerCount()).toBe(1);
});

it('does nothing if no transaction batches', async () => {
const listener = jest.fn();

getTransactionsMock.mockReturnValue([]);
getTransactionBatchesMock.mockReturnValueOnce([
{ ...TRANSACTION_BATCH_META_MOCK },
]);
getTransactionBatchesMock.mockReturnValueOnce([]);

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

triggerOnStateChange();
await flushPromises();

expect(listener).toHaveBeenCalledTimes(0);
expect(getGasFeeControllerEstimatesMock).toHaveBeenCalledTimes(0);
expect(gasFeeFlowMock.getGasFees).toHaveBeenCalledTimes(0);
});

describe('fetches GasFeeController data for batches', () => {
it('for each unique chain ID in batches', async () => {
getTransactionsMock.mockReturnValue([]);
getTransactionBatchesMock.mockReturnValue([
{
...TRANSACTION_BATCH_META_MOCK,
chainId: '0x1',
networkClientId: 'networkClientId1',
},
{
...TRANSACTION_BATCH_META_MOCK,
chainId: '0x2',
networkClientId: 'networkClientId2',
},
{
...TRANSACTION_BATCH_META_MOCK,
chainId: '0x2',
networkClientId: 'networkClientId3',
},
{
...TRANSACTION_BATCH_META_MOCK,
chainId: '0x3',
networkClientId: 'networkClientId4',
},
]);

new GasFeePoller(constructorOptions);

triggerOnStateChange();
await flushPromises();

expect(getGasFeeControllerEstimatesMock).toHaveBeenCalledTimes(3);
expect(getGasFeeControllerEstimatesMock).toHaveBeenCalledWith({
networkClientId: 'networkClientId1',
Expand Down Expand Up @@ -348,6 +529,7 @@ describe('GasFeePoller', () => {
await flushPromises();

getTransactionsMock.mockReturnValue([]);
getTransactionBatchesMock.mockReturnValue([]);

triggerOnStateChange();
await flushPromises();
Expand Down
Loading
Loading