Skip to content

Commit 83a199e

Browse files
vinistevampedronfigueiredomatthewwalsh0OGPoyraz
authored
Add gasFeeEstimates for batch transactions (#5886)
## Explanation This PR enhances `TransactionBatchMeta` by adding support for `gasFeeEstimates` and `status`. When both `useHook` and `requireApproval` are `true`, the batch approval flow is triggered, and `gasFeeEstimates` are populated using the `DefaultGasFeeFlow`. These estimates are then passed along with the request to be consumed by the client. ## Changes - Extended `TransactionBatchMeta` to include: - `gasFeeEstimates` - `status` - Introduced a new helper function: `prepareApprovalData` - Responsible for preparing approval data before creating the request <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> * Related to MetaMask/MetaMask-planning#5006 ## Changelog <!-- THIS SECTION IS NO LONGER NEEDED. The process for updating changelogs has changed. Please consult the "Updating changelogs" section of the Contributing doc for more. --> ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/contributing.md#updating-changelogs), highlighting breaking changes as necessary - [ ] I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes --------- Co-authored-by: Pedro Figueiredo <pedro.figueiredo@consensys.net> Co-authored-by: Matthew Walsh <matthew.walsh@consensys.net> Co-authored-by: OGPoyraz <omergoktugpoyraz@gmail.com>
1 parent c7f9f73 commit 83a199e

File tree

5 files changed

+228
-52
lines changed

5 files changed

+228
-52
lines changed

packages/transaction-controller/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Add `gasFeeEstimates` property to `TransactionBatchMeta`, populated using `DefaultGasFeeFlow` ([#5886](https://github.com/MetaMask/core/pull/5886))
13+
1014
## [57.3.0]
1115

1216
### Added

packages/transaction-controller/src/TransactionController.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,27 +1034,28 @@ export class TransactionController extends BaseController<
10341034
addTransaction: this.addTransaction.bind(this),
10351035
getChainId: this.#getChainId.bind(this),
10361036
getEthQuery: (networkClientId) => this.#getEthQuery({ networkClientId }),
1037+
getGasFeeEstimates: this.#getGasFeeEstimates,
10371038
getInternalAccounts: this.#getInternalAccounts.bind(this),
1039+
getPendingTransactionTracker: (networkClientId: NetworkClientId) =>
1040+
this.#createPendingTransactionTracker({
1041+
provider: this.#getProvider({ networkClientId }),
1042+
blockTracker,
1043+
chainId: this.#getChainId(networkClientId),
1044+
networkClientId,
1045+
}),
10381046
getTransaction: (transactionId) =>
10391047
this.#getTransactionOrThrow(transactionId),
10401048
isSimulationEnabled: this.#isSimulationEnabled,
10411049
messenger: this.messagingSystem,
10421050
publishBatchHook: this.#publishBatchHook,
10431051
publicKeyEIP7702: this.#publicKeyEIP7702,
1044-
request,
1045-
updateTransaction: this.#updateTransactionInternal.bind(this),
10461052
publishTransaction: (
10471053
ethQuery: EthQuery,
10481054
transactionMeta: TransactionMeta,
10491055
) => this.#publishTransaction(ethQuery, transactionMeta) as Promise<Hex>,
1050-
getPendingTransactionTracker: (networkClientId: NetworkClientId) =>
1051-
this.#createPendingTransactionTracker({
1052-
provider: this.#getProvider({ networkClientId }),
1053-
blockTracker,
1054-
chainId: this.#getChainId(networkClientId),
1055-
networkClientId,
1056-
}),
1056+
request,
10571057
update: this.update.bind(this),
1058+
updateTransaction: this.#updateTransactionInternal.bind(this),
10581059
});
10591060
}
10601061

