Skip to content
Open
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
8 changes: 6 additions & 2 deletions packages/transaction-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- **BREAKING:** Expand saved gas fee support to allow transaction-scoped lookup, saved gas fee estimate levels, and legacy gas price values. Consumers that provide `getSavedGasFees` must now accept `TransactionMeta` instead of a chain ID. ([#8993](https://github.com/MetaMask/core/pull/8993))

## [68.0.0]

### Changed
Expand All @@ -20,8 +24,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Removed

- **BREAKING:** Remove incoming transaction support from `TransactionController` ([#9012](https://github.com/MetaMask/core/pull/9012))
- Removed constructor option `incomingTransactions`.
- Removed public methods `startIncomingTransactionPolling`, `stopIncomingTransactionPolling`, `updateIncomingTransactions`.
- The constructor option `incomingTransactions` is ignored for backwards compatibility.
- Removed public method `updateIncomingTransactions`; `startIncomingTransactionPolling` and `stopIncomingTransactionPolling` are retained as no-ops for backwards compatibility.
- Removed event `TransactionController:incomingTransactionsReceived`.
- Removed exported constant `INCOMING_TRANSACTIONS_SUPPORTED_CHAIN_IDS`.
- Removed exported types `TransactionControllerIncomingTransactionsReceivedEvent`, `TransactionControllerStartIncomingTransactionPollingAction`, `TransactionControllerStopIncomingTransactionPollingAction`, `TransactionControllerUpdateIncomingTransactionsAction`, `TransactionResponse`, `GetAccountTransactionsRequest`, `GetAccountTransactionsResponse`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8037,6 +8037,28 @@ describe('TransactionController', () => {
}
`);
});

it('accepts ignored incoming transaction compatibility options', () => {
expect(() =>
setupController({
options: {
incomingTransactions: {
client: 'extension-test',
includeTokenTransfers: false,
isEnabled: () => false,
updateTransactions: true,
},
},
}),
).not.toThrow();
});

it('keeps incoming transaction polling compatibility methods as no-ops', () => {
const { controller } = setupController();

expect(() => controller.startIncomingTransactionPolling()).not.toThrow();
expect(() => controller.stopIncomingTransactionPolling()).not.toThrow();
});
});

describe('messenger actions', () => {
Expand Down
52 changes: 48 additions & 4 deletions packages/transaction-controller/src/TransactionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { TypedTxData } from '@ethereumjs/tx';
import type {
AccountsControllerGetSelectedAccountAction,
AccountsControllerGetStateAction,
AccountsControllerSelectedAccountChangeEvent,
} from '@metamask/accounts-controller';
import type {
AcceptResultCallbacks,
Expand All @@ -21,7 +22,11 @@ import {
convertHexToDecimal,
} from '@metamask/controller-utils';
import type { TraceCallback, TraceContext } from '@metamask/controller-utils';
import type { AccountActivityServiceTransactionUpdatedEvent } from '@metamask/core-backend';
import type {
AccountActivityServiceStatusChangedEvent,
AccountActivityServiceTransactionUpdatedEvent,
BackendWebSocketServiceConnectionStateChangedEvent,
} from '@metamask/core-backend';
import type {
FetchGasFeeEstimateOptions,
GasFeeControllerFetchGasFeeEstimatesAction,
Expand Down Expand Up @@ -313,6 +318,18 @@ export type TransactionControllerActions =
| TransactionControllerGetStateAction
| TransactionControllerMethodActions;

/**
* @deprecated Incoming transaction support has been removed. These options are ignored.
*/
export type IncomingTransactionCompatibilityOptions = {
client?: string;
includeTokenTransfers?: boolean;
isEnabled?: () => boolean;
updateTransactions?: boolean;
/** @deprecated Ignored as incoming transaction support has been removed. */
etherscanApiKeysByChainId?: Record<Hex, string>;
};

/** TransactionController constructor options. */
export type TransactionControllerOptions = {
/** Whether to disable additional processing on swaps transactions. */
Expand All @@ -322,13 +339,20 @@ export type TransactionControllerOptions = {
getPermittedAccounts?: (origin?: string) => Promise<string[]>;

/** Gets the saved gas fee config. */
getSavedGasFees?: (chainId: Hex) => SavedGasFees | undefined;
getSavedGasFees?: (
transactionMeta: TransactionMeta,
) => SavedGasFees | undefined;

/**
* Gets the transaction simulation configuration.
*/
getSimulationConfig?: GetSimulationConfig;

/**
* @deprecated Incoming transaction support has been removed. This option is ignored.
*/
incomingTransactions?: IncomingTransactionCompatibilityOptions;

/**
* Callback to determine whether gas fee updates should be enabled for a given transaction.
* Returns true to enable updates, false to disable them.
Expand Down Expand Up @@ -422,7 +446,10 @@ export type AllowedActions =
* The external events available to the {@link TransactionController}.
*/
export type AllowedEvents =
| AccountActivityServiceStatusChangedEvent
| AccountActivityServiceTransactionUpdatedEvent
| AccountsControllerSelectedAccountChangeEvent
| BackendWebSocketServiceConnectionStateChangedEvent
| NetworkControllerStateChangeEvent;

/**
Expand Down Expand Up @@ -699,7 +726,9 @@ export class TransactionController extends BaseController<

readonly #getPermittedAccounts?: (origin?: string) => Promise<string[]>;

readonly #getSavedGasFees: (chainId: Hex) => SavedGasFees | undefined;
readonly #getSavedGasFees: (
transactionMeta: TransactionMeta,
) => SavedGasFees | undefined;

readonly #getSimulationConfig: GetSimulationConfig;

Expand Down Expand Up @@ -793,7 +822,8 @@ export class TransactionController extends BaseController<
((): ReturnType<BeforeSignHook> => Promise.resolve({}));
this.#getPermittedAccounts = getPermittedAccounts;
this.#getSavedGasFees =
getSavedGasFees ?? ((_chainId): SavedGasFees | undefined => undefined);
getSavedGasFees ??
((_transactionMeta): SavedGasFees | undefined => undefined);
this.#getSimulationConfig =
getSimulationConfig ??
((): ReturnType<GetSimulationConfig> => Promise.resolve({}));
Expand Down Expand Up @@ -927,6 +957,20 @@ export class TransactionController extends BaseController<
this.#stopAllTracking();
}

/**
* @deprecated Incoming transaction support has been removed. This method is retained as a no-op for backwards compatibility.
*/
startIncomingTransactionPolling(): void {
noop();
}

/**
* @deprecated Incoming transaction support has been removed. This method is retained as a no-op for backwards compatibility.
*/
stopIncomingTransactionPolling(): void {
noop();
}

/**
* Handle new method data request.
*
Expand Down
8 changes: 5 additions & 3 deletions packages/transaction-controller/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1257,13 +1257,15 @@ export type DappSuggestedGasFees = {
};

/**
* Gas values saved by the user for a specific chain.
* Gas values saved by the user for a specific chain and account.
*/
// Convert to a `type` in a future major version.
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
export interface SavedGasFees {
maxBaseFee: string;
priorityFee: string;
level?: UserFeeLevel | GasFeeEstimateLevel;
maxBaseFee?: string;
priorityFee?: string;
gasPrice?: string;
}

/**
Expand Down
93 changes: 92 additions & 1 deletion packages/transaction-controller/src/utils/gas-fees.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import type { NetworkClientId } from '@metamask/network-controller';

import type { TransactionControllerMessenger } from '../TransactionController';
import type { GasFeeFlow, GasFeeFlowResponse } from '../types';
import { GasFeeEstimateType, TransactionType, UserFeeLevel } from '../types';
import {
GasFeeEstimateLevel,
GasFeeEstimateType,
TransactionType,
UserFeeLevel,
} from '../types';
import type { UpdateGasFeesRequest } from './gas-fees';
import { gweiDecimalToWeiDecimal, updateGasFees } from './gas-fees';
import { rpcRequest } from './provider';
Expand All @@ -15,8 +20,12 @@ jest.mock('./provider', () => ({
console.error = jest.fn();

const GAS_MOCK = 123;
const GAS_LOW_MOCK = 111;
const GAS_HIGH_MOCK = 789;
const GAS_HEX_MOCK = toHex(GAS_MOCK);
const GAS_HEX_WEI_MOCK = toHex(GAS_MOCK * 1e9);
const GAS_LOW_HEX_WEI_MOCK = toHex(GAS_LOW_MOCK * 1e9);
const GAS_HIGH_HEX_WEI_MOCK = toHex(GAS_HIGH_MOCK * 1e9);
const ORIGIN_MOCK = 'test.com';
const MESSENGER_MOCK = {} as unknown as TransactionControllerMessenger;
const NETWORK_CLIENT_ID_MOCK = 'testNetworkClientId' as NetworkClientId;
Expand All @@ -35,17 +44,27 @@ const UPDATE_GAS_FEES_REQUEST_MOCK = {
const FLOW_RESPONSE_FEE_MARKET_MOCK = {
estimates: {
type: GasFeeEstimateType.FeeMarket,
low: {
maxFeePerGas: GAS_LOW_HEX_WEI_MOCK,
maxPriorityFeePerGas: GAS_LOW_HEX_WEI_MOCK,
},
medium: {
maxFeePerGas: GAS_HEX_WEI_MOCK,
maxPriorityFeePerGas: GAS_HEX_WEI_MOCK,
},
high: {
maxFeePerGas: GAS_HIGH_HEX_WEI_MOCK,
maxPriorityFeePerGas: GAS_HIGH_HEX_WEI_MOCK,
},
},
} as GasFeeFlowResponse;

const FLOW_RESPONSE_LEGACY_MOCK = {
estimates: {
type: GasFeeEstimateType.Legacy,
low: GAS_LOW_HEX_WEI_MOCK,
medium: GAS_HEX_WEI_MOCK,
high: GAS_HIGH_HEX_WEI_MOCK,
},
} as GasFeeFlowResponse;

Expand Down Expand Up @@ -183,6 +202,78 @@ describe('gas-fees', () => {
expect(updateGasFeeRequest.getGasFeeEstimates).not.toHaveBeenCalled();
});

it('calls getSavedGasFees with the transaction metadata', async () => {
updateGasFeeRequest.txMeta.type = TransactionType.simpleSend;

await updateGasFees(updateGasFeeRequest);

expect(updateGasFeeRequest.getSavedGasFees).toHaveBeenCalledWith(
updateGasFeeRequest.txMeta,
);
});

it('does not call getSavedGasFees if initial gas fee params are provided', async () => {
updateGasFeeRequest.txMeta.type = TransactionType.simpleSend;
updateGasFeeRequest.txMeta.txParams.maxFeePerGas = GAS_HEX_MOCK;
updateGasFeeRequest.txMeta.txParams.maxPriorityFeePerGas = GAS_HEX_MOCK;

await updateGasFees(updateGasFeeRequest);

expect(updateGasFeeRequest.getSavedGasFees).not.toHaveBeenCalled();
});

it('uses saved fee market estimate level if saved gas fees include a level', async () => {
updateGasFeeRequest.txMeta.type = TransactionType.simpleSend;
updateGasFeeRequest.getSavedGasFees.mockReturnValueOnce({
level: GasFeeEstimateLevel.High,
});
mockGasFeeFlowMockResponse(FLOW_RESPONSE_FEE_MARKET_MOCK);

await updateGasFees(updateGasFeeRequest);

expect(updateGasFeeRequest.txMeta.txParams.maxFeePerGas).toBe(
GAS_HIGH_HEX_WEI_MOCK,
);
expect(updateGasFeeRequest.txMeta.txParams.maxPriorityFeePerGas).toBe(
GAS_HIGH_HEX_WEI_MOCK,
);
expect(updateGasFeeRequest.txMeta.userFeeLevel).toBe(
GasFeeEstimateLevel.High,
);
});

it('uses saved legacy estimate level if saved gas fees include a level', async () => {
updateGasFeeRequest.eip1559 = false;
updateGasFeeRequest.txMeta.type = TransactionType.simpleSend;
updateGasFeeRequest.getSavedGasFees.mockReturnValueOnce({
level: GasFeeEstimateLevel.Low,
});
mockGasFeeFlowMockResponse(FLOW_RESPONSE_LEGACY_MOCK);

await updateGasFees(updateGasFeeRequest);

expect(updateGasFeeRequest.txMeta.txParams.gasPrice).toBe(
GAS_LOW_HEX_WEI_MOCK,
);
expect(updateGasFeeRequest.txMeta.userFeeLevel).toBe(
GasFeeEstimateLevel.Low,
);
});

it('uses saved gasPrice if saved gas fees include a legacy custom value', async () => {
updateGasFeeRequest.eip1559 = false;
updateGasFeeRequest.txMeta.type = TransactionType.simpleSend;
updateGasFeeRequest.getSavedGasFees.mockReturnValueOnce({
level: UserFeeLevel.CUSTOM,
gasPrice: '10',
});

await updateGasFees(updateGasFeeRequest);

expect(updateGasFeeRequest.txMeta.txParams.gasPrice).toBe('0x2540be400');
expect(updateGasFeeRequest.txMeta.userFeeLevel).toBe(UserFeeLevel.CUSTOM);
});

describe('sets maxFeePerGas', () => {
it('to undefined if not eip1559', async () => {
updateGasFeeRequest.eip1559 = false;
Expand Down
Loading
Loading