Skip to content

Commit 74b9399

Browse files
runway-github[bot]khanti42metamaskbotJulink-ethBattambang
authored
chore(runway): cherry-pick fix: hardware wallet eip 7702 issue () (#27892)
- fix: hardware wallet eip 7702 issue (cp-7.71.0) (#27615) <!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> This PR will provide a fix for hardware wallet to gas free network like Monad and Sei. Due to currently Hardware wallet is not supported for EIP 7702 gas sponsorship, and Swap feature is not working for hardware wallet user. This fix will fall back the Gasless transaction to User pay gas previous model so that user can still do the swap and sign transaction like bfore. This is temporately fix for current version of extensions, and we will do a proper support in the future. Similar to extension PR: MetaMask/metamask-extension#40915 Ticket: https://consensyssoftware.atlassian.net/jira/software/c/projects/NEB/boards/3738/backlog?selectedIssue=NEB-767 ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: Hardware wallet user will fall back to use `User pay gas` for those Gasless network due to hardware wallet not supported in Gasless network like Sei and Monad. ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: Gas sponsorship disabled for hardware wallet accounts Scenario: Hardware wallet user does not use gas sponsorship on sponsored network Given the user has added a hardware wallet account (Ledger or QR-based) And the hardware wallet account is selected as the active account And the user has added a gas-sponsored network (e.g. Monad) When the user attempts to perform a swap a dapp interaction or send a transaction on the sponsored network Then the transaction should not use gas sponsorship And the UI should not display any gas sponsorship labels (e.g. "No network fee", "Paid by MetaMask") And the user should see the normal network gas fee And the transaction should follow the standard user-pays-gas flow ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> > With HW account: Network list: <img width="392" height="800" alt="Screenshot 2026-03-18 at 16 05 48" src="https://github.com/user-attachments/assets/601c30cc-fd78-456e-87b5-cc70e7ce3433" /> Tx flow: <img width="392" height="800" alt="Screenshot 2026-03-18 at 15 53 04" src="https://github.com/user-attachments/assets/7c7b60cf-3f79-4701-a1e5-1fbba2bfe84e" /> <img width="392" height="800" alt="Screenshot 2026-03-18 at 15 55 20" src="https://github.com/user-attachments/assets/a3258815-f1d2-4abf-8b22-209b73a0fbba" /> <img width="392" height="800" alt="Screenshot 2026-03-18 at 15 55 47" src="https://github.com/user-attachments/assets/9275c82c-e56a-47ad-a203-a34f67ed6ed9" /> ### **After** > With HW account: Network list: <img width="392" height="800" alt="Screenshot 2026-03-18 at 16 06 19" src="https://github.com/user-attachments/assets/03a296be-17fe-4387-baa0-23bae3ba00eb" /> Tx flow: <img width="392" height="800" alt="Screenshot 2026-03-18 at 15 49 55" src="https://github.com/user-attachments/assets/b39bc1b0-4b28-4e06-ae06-a381458ba7f6" /> <img width="392" height="800" alt="Screenshot 2026-03-18 at 15 50 29" src="https://github.com/user-attachments/assets/76a31213-9ecd-4290-b407-58ffb8d82f48" /> <!-- [screenshots/recordings] --> ## **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. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches gasless sponsorship and transaction publishing paths (including 7702 delegation), which can affect whether transactions are sponsored vs user-paid and could change behavior on supported chains. Changes are scoped to hardware-wallet detection gates with added tests, reducing regression risk. > > **Overview** > Hardware wallet accounts now **opt out of gasless / EIP-7702 sponsorship**, forcing swaps/bridge and confirmations to use the normal *user-pays-gas* path. > > This adds an `accountSupports7702` gate to `TransactionControllerInit` so `Delegation7702PublishHook` and `isEIP7702GasFeeTokensEnabled` only activate for keyrings that support 7702, and updates `useIsGaslessSupported`/`useIsGasIncluded7702Supported` (via new `useIsHardwareWalletForBridge`) to report unsupported for hardware signers. > > Network selection UI (`NetworkSelector`, `NetworkMultiSelectorList`, `CustomNetwork`) now hides the “No network fee” sponsored label for hardware wallets, and a patched `@metamask/bridge-status-controller` waits for approval tx confirmation when required. Tests were added/updated to cover the new hardware-wallet gating behavior. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 83f66fc. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: metamaskbot <metamaskbot@users.noreply.github.com> Co-authored-by: Julien Fontanel <julien.fontanel@consensys.net> Co-authored-by: Frederic HENG <frederic.heng@consensys.net> Co-authored-by: Arafet (CN - Hong Kong) <52028926+arafetbenmakhlouf@users.noreply.github.com> Co-authored-by: tommasini <46944231+tommasini@users.noreply.github.com> [c4b93de](c4b93de) --------- Co-authored-by: khanti42 <florin.dzeladini@consensys.net> Co-authored-by: metamaskbot <metamaskbot@users.noreply.github.com> Co-authored-by: Julien Fontanel <julien.fontanel@consensys.net> Co-authored-by: Frederic HENG <frederic.heng@consensys.net> Co-authored-by: Arafet (CN - Hong Kong) <52028926+arafetbenmakhlouf@users.noreply.github.com> Co-authored-by: tommasini <46944231+tommasini@users.noreply.github.com>
1 parent 6c47666 commit 74b9399

20 files changed

Lines changed: 575 additions & 20 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
diff --git a/dist/bridge-status-controller.cjs b/dist/bridge-status-controller.cjs
2+
index b787174f2c8c448ed1ad9c8884204c5c8b6858be..af058623871badb4891564c003693ea19d0aa676 100644
3+
--- a/dist/bridge-status-controller.cjs
4+
+++ b/dist/bridge-status-controller.cjs
5+
@@ -834,7 +834,13 @@ class BridgeStatusController extends (0, polling_controller_1.StaticIntervalPoll
6+
? quoteResponse.approval
7+
: undefined, quoteResponse.resetApproval, requireApproval);
8+
approvalTxId = approvalTxMeta?.id;
9+
- await (0, transaction_1.handleMobileHardwareWalletDelay)(requireApproval);
10+
+ if (requireApproval && approvalTxMeta) {
11+
+ await (0, transaction_1.handleMobileHardwareWalletDelay)(requireApproval);
12+
+ await __classPrivateFieldGet(this, _BridgeStatusController_waitForTxConfirmation, "f").call(this, approvalTxMeta.id);
13+
+ }
14+
+ else {
15+
+ await (0, transaction_1.handleMobileHardwareWalletDelay)(requireApproval);
16+
+ }
17+
// Generate actionId for pre-submission history (non-batch EVM only)
18+
const actionId = (0, transaction_1.generateActionId)().toString();
19+
// Add pre-submission history keyed by actionId
20+
diff --git a/dist/bridge-status-controller.mjs b/dist/bridge-status-controller.mjs
21+
index 2fe71bdd2caf4d62f7946e9466b31367d360cd7c..fb9c0bc45abf88873452667b85ef2ea0cdfd929c 100644
22+
--- a/dist/bridge-status-controller.mjs
23+
+++ b/dist/bridge-status-controller.mjs
24+
@@ -831,7 +831,13 @@ export class BridgeStatusController extends StaticIntervalPollingController() {
25+
? quoteResponse.approval
26+
: undefined, quoteResponse.resetApproval, requireApproval);
27+
approvalTxId = approvalTxMeta?.id;
28+
- await handleMobileHardwareWalletDelay(requireApproval);
29+
+ if (requireApproval && approvalTxMeta) {
30+
+ await handleMobileHardwareWalletDelay(requireApproval);
31+
+ await __classPrivateFieldGet(this, _BridgeStatusController_waitForTxConfirmation, "f").call(this, approvalTxMeta.id);
32+
+ }
33+
+ else {
34+
+ await handleMobileHardwareWalletDelay(requireApproval);
35+
+ }
36+
// Generate actionId for pre-submission history (non-batch EVM only)
37+
const actionId = generateActionId().toString();
38+
// Add pre-submission history keyed by actionId
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
diff --git a/dist/bridge-status-controller.cjs b/dist/bridge-status-controller.cjs
2+
index ec19aeeecfa32a3cdf955ccc1152829ee4ddfd8f..d9b427f9f0f4b05238d79c731fc81566634a7c25 100644
3+
--- a/dist/bridge-status-controller.cjs
4+
+++ b/dist/bridge-status-controller.cjs
5+
@@ -855,7 +855,13 @@
6+
? quoteResponse.approval
7+
: undefined, quoteResponse.resetApproval, requireApproval);
8+
approvalTxId = approvalTxMeta?.id;
9+
- await (0, transaction_1.handleMobileHardwareWalletDelay)(requireApproval);
10+
+ if (requireApproval && approvalTxMeta) {
11+
+ await (0, transaction_1.handleMobileHardwareWalletDelay)(requireApproval);
12+
+ await __classPrivateFieldGet(this, _BridgeStatusController_waitForTxConfirmation, "f").call(this, approvalTxMeta.id);
13+
+ }
14+
+ else {
15+
+ await (0, transaction_1.handleMobileHardwareWalletDelay)(requireApproval);
16+
+ }
17+
// Generate actionId for pre-submission history (non-batch EVM only)
18+
const actionId = (0, transaction_1.generateActionId)().toString();
19+
// Add pre-submission history keyed by actionId
20+
diff --git a/dist/bridge-status-controller.mjs b/dist/bridge-status-controller.mjs
21+
index a5661d63c35b5ad3526c1804936dc0e189c90c29..86efc019968599662466e643dae7002ebf5f5014 100644
22+
--- a/dist/bridge-status-controller.mjs
23+
+++ b/dist/bridge-status-controller.mjs
24+
@@ -852,7 +852,13 @@
25+
? quoteResponse.approval
26+
: undefined, quoteResponse.resetApproval, requireApproval);
27+
approvalTxId = approvalTxMeta?.id;
28+
- await handleMobileHardwareWalletDelay(requireApproval);
29+
+ if (requireApproval && approvalTxMeta) {
30+
+ await handleMobileHardwareWalletDelay(requireApproval);
31+
+ await __classPrivateFieldGet(this, _BridgeStatusController_waitForTxConfirmation, "f").call(this, approvalTxMeta.id);
32+
+ }
33+
+ else {
34+
+ await handleMobileHardwareWalletDelay(requireApproval);
35+
+ }
36+
// Generate actionId for pre-submission history (non-batch EVM only)
37+
const actionId = generateActionId().toString();
38+
// Add pre-submission history keyed by actionId

