From 7882d53dfc53e7a60133b8505a3964b11e11fec6 Mon Sep 17 00:00:00 2001
From: Vinicius Stevam <45455812+vinistevam@users.noreply.github.com>
Date: Fri, 24 Oct 2025 14:50:14 +0000
Subject: [PATCH] chore(runway): cherry-pick fix: cp-7.58.0 fix gasless
transaction support logic (#21604)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## **Description**
This PR fixed and aligns the gasless transaction logic vie 7702 and
smart transactions .
It ensures consistent handling of Smart Transactions, `sendBundle`
support, and EIP-7702 activation.
### Changes
#### Updated `useIsGaslessSupported` hook
Gasless is now considered supported when:
- Smart Transactions are enabled **and** the chain supports
`sendBundle`, or
- Smart Transactions are disabled **and** the chain supports EIP-7702
(`is7702Supported`).
#### Updated `isEIP7702GasFeeTokensEnabled`
EIP-7702 gas fee tokens are now enabled when:
- Smart Transactions are **not** enabled, or
- The chain **does not** support `sendBundle`.
This prevents EIP-7702 activation when full Smart Transaction support is
available.
## **Changelog**
CHANGELOG entry: null
## **Related issues**
Fixes: https://github.com/MetaMask/metamask-mobile/issues/21613
https://github.com/MetaMask/mobile-planning/issues/2356
## **Manual testing steps**
```gherkin
Feature: Pay gas fees using an alternative token on Arbitrum mainnet
Scenario: User sends a token transaction using USDC to pay for gas fees
Given user has USDC to pay for the transaction
And the user has an upgraded account on Arbitrum mainnet
And the user has insufficient ETH balance to cover gas fees
When the user sends a USDC token transaction on Arbitrum mainnet
And selects USDC as the gas fee token in the "Network Fee" row
And confirms the transaction
Then the transaction should be successfully submitted
And the user should see on Etherscan that gas fees were paid in USDC
And the USDC balance of the account should decrease accordingly
```
## **Screenshots/Recordings**
[Screencast from 2025-10-24
15-48-57.webm](https://github.com/user-attachments/assets/ac5e2ac8-5849-4057-8f95-658e632c40cb)
### **Before**
### **After**
## **Pre-merge author checklist**
- [ ] I’ve followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [ ] I've completed the PR template to the best of my ability
- [ ] I’ve included tests if applicable
- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
---
> [!NOTE]
> Updates gasless logic to require Smart Transactions plus sendBundle
support or EIP-7702, and enables EIP-7702 gas fee tokens when Smart
Transactions are off or sendBundle is unsupported, with accompanying
tests.
>
> - **Hooks**
> - `useIsGaslessSupported`:
> - Switch to `selectShouldUseSmartTransaction` and gate gasless on
`isSmartTransaction && sendBundle` or EIP-7702.
> - Short-circuit `isAtomicBatchSupported`/`isRelaySupported` when smart
+ sendBundle is supported.
> - Adds helper `isSmartTransactionAndBundleSupported` and JSDoc.
> - **Controller**
> - `transaction-controller-init.ts`:
> - `isEIP7702GasFeeTokensEnabled` now returns true when smart
transactions are disabled or `sendBundle` is not supported.
> - **Tests**
> - Revamp `useIsGaslessSupported.test.ts` to cover Smart Tx vs EIP-7702
paths, sendBundle gating, and edge cases.
> - Add test for `isEIP7702GasFeeTokensEnabled` option behavior in
`transaction-controller-init.test.ts`.
>
> Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
1f6024d1fdd97cf8fe837f578b734e892d7f3cff. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).
---
.../hooks/gas/useIsGaslessSupported.test.ts | 431 ++++++++++--------
.../hooks/gas/useIsGaslessSupported.ts | 39 +-
.../transaction-controller-init.test.ts | 13 +
.../transaction-controller-init.ts | 13 +-
4 files changed, 289 insertions(+), 207 deletions(-)
diff --git a/app/components/Views/confirmations/hooks/gas/useIsGaslessSupported.test.ts b/app/components/Views/confirmations/hooks/gas/useIsGaslessSupported.test.ts
index 03feefb8c7b5..fd8400e8b6fe 100644
--- a/app/components/Views/confirmations/hooks/gas/useIsGaslessSupported.test.ts
+++ b/app/components/Views/confirmations/hooks/gas/useIsGaslessSupported.test.ts
@@ -15,6 +15,37 @@ jest.mock('../../../../../util/transaction-controller');
jest.mock('../../../../../util/transactions/transaction-relay');
jest.mock('../transactions/useTransactionMetadataRequest');
+const SMART_TRANSACTIONS_ENABLED_STATE = {
+ swaps: {
+ featureFlags: {
+ smart_transactions: {
+ mobile_active: true,
+ extension_active: true,
+ },
+ smartTransactions: {
+ mobileActive: true,
+ extensionActive: true,
+ mobileActiveIOS: true,
+ mobileActiveAndroid: true,
+ },
+ },
+ '0x1': {
+ isLive: true,
+ featureFlags: {
+ smartTransactions: {
+ expectedDeadline: 45,
+ maxDeadline: 160,
+ mobileReturnTxHashAsap: false,
+ mobileActive: true,
+ extensionActive: true,
+ mobileActiveIOS: true,
+ mobileActiveAndroid: true,
+ },
+ },
+ },
+ },
+};
+
describe('useIsGaslessSupported', () => {
const mockUseTransactionMetadataRequest = jest.mocked(
useTransactionMetadataRequest,
@@ -33,46 +64,19 @@ describe('useIsGaslessSupported', () => {
isSendBundleSupportedMock.mockResolvedValue(false);
});
- describe('when gasless supported', () => {
+ describe('Gasless Smart Transactions', () => {
it('returns isSupported and isSmartTransaction as true', async () => {
const stateWithSmartTransactionEnabled = merge(
{},
transferConfirmationState,
- {
- swaps: {
- featureFlags: {
- smart_transactions: {
- mobile_active: true,
- extension_active: true,
- },
- smartTransactions: {
- mobileActive: true,
- extensionActive: true,
- mobileActiveIOS: true,
- mobileActiveAndroid: true,
- },
- },
- '0x1': {
- isLive: true,
- featureFlags: {
- smartTransactions: {
- expectedDeadline: 45,
- maxDeadline: 160,
- mobileReturnTxHashAsap: false,
- mobileActive: true,
- extensionActive: true,
- mobileActiveIOS: true,
- mobileActiveAndroid: true,
- },
- },
- },
- },
- },
+ SMART_TRANSACTIONS_ENABLED_STATE,
);
isSendBundleSupportedMock.mockResolvedValue(true);
+
const { result } = renderHookWithProvider(() => useIsGaslessSupported(), {
state: stateWithSmartTransactionEnabled,
});
+
await waitFor(() =>
expect(result.current).toEqual({
isSupported: true,
@@ -80,204 +84,243 @@ describe('useIsGaslessSupported', () => {
}),
);
});
- });
- it('returns isSupported and isSmartTransaction as false when smart transactions are disabled', async () => {
- const { result } = renderHookWithProvider(() => useIsGaslessSupported(), {
- state: transferTransactionStateMock,
- });
- await waitFor(() =>
- expect(result.current).toEqual({
- isSupported: false,
- isSmartTransaction: false,
- }),
- );
- });
+ it('returns isSupported and isSmartTransaction as false when smart transactions are disabled', async () => {
+ const { result } = renderHookWithProvider(() => useIsGaslessSupported(), {
+ state: transferTransactionStateMock,
+ });
- it('returns isSupported and isSmartTransaction as false when chainId is undefined', async () => {
- mockUseTransactionMetadataRequest.mockReturnValue(undefined);
- const { result } = renderHookWithProvider(() => useIsGaslessSupported(), {
- state: transferTransactionStateMock,
+ await waitFor(() =>
+ expect(result.current).toEqual({
+ isSupported: false,
+ isSmartTransaction: false,
+ }),
+ );
});
- await waitFor(() =>
- expect(result.current).toEqual({
- isSupported: false,
- isSmartTransaction: false,
- }),
- );
- });
- it('returns isSupported and isSmartTransaction as false when transactionMeta is null', async () => {
- mockUseTransactionMetadataRequest.mockReturnValue(undefined);
- const { result } = renderHookWithProvider(() => useIsGaslessSupported(), {
- state: transferTransactionStateMock,
- });
- await waitFor(() =>
- expect(result.current).toEqual({
- isSupported: false,
- isSmartTransaction: false,
- }),
- );
- });
+ it('returns false if smart transaction is enabled but sendBundle is not supported', async () => {
+ isSendBundleSupportedMock.mockResolvedValue(false);
- it('returns isSupported true and isSmartTransaction: false when EIP-7702 conditions met', async () => {
- isRelaySupportedMock.mockResolvedValue(true);
- isSendBundleSupportedMock.mockResolvedValue(false);
- isAtomicBatchSupportedMock.mockResolvedValue([
- {
- chainId: '0x1',
- isSupported: true,
- delegationAddress: '0xde1',
- },
- ]);
+ const stateWithSmartTransactionEnabled = merge(
+ {},
+ transferConfirmationState,
+ SMART_TRANSACTIONS_ENABLED_STATE,
+ );
- const state = merge({}, transferTransactionStateMock);
- const { result } = renderHookWithProvider(() => useIsGaslessSupported(), {
- state,
- });
- await waitFor(() => {
- expect(result.current).toEqual({
- isSupported: true,
- isSmartTransaction: false,
+ const { result } = renderHookWithProvider(() => useIsGaslessSupported(), {
+ state: stateWithSmartTransactionEnabled,
});
+
+ await waitFor(() =>
+ expect(result.current).toEqual({
+ isSupported: false,
+ isSmartTransaction: true,
+ }),
+ );
});
});
- it('returns isSupported false and isSmartTransaction: false when atomicBatchSupported account not upgraded', async () => {
- isRelaySupportedMock.mockResolvedValue(true);
- isSendBundleSupportedMock.mockResolvedValue(false);
- isAtomicBatchSupportedMock.mockResolvedValue([
- {
- chainId: '0x1',
- isSupported: false,
- delegationAddress: undefined,
- },
- ]);
+ describe('Gasless EIP-7702', () => {
+ it('returns isSupported true and isSmartTransaction: false when EIP-7702 conditions met', async () => {
+ isRelaySupportedMock.mockResolvedValue(true);
+ isSendBundleSupportedMock.mockResolvedValue(false);
+ isAtomicBatchSupportedMock.mockResolvedValue([
+ {
+ chainId: '0x1',
+ isSupported: true,
+ delegationAddress: '0xde1',
+ },
+ ]);
- const state = merge({}, transferTransactionStateMock);
- const { result } = renderHookWithProvider(() => useIsGaslessSupported(), {
- state,
- });
- await waitFor(() => {
- expect(result.current).toEqual({
- isSupported: false,
- isSmartTransaction: false,
+ const state = merge({}, transferTransactionStateMock);
+ const { result } = renderHookWithProvider(() => useIsGaslessSupported(), {
+ state,
+ });
+
+ await waitFor(() => {
+ expect(result.current).toEqual({
+ isSupported: true,
+ isSmartTransaction: false,
+ });
});
});
- });
- it('returns isSupported false and isSmartTransaction: false when relay not supported', async () => {
- isRelaySupportedMock.mockResolvedValue(false);
- isSendBundleSupportedMock.mockResolvedValue(false);
- isAtomicBatchSupportedMock.mockResolvedValue([
- {
- chainId: '0x1',
- isSupported: true,
- delegationAddress: '0xde1',
- },
- ]);
+ it('returns isSupported false and isSmartTransaction: false when atomicBatchSupported account not upgraded', async () => {
+ isRelaySupportedMock.mockResolvedValue(true);
+ isSendBundleSupportedMock.mockResolvedValue(false);
+ isAtomicBatchSupportedMock.mockResolvedValue([
+ {
+ chainId: '0x1',
+ isSupported: false,
+ delegationAddress: undefined,
+ },
+ ]);
+
+ const state = merge({}, transferTransactionStateMock);
+ const { result } = renderHookWithProvider(() => useIsGaslessSupported(), {
+ state,
+ });
- const state = merge({}, transferTransactionStateMock);
- const { result } = renderHookWithProvider(() => useIsGaslessSupported(), {
- state,
+ await waitFor(() => {
+ expect(result.current).toEqual({
+ isSupported: false,
+ isSmartTransaction: false,
+ });
+ });
});
- await waitFor(() => {
- expect(result.current).toEqual({
- isSupported: false,
- isSmartTransaction: false,
+
+ it('returns isSupported false and isSmartTransaction: false when relay not supported', async () => {
+ isRelaySupportedMock.mockResolvedValue(false);
+ isSendBundleSupportedMock.mockResolvedValue(false);
+ isAtomicBatchSupportedMock.mockResolvedValue([
+ {
+ chainId: '0x1',
+ isSupported: true,
+ delegationAddress: '0xde1',
+ },
+ ]);
+
+ const state = merge({}, transferTransactionStateMock);
+ const { result } = renderHookWithProvider(() => useIsGaslessSupported(), {
+ state,
+ });
+
+ await waitFor(() => {
+ expect(result.current).toEqual({
+ isSupported: false,
+ isSmartTransaction: false,
+ });
});
});
- });
- it('returns isSupported false if the transaction is contract deployment (no "to" param)', async () => {
- mockUseTransactionMetadataRequest.mockReturnValue({
- chainId: '0x1',
- txParams: { from: '0x123' }, // no "to"
- } as unknown as TransactionMeta);
- isRelaySupportedMock.mockResolvedValue(true);
- isSendBundleSupportedMock.mockResolvedValue(false);
- isAtomicBatchSupportedMock.mockResolvedValue([
- {
+ it('returns isSupported false if the transaction is contract deployment (no "to" param)', async () => {
+ mockUseTransactionMetadataRequest.mockReturnValue({
chainId: '0x1',
- isSupported: true,
- delegationAddress: '0xde1',
- },
- ]);
+ txParams: { from: '0x123' }, // no "to"
+ } as unknown as TransactionMeta);
+ isRelaySupportedMock.mockResolvedValue(true);
+ isSendBundleSupportedMock.mockResolvedValue(false);
+ isAtomicBatchSupportedMock.mockResolvedValue([
+ {
+ chainId: '0x1',
+ isSupported: true,
+ delegationAddress: '0xde1',
+ },
+ ]);
- const state = merge({}, transferTransactionStateMock);
- const { result } = renderHookWithProvider(() => useIsGaslessSupported(), {
- state,
- });
- await waitFor(() => {
- expect(result.current).toEqual({
- isSupported: false,
- isSmartTransaction: false,
+ const state = merge({}, transferTransactionStateMock);
+ const { result } = renderHookWithProvider(() => useIsGaslessSupported(), {
+ state,
+ });
+
+ await waitFor(() => {
+ expect(result.current).toEqual({
+ isSupported: false,
+ isSmartTransaction: false,
+ });
});
});
- });
- it('returns isSupported false and isSmartTransaction: false when no matching chain support in atomicBatch', async () => {
- isRelaySupportedMock.mockResolvedValue(true);
- isSendBundleSupportedMock.mockResolvedValue(false);
- isAtomicBatchSupportedMock.mockResolvedValue([
- {
- chainId: '0x3',
- isSupported: true,
- delegationAddress: '0xde1',
- },
- ]);
+ it('returns isSupported false and isSmartTransaction: false when no matching chain support in atomicBatch', async () => {
+ isRelaySupportedMock.mockResolvedValue(true);
+ isSendBundleSupportedMock.mockResolvedValue(false);
+ isAtomicBatchSupportedMock.mockResolvedValue([
+ {
+ chainId: '0x3',
+ isSupported: true,
+ delegationAddress: '0xde1',
+ },
+ ]);
- const state = merge({}, transferTransactionStateMock);
- const { result } = renderHookWithProvider(() => useIsGaslessSupported(), {
- state,
- });
- await waitFor(() => {
- expect(result.current).toEqual({
- isSupported: false,
- isSmartTransaction: false,
+ const state = merge({}, transferTransactionStateMock);
+ const { result } = renderHookWithProvider(() => useIsGaslessSupported(), {
+ state,
+ });
+
+ await waitFor(() => {
+ expect(result.current).toEqual({
+ isSupported: false,
+ isSmartTransaction: false,
+ });
});
});
- });
- it('returns isSupported false and isSmartTransaction: false if isAtomicBatchSupported returns undefined', async () => {
- isRelaySupportedMock.mockResolvedValue(true);
- isSendBundleSupportedMock.mockResolvedValue(false);
- isAtomicBatchSupportedMock.mockResolvedValue(
- undefined as unknown as ReturnType,
- );
- const state = merge({}, transferTransactionStateMock);
- const { result } = renderHookWithProvider(() => useIsGaslessSupported(), {
- state,
+ it('returns isSupported false and isSmartTransaction: false if isAtomicBatchSupported returns undefined', async () => {
+ isRelaySupportedMock.mockResolvedValue(true);
+ isSendBundleSupportedMock.mockResolvedValue(false);
+ isAtomicBatchSupportedMock.mockResolvedValue(
+ undefined as unknown as ReturnType,
+ );
+
+ const state = merge({}, transferTransactionStateMock);
+ const { result } = renderHookWithProvider(() => useIsGaslessSupported(), {
+ state,
+ });
+
+ await waitFor(() => {
+ expect(result.current).toEqual({
+ isSupported: false,
+ isSmartTransaction: false,
+ });
+ });
});
- await waitFor(() => {
- expect(result.current).toEqual({
- isSupported: false,
- isSmartTransaction: false,
+
+ it('returns isSupported false and isSmartTransaction: false if isRelaySupported returns undefined', async () => {
+ isRelaySupportedMock.mockResolvedValue(
+ undefined as unknown as ReturnType,
+ );
+ isSendBundleSupportedMock.mockResolvedValue(false);
+ isAtomicBatchSupportedMock.mockResolvedValue([
+ {
+ chainId: '0x1',
+ isSupported: true,
+ delegationAddress: '0xde1',
+ },
+ ]);
+
+ const state = merge({}, transferTransactionStateMock);
+ const { result } = renderHookWithProvider(() => useIsGaslessSupported(), {
+ state,
+ });
+
+ await waitFor(() => {
+ expect(result.current).toEqual({
+ isSupported: false,
+ isSmartTransaction: false,
+ });
});
});
});
- it('returns isSupported false and isSmartTransaction: false if isRelaySupported returns undefined', async () => {
- isRelaySupportedMock.mockResolvedValue(
- undefined as unknown as ReturnType,
- );
- isSendBundleSupportedMock.mockResolvedValue(false);
- isAtomicBatchSupportedMock.mockResolvedValue([
- {
- chainId: '0x1',
- isSupported: true,
- delegationAddress: '0xde1',
- },
- ]);
- const state = merge({}, transferTransactionStateMock);
- const { result } = renderHookWithProvider(() => useIsGaslessSupported(), {
- state,
+ describe('Edge Cases', () => {
+ it('returns isSupported and isSmartTransaction as false when chainId is undefined', async () => {
+ mockUseTransactionMetadataRequest.mockReturnValue(undefined);
+
+ const { result } = renderHookWithProvider(() => useIsGaslessSupported(), {
+ state: transferTransactionStateMock,
+ });
+
+ await waitFor(() =>
+ expect(result.current).toEqual({
+ isSupported: false,
+ isSmartTransaction: false,
+ }),
+ );
});
- await waitFor(() => {
- expect(result.current).toEqual({
- isSupported: false,
- isSmartTransaction: false,
+
+ it('returns isSupported and isSmartTransaction as false when transactionMeta is null', async () => {
+ mockUseTransactionMetadataRequest.mockReturnValue(undefined);
+
+ const { result } = renderHookWithProvider(() => useIsGaslessSupported(), {
+ state: transferTransactionStateMock,
});
+
+ await waitFor(() =>
+ expect(result.current).toEqual({
+ isSupported: false,
+ isSmartTransaction: false,
+ }),
+ );
});
});
});
diff --git a/app/components/Views/confirmations/hooks/gas/useIsGaslessSupported.ts b/app/components/Views/confirmations/hooks/gas/useIsGaslessSupported.ts
index 89b7fbd82882..e3ce76a46e1b 100644
--- a/app/components/Views/confirmations/hooks/gas/useIsGaslessSupported.ts
+++ b/app/components/Views/confirmations/hooks/gas/useIsGaslessSupported.ts
@@ -1,6 +1,6 @@
import { useSelector } from 'react-redux';
import { useTransactionMetadataRequest } from '../transactions/useTransactionMetadataRequest';
-import { selectSmartTransactionsEnabled } from '../../../../../selectors/smartTransactionsController';
+import { selectShouldUseSmartTransaction } from '../../../../../selectors/smartTransactionsController';
import { RootState } from '../../../../../reducers';
import { useAsyncResult } from '../../../../hooks/useAsyncResult';
import { isSendBundleSupported } from '../../../../../util/transactions/sentinel-api';
@@ -8,6 +8,17 @@ import { isRelaySupported } from '../../../../../util/transactions/transaction-r
import { isAtomicBatchSupported } from '../../../../../util/transaction-controller';
import { Hex } from '@metamask/utils';
+/**
+ * Hook to determine if gasless transactions are supported for the current confirmation context.
+ *
+ * Gasless support can be enabled in two ways:
+ * - Via 7702: Supported when the current account is upgraded, the chain supports atomic batch, relay is available, and the transaction is not a contract deployment.
+ * - Via Smart Transactions: Supported when smart transactions are enabled and sendBundle is supported for the chain.
+ *
+ * @returns An object containing:
+ * - `isSupported`: `true` if gasless transactions are supported via either 7702 or smart transactions with sendBundle.
+ * - `isSmartTransaction`: `true` if smart transactions are enabled for the current chain.
+ */
export function useIsGaslessSupported() {
const transactionMeta = useTransactionMetadataRequest();
@@ -15,11 +26,20 @@ export function useIsGaslessSupported() {
const { from } = txParams ?? {};
const isSmartTransaction = useSelector((state: RootState) =>
- selectSmartTransactionsEnabled(state, chainId),
+ selectShouldUseSmartTransaction(state, chainId),
+ );
+
+ const { value: sendBundleSupportsChain } = useAsyncResult(
+ async () => (chainId ? isSendBundleSupported(chainId) : false),
+ [chainId],
+ );
+
+ const isSmartTransactionAndBundleSupported = Boolean(
+ isSmartTransaction && sendBundleSupportsChain,
);
const { value: atomicBatchSupportResult } = useAsyncResult(async () => {
- if (isSmartTransaction) {
+ if (isSmartTransactionAndBundleSupported) {
return undefined;
}
@@ -27,20 +47,15 @@ export function useIsGaslessSupported() {
address: from as Hex,
chainIds: [chainId as Hex],
});
- }, [chainId, from, isSmartTransaction]);
+ }, [chainId, from, isSmartTransactionAndBundleSupported]);
const { value: relaySupportsChain } = useAsyncResult(async () => {
- if (isSmartTransaction) {
+ if (isSmartTransactionAndBundleSupported) {
return undefined;
}
return isRelaySupported(chainId as Hex);
- }, [chainId, isSmartTransaction]);
-
- const { value: sendBundleSupportsChain } = useAsyncResult(
- async () => (chainId ? isSendBundleSupported(chainId) : false),
- [chainId],
- );
+ }, [chainId, isSmartTransactionAndBundleSupported]);
const atomicBatchChainSupport = atomicBatchSupportResult?.find(
(result) => result.chainId.toLowerCase() === chainId?.toLowerCase(),
@@ -55,7 +70,7 @@ export function useIsGaslessSupported() {
);
const isSupported = Boolean(
- (isSmartTransaction && sendBundleSupportsChain) || is7702Supported,
+ isSmartTransactionAndBundleSupported || is7702Supported,
);
return {
diff --git a/app/core/Engine/controllers/transaction-controller/transaction-controller-init.test.ts b/app/core/Engine/controllers/transaction-controller/transaction-controller-init.test.ts
index 92d339a57d66..1cea96a0c542 100644
--- a/app/core/Engine/controllers/transaction-controller/transaction-controller-init.test.ts
+++ b/app/core/Engine/controllers/transaction-controller/transaction-controller-init.test.ts
@@ -591,4 +591,17 @@ describe('Transaction Controller Init', () => {
expect(handler).toHaveBeenCalledWith(...expectedArgs);
});
});
+
+ describe('option isEIP7702GasFeeTokensEnabled', () => {
+ it('returns false when feature flag is enabled for current chain', async () => {
+ const mockTransactionMeta = {
+ id: '123',
+ status: 'approved',
+ chainId: '0x1',
+ } as unknown as TransactionMeta;
+ const optionFn = testConstructorOption('isEIP7702GasFeeTokensEnabled');
+
+ expect(await optionFn?.(mockTransactionMeta)).toBe(false);
+ });
+ });
});
diff --git a/app/core/Engine/controllers/transaction-controller/transaction-controller-init.ts b/app/core/Engine/controllers/transaction-controller/transaction-controller-init.ts
index 353ba88ddd99..0dcd2de665a5 100644
--- a/app/core/Engine/controllers/transaction-controller/transaction-controller-init.ts
+++ b/app/core/Engine/controllers/transaction-controller/transaction-controller-init.ts
@@ -126,7 +126,18 @@ export const TransactionControllerInit: ControllerInitFunction<
const { chainId } = transactionMeta;
const state = getState();
- return !selectShouldUseSmartTransaction(state, chainId);
+ const isSmartTransactionEnabled = selectShouldUseSmartTransaction(
+ state,
+ chainId,
+ );
+ const isSendBundleSupportedChain = await isSendBundleSupported(
+ chainId,
+ );
+
+ // EIP7702 gas fee tokens are enabled when:
+ // - Smart transactions are NOT enabled, OR
+ // - Send bundle is NOT supported
+ return !isSmartTransactionEnabled || !isSendBundleSupportedChain;
},
isSimulationEnabled: () =>
preferencesController.state.useTransactionSimulations,