packages/transaction-controller/src/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,9 @@ export type TransactionBatchMeta = {
504504
*/
505505
from: string;
506506

507+
/** Alternate EIP-1559 gas fee estimates for multiple priority levels. */
508+
gasFeeEstimates?: GasFeeEstimates;
509+
507510
/**
508511
* Maximum number of units of gas to use for this transaction batch.
509512
*/
@@ -524,6 +527,9 @@ export type TransactionBatchMeta = {
524527
*/
525528
origin?: string;
526529

530+
/** Current status of the transaction. */
531+
status: TransactionStatus;
532+
527533
/**
528534
* Data for any EIP-7702 transactions.
529535
*/

packages/transaction-controller/src/utils/batch.test.ts

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,17 @@ import {
2525
type TransactionMeta,
2626
determineTransactionType,
2727
TransactionType,
28+
GasFeeEstimateLevel,
29+
GasFeeEstimateType,
2830
} from '..';
2931
import { flushPromises } from '../../../../tests/helpers';
32+
import { DefaultGasFeeFlow } from '../gas-flows/DefaultGasFeeFlow';
3033
import { SequentialPublishBatchHook } from '../hooks/SequentialPublishBatchHook';
31-
import type { PublishBatchHook, TransactionBatchSingleRequest } from '../types';
34+
import type {
35+
GasFeeFlow,
36+
PublishBatchHook,
37+
TransactionBatchSingleRequest,
38+
} from '../types';
3239

3340
jest.mock('./eip7702');
3441
jest.mock('./feature-flags');
@@ -168,6 +175,18 @@ function mockRequestApproval(
168175
};
169176
}
170177

178+
/**
179+
* Creates a mock GasFeeFlow.
180+
*
181+
* @returns The mock GasFeeFlow.
182+
*/
183+
function createGasFeeFlowMock(): jest.Mocked<GasFeeFlow> {
184+
return {
185+
matchesTransaction: jest.fn(),
186+
getGasFees: jest.fn(),
187+
};
188+
}
189+
171190
describe('Batch Utils', () => {
172191
const doesChainSupportEIP7702Mock = jest.mocked(doesChainSupportEIP7702);
173192
const getEIP7702SupportedChainsMock = jest.mocked(getEIP7702SupportedChains);
@@ -214,6 +233,12 @@ describe('Batch Utils', () => {
214233

215234
let updateMock: jest.MockedFn<AddBatchTransactionOptions['update']>;
216235

236+
let getGasFeeEstimatesMock: jest.MockedFn<
237+
AddBatchTransactionOptions['getGasFeeEstimates']
238+
>;
239+
240+
let getGasFeesMock: jest.Mock;
241+
217242
let request: AddBatchTransactionOptions;
218243

219244
beforeEach(() => {
@@ -225,6 +250,32 @@ describe('Batch Utils', () => {
225250
getPendingTransactionTrackerMock = jest.fn();
226251
updateMock = jest.fn();
227252

253+
getGasFeeEstimatesMock = jest
254+
.fn()
255+
.mockResolvedValue(createGasFeeFlowMock());
256+
257+
getGasFeesMock = jest.fn().mockResolvedValue({
258+
estimates: {
259+
type: GasFeeEstimateType.FeeMarket,
260+
[GasFeeEstimateLevel.Low]: {
261+
maxFeePerGas: '0x1',
262+
maxPriorityFeePerGas: '0x1',
263+
},
264+
[GasFeeEstimateLevel.Medium]: {
265+
maxFeePerGas: '0x2',
266+
maxPriorityFeePerGas: '0x1',
267+
},
268+
[GasFeeEstimateLevel.High]: {
269+
maxFeePerGas: '0x3',
270+
maxPriorityFeePerGas: '0x1',
271+
},
272+
},
273+
});
274+
275+
jest
276+
.spyOn(DefaultGasFeeFlow.prototype, 'getGasFees')
277+
.mockImplementation(getGasFeesMock);
278+
228279
determineTransactionTypeMock.mockResolvedValue({
229280
type: TransactionType.simpleSend,
230281
});
@@ -273,6 +324,7 @@ describe('Batch Utils', () => {
273324
publishTransaction: publishTransactionMock,
274325
getPendingTransactionTracker: getPendingTransactionTrackerMock,
275326
update: updateMock,
327+
getGasFeeEstimates: getGasFeeEstimatesMock,
276328
};
277329
});
278330

@@ -767,6 +819,8 @@ describe('Batch Utils', () => {
767819
}),
768820
true,
769821
);
822+
expect(simulateGasBatchMock).toHaveBeenCalledTimes(1);
823+
expect(getGasFeesMock).toHaveBeenCalledTimes(1);
770824
},
771825
);
772826

@@ -1466,6 +1520,74 @@ describe('Batch Utils', () => {
14661520
expect(result?.batchId).toMatch(/^0x[0-9a-f]{32}$/u);
14671521
});
14681522

1523+
it('updates gas properties', async () => {
1524+
const { approve } = mockRequestApproval(MESSENGER_MOCK, {
1525+
state: 'approved',
1526+
});
1527+
mockSequentialPublishBatchHookResults();
1528+
setupSequentialPublishBatchHookMock(() => sequentialPublishBatchHook);
1529+
1530+
const resultPromise = addTransactionBatch({
1531+
...request,
1532+
publishBatchHook: undefined,
1533+
messenger: MESSENGER_MOCK,
1534+
request: {
1535+
...request.request,
1536+
origin: ORIGIN_MOCK,
1537+
disable7702: true,
1538+
disableHook: true,
1539+
},
1540+
}).catch(() => {
1541+
// Intentionally empty
1542+
});
1543+
1544+
await flushPromises();
1545+
approve();
1546+
await executePublishHooks();
1547+
1548+
await resultPromise;
1549+
1550+
expect(simulateGasBatchMock).toHaveBeenCalledTimes(1);
1551+
expect(simulateGasBatchMock).toHaveBeenCalledWith({
1552+
chainId: CHAIN_ID_MOCK,
1553+
from: FROM_MOCK,
1554+
transactions: [
1555+
{
1556+
params: TRANSACTION_BATCH_PARAMS_MOCK,
1557+
},
1558+
{
1559+
params: TRANSACTION_BATCH_PARAMS_MOCK,
1560+
},
1561+
],
1562+
});
1563+
expect(getGasFeesMock).toHaveBeenCalledTimes(1);
1564+
expect(getGasFeesMock).toHaveBeenCalledWith(
1565+
expect.objectContaining({
1566+
gasFeeControllerData: expect.any(Object),
1567+
messenger: MESSENGER_MOCK,
1568+
transactionMeta: {
1569+
chainId: CHAIN_ID_MOCK,
1570+
gas: GAS_TOTAL_MOCK,
1571+
from: FROM_MOCK,
1572+
networkClientId: NETWORK_CLIENT_ID_MOCK,
1573+
txParams: { from: FROM_MOCK, gas: GAS_TOTAL_MOCK },
1574+
origin: ORIGIN_MOCK,
1575+
id: expect.any(String),
1576+
status: 'unapproved',
1577+
time: expect.any(Number),
1578+
transactions: [
1579+
{
1580+
params: TRANSACTION_BATCH_PARAMS_MOCK,
1581+
},
1582+
{
1583+
params: TRANSACTION_BATCH_PARAMS_MOCK,
1584+
},
1585+
],
1586+
},
1587+
}),
1588+
);
1589+
});
1590+
14691591
it('saves a transaction batch and then cleans the specific batch by ID', async () => {
14701592
const { approve } = mockRequestApproval(MESSENGER_MOCK, {
14711593
state: 'approved',
@@ -1545,6 +1667,8 @@ describe('Batch Utils', () => {
15451667
expect(state.transactionBatches).toStrictEqual([
15461668
{ id: 'batch1', chainId: '0x1', transactions: [] },
15471669
]);
1670+
expect(simulateGasBatchMock).toHaveBeenCalledTimes(1);
1671+
expect(getGasFeesMock).toHaveBeenCalledTimes(1);
15481672
});
15491673
});
15501674
});
@@ -1642,6 +1766,7 @@ describe('Batch Utils', () => {
16421766
});
16431767

16441768
it('does not throw if error getting provider', async () => {
1769+
getEIP7702UpgradeContractAddressMock.mockReturnValue(undefined);
16451770
getEIP7702SupportedChainsMock.mockReturnValueOnce([
16461771
CHAIN_ID_MOCK,
16471772
CHAIN_ID_2_MOCK,

0 commit comments

Comments
 (0)