app/components/UI/Bridge/hooks/useBridgeQuoteRequest/useBridgeQuoteRequest.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,50 @@ describe('useBridgeQuoteRequest', () => {
484484
});
485485
});
486486

487+
describe('hardware wallet accounts', () => {
488+
it('sends gasIncluded and gasIncluded7702 false when useIsGasIncluded7702Supported dispatches false for hardware wallet', async () => {
489+
// useIsGasIncluded7702Supported now incorporates the HW wallet check and
490+
// dispatches isGasIncluded7702Supported=false for hardware wallets.
491+
// useIsGasIncludedSTXSendBundleSupported already dispatches false for HW
492+
// wallets via selectShouldUseSmartTransaction.
493+
const testState = createBridgeTestState({
494+
bridgeReducerOverrides: {
495+
isGasIncludedSTXSendBundleSupported: false,
496+
isGasIncluded7702Supported: false,
497+
sourceToken: {
498+
address: '0xSourceToken',
499+
chainId: '0x1',
500+
decimals: 18,
501+
symbol: 'SRC',
502+
},
503+
destToken: {
504+
address: '0xDestToken',
505+
chainId: '0x1',
506+
decimals: 18,
507+
symbol: 'DEST',
508+
},
509+
},
510+
});
511+
512+
const { result } = renderHookWithProvider(() => useBridgeQuoteRequest(), {
513+
state: testState,
514+
});
515+
516+
await act(async () => {
517+
await result.current();
518+
jest.advanceTimersByTime(DEBOUNCE_WAIT);
519+
});
520+
521+
expect(spyUpdateBridgeQuoteRequestParams).toHaveBeenCalledWith(
522+
expect.objectContaining({
523+
gasIncluded: false,
524+
gasIncluded7702: false,
525+
}),
526+
undefined,
527+
);
528+
});
529+
});
530+
487531
describe('insufficientBal parameter', () => {
488532
it('includes insufficientBal false when balance is sufficient', async () => {
489533
mockUseIsInsufficientBalance.mockReturnValue(false);

app/components/UI/Bridge/hooks/useIsGasIncluded7702Supported/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ import {
88
formatChainIdToHex,
99
isNonEvmChainId,
1010
} from '@metamask/bridge-controller';
11+
import { useIsHardwareWalletForBridge } from '../useIsHardwareWalletForBridge';
1112

1213
/**
1314
* Hook that determines if 7702 gasless support is available for bridge/swap.
1415
* Should be used at the page level (e.g., BridgeView) to avoid repeated calculations.
1516
*
16-
* Requirement for 7702:
17+
* Requirements for 7702:
1718
* - Relay must be supported (for 7702 delegation)
19+
* - Source wallet must not be a hardware wallet
1820
*
1921
* @param chainId - The chain ID to check (can be Hex, CAIP, or other format) - only EVM chains are supported
2022
*/
@@ -40,9 +42,11 @@ export const useIsGasIncluded7702Supported = (
4042
return isRelaySupported(evmChainId as Hex);
4143
}, [evmChainId]);
4244

45+
const isHardwareWallet = useIsHardwareWalletForBridge();
46+
4347
// 7702 is available when ALL conditions are met
4448
const isGasIncluded7702Supported = Boolean(
45-
evmChainId && !!isRelaySupportedForChain,
49+
evmChainId && !!isRelaySupportedForChain && !isHardwareWallet,
4650
);
4751

4852
useEffect(() => {

app/components/UI/Bridge/hooks/useIsGasIncluded7702Supported/useIsGasIncluded7702Supported.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,18 @@ import configureStore from '../../../../../util/test/configureStore';
77

88
// Mock dependencies
99
jest.mock('../../../../../util/transactions/transaction-relay');
10+
jest.mock('../useIsHardwareWalletForBridge', () => ({
11+
useIsHardwareWalletForBridge: jest.fn().mockReturnValue(false),
12+
}));
1013

1114
const mockIsRelaySupported = jest.mocked(isRelaySupported);
15+
const { useIsHardwareWalletForBridge } = jest.requireMock(
16+
'../useIsHardwareWalletForBridge',
17+
);
18+
const mockUseIsHardwareWalletForBridge =
19+
useIsHardwareWalletForBridge as jest.MockedFunction<
20+
typeof useIsHardwareWalletForBridge
21+
>;
1222

1323
describe('useIsGasIncluded7702Supported', () => {
1424
const MAINNET_CHAIN_ID = '0x1' as Hex;
@@ -28,6 +38,7 @@ describe('useIsGasIncluded7702Supported', () => {
2838
beforeEach(() => {
2939
jest.clearAllMocks();
3040
mockIsRelaySupported.mockResolvedValue(false);
41+
mockUseIsHardwareWalletForBridge.mockReturnValue(false);
3142
});
3243

3344
afterEach(() => {
@@ -147,6 +158,32 @@ describe('useIsGasIncluded7702Supported', () => {
147158
});
148159
});
149160

161+
describe('when source wallet is a hardware account', () => {
162+
it('updates isGasIncluded7702Supported to false even when relay is supported', async () => {
163+
mockIsRelaySupported.mockResolvedValue(true);
164+
mockUseIsHardwareWalletForBridge.mockReturnValue(true);
165+
166+
const { store } = renderHookWithProvider(
167+
() => useIsGasIncluded7702Supported(MAINNET_CHAIN_ID),
168+
{ state: {} },
169+
);
170+
171+
await expectGasIncluded7702State(store, false);
172+
});
173+
174+
it('updates isGasIncluded7702Supported to false for hardware wallet regardless of chain', async () => {
175+
mockIsRelaySupported.mockResolvedValue(true);
176+
mockUseIsHardwareWalletForBridge.mockReturnValue(true);
177+
178+
const { store } = renderHookWithProvider(
179+
() => useIsGasIncluded7702Supported('eip155:59144'), // Linea
180+
{ state: {} },
181+
);
182+
183+
await expectGasIncluded7702State(store, false);
184+
});
185+
});
186+
150187
describe('edge cases', () => {
151188
it('handles case-insensitive chainId matching', async () => {
152189
mockIsRelaySupported.mockResolvedValue(true);
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { renderHook } from '@testing-library/react-hooks';
2+
import { useSelector } from 'react-redux';
3+
import { useIsHardwareWalletForBridge } from './index';
4+
import { isHardwareAccount } from '../../../../../util/address';
5+
6+
jest.mock('react-redux', () => ({
7+
useSelector: jest.fn(),
8+
}));
9+
10+
jest.mock('../../../../../util/address', () => ({
11+
isHardwareAccount: jest.fn(),
12+
}));
13+
14+
const mockUseSelector = useSelector as jest.MockedFunction<typeof useSelector>;
15+
const mockIsHardwareAccount = isHardwareAccount as jest.MockedFunction<
16+
typeof isHardwareAccount
17+
>;
18+
19+
describe('useIsHardwareWalletForBridge', () => {
20+
beforeEach(() => {
21+
jest.clearAllMocks();
22+
mockUseSelector.mockReturnValue(undefined);
23+
mockIsHardwareAccount.mockReturnValue(false);
24+
});
25+
26+
it('returns false when source wallet address is undefined', () => {
27+
mockUseSelector.mockReturnValue(undefined);
28+
29+
const { result } = renderHook(() => useIsHardwareWalletForBridge());
30+
31+
expect(result.current).toBe(false);
32+
expect(mockIsHardwareAccount).not.toHaveBeenCalled();
33+
});
34+
35+
it('returns true when source wallet is a hardware account', () => {
36+
const address = '0x1234567890123456789012345678901234567890';
37+
mockUseSelector.mockReturnValue(address);
38+
mockIsHardwareAccount.mockReturnValue(true);
39+
40+
const { result } = renderHook(() => useIsHardwareWalletForBridge());
41+
42+
expect(result.current).toBe(true);
43+
expect(mockIsHardwareAccount).toHaveBeenCalledWith(address);
44+
});
45+
46+
it('returns false when source wallet is not a hardware account', () => {
47+
const address = '0x1234567890123456789012345678901234567890';
48+
mockUseSelector.mockReturnValue(address);
49+
mockIsHardwareAccount.mockReturnValue(false);
50+
51+
const { result } = renderHook(() => useIsHardwareWalletForBridge());
52+
53+
expect(result.current).toBe(false);
54+
expect(mockIsHardwareAccount).toHaveBeenCalledWith(address);
55+
});
56+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { useMemo } from 'react';
2+
import { useSelector } from 'react-redux';
3+
import { selectSourceWalletAddress } from '../../../../../selectors/bridge';
4+
import { isHardwareAccount } from '../../../../../util/address';
5+
6+
/**
7+
* Returns whether the current bridge source account is a hardware wallet.
8+
* Used to omit gas-included / 7702 params from bridge quote requests so responses
9+
* are non-sponsored for hardware signers.
10+
*/
11+
export function useIsHardwareWalletForBridge(): boolean {
12+
const walletAddress = useSelector(selectSourceWalletAddress);
13+
14+
return useMemo(
15+
() => Boolean(walletAddress && isHardwareAccount(walletAddress)),
16+
[walletAddress],
17+
);
18+
}

app/components/UI/Bridge/utils/transaction.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ export const getIsBridgeTransaction = (txMeta: TransactionMeta) => {
1111
return (
1212
origin === ORIGIN_METAMASK &&
1313
(txMeta.type === TransactionType.bridgeApproval ||
14-
txMeta.type === TransactionType.bridge)
14+
txMeta.type === TransactionType.bridge ||
15+
txMeta.type === TransactionType.swap ||
16+
txMeta.type === TransactionType.swapApproval)
1517
);
1618
};
1719

app/components/UI/NetworkMultiSelectorList/NetworkMultiSelectorList.test.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,27 @@ jest.mock('react-redux', () => ({
3131
useSelector: jest.fn(),
3232
}));
3333

34+
// Avoid loading keyring-utils, keyring-api, and the network/Engine chain in this test
35+
jest.mock('../../../selectors/accountsController', () => ({
36+
selectSelectedInternalAccountFormattedAddress: jest.fn(),
37+
}));
38+
39+
jest.mock('../../../util/address', () => ({
40+
isHardwareAccount: jest.fn(() => false),
41+
}));
42+
43+
jest.mock('@metamask/keyring-api', () => ({
44+
EntropySourceId: {},
45+
BtcMethod: {},
46+
EthMethod: {},
47+
SolAccountType: {},
48+
SolMethod: {},
49+
TrxMethod: {},
50+
isEvmAccountType: jest.fn(),
51+
KeyringAccountType: {},
52+
EthScope: {},
53+
}));
54+
3455
jest.mock('react-native-safe-area-context', () => ({
3556
useSafeAreaInsets: jest.fn(),
3657
}));

app/components/UI/NetworkMultiSelectorList/NetworkMultiSelectorList.tsx

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ import { selectEvmChainId } from '../../../selectors/networkController';
5959
import { formatChainIdToCaip } from '@metamask/bridge-controller';
6060
import { NETWORK_MULTI_SELECTOR_TEST_IDS } from '../NetworkMultiSelector/NetworkMultiSelector.constants';
6161
import { getGasFeesSponsoredNetworkEnabled } from '../../../selectors/featureFlagController/gasFeesSponsored/index.ts';
62+
import { selectSelectedInternalAccountFormattedAddress } from '../../../selectors/accountsController';
63+
import { isHardwareAccount } from '../../../util/address';
6264
import { strings } from '../../../../locales/i18n';
6365
import TagColored, {
6466
TagColor,
@@ -106,6 +108,12 @@ const NetworkMultiSelectList = ({
106108
const isGasFeesSponsoredNetworkEnabled = useSelector(
107109
getGasFeesSponsoredNetworkEnabled,
108110
);
111+
const selectedAddress = useSelector(
112+
selectSelectedInternalAccountFormattedAddress,
113+
);
114+
const isHardwareWallet = Boolean(
115+
selectedAddress && isHardwareAccount(selectedAddress),
116+
);
109117

110118
const { styles } = useStyles(styleSheet, {});
111119

@@ -269,7 +277,8 @@ const NetworkMultiSelectList = ({
269277
const isDisabled = isLoading || isSelectionDisabled;
270278
const showButtonIcon = Boolean(networkTypeOrRpcUrl);
271279

272-
const isGasSponsored = isGasFeesSponsoredNetworkEnabled(chainId);
280+
const isGasSponsored =
281+
!isHardwareWallet && isGasFeesSponsoredNetworkEnabled(chainId);
273282

274283
return (
275284
<View>
@@ -342,6 +351,7 @@ const NetworkMultiSelectList = ({
342351
isSelectAllNetworksSection,
343352
openRpcModal,
344353
isGasFeesSponsoredNetworkEnabled,
354+
isHardwareWallet,
345355
styles.centeredNetworkCell,
346356
styles.noNetworkFeeContainer,
347357
],
@@ -351,11 +361,17 @@ const NetworkMultiSelectList = ({
351361
if (!networks.length || !isAutoScrollEnabled) return;
352362
if (networksLengthRef.current !== networks.length) {
353363
const selectedNetwork = networks.find(({ isSelected }) => isSelected);
354-
networkListRef?.current?.scrollToOffset({
355-
offset: selectedNetwork?.yOffset ?? 0,
356-
animated: false,
357-
});
364+
const offset = selectedNetwork?.yOffset ?? 0;
358365
networksLengthRef.current = networks.length;
366+
// Defer scroll so FlashList has time to lay out items and avoid "index out of bounds"
367+
requestAnimationFrame(() => {
368+
if (networkListRef?.current?.scrollToOffset) {
369+
networkListRef.current.scrollToOffset({
370+
offset,
371+
animated: false,
372+
});
373+
}
374+
});
359375
}
360376
}, [networks, isAutoScrollEnabled]);
361377

0 commit comments

Comments
 (0)