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
4 changes: 4 additions & 0 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]

### Fixed

- Strip redundant `gasPrice` from fee-market transactions (type `0x2`/`0x4`) instead of rejecting them, fixing compatibility with RPCs that return both `gasPrice` and EIP-1559 fields (e.g. Arbitrum) ([#7877](https://github.com/MetaMask/core/issues/7877))

### Added

- Add optional `submissionMethod` property and `TransactionSubmissionMethod` enum to `TransactionMeta` ([#8375](https://github.com/MetaMask/core/pull/8375))
Expand Down
50 changes: 50 additions & 0 deletions packages/transaction-controller/src/utils/validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,56 @@ describe('validation', () => {
);
});

it('strips gasPrice instead of throwing when type is fee-market and EIP-1559 fields are present', () => {
const txParams = {
from: FROM_MOCK,
to: TO_MOCK,
gasPrice: '0x01',
maxFeePerGas: '0x02',
maxPriorityFeePerGas: '0x01',
type: TransactionEnvelopeType.feeMarket,
// TODO: Replace `any` with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any;

expect(() => validateTxParams(txParams)).not.toThrow();
expect(txParams.gasPrice).toBeUndefined();
expect(txParams.maxFeePerGas).toBe('0x02');
});

it('strips gasPrice instead of throwing when type is setCode and EIP-1559 fields are present', () => {
const txParams = {
from: FROM_MOCK,
to: TO_MOCK,
gasPrice: '0x01',
maxFeePerGas: '0x02',
maxPriorityFeePerGas: '0x01',
type: TransactionEnvelopeType.setCode,
// TODO: Replace `any` with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any;

expect(() => validateTxParams(txParams)).not.toThrow();
expect(txParams.gasPrice).toBeUndefined();
});

it('still throws if gasPrice and EIP-1559 fields are present but no explicit type', () => {
expect(() =>
validateTxParams({
from: FROM_MOCK,
to: TO_MOCK,
gasPrice: '0x01',
maxFeePerGas: '0x01',
// TODO: Replace `any` with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any),
).toThrow(
rpcErrors.invalidParams(
'Invalid transaction params: specified gasPrice but also included maxFeePerGas, these cannot be mixed',
),
);
});

it('throws if gasPrice, maxPriorityFeePerGas or maxFeePerGas is not a valid hexadecimal string', () => {
expect(() =>
validateTxParams({
Expand Down
19 changes: 19 additions & 0 deletions packages/transaction-controller/src/utils/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,9 +353,28 @@ function validateParamChainId(
/**
* Validates gas values.
*
* Some RPC providers (e.g. Arbitrum's eth_fillTransaction) return both
* gasPrice and EIP-1559 fee fields. When the envelope type is explicitly
* fee-market (0x2/0x4), strip the redundant gasPrice before validating
* mutual exclusivity, matching the normalization already done elsewhere
* in the codebase (GasFeePoller, updateGasFees, retry utils).
*
* @param txParams - The transaction parameters to validate.
*/
function validateGasFeeParams(txParams: TransactionParams): void {
const type = txParams.type as TransactionEnvelopeType | undefined;

// When the envelope type explicitly indicates a fee-market transaction,
// strip gasPrice rather than rejecting — some RPCs return both fields.
if (
txParams.gasPrice &&
(txParams.maxFeePerGas || txParams.maxPriorityFeePerGas) &&
type &&
TRANSACTION_ENVELOPE_TYPES_FEE_MARKET.includes(type)
) {
delete txParams.gasPrice;
}

if (txParams.gasPrice) {
ensureProperTransactionEnvelopeTypeProvided(txParams, 'gasPrice');
ensureMutuallyExclusiveFieldsNotProvided(
Expand Down