diff --git a/app/components/Approvals/TransactionApproval/TransactionApproval.test.tsx b/app/components/Approvals/TransactionApproval/TransactionApproval.test.tsx
index b534d116a225..2034161327e5 100644
--- a/app/components/Approvals/TransactionApproval/TransactionApproval.test.tsx
+++ b/app/components/Approvals/TransactionApproval/TransactionApproval.test.tsx
@@ -85,7 +85,10 @@ describe('TransactionApproval', () => {
} as any);
const wrapper = shallow(
- ,
+ ,
);
expect(wrapper).toMatchSnapshot();
diff --git a/app/components/Approvals/TransactionApproval/TransactionApproval.tsx b/app/components/Approvals/TransactionApproval/TransactionApproval.tsx
index 8a52bb36c7cd..a6ba56e41b2c 100644
--- a/app/components/Approvals/TransactionApproval/TransactionApproval.tsx
+++ b/app/components/Approvals/TransactionApproval/TransactionApproval.tsx
@@ -5,8 +5,8 @@ import Approval from '../../Views/confirmations/legacy/Approval';
import Approve from '../../Views/confirmations/legacy/ApproveView/Approve';
import QRSigningModal from '../../UI/QRHardware/QRSigningModal';
import withQRHardwareAwareness from '../../UI/QRHardware/withQRHardwareAwareness';
-import { IQRState } from '../../UI/QRHardware/types';
import { useConfirmationRedesignEnabled } from '../../Views/confirmations/hooks/useConfirmationRedesignEnabled';
+import { QrScanRequest, QrScanRequestType } from '@metamask/eth-qr-keyring';
export enum TransactionModalType {
Transaction = 'transaction',
@@ -19,15 +19,22 @@ export interface TransactionApprovalProps {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
navigation: any;
onComplete: () => void;
- QRState?: IQRState;
+ pendingScanRequest?: QrScanRequest;
isSigningQRObject?: boolean;
}
const TransactionApprovalInternal = (props: TransactionApprovalProps) => {
const { approvalRequest } = useApprovalRequest();
const { isRedesignedEnabled } = useConfirmationRedesignEnabled();
- const { onComplete: propsOnComplete } = props;
- const isQRSigning = props.isSigningQRObject && !props.transactionType;
+ const {
+ onComplete: propsOnComplete,
+ pendingScanRequest,
+ isSigningQRObject,
+ } = props;
+ const isQRSigning =
+ isSigningQRObject &&
+ pendingScanRequest?.type === QrScanRequestType.SIGN &&
+ !props.transactionType;
const onComplete = useCallback(() => {
propsOnComplete();
@@ -64,9 +71,7 @@ const TransactionApprovalInternal = (props: TransactionApprovalProps) => {
return (
-`;
+exports[`TransactionApproval renders QR signing modal if signing QR object is exists 1`] = `""`;
exports[`TransactionApproval renders approval component if transaction type is dapp 1`] = `
{
// Queue txMetaId to listen for confirmation event
addTransactionMetaIdForListening(transactionMeta.id);
- await KeyringController.resetQRKeyringState();
-
const isLedgerAccount = isHardwareAccount(
transactionMeta.txParams.from,
[ExtendedKeyringTypes.ledger],
);
+ // As the `TransactionController:unapprovedTransactionAdded` event is emitted
+ // before the approval request is added to `ApprovalController`, we need to wait
+ // for the next tick to make sure the approval request is present when auto-approve it
+ await new Promise((resolve) => setTimeout(resolve, 0));
+
// For Ledger Accounts we handover the signing to the confirmation flow
if (isLedgerAccount) {
const deviceId = await getDeviceId();
diff --git a/app/components/UI/EvmAccountSelectorList/EvmAccountSelectorList.tsx b/app/components/UI/EvmAccountSelectorList/EvmAccountSelectorList.tsx
index 92e8ad446115..ef8ebcdb7de0 100644
--- a/app/components/UI/EvmAccountSelectorList/EvmAccountSelectorList.tsx
+++ b/app/components/UI/EvmAccountSelectorList/EvmAccountSelectorList.tsx
@@ -420,6 +420,7 @@ const EvmAccountSelectorList = ({
} = item.data;
const internalAccount = internalAccountsById[id];
+
const shortAddress = formatAddress(address, 'short');
const tagLabel = isMultichainAccountsState1Enabled
? undefined
diff --git a/app/components/UI/QRHardware/AnimatedQRScanner.tsx b/app/components/UI/QRHardware/AnimatedQRScanner.tsx
index 2b5bd581e79e..ffb83949c051 100644
--- a/app/components/UI/QRHardware/AnimatedQRScanner.tsx
+++ b/app/components/UI/QRHardware/AnimatedQRScanner.tsx
@@ -32,6 +32,7 @@ import Icon, {
IconName,
IconSize,
} from '../../../component-library/components/Icons/Icon';
+import { QrScanRequestType } from '@metamask/eth-qr-keyring';
const createStyles = (theme: Theme) =>
StyleSheet.create({
@@ -99,7 +100,7 @@ const frameImage = require('../../../images/frame.png'); // eslint-disable-line
interface AnimatedQRScannerProps {
visible: boolean;
- purpose: 'sync' | 'sign';
+ purpose: QrScanRequestType;
onScanSuccess: (ur: UR) => void;
onScanError: (error: string) => void;
hideModal: () => void;
@@ -126,7 +127,7 @@ const AnimatedQRScannerModal = (props: AnimatedQRScannerProps) => {
const { hasPermission, requestPermission } = useCameraPermission();
let expectedURTypes: string[];
- if (purpose === 'sync') {
+ if (purpose === QrScanRequestType.PAIR) {
expectedURTypes = [
SUPPORTED_UR_TYPE.CRYPTO_HDKEY,
SUPPORTED_UR_TYPE.CRYPTO_ACCOUNT,
@@ -152,7 +153,7 @@ const AnimatedQRScannerModal = (props: AnimatedQRScannerProps) => {
{strings('connect_qr_hardware.hint_text')}
{strings(
- purpose === 'sync'
+ purpose === QrScanRequestType.PAIR
? 'connect_qr_hardware.purpose_connect'
: 'connect_qr_hardware.purpose_sign',
)}
@@ -202,7 +203,7 @@ const AnimatedQRScannerModal = (props: AnimatedQRScannerProps) => {
onScanSuccess(ur);
setProgress(0);
setURDecoder(new URRegistryDecoder());
- } else if (purpose === 'sync') {
+ } else if (purpose === QrScanRequestType.PAIR) {
trackEvent(
createEventBuilder(MetaMetricsEvents.HARDWARE_WALLET_ERROR)
.addProperties({
diff --git a/app/components/UI/QRHardware/QRSigningDetails.tsx b/app/components/UI/QRHardware/QRSigningDetails.tsx
index 7582fd82bc34..2f42d740f57c 100644
--- a/app/components/UI/QRHardware/QRSigningDetails.tsx
+++ b/app/components/UI/QRHardware/QRSigningDetails.tsx
@@ -1,10 +1,4 @@
-import React, {
- Fragment,
- useCallback,
- useEffect,
- useMemo,
- useState,
-} from 'react';
+import React, { Fragment, useCallback, useEffect, useState } from 'react';
import Engine from '../../../core/Engine';
import {
StyleSheet,
@@ -23,7 +17,6 @@ import AnimatedQRScannerModal from './AnimatedQRScanner';
import { fontStyles } from '../../../styles/common';
import AccountInfoCard from '../AccountInfoCard';
import ActionView from '../ActionView';
-import { IQRState } from './types';
import { UR } from '@ngraveio/bc-ur';
import { ETHSignature } from '@keystonehq/bc-ur-registry-eth';
import { stringify as uuidStringify } from 'uuid';
@@ -34,9 +27,10 @@ import { useNavigation } from '@react-navigation/native';
import { useTheme } from '../../../util/theme';
import Device from '../../../util/device';
import { useMetrics } from '../../../components/hooks/useMetrics';
+import { QrScanRequest, QrScanRequestType } from '@metamask/eth-qr-keyring';
interface IQRSigningDetails {
- QRState: IQRState;
+ pendingScanRequest: QrScanRequest;
successCallback?: () => void;
failureCallback?: (error: string) => void;
cancelCallback?: () => void;
@@ -117,7 +111,7 @@ const createStyles = (colors: any) =>
});
const QRSigningDetails = ({
- QRState,
+ pendingScanRequest,
successCallback,
failureCallback,
cancelCallback,
@@ -133,12 +127,6 @@ const QRSigningDetails = ({
const { trackEvent, createEventBuilder } = useMetrics();
const styles = createStyles(colors);
const navigation = useNavigation();
- const KeyringController = useMemo(() => {
- // TODO: Replace "any" with type
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const { KeyringController: keyring } = Engine.context as any;
- return keyring;
- }, []);
const [scannerVisible, setScannerVisible] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const [shouldPause, setShouldPause] = useState(false);
@@ -195,11 +183,14 @@ const QRSigningDetails = ({
return;
}
e.preventDefault();
- KeyringController.cancelQRSignRequest().then(() => {
- navigation.dispatch(e.data.action);
- });
+ if (pendingScanRequest) {
+ Engine.getQrKeyringScanner().rejectPendingScan(
+ new Error('Scan canceled'),
+ );
+ }
+ navigation.dispatch(e.data.action);
});
- }, [KeyringController, hasSentOrCanceled, navigation]);
+ }, [pendingScanRequest, hasSentOrCanceled, navigation]);
const resetError = () => {
setErrorMessage('');
@@ -215,11 +206,15 @@ const QRSigningDetails = ({
};
const onCancel = useCallback(async () => {
- await KeyringController.cancelQRSignRequest();
+ if (pendingScanRequest) {
+ Engine.getQrKeyringScanner().rejectPendingScan(
+ new Error('Scan canceled'),
+ );
+ }
setSentOrCanceled(true);
hideScanner();
cancelCallback?.();
- }, [KeyringController, cancelCallback]);
+ }, [pendingScanRequest, cancelCallback]);
const onScanSuccess = useCallback(
(ur: UR) => {
@@ -228,11 +223,11 @@ const QRSigningDetails = ({
const buffer = signature.getRequestId();
if (buffer) {
const requestId = uuidStringify(buffer);
- if (QRState.sign.request?.requestId === requestId) {
- KeyringController.submitQRSignature(
- QRState.sign.request?.requestId as string,
- ur.cbor.toString('hex'),
- );
+ if (pendingScanRequest?.request?.requestId === requestId) {
+ Engine.getQrKeyringScanner().resolvePendingScan({
+ type: ur.type,
+ cbor: ur.cbor.toString('hex'),
+ });
setSentOrCanceled(true);
successCallback?.();
return;
@@ -250,8 +245,7 @@ const QRSigningDetails = ({
failureCallback?.(strings('transaction.mismatched_qr_request_id'));
},
[
- KeyringController,
- QRState.sign.request?.requestId,
+ pendingScanRequest?.request?.requestId,
failureCallback,
successCallback,
trackEvent,
@@ -287,7 +281,7 @@ const QRSigningDetails = ({
return (
- {QRState?.sign?.request && (
+ {pendingScanRequest?.request && (
void;
onCancel?: () => void;
onFailure?: (error: string) => void;
@@ -37,7 +37,7 @@ const createStyles = (colors: any) =>
const QRSigningModal = ({
isVisible,
- QRState,
+ pendingScanRequest,
onSuccess,
onCancel,
onFailure,
@@ -85,7 +85,7 @@ const QRSigningModal = ({
>
,
@@ -12,43 +13,18 @@ const withQRHardwareAwareness = (
// TODO: Replace "any" with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const QRHardwareAwareness = (props: any) => {
- const [QRState, SetQRState] = useState({
- sync: {
- reading: false,
- },
- sign: {},
- });
-
- // TODO: Replace "any" with type
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const subscribeKeyringState = (value: any) => {
- SetQRState(value);
- };
-
- useEffect(() => {
- // This ensures that a QR keyring gets created if it doesn't already exist.
- // This is intentionally not awaited (the subscription still gets setup correctly if called
- // before the keyring is created).
- // TODO: Stop automatically creating keyrings
- Engine.context.KeyringController.getOrAddQRKeyring();
- Engine.controllerMessenger.subscribe(
- 'KeyringController:qrKeyringStateChange',
- subscribeKeyringState,
- );
- return () => {
- Engine.controllerMessenger.unsubscribe(
- 'KeyringController:qrKeyringStateChange',
- subscribeKeyringState,
- );
- };
- }, []);
+ const { pendingScanRequest } = useSelector(
+ (state: RootState) => state.qrKeyringScanner,
+ );
return (
);
};
diff --git a/app/components/UI/Transactions/index.js b/app/components/UI/Transactions/index.js
index 0c8f96a29c1a..e88e5ac91a9b 100644
--- a/app/components/UI/Transactions/index.js
+++ b/app/components/UI/Transactions/index.js
@@ -556,8 +556,7 @@ class Transactions extends PureComponent {
};
signQRTransaction = async (tx) => {
- const { KeyringController, ApprovalController } = Engine.context;
- await KeyringController.resetQRKeyringState();
+ const { ApprovalController } = Engine.context;
await ApprovalController.accept(tx.id, undefined, { waitForResult: true });
};
diff --git a/app/components/UI/Transactions/index.test.tsx b/app/components/UI/Transactions/index.test.tsx
index 8c752f73a90d..603545ea53b6 100644
--- a/app/components/UI/Transactions/index.test.tsx
+++ b/app/components/UI/Transactions/index.test.tsx
@@ -57,9 +57,6 @@ jest.mock('../../../util/transaction-controller', () => ({
jest.mock('../../../core/Engine', () => ({
context: {
- KeyringController: {
- resetQRKeyringState: jest.fn(),
- },
ApprovalController: {
accept: jest.fn(),
reject: jest.fn(),
@@ -641,9 +638,6 @@ describe('Transactions', () => {
});
it('should test Engine context methods', () => {
- expect(
- Engine.context.KeyringController.resetQRKeyringState,
- ).toBeDefined();
expect(Engine.context.ApprovalController.accept).toBeDefined();
expect(
Engine.context.TransactionController.stopTransaction,
diff --git a/app/components/Views/AccountActions/AccountActions.tsx b/app/components/Views/AccountActions/AccountActions.tsx
index 0efa3d6b9f38..85e470298963 100644
--- a/app/components/Views/AccountActions/AccountActions.tsx
+++ b/app/components/Views/AccountActions/AccountActions.tsx
@@ -47,6 +47,7 @@ import { useEIP7702Networks } from '../confirmations/hooks/7702/useEIP7702Networ
import { isEvmAccountType } from '@metamask/keyring-api';
import { toHex } from '@metamask/controller-utils';
import { getMultichainBlockExplorer } from '../../../core/Multichain/networks';
+import { forgetQrDevice } from '../../../core/QrKeyring/QrKeyring';
interface AccountActionsParams {
selectedAccount: InternalAccount;
@@ -308,7 +309,7 @@ const AccountActions = () => {
);
break;
case ExtendedKeyringTypes.qr:
- await controllers.KeyringController.forgetQRDevice();
+ await forgetQrDevice();
trackEvent(
createEventBuilder(MetaMetricsEvents.HARDWARE_WALLET_FORGOTTEN)
.addProperties({
diff --git a/app/components/Views/ActivityView/index.test.tsx b/app/components/Views/ActivityView/index.test.tsx
index 280fcfc54da9..cc114ddb70fb 100644
--- a/app/components/Views/ActivityView/index.test.tsx
+++ b/app/components/Views/ActivityView/index.test.tsx
@@ -47,11 +47,6 @@ jest.mock('@react-navigation/native', () => ({
}));
jest.mock('../../../core/Engine', () => ({
- context: {
- KeyringController: {
- getOrAddQRKeyring: jest.fn(),
- },
- },
controllerMessenger: {
subscribe: jest.fn(),
unsubscribe: jest.fn(),
diff --git a/app/components/Views/Asset/index.test.js b/app/components/Views/Asset/index.test.js
index d0b17b5488bf..7d5dcbedd61d 100644
--- a/app/components/Views/Asset/index.test.js
+++ b/app/components/Views/Asset/index.test.js
@@ -235,7 +235,6 @@ jest.mock('../../../core/Engine', () => {
return {
context: {
KeyringController: {
- getOrAddQRKeyring: async () => ({ subscribe: () => ({}) }),
state: {
keyrings: [
{
diff --git a/app/components/Views/ConnectQRHardware/index.test.tsx b/app/components/Views/ConnectQRHardware/index.test.tsx
index 74c795d5fa77..5a94be652e63 100644
--- a/app/components/Views/ConnectQRHardware/index.test.tsx
+++ b/app/components/Views/ConnectQRHardware/index.test.tsx
@@ -6,12 +6,12 @@ import { fireEvent } from '@testing-library/react-native';
import { QR_CONTINUE_BUTTON } from '../../../../wdio/screen-objects/testIDs/Components/ConnectQRHardware.testIds';
import { backgroundState } from '../../../util/test/initial-root-state';
import { act } from '@testing-library/react-hooks';
-import PAGINATION_OPERATIONS from '../../../constants/pagination';
import {
ACCOUNT_SELECTOR_FORGET_BUTTON,
ACCOUNT_SELECTOR_NEXT_BUTTON,
ACCOUNT_SELECTOR_PREVIOUS_BUTTON,
} from '../../../../wdio/screen-objects/testIDs/Components/AccountSelector.testIds';
+import { QrKeyringBridge } from '@metamask/eth-qr-keyring';
import { removeAccountsFromPermissions } from '../../../core/Permissions';
jest.mock('../../../core/Permissions', () => ({
@@ -29,8 +29,8 @@ const mockedNavigate = {
const mockPage0Accounts = [
{
- address: '0x4x678901234567890123456789012345678901210',
- shortenedAddress: '0x4x678...01210',
+ address: '0x4678901234567890123456789012345678901210',
+ shortenedAddress: '0x46789...01210',
balance: '0x0',
index: 0,
},
@@ -93,6 +93,23 @@ const mockPage1Accounts = [
},
];
+const mockQrKeyring = {
+ getFirstPage: jest.fn(),
+ getNextPage: jest.fn(),
+ getPreviousPage: jest.fn(),
+ forgetDevice: jest.fn(),
+ getAccounts: jest
+ .fn()
+ .mockReturnValue([
+ '0x4678901234567890123456789012345678901210',
+ '0x49A10E12ceaacC302548d3c1C72836C9298d180e',
+ ]),
+};
+
+const mockQrKeyringBridge: QrKeyringBridge = {
+ requestScan: jest.fn(),
+};
+
jest.mock('@react-navigation/native', () => {
const actualNav = jest.requireActual('@react-navigation/native');
return {
@@ -111,30 +128,11 @@ jest.mock('../../../core/Engine', () => ({
keyrings: [],
},
getAccounts: jest.fn(),
- getOrAddQRKeyring: jest.fn(),
- withKeyring: jest
- .fn()
- .mockImplementation(
- (_selector: unknown, operation: (args: unknown) => void) =>
- operation({
- keyring: {
- cancelSync: jest.fn(),
- submitCryptoAccount: jest.fn(),
- submitCryptoHDKey: jest.fn(),
- getAccounts: jest
- .fn()
- .mockReturnValue([
- '0x4x678901234567890123456789012345678901210',
- '0xa1e359811322d97991e03f863a0c30c2cf029cd24',
- ]),
- },
- metadata: { id: '1234' },
- }),
- ),
- connectQRHardware: jest.fn(),
- forgetQRDevice: jest
- .fn()
- .mockReturnValue({ remainingAccounts: ['0xdeadbeef'] }),
+ withKeyring: (_selector: unknown, operation: (args: unknown) => void) =>
+ operation({
+ keyring: mockQrKeyring,
+ metadata: { id: '1234' },
+ }),
},
AccountTrackerController: {
syncBalanceWithAddresses: jest.fn(),
@@ -144,6 +142,7 @@ jest.mock('../../../core/Engine', () => ({
subscribe: jest.fn(),
unsubscribe: jest.fn(),
},
+ qrKeyringScanner: mockQrKeyringBridge,
setSelectedAddress: jest.fn(),
}));
const MockEngine = jest.mocked(Engine);
@@ -158,19 +157,9 @@ const mockInitialState = {
describe('ConnectQRHardware', () => {
const mockKeyringController = MockEngine.context.KeyringController;
- mockKeyringController.connectQRHardware.mockImplementation((page) => {
- switch (page) {
- case PAGINATION_OPERATIONS.GET_NEXT_PAGE:
- return Promise.resolve(mockPage1Accounts);
-
- case PAGINATION_OPERATIONS.GET_PREVIOUS_PAGE:
- return Promise.resolve(mockPage0Accounts);
-
- default:
- // return account lists in first page.
- return Promise.resolve(mockPage0Accounts);
- }
- });
+ mockQrKeyring.getFirstPage.mockResolvedValue(mockPage0Accounts);
+ mockQrKeyring.getNextPage.mockResolvedValue(mockPage1Accounts);
+ mockQrKeyring.getPreviousPage.mockResolvedValue(mockPage0Accounts);
const mockAccountTrackerController =
MockEngine.context.AccountTrackerController;
@@ -219,10 +208,7 @@ describe('ConnectQRHardware', () => {
fireEvent.press(button);
});
- expect(mockKeyringController.connectQRHardware).toHaveBeenCalledTimes(1);
- expect(mockKeyringController.connectQRHardware).toHaveBeenCalledWith(
- PAGINATION_OPERATIONS.GET_FIRST_PAGE,
- );
+ expect(mockQrKeyring.getFirstPage).toHaveBeenCalledTimes(1);
mockPage0Accounts.forEach((account) => {
expect(getByText(account.shortenedAddress)).toBeDefined();
@@ -250,10 +236,7 @@ describe('ConnectQRHardware', () => {
fireEvent.press(nextButton);
});
- expect(mockKeyringController.connectQRHardware).toHaveBeenCalledTimes(2);
- expect(mockKeyringController.connectQRHardware).toHaveBeenCalledWith(
- PAGINATION_OPERATIONS.GET_NEXT_PAGE,
- );
+ expect(mockQrKeyring.getNextPage).toHaveBeenCalledTimes(1);
mockPage1Accounts.forEach((account) => {
expect(getByText(account.shortenedAddress)).toBeDefined();
@@ -287,10 +270,7 @@ describe('ConnectQRHardware', () => {
fireEvent.press(prevButton);
});
- expect(mockKeyringController.connectQRHardware).toHaveBeenCalledTimes(3);
- expect(mockKeyringController.connectQRHardware).toHaveBeenCalledWith(
- PAGINATION_OPERATIONS.GET_PREVIOUS_PAGE,
- );
+ expect(mockQrKeyring.getPreviousPage).toHaveBeenCalledTimes(1);
mockPage0Accounts.forEach((account) => {
expect(getByText(account.shortenedAddress)).toBeDefined();
@@ -299,6 +279,7 @@ describe('ConnectQRHardware', () => {
it('removes any hardware wallet accounts from existing permissions', async () => {
mockKeyringController.getAccounts.mockResolvedValue([]);
+ const withKeyringSpy = jest.spyOn(mockKeyringController, 'withKeyring');
const { getByTestId } = renderWithProvider(
,
@@ -318,11 +299,11 @@ describe('ConnectQRHardware', () => {
fireEvent.press(forgetButton);
});
- expect(mockKeyringController.withKeyring).toHaveBeenCalled();
+ expect(withKeyringSpy).toHaveBeenCalled();
expect(MockRemoveAccountsFromPermissions).toHaveBeenCalledWith([
- '0x4x678901234567890123456789012345678901210',
- '0xa1e359811322d97991e03f863a0c30c2cf029cd24',
+ '0x4678901234567890123456789012345678901210',
+ '0x49A10E12ceaacC302548d3c1C72836C9298d180e',
]);
- expect(mockKeyringController.forgetQRDevice).toHaveBeenCalled();
+ expect(mockQrKeyring.forgetDevice).toHaveBeenCalled();
});
});
diff --git a/app/components/Views/ConnectQRHardware/index.tsx b/app/components/Views/ConnectQRHardware/index.tsx
index 1837f8484f6f..3f451ee67bc6 100644
--- a/app/components/Views/ConnectQRHardware/index.tsx
+++ b/app/components/Views/ConnectQRHardware/index.tsx
@@ -3,7 +3,6 @@ import React, {
useCallback,
useEffect,
useMemo,
- useRef,
useState,
} from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
@@ -24,18 +23,17 @@ import { MetaMetricsEvents } from '../../../core/Analytics';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import { useTheme } from '../../../util/theme';
-import { SUPPORTED_UR_TYPE } from '../../../constants/qr';
import { fontStyles } from '../../../styles/common';
import Logger from '../../../util/Logger';
import { removeAccountsFromPermissions } from '../../../core/Permissions';
import { useMetrics } from '../../../components/hooks/useMetrics';
-import type { MetaMaskKeyring as QRKeyring } from '@keystonehq/metamask-airgapped-keyring';
-import { KeyringTypes } from '@metamask/keyring-controller';
import ExtendedKeyringTypes, {
HardwareDeviceTypes,
} from '../../../constants/keyringTypes';
import { ThemeColors } from '@metamask/design-tokens';
-import PAGINATION_OPERATIONS from '../../../constants/pagination';
+import { QrScanRequestType } from '@metamask/eth-qr-keyring';
+import { withQrKeyring } from '../../../core/QrKeyring/QrKeyring';
+import { getChecksumAddress } from '@metamask/utils';
interface IConnectQRHardwareProps {
// TODO: Replace "any" with type
@@ -85,75 +83,16 @@ const createStyles = (colors: ThemeColors, insets: EdgeInsets) =>
},
});
-/**
- * Initiate a QR hardware wallet connection
- *
- * This returns a tuple containing a set of QR interactions, followed by a Promise representing
- * the QR connection process overall.
- *
- * The QR interactions are returned here to ensure that we get the correct references for the
- * specific keyring instance we initiated the scan with. This is to ensure that we always resolve
- * the `connectQRHardware` Promise. There are equivalent methods on the `KeyringController` class
- * that we could use (e.g. `KeyringController.cancelQRSynchronization` would call
- * `keyring.cancelSync` for us), but these methods are unsafe to use because they might end up
- * calling the method on the wrong keyring instance (e.g. if the user had locked and unlocked the
- * app since initiating the scan). They will be deprecated in a future `KeyringController` version.
- *
- * TODO: Refactor the QR Keyring to separate interaction methods from keyring operations, so that
- * interactions are not affected by our keyring operation locks and by our lock/unlock operations.
- *
- * @param page - The page of accounts to request (either 0, 1, or -1), for "first page",
- * "next page", and "previous page" respectively.
- * @returns A tuple of QR keyring interactions, and a Promise representing the QR hardware wallet
- * connection process.
- */
-async function initiateQRHardwareConnection(
- page: 0 | 1 | -1,
-): Promise<
- [
- Pick,
- ReturnType<
- (typeof Engine)['context']['KeyringController']['connectQRHardware']
- >,
- ]
-> {
- const KeyringController = Engine.context.KeyringController;
-
- const qrInteractions = await KeyringController.withKeyring(
- { type: KeyringTypes.qr },
- // @ts-expect-error The QR Keyring type is not compatible with our keyring type yet
- async ({ keyring }: QRKeyring) => ({
- cancelSync: keyring.cancelSync.bind(keyring),
- submitCryptoAccount: keyring.submitCryptoAccount.bind(keyring),
- submitCryptoHDKey: keyring.submitCryptoHDKey.bind(keyring),
- }),
- { createIfMissing: true },
- );
-
- const connectQRHardwarePromise = KeyringController.connectQRHardware(page);
-
- return [qrInteractions, connectQRHardwarePromise];
-}
-
const ConnectQRHardware = ({ navigation }: IConnectQRHardwareProps) => {
const { colors } = useTheme();
const { trackEvent, createEventBuilder } = useMetrics();
const insets = useSafeAreaInsets();
const styles = createStyles(colors, insets);
- const KeyringController = useMemo(() => {
- // TODO: Replace "any" with type
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const { KeyringController: keyring } = Engine.context as any;
- return keyring;
- }, []);
+ const [isScanning, setIsScanning] = useState(false);
+
+ const KeyringController = useMemo(() => Engine.context.KeyringController, []);
- const [QRState, setQRState] = useState({
- sync: {
- reading: false,
- },
- });
- const [scannerVisible, setScannerVisible] = useState(false);
const [blockingModalVisible, setBlockingModalVisible] = useState(false);
const [accounts, setAccounts] = useState<
{ address: string; index: number; balance: string }[]
@@ -165,66 +104,13 @@ const ConnectQRHardware = ({ navigation }: IConnectQRHardwareProps) => {
const [existingAccounts, setExistingAccounts] = useState([]);
- const qrInteractionsRef =
- useRef<
- Pick<
- QRKeyring,
- 'cancelSync' | 'submitCryptoAccount' | 'submitCryptoHDKey'
- >
- >();
-
- const showScanner = useCallback(() => {
- setScannerVisible(true);
- }, []);
-
- const hideScanner = useCallback(() => {
- setScannerVisible(false);
- }, []);
-
- const hideModal = useCallback(() => {
- qrInteractionsRef.current?.cancelSync();
- hideScanner();
- }, [hideScanner]);
-
useEffect(() => {
KeyringController.getAccounts().then((value: string[]) => {
setExistingAccounts(value);
});
}, [KeyringController]);
- // TODO: Replace "any" with type
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const subscribeKeyringState = useCallback((storeValue: any) => {
- setQRState(storeValue);
- }, []);
-
- useEffect(() => {
- // This ensures that a QR keyring gets created if it doesn't already exist.
- // This is intentionally not awaited (the subscription still gets setup correctly if called
- // before the keyring is created).
- // TODO: Stop automatically creating keyrings
- Engine.context.KeyringController.getOrAddQRKeyring();
- Engine.controllerMessenger.subscribe(
- 'KeyringController:qrKeyringStateChange',
- subscribeKeyringState,
- );
- return () => {
- Engine.controllerMessenger.unsubscribe(
- 'KeyringController:qrKeyringStateChange',
- subscribeKeyringState,
- );
- };
- }, [KeyringController, subscribeKeyringState]);
-
- useEffect(() => {
- if (QRState.sync.reading) {
- showScanner();
- } else {
- hideScanner();
- }
- }, [QRState.sync, hideScanner, showScanner]);
-
- const onConnectHardware = useCallback(async () => {
+ const onConnectHardware = async () => {
trackEvent(
createEventBuilder(MetaMetricsEvents.CONTINUE_QR_HARDWARE_WALLET)
.addProperties({
@@ -233,19 +119,23 @@ const ConnectQRHardware = ({ navigation }: IConnectQRHardwareProps) => {
.build(),
);
resetError();
- const [qrInteractions, connectQRHardwarePromise] =
- await initiateQRHardwareConnection(PAGINATION_OPERATIONS.GET_FIRST_PAGE);
-
- qrInteractionsRef.current = qrInteractions;
- const firstPageAccounts = await connectQRHardwarePromise;
- delete qrInteractionsRef.current;
-
- setAccounts(firstPageAccounts);
- }, [resetError, trackEvent, createEventBuilder]);
+ try {
+ setIsScanning(true);
+ const firstAccountsPage = await withQrKeyring(({ keyring }) =>
+ keyring.getFirstPage(),
+ );
+ setAccounts(
+ // TODO: Add `balance` to the QR Keyring accounts or remove it from the expected type
+ firstAccountsPage.map((account) => ({ ...account, balance: '0x0' })),
+ );
+ } finally {
+ setIsScanning(false);
+ }
+ };
const onScanSuccess = useCallback(
- (ur: UR) => {
- hideScanner();
+ async (ur: UR) => {
+ setIsScanning(false);
trackEvent(
createEventBuilder(MetaMetricsEvents.CONNECT_HARDWARE_WALLET_SUCCESS)
.addProperties({
@@ -253,56 +143,47 @@ const ConnectQRHardware = ({ navigation }: IConnectQRHardwareProps) => {
})
.build(),
);
- if (!qrInteractionsRef.current) {
- const errorMessage = 'Missing QR keyring interactions';
- setErrorMsg(errorMessage);
- throw new Error(errorMessage);
- }
- if (ur.type === SUPPORTED_UR_TYPE.CRYPTO_HDKEY) {
- qrInteractionsRef.current.submitCryptoHDKey(ur.cbor.toString('hex'));
- } else {
- qrInteractionsRef.current.submitCryptoAccount(ur.cbor.toString('hex'));
- }
+ Engine.getQrKeyringScanner().resolvePendingScan({
+ type: ur.type,
+ cbor: ur.cbor.toString('hex'),
+ });
resetError();
},
- [hideScanner, resetError, trackEvent, createEventBuilder],
+ [resetError, trackEvent, createEventBuilder],
);
- const onScanError = useCallback(
- async (error: string) => {
- hideScanner();
- setErrorMsg(error);
- if (qrInteractionsRef.current) {
- qrInteractionsRef.current.cancelSync();
- }
- },
- [hideScanner],
- );
+ const onScanError = useCallback(async (error: string) => {
+ setErrorMsg(error);
+ Engine.getQrKeyringScanner().rejectPendingScan(new Error(error));
+ }, []);
+
+ const cancelScan = useCallback(() => {
+ Engine.getQrKeyringScanner().rejectPendingScan(new Error('Scan cancelled'));
+ }, []);
const nextPage = useCallback(async () => {
resetError();
- const [qrInteractions, connectQRHardwarePromise] =
- await initiateQRHardwareConnection(PAGINATION_OPERATIONS.GET_NEXT_PAGE);
-
- qrInteractionsRef.current = qrInteractions;
- const nextPageAccounts = await connectQRHardwarePromise;
- delete qrInteractionsRef.current;
-
- setAccounts(nextPageAccounts);
+ const nextAccountsPage = await withQrKeyring(async ({ keyring }) =>
+ keyring.getNextPage(),
+ );
+ setAccounts(
+ // TODO: Add `balance` to the QR Keyring accounts or remove it from the expected type
+ nextAccountsPage.map((account) => ({ ...account, balance: '0x0' })),
+ );
}, [resetError]);
const prevPage = useCallback(async () => {
resetError();
- const [qrInteractions, connectQRHardwarePromise] =
- await initiateQRHardwareConnection(
- PAGINATION_OPERATIONS.GET_PREVIOUS_PAGE,
- );
-
- qrInteractionsRef.current = qrInteractions;
- const previousPageAccounts = await connectQRHardwarePromise;
- delete qrInteractionsRef.current;
-
- setAccounts(previousPageAccounts);
+ const previousAccountsPage = await withQrKeyring(async ({ keyring }) =>
+ keyring.getPreviousPage(),
+ );
+ setAccounts(
+ // TODO: Add `balance` to the QR Keyring accounts or remove it from the expected type
+ previousAccountsPage.map((account) => ({
+ ...account,
+ balance: '0x0',
+ })),
+ );
}, [resetError]);
const onCheck = useCallback(() => {
@@ -314,35 +195,39 @@ const ConnectQRHardware = ({ navigation }: IConnectQRHardwareProps) => {
resetError();
setBlockingModalVisible(true);
try {
- for (const index of accountIndexs) {
- await KeyringController.unlockQRHardwareWalletAccount(index);
- }
+ await withQrKeyring(async ({ keyring }) => {
+ for (const index of accountIndexs) {
+ keyring.setAccountToUnlock(index);
+ await keyring.addAccounts(1);
+ }
+ });
} catch (err) {
Logger.log('Error: Connecting QR hardware wallet', err);
}
setBlockingModalVisible(false);
navigation.pop(2);
},
- [KeyringController, navigation, resetError],
+ [navigation, resetError],
);
const onForget = useCallback(async () => {
resetError();
- // Permissions need to be updated before the hardware wallet is forgotten.
- // This is because `removeAccountsFromPermissions` relies on the account
- // existing in AccountsController in order to resolve a hex address
- // back into CAIP Account Id. Hex addresses are used in
- // `removeAccountsFromPermissions` because too many places in the UI still
- // operate on hex addresses rather than CAIP Account Id.
- await Engine.context.KeyringController.withKeyring(
- { type: ExtendedKeyringTypes.qr },
- async ({ keyring }) => {
- const keyringAccounts = await keyring.getAccounts();
- removeAccountsFromPermissions(keyringAccounts);
- },
- );
- const { remainingAccounts } = await KeyringController.forgetQRDevice();
- Engine.setSelectedAddress(remainingAccounts.at(-1));
+ const remainingAccounts = KeyringController.state.keyrings
+ .filter((keyring) => keyring.type !== ExtendedKeyringTypes.qr)
+ .flatMap((keyring) => keyring.accounts);
+ Engine.setSelectedAddress(remainingAccounts[remainingAccounts.length - 1]);
+ await withQrKeyring(async ({ keyring }) => {
+ const existingQrAccounts = await keyring.getAccounts();
+ // Permissions need to be updated before the hardware wallet is forgotten.
+ // This is because `removeAccountsFromPermissions` relies on the account
+ // existing in AccountsController in order to resolve a hex address
+ // back into CAIP Account Id. Hex addresses are used in
+ // `removeAccountsFromPermissions` because too many places in the UI still
+ // operate on hex addresses rather than CAIP Account Id.
+ removeAccountsFromPermissions(existingQrAccounts.map(getChecksumAddress));
+ await keyring.forgetDevice();
+ return existingQrAccounts;
+ });
navigation.pop(2);
}, [KeyringController, navigation, resetError]);
@@ -392,11 +277,11 @@ const ConnectQRHardware = ({ navigation }: IConnectQRHardwareProps) => {
)}
{strings('common.please_wait')}
diff --git a/app/components/Views/TransactionsView/index.test.tsx b/app/components/Views/TransactionsView/index.test.tsx
index 9d880aeefb32..68a6e57dda64 100644
--- a/app/components/Views/TransactionsView/index.test.tsx
+++ b/app/components/Views/TransactionsView/index.test.tsx
@@ -174,8 +174,6 @@ jest.mock('../../UI/Transactions', () => jest.fn());
jest.mock('../../../core/Engine', () => ({
context: {
KeyringController: {
- getOrAddQRKeyring: jest.fn(),
- cancelQRSignRequest: jest.fn().mockResolvedValue(undefined),
state: {
keyrings: [],
},
diff --git a/app/components/Views/UnifiedTransactionsView/useUnifiedTxActions.test.ts b/app/components/Views/UnifiedTransactionsView/useUnifiedTxActions.test.ts
index 31824883c160..710967076b1c 100644
--- a/app/components/Views/UnifiedTransactionsView/useUnifiedTxActions.test.ts
+++ b/app/components/Views/UnifiedTransactionsView/useUnifiedTxActions.test.ts
@@ -74,9 +74,6 @@ jest.mock('../../../core/Engine', () => ({
accept: jest.fn(),
reject: jest.fn(),
},
- KeyringController: {
- resetQRKeyringState: jest.fn(),
- },
},
}));
@@ -93,7 +90,6 @@ describe('useUnifiedTxActions', () => {
interface EngineContextMock {
TransactionController: { stopTransaction: jest.Mock };
ApprovalController: { accept: jest.Mock; reject: jest.Mock };
- KeyringController: { resetQRKeyringState: jest.Mock };
}
const engineContext = Engine.context as unknown as EngineContextMock;
@@ -421,9 +417,6 @@ describe('useUnifiedTxActions', () => {
await result.current.signQRTransaction(tx);
});
- expect(
- engineContext.KeyringController.resetQRKeyringState,
- ).toHaveBeenCalled();
expect(engineContext.ApprovalController.accept).toHaveBeenCalledWith(
'12',
undefined,
diff --git a/app/components/Views/UnifiedTransactionsView/useUnifiedTxActions.ts b/app/components/Views/UnifiedTransactionsView/useUnifiedTxActions.ts
index fdd561c16c6b..269ea93cf864 100644
--- a/app/components/Views/UnifiedTransactionsView/useUnifiedTxActions.ts
+++ b/app/components/Views/UnifiedTransactionsView/useUnifiedTxActions.ts
@@ -297,8 +297,7 @@ export function useUnifiedTxActions() {
};
const signQRTransaction = async (tx: TransactionMeta) => {
- const { KeyringController, ApprovalController } = Engine.context;
- await KeyringController.resetQRKeyringState();
+ const { ApprovalController } = Engine.context;
await ApprovalController.accept(tx.id, undefined, { waitForResult: true });
};
diff --git a/app/components/Views/confirmations/components/confirm/confirm-component.test.tsx b/app/components/Views/confirmations/components/confirm/confirm-component.test.tsx
index 07b0c6e5b580..60be72a70415 100644
--- a/app/components/Views/confirmations/components/confirm/confirm-component.test.tsx
+++ b/app/components/Views/confirmations/components/confirm/confirm-component.test.tsx
@@ -109,7 +109,6 @@ jest.mock('../../../../../core/Engine', () => ({
},
],
},
- getOrAddQRKeyring: jest.fn(),
},
NetworkController: {
getNetworkConfigurationByNetworkClientId: jest.fn(),
diff --git a/app/components/Views/confirmations/components/footer/footer.test.tsx b/app/components/Views/confirmations/components/footer/footer.test.tsx
index 3513960e7736..9e01d9302a8f 100644
--- a/app/components/Views/confirmations/components/footer/footer.test.tsx
+++ b/app/components/Views/confirmations/components/footer/footer.test.tsx
@@ -134,7 +134,7 @@ describe('Footer', () => {
it('renders confirm button text "Get Signature" if QR signing is in progress', () => {
jest.spyOn(QRHardwareHook, 'useQRHardwareContext').mockReturnValue({
- isQRSigningInProgress: true,
+ isSigningQRObject: true,
} as QRHardwareHook.QRHardwareContextType);
const { getByText } = renderWithProvider(, {
state: personalSignatureConfirmationState,
diff --git a/app/components/Views/confirmations/components/footer/footer.tsx b/app/components/Views/confirmations/components/footer/footer.tsx
index f3f433755be3..f4ccad68f8c1 100644
--- a/app/components/Views/confirmations/components/footer/footer.tsx
+++ b/app/components/Views/confirmations/components/footer/footer.tsx
@@ -44,11 +44,8 @@ export const Footer = () => {
hasDangerAlerts,
hasUnconfirmedDangerAlerts,
} = useAlerts();
-
- const { isQRSigningInProgress, needsCameraPermission } =
- useQRHardwareContext();
-
const { onConfirm, onReject } = useConfirmActions();
+ const { isSigningQRObject, needsCameraPermission } = useQRHardwareContext();
const { securityAlertResponse } = useSecurityAlertResponse();
const confirmDisabled = needsCameraPermission;
const transactionMetadata = useTransactionMetadataRequest();
@@ -114,7 +111,7 @@ export const Footer = () => {
});
const confirmButtonLabel = () => {
- if (isQRSigningInProgress) {
+ if (isSigningQRObject) {
return strings('confirm.qr_get_sign');
}
diff --git a/app/components/Views/confirmations/components/info-root/info-root.test.tsx b/app/components/Views/confirmations/components/info-root/info-root.test.tsx
index a85229555e20..82979c8d682e 100644
--- a/app/components/Views/confirmations/components/info-root/info-root.test.tsx
+++ b/app/components/Views/confirmations/components/info-root/info-root.test.tsx
@@ -70,7 +70,6 @@ jest.mock('../../../../../core/Engine', () => ({
state: {
keyrings: [],
},
- getOrAddQRKeyring: jest.fn(),
},
GasFeeController: {
startPolling: jest.fn(),
diff --git a/app/components/Views/confirmations/components/info/personal-sign/personal-sign.test.tsx b/app/components/Views/confirmations/components/info/personal-sign/personal-sign.test.tsx
index 97a20ae8cb33..72583a9c8629 100644
--- a/app/components/Views/confirmations/components/info/personal-sign/personal-sign.test.tsx
+++ b/app/components/Views/confirmations/components/info/personal-sign/personal-sign.test.tsx
@@ -15,7 +15,6 @@ jest.mock('../../../../../../core/Engine', () => ({
state: {
keyrings: [],
},
- getOrAddQRKeyring: jest.fn(),
},
AccountsController: {
state: {
diff --git a/app/components/Views/confirmations/components/info/switch-account-type/switch-account-type.test.tsx b/app/components/Views/confirmations/components/info/switch-account-type/switch-account-type.test.tsx
index 378a2df94333..754f4d629053 100644
--- a/app/components/Views/confirmations/components/info/switch-account-type/switch-account-type.test.tsx
+++ b/app/components/Views/confirmations/components/info/switch-account-type/switch-account-type.test.tsx
@@ -25,7 +25,6 @@ jest.mock('../../../../../../core/Engine', () => ({
state: {
keyrings: [],
},
- getOrAddQRKeyring: jest.fn(),
},
GasFeeController: {
startPolling: jest.fn(),
diff --git a/app/components/Views/confirmations/components/info/typed-sign-v1/typed-sign-v1.test.tsx b/app/components/Views/confirmations/components/info/typed-sign-v1/typed-sign-v1.test.tsx
index 2b72c7a92e87..0df77395616c 100644
--- a/app/components/Views/confirmations/components/info/typed-sign-v1/typed-sign-v1.test.tsx
+++ b/app/components/Views/confirmations/components/info/typed-sign-v1/typed-sign-v1.test.tsx
@@ -11,7 +11,6 @@ jest.mock('../../../../../../core/Engine', () => ({
state: {
keyrings: [],
},
- getOrAddQRKeyring: jest.fn(),
},
AccountsController: {
state: {
diff --git a/app/components/Views/confirmations/components/info/typed-sign-v3v4/typed-sign-v3v4.test.tsx b/app/components/Views/confirmations/components/info/typed-sign-v3v4/typed-sign-v3v4.test.tsx
index ac1380255e75..cf0c530ffb31 100644
--- a/app/components/Views/confirmations/components/info/typed-sign-v3v4/typed-sign-v3v4.test.tsx
+++ b/app/components/Views/confirmations/components/info/typed-sign-v3v4/typed-sign-v3v4.test.tsx
@@ -16,7 +16,6 @@ jest.mock('../../../../../../core/Engine', () => ({
state: {
keyrings: [],
},
- getOrAddQRKeyring: jest.fn(),
},
NetworkController: {
findNetworkClientIdByChainId: () => 123,
diff --git a/app/components/Views/confirmations/components/qr-info/qr-info.test.tsx b/app/components/Views/confirmations/components/qr-info/qr-info.test.tsx
index 48c396329233..3ff910aa33ea 100644
--- a/app/components/Views/confirmations/components/qr-info/qr-info.test.tsx
+++ b/app/components/Views/confirmations/components/qr-info/qr-info.test.tsx
@@ -8,22 +8,22 @@ import { typedSignV3ConfirmationState } from '../../../../../util/test/confirm-d
// eslint-disable-next-line import/no-namespace
import * as QRHardwareHook from '../../context/qr-hardware-context/qr-hardware-context';
import QRInfo from './qr-info';
-import Engine from '../../../../../core/Engine';
+import { QrScanRequest, QrScanRequestType } from '@metamask/eth-qr-keyring';
+
+const mockQrKeyringBridge = {
+ requestScan: jest.fn(),
+ resolvePendingScan: jest.fn(),
+ rejectPendingScan: jest.fn(),
+};
jest.mock('../../../../../core/Engine', () => ({
- context: {
- KeyringController: {
- submitQRSignature: jest.fn(),
- },
- },
+ getQrKeyringScanner: jest.fn(() => mockQrKeyringBridge),
}));
jest.mock('uuid', () => ({
stringify: jest.fn().mockReturnValue('c95ecc76-d6e9-4a0a-afa3-31429bc80566'),
}));
-const MockEngine = jest.mocked(Engine);
-
const MockView = View;
const MockText = Text;
const MockButton = Button;
@@ -50,23 +50,21 @@ jest.mock('../../../../UI/QRHardware/AnimatedQRScanner', () => ({
),
}));
-const mockQRState = {
- sign: {
- request: {
- requestId: 'c95ecc76-d6e9-4a0a-afa3-31429bc80566',
- payload: {
- type: 'eth-sign-request',
- cbor: 'a501d82550c95ecc76d6e94a0aafa331429bc8056602581f4578616d706c652060706572736f6e616c5f7369676e60206d657373616765030305d90130a2018a182cf5183cf500f500f400f4021a73eadf6d0654126f6e36f2fbc44016d788c91b82ab4c50f74e17',
- },
- title: 'Scan with your Keystone',
- description:
- 'After your Keystone has signed this message, click on "Scan Keystone" to receive the signature',
+const mockPendingScanRequest: QrScanRequest = {
+ request: {
+ requestId: 'c95ecc76-d6e9-4a0a-afa3-31429bc80566',
+ payload: {
+ type: 'eth-sign-request',
+ cbor: 'a501d82550c95ecc76d6e94a0aafa331429bc8056602581f4578616d706c652060706572736f6e616c5f7369676e60206d657373616765030305d90130a2018a182cf5183cf500f500f400f4021a73eadf6d0654126f6e36f2fbc44016d788c91b82ab4c50f74e17',
},
+ requestTitle: 'Scan with your Keystone',
+ requestDescription:
+ 'After your Keystone has signed this message, click on "Scan Keystone" to receive the signature',
},
+ type: QrScanRequestType.SIGN,
};
describe('QRInfo', () => {
- const mockKeyringController = MockEngine.context.KeyringController;
const mockSetRequestCompleted = jest.fn();
beforeEach(() => {
@@ -75,7 +73,8 @@ describe('QRInfo', () => {
const createQRHardwareHookSpy = (mockedValues = {}) => {
jest.spyOn(QRHardwareHook, 'useQRHardwareContext').mockReturnValue({
- QRState: mockQRState,
+ pendingScanRequest: mockPendingScanRequest,
+ isSigningQRObject: true,
setRequestCompleted: mockSetRequestCompleted,
...mockedValues,
} as unknown as QRHardwareHook.QRHardwareContextType);
@@ -137,7 +136,7 @@ describe('QRInfo', () => {
it('submits request when onScanSuccess is called by scanner', () => {
jest.spyOn(ETHSignature, 'fromCBOR').mockReturnValue({
- getRequestId: () => mockQRState.sign.request?.requestId,
+ getRequestId: () => mockPendingScanRequest.request?.requestId,
} as unknown as ETHSignature);
const mockSetScannerVisible = jest.fn();
createQRHardwareHookSpy({
@@ -148,7 +147,7 @@ describe('QRInfo', () => {
state: typedSignV3ConfirmationState,
});
fireEvent.press(getByText('onScanSuccess'));
- expect(mockKeyringController.submitQRSignature).toHaveBeenCalledTimes(1);
+ expect(mockQrKeyringBridge.resolvePendingScan).toHaveBeenCalledTimes(1);
expect(mockSetRequestCompleted).toHaveBeenCalledTimes(1);
expect(mockSetScannerVisible).toHaveBeenCalledTimes(1);
expect(mockSetScannerVisible).toHaveBeenCalledWith(false);
diff --git a/app/components/Views/confirmations/components/qr-info/qr-info.tsx b/app/components/Views/confirmations/components/qr-info/qr-info.tsx
index 488aefe3012e..b3302478532c 100644
--- a/app/components/Views/confirmations/components/qr-info/qr-info.tsx
+++ b/app/components/Views/confirmations/components/qr-info/qr-info.tsx
@@ -15,10 +15,11 @@ import { useStyles } from '../../../../hooks/useStyles';
import { useQRHardwareContext } from '../../context/qr-hardware-context';
import { ConfirmationInfoComponentIDs } from '../../constants/info-ids';
import styleSheet from './qr-info.styles';
+import { QrScanRequestType } from '@metamask/eth-qr-keyring';
const QRInfo = () => {
const {
- QRState,
+ pendingScanRequest,
cameraError,
scannerVisible,
setRequestCompleted,
@@ -29,8 +30,6 @@ const QRInfo = () => {
const [errorMessage, setErrorMessage] = useState();
const [shouldPause, setShouldPause] = useState(false);
- const KeyringController = Engine.context.KeyringController;
-
useEffect(() => {
if (scannerVisible) {
setErrorMessage(undefined);
@@ -44,11 +43,11 @@ const QRInfo = () => {
const buffer = signature.getRequestId();
if (buffer) {
const requestId = uuidStringify(buffer);
- if (QRState?.sign?.request?.requestId === requestId) {
- KeyringController.submitQRSignature(
- QRState.sign.request?.requestId as string,
- ur.cbor.toString('hex'),
- );
+ if (pendingScanRequest?.request?.requestId === requestId) {
+ Engine.getQrKeyringScanner().resolvePendingScan({
+ type: ur.type,
+ cbor: ur.cbor.toString('hex'),
+ });
setRequestCompleted();
return;
}
@@ -64,11 +63,10 @@ const QRInfo = () => {
setErrorMessage(strings('transaction.mismatched_qr_request_id'));
},
[
- QRState?.sign?.request?.requestId,
+ pendingScanRequest?.request?.requestId,
createEventBuilder,
setRequestCompleted,
setScannerVisible,
- KeyringController,
trackEvent,
],
);
@@ -83,45 +81,46 @@ const QRInfo = () => {
return (
- {QRState?.sign?.request && (
-
-
- {/* todo: to be replaced by alert system */}
- {errorMessage && (
- setErrorMessage(undefined)}
- style={styles.alert}
- >
- {errorMessage}
-
- )}
- {cameraError && (
-
- {cameraError}
-
- )}
-
-
- {strings('confirm.qr_scan_text')}
-
-
-
-
-
- )}
+ {pendingScanRequest?.type === QrScanRequestType.SIGN &&
+ pendingScanRequest?.request && (
+
+
+ {/* todo: to be replaced by alert system */}
+ {errorMessage && (
+ setErrorMessage(undefined)}
+ style={styles.alert}
+ >
+ {errorMessage}
+
+ )}
+ {cameraError && (
+
+ {cameraError}
+
+ )}
+
+
+ {strings('confirm.qr_scan_text')}
+
+
+
+
+
+ )}
setScannerVisible(false)}
diff --git a/app/components/Views/confirmations/components/rows/switch-account-type-info-row/switch-account-type-info-row.test.tsx b/app/components/Views/confirmations/components/rows/switch-account-type-info-row/switch-account-type-info-row.test.tsx
index f12a18431065..caf6a9082581 100644
--- a/app/components/Views/confirmations/components/rows/switch-account-type-info-row/switch-account-type-info-row.test.tsx
+++ b/app/components/Views/confirmations/components/rows/switch-account-type-info-row/switch-account-type-info-row.test.tsx
@@ -20,7 +20,6 @@ jest.mock('../../../../../../core/Engine', () => ({
state: {
keyrings: [],
},
- getOrAddQRKeyring: jest.fn(),
},
GasFeeController: {
startPolling: jest.fn(),
diff --git a/app/components/Views/confirmations/context/qr-hardware-context/qr-hardware-context.test.tsx b/app/components/Views/confirmations/context/qr-hardware-context/qr-hardware-context.test.tsx
index 06d9d2e04ae7..a51eb4cb3781 100644
--- a/app/components/Views/confirmations/context/qr-hardware-context/qr-hardware-context.test.tsx
+++ b/app/components/Views/confirmations/context/qr-hardware-context/qr-hardware-context.test.tsx
@@ -2,10 +2,8 @@ import React from 'react';
import { userEvent } from '@testing-library/react-native';
import { ConfirmationFooterSelectorIDs } from '../../../../../../e2e/selectors/Confirmation/ConfirmationView.selectors';
-import Engine from '../../../../../core/Engine';
import renderWithProvider from '../../../../../util/test/renderWithProvider';
import { personalSignatureConfirmationState } from '../../../../../util/test/confirm-data-helpers';
-import { IQRState } from '../../../../UI/QRHardware/types';
import { Footer } from '../../components/footer';
import QRInfo from '../../components/qr-info';
// eslint-disable-next-line import/no-namespace
@@ -16,6 +14,13 @@ import {
QRHardwareContextProvider,
useQRHardwareContext,
} from './qr-hardware-context';
+import { QrScanRequest, QrScanRequestType } from '@metamask/eth-qr-keyring';
+
+const mockQrScanner = {
+ requestScan: jest.fn(),
+ resolvePendingScan: jest.fn(),
+ rejectPendingScan: jest.fn(),
+};
jest.mock('../../hooks/transactions/useTransactionConfirm', () => ({
useTransactionConfirm: jest.fn(() => ({
@@ -24,17 +29,12 @@ jest.mock('../../hooks/transactions/useTransactionConfirm', () => ({
}));
jest.mock('../../../../../core/Engine', () => ({
- context: {
- KeyringController: {
- getOrAddQRKeyring: jest.fn(),
- cancelQRSignRequest: jest.fn().mockResolvedValue(undefined),
- },
- },
controllerMessenger: {
subscribe: jest.fn(),
unsubscribe: jest.fn(),
},
rejectPendingApproval: jest.fn(),
+ getQrKeyringScanner: jest.fn(() => mockQrScanner),
}));
jest.mock('../../hooks/gas/useGasFeeToken');
@@ -50,20 +50,18 @@ jest.mock('@react-navigation/native', () => ({
}),
}));
-const mockQRState = {
- sign: {
- request: {
- requestId: 'c95ecc76-d6e9-4a0a-afa3-31429bc80566',
- payload: {
- type: 'eth-sign-request',
- cbor: 'a501d82550c95ecc76d6e94a0aafa331429bc8056602581f4578616d706c652060706572736f6e616c5f7369676e60206d657373616765030305d90130a2018a182cf5183cf500f500f400f4021a73eadf6d0654126f6e36f2fbc44016d788c91b82ab4c50f74e17',
- },
- title: 'Scan with your Keystone',
- description:
- 'After your Keystone has signed this message, click on "Scan Keystone" to receive the signature',
+const mockPendingScanRequest: QrScanRequest = {
+ request: {
+ requestId: 'c95ecc76-d6e9-4a0a-afa3-31429bc80566',
+ payload: {
+ type: 'eth-sign-request',
+ cbor: 'a501d82550c95ecc76d6e94a0aafa331429bc8056602581f4578616d706c652060706572736f6e616c5f7369676e60206d657373616765030305d90130a2018a182cf5183cf500f500f400f4021a73eadf6d0654126f6e36f2fbc44016d788c91b82ab4c50f74e17',
},
+ requestTitle: 'Scan with your Keystone',
+ requestDescription:
+ 'After your Keystone has signed this message, click on "Scan Keystone" to receive the signature',
},
- sync: { reading: false },
+ type: QrScanRequestType.SIGN,
};
describe('QRHardwareContext', () => {
@@ -75,9 +73,8 @@ describe('QRHardwareContext', () => {
};
const createQRHardwareAwarenessSpy = (mockedValues: {
- isQRSigningInProgress: boolean;
isSigningQRObject: boolean;
- QRState: IQRState;
+ pendingScanRequest: QrScanRequest | undefined;
}) => {
jest
.spyOn(QRHardwareAwareness, 'useQRHardwareAwareness')
@@ -87,9 +84,8 @@ describe('QRHardwareContext', () => {
it('should pass correct value of needsCameraPermission to child components', () => {
createCameraSpy({ cameraError: undefined, hasCameraPermission: false });
createQRHardwareAwarenessSpy({
- isQRSigningInProgress: true,
isSigningQRObject: true,
- QRState: mockQRState,
+ pendingScanRequest: mockPendingScanRequest,
});
const { getByTestId } = renderWithProvider(
@@ -104,12 +100,11 @@ describe('QRHardwareContext', () => {
).toBe(true);
});
- it('does not invokes KeyringController.cancelQRSignRequest when request is cancelled id QR signing is not in progress', async () => {
+ it('does not invoke rejectPendingScan when request is cancelled id QR signing is not in progress', async () => {
createCameraSpy({ cameraError: undefined, hasCameraPermission: false });
createQRHardwareAwarenessSpy({
- isQRSigningInProgress: false,
- isSigningQRObject: true,
- QRState: mockQRState,
+ isSigningQRObject: false,
+ pendingScanRequest: undefined,
});
const { getByText } = renderWithProvider(
@@ -120,17 +115,14 @@ describe('QRHardwareContext', () => {
},
);
await userEvent.press(getByText('Cancel'));
- expect(
- Engine.context.KeyringController.cancelQRSignRequest,
- ).toHaveBeenCalledTimes(0);
+ expect(mockQrScanner.rejectPendingScan).toHaveBeenCalledTimes(0);
});
- it('invokes KeyringController.cancelQRSignRequest when request is cancelled', async () => {
+ it('invokes rejectPendingScan when request is cancelled', async () => {
createCameraSpy({ cameraError: undefined, hasCameraPermission: false });
createQRHardwareAwarenessSpy({
- isQRSigningInProgress: true,
isSigningQRObject: true,
- QRState: mockQRState,
+ pendingScanRequest: mockPendingScanRequest,
});
const { getByText } = renderWithProvider(
@@ -141,17 +133,14 @@ describe('QRHardwareContext', () => {
},
);
await userEvent.press(getByText('Cancel'));
- expect(
- Engine.context.KeyringController.cancelQRSignRequest,
- ).toHaveBeenCalledTimes(1);
+ expect(mockQrScanner.rejectPendingScan).toHaveBeenCalledTimes(1);
});
- it('passes correct value of QRState components', () => {
+ it('passes correct value of pendingScanRequest components', () => {
createCameraSpy({ cameraError: undefined, hasCameraPermission: false });
createQRHardwareAwarenessSpy({
- isQRSigningInProgress: true,
isSigningQRObject: true,
- QRState: mockQRState,
+ pendingScanRequest: mockPendingScanRequest,
});
const { getByText } = renderWithProvider(
@@ -167,9 +156,8 @@ describe('QRHardwareContext', () => {
it('passes correct value of scannerVisible to child components', async () => {
createCameraSpy({ cameraError: undefined, hasCameraPermission: true });
createQRHardwareAwarenessSpy({
- isQRSigningInProgress: true,
isSigningQRObject: true,
- QRState: mockQRState,
+ pendingScanRequest: mockPendingScanRequest,
});
const { getByText } = renderWithProvider(
diff --git a/app/components/Views/confirmations/context/qr-hardware-context/qr-hardware-context.tsx b/app/components/Views/confirmations/context/qr-hardware-context/qr-hardware-context.tsx
index e408af0379af..c9ad624e4f0d 100644
--- a/app/components/Views/confirmations/context/qr-hardware-context/qr-hardware-context.tsx
+++ b/app/components/Views/confirmations/context/qr-hardware-context/qr-hardware-context.tsx
@@ -9,15 +9,14 @@ import React, {
import { useNavigation, NavigationAction } from '@react-navigation/native';
import Engine from '../../../../../core/Engine';
-import { IQRState } from '../../../../UI/QRHardware/types';
import { useCamera } from './useCamera';
import { useQRHardwareAwareness } from './useQRHardwareAwareness';
+import { QrScanRequest } from '@metamask/eth-qr-keyring';
export interface QRHardwareContextType {
- QRState?: IQRState;
+ pendingScanRequest?: QrScanRequest;
cameraError: string | undefined;
cancelQRScanRequestIfPresent: () => Promise;
- isQRSigningInProgress: boolean;
isSigningQRObject: boolean;
needsCameraPermission: boolean;
scannerVisible: boolean;
@@ -26,10 +25,9 @@ export interface QRHardwareContextType {
}
export const QRHardwareContext = createContext({
- QRState: undefined,
+ pendingScanRequest: undefined,
cameraError: undefined,
cancelQRScanRequestIfPresent: () => Promise.resolve(),
- isQRSigningInProgress: false,
isSigningQRObject: false,
needsCameraPermission: false,
scannerVisible: false,
@@ -41,25 +39,25 @@ export const QRHardwareContextProvider: React.FC<{
children: ReactElement[] | ReactElement;
}> = ({ children }) => {
const navigation = useNavigation();
- const { isQRSigningInProgress, isSigningQRObject, QRState } =
- useQRHardwareAwareness();
+ const { isSigningQRObject, pendingScanRequest } = useQRHardwareAwareness();
const { cameraError, hasCameraPermission } = useCamera(isSigningQRObject);
const [scannerVisible, setScannerVisible] = useState(false);
const [isRequestCompleted, setRequestCompleted] = useState(false);
- const KeyringController = Engine.context.KeyringController;
-
const cancelRequest = useCallback(
(e: { preventDefault: () => void; data: { action: NavigationAction } }) => {
if (isRequestCompleted) {
return;
}
e.preventDefault();
- KeyringController.cancelQRSignRequest().then(() => {
- navigation.dispatch(e.data.action);
- });
+ if (isSigningQRObject) {
+ Engine.getQrKeyringScanner().rejectPendingScan(
+ new Error('Request cancelled'),
+ );
+ }
+ navigation.dispatch(e.data.action);
},
- [KeyringController, isRequestCompleted, navigation],
+ [isRequestCompleted, navigation, isSigningQRObject],
);
useEffect(() => {
@@ -68,28 +66,24 @@ export const QRHardwareContextProvider: React.FC<{
}, [cancelRequest, navigation]);
const cancelQRScanRequestIfPresent = useCallback(async () => {
- if (!isQRSigningInProgress) {
+ if (!isSigningQRObject) {
return;
}
- await KeyringController.cancelQRSignRequest();
+ Engine.getQrKeyringScanner().rejectPendingScan(
+ new Error('Request cancelled'),
+ );
setRequestCompleted(true);
setScannerVisible(false);
- }, [
- KeyringController,
- isQRSigningInProgress,
- setRequestCompleted,
- setScannerVisible,
- ]);
+ }, [isSigningQRObject, setRequestCompleted, setScannerVisible]);
return (
setRequestCompleted(true),
setScannerVisible,
diff --git a/app/components/Views/confirmations/context/qr-hardware-context/useQRHardwareAwareness.test.ts b/app/components/Views/confirmations/context/qr-hardware-context/useQRHardwareAwareness.test.ts
index 641992a3003f..7eb39c58dedf 100644
--- a/app/components/Views/confirmations/context/qr-hardware-context/useQRHardwareAwareness.test.ts
+++ b/app/components/Views/confirmations/context/qr-hardware-context/useQRHardwareAwareness.test.ts
@@ -1,40 +1,18 @@
-import { renderHook } from '@testing-library/react-native';
import { useQRHardwareAwareness } from './useQRHardwareAwareness';
-import Engine from '../../../../../core/Engine';
-
-jest.mock('../../../../../core/Engine', () => ({
- context: {
- KeyringController: {
- getOrAddQRKeyring: jest.fn(),
- },
- },
- controllerMessenger: {
- subscribe: jest.fn(),
- unsubscribe: jest.fn(),
- },
-}));
+import { renderHookWithProvider } from '../../../../../util/test/renderWithProvider';
const initialState = {
- QRState: {
- sync: {
- reading: false,
- },
- sign: {},
- },
- isQRSigningInProgress: false,
+ pendingScanRequest: undefined,
isSigningQRObject: false,
};
describe('useQRHardwareAwareness', () => {
- it('invokes required methods from Engine', () => {
- renderHook(() => useQRHardwareAwareness());
- expect(
- Engine.context.KeyringController.getOrAddQRKeyring,
- ).toHaveBeenCalledTimes(1);
- expect(Engine.controllerMessenger.subscribe).toHaveBeenCalledTimes(1);
- });
it('returns correct initial values', () => {
- const { result } = renderHook(() => useQRHardwareAwareness());
+ const { result } = renderHookWithProvider(() => useQRHardwareAwareness(), {
+ state: {
+ qrKeyringScanner: {},
+ },
+ });
expect(result.current).toMatchObject(initialState);
});
});
diff --git a/app/components/Views/confirmations/context/qr-hardware-context/useQRHardwareAwareness.ts b/app/components/Views/confirmations/context/qr-hardware-context/useQRHardwareAwareness.ts
index e672a6a07f34..595d06ae2043 100644
--- a/app/components/Views/confirmations/context/qr-hardware-context/useQRHardwareAwareness.ts
+++ b/app/components/Views/confirmations/context/qr-hardware-context/useQRHardwareAwareness.ts
@@ -1,43 +1,14 @@
-import { useEffect, useState } from 'react';
-
-import Engine from '../../../../../core/Engine';
-import { IQRState } from '../../../../UI/QRHardware/types';
+import { QrScanRequestType } from '@metamask/eth-qr-keyring';
+import { RootState } from 'app/components/UI/BasicFunctionality/BasicFunctionalityModal/BasicFunctionalityModal.test';
+import { useSelector } from 'react-redux';
export const useQRHardwareAwareness = () => {
- const [QRState, SetQRState] = useState({
- sync: {
- reading: false,
- },
- sign: {},
- });
-
- // TODO: Replace "any" with type
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const subscribeKeyringState = (value: any) => {
- SetQRState(value);
- };
-
- useEffect(() => {
- // This ensures that a QR keyring gets created if it doesn't already exist.
- // This is intentionally not awaited (the subscription still gets setup correctly if called
- // before the keyring is created).
- // TODO: Stop automatically creating keyrings
- Engine.context.KeyringController.getOrAddQRKeyring();
- Engine.controllerMessenger.subscribe(
- 'KeyringController:qrKeyringStateChange',
- subscribeKeyringState,
- );
- return () => {
- Engine.controllerMessenger.unsubscribe(
- 'KeyringController:qrKeyringStateChange',
- subscribeKeyringState,
- );
- };
- }, []);
+ const { pendingScanRequest } = useSelector(
+ (state: RootState) => state.qrKeyringScanner,
+ );
return {
- isQRSigningInProgress: QRState?.sign?.request !== undefined,
- isSigningQRObject: !!QRState?.sign?.request,
- QRState,
+ isSigningQRObject: pendingScanRequest?.type === QrScanRequestType.SIGN,
+ pendingScanRequest,
};
};
diff --git a/app/components/Views/confirmations/hooks/useConfirmActions.test.ts b/app/components/Views/confirmations/hooks/useConfirmActions.test.ts
index 19ded1dc6869..cfe24f9a7968 100644
--- a/app/components/Views/confirmations/hooks/useConfirmActions.test.ts
+++ b/app/components/Views/confirmations/hooks/useConfirmActions.test.ts
@@ -92,7 +92,7 @@ describe('useConfirmAction', () => {
);
const mockSetScannerVisible = jest.fn().mockResolvedValue(undefined);
jest.spyOn(QRHardwareHook, 'useQRHardwareContext').mockReturnValue({
- isQRSigningInProgress: true,
+ isSigningQRObject: true,
setScannerVisible: mockSetScannerVisible,
} as unknown as QRHardwareHook.QRHardwareContextType);
const { result } = renderHookWithProvider(() => useConfirmActions(), {
diff --git a/app/components/Views/confirmations/hooks/useConfirmActions.ts b/app/components/Views/confirmations/hooks/useConfirmActions.ts
index 3e1987797b33..c4922ef6a463 100644
--- a/app/components/Views/confirmations/hooks/useConfirmActions.ts
+++ b/app/components/Views/confirmations/hooks/useConfirmActions.ts
@@ -27,11 +27,8 @@ export const useConfirmActions = () => {
} = useApprovalRequest();
const { onConfirm: onTransactionConfirm } = useTransactionConfirm();
const { captureSignatureMetrics } = useSignatureMetrics();
- const {
- cancelQRScanRequestIfPresent,
- isQRSigningInProgress,
- setScannerVisible,
- } = useQRHardwareContext();
+ const { cancelQRScanRequestIfPresent, isSigningQRObject, setScannerVisible } =
+ useQRHardwareContext();
const { ledgerSigningInProgress, openLedgerSignModal } = useLedgerContext();
const navigation = useNavigation();
const transactionMetadata = useTransactionMetadataRequest();
@@ -107,7 +104,7 @@ export const useConfirmActions = () => {
return;
}
- if (isQRSigningInProgress) {
+ if (isSigningQRObject) {
setScannerVisible(true);
return;
}
@@ -130,9 +127,9 @@ export const useConfirmActions = () => {
PPOMUtil.clearSignatureSecurityAlertResponse();
}
}, [
+ isSigningQRObject,
captureSignatureMetrics,
handleSmartTransaction,
- isQRSigningInProgress,
isSignatureReq,
isTransactionReq,
ledgerSigningInProgress,
diff --git a/app/components/Views/confirmations/hooks/useConfirmationRedesignEnabled.test.ts b/app/components/Views/confirmations/hooks/useConfirmationRedesignEnabled.test.ts
index 46413628dcb7..f1173ff78488 100644
--- a/app/components/Views/confirmations/hooks/useConfirmationRedesignEnabled.test.ts
+++ b/app/components/Views/confirmations/hooks/useConfirmationRedesignEnabled.test.ts
@@ -31,7 +31,6 @@ jest.mock('../../../../core/Engine', () => ({
state: {
keyrings: [],
},
- getOrAddQRKeyring: jest.fn(),
},
},
controllerMessenger: {
diff --git a/app/components/Views/confirmations/legacy/Approval/index.js b/app/components/Views/confirmations/legacy/Approval/index.js
index 44444e3ce4db..ba8560d9931a 100644
--- a/app/components/Views/confirmations/legacy/Approval/index.js
+++ b/app/components/Views/confirmations/legacy/Approval/index.js
@@ -174,7 +174,6 @@ class Approval extends PureComponent {
try {
const { transactionHandled } = this.state;
const { transaction, selectedAddress } = this.props;
- const { KeyringController } = Engine.context;
const isLedgerAccount = isHardwareAccount(selectedAddress, [
ExtendedKeyringTypes.ledger,
@@ -182,8 +181,10 @@ class Approval extends PureComponent {
if (!transactionHandled) {
if (isQRHardwareAccount(selectedAddress)) {
- KeyringController.cancelQRSignRequest();
- } else if (!isLedgerAccount) {
+ Engine.getQrKeyringScanner().rejectPendingScan(
+ new Error('Transaction cancelled'),
+ );
+ } else {
Engine.rejectPendingApproval(
transaction?.id,
providerErrors.userRejectedRequest(),
@@ -570,7 +571,6 @@ class Approval extends PureComponent {
},
(transactionMeta) => transactionMeta.id === transaction.id,
);
- await KeyringController.resetQRKeyringState();
const fullTx = transactions.find(({ id }) => id === transaction.id);
diff --git a/app/components/Views/confirmations/legacy/Approval/index.test.tsx b/app/components/Views/confirmations/legacy/Approval/index.test.tsx
index 30bb9383ad3c..684d55e1773c 100644
--- a/app/components/Views/confirmations/legacy/Approval/index.test.tsx
+++ b/app/components/Views/confirmations/legacy/Approval/index.test.tsx
@@ -33,10 +33,6 @@ jest.mock('../../../../../util/dappTransactions', () => ({
jest.mock('../../../../../core/Engine', () => ({
rejectPendingApproval: jest.fn(),
context: {
- KeyringController: {
- resetQRKeyringState: jest.fn(),
- getOrAddQRKeyring: jest.fn(),
- },
GasFeeController: {
getGasFeeEstimatesAndStartPolling: jest.fn().mockResolvedValue(null),
stopPolling: jest.fn(),
diff --git a/app/components/Views/confirmations/legacy/Approve/index.js b/app/components/Views/confirmations/legacy/Approve/index.js
index b2904ee3a212..d9e6c972e36d 100644
--- a/app/components/Views/confirmations/legacy/Approve/index.js
+++ b/app/components/Views/confirmations/legacy/Approve/index.js
@@ -585,7 +585,6 @@ class Approve extends PureComponent {
},
};
await updateTransaction(updatedTx);
- await KeyringController.resetQRKeyringState();
// For Ledger Accounts we handover the signing to the confirmation flow
if (isLedgerAccount) {
diff --git a/app/components/Views/confirmations/legacy/Approve/index.test.tsx b/app/components/Views/confirmations/legacy/Approve/index.test.tsx
index fe3a7effe426..6e5f4015387b 100644
--- a/app/components/Views/confirmations/legacy/Approve/index.test.tsx
+++ b/app/components/Views/confirmations/legacy/Approve/index.test.tsx
@@ -56,9 +56,6 @@ jest.mock('../../../../../core/Engine', () => ({
AssetsContractController: {
getERC20BalanceOf: jest.fn().mockResolvedValue(null),
},
- KeyringController: {
- getOrAddQRKeyring: jest.fn(),
- },
TransactionController: {
getNonceLock: jest
.fn()
diff --git a/app/components/Views/confirmations/legacy/ApproveView/Approve/index.js b/app/components/Views/confirmations/legacy/ApproveView/Approve/index.js
index 4c6167dfcfab..c9b34fe6f5f2 100644
--- a/app/components/Views/confirmations/legacy/ApproveView/Approve/index.js
+++ b/app/components/Views/confirmations/legacy/ApproveView/Approve/index.js
@@ -600,7 +600,6 @@ class Approve extends PureComponent {
},
};
await updateTransaction(updatedTx);
- await KeyringController.resetQRKeyringState();
// For Ledger Accounts we handover the signing to the confirmation flow
if (isLedgerAccount) {
diff --git a/app/components/Views/confirmations/legacy/ApproveView/Approve/index.test.tsx b/app/components/Views/confirmations/legacy/ApproveView/Approve/index.test.tsx
index 8e1f0e60e250..ecedf876a314 100644
--- a/app/components/Views/confirmations/legacy/ApproveView/Approve/index.test.tsx
+++ b/app/components/Views/confirmations/legacy/ApproveView/Approve/index.test.tsx
@@ -56,9 +56,6 @@ jest.mock('../../../../../../core/Engine', () => ({
AssetsContractController: {
getERC20BalanceOf: jest.fn().mockResolvedValue(null),
},
- KeyringController: {
- getOrAddQRKeyring: jest.fn(),
- },
TransactionController: {
getNonceLock: jest
.fn()
diff --git a/app/components/Views/confirmations/legacy/Send/index.js b/app/components/Views/confirmations/legacy/Send/index.js
index 63a8faa5189a..4f7cbb1b059b 100644
--- a/app/components/Views/confirmations/legacy/Send/index.js
+++ b/app/components/Views/confirmations/legacy/Send/index.js
@@ -576,7 +576,6 @@ class Send extends PureComponent {
networkClientId: globalNetworkClientId,
origin: TransactionTypes.MMM,
});
- await KeyringController.resetQRKeyringState();
await ApprovalController.accept(transactionMeta.id, undefined, {
waitForResult: true,
});
diff --git a/app/components/Views/confirmations/legacy/SendFlow/Confirm/index.js b/app/components/Views/confirmations/legacy/SendFlow/Confirm/index.js
index bb2913345d28..1775a057555c 100644
--- a/app/components/Views/confirmations/legacy/SendFlow/Confirm/index.js
+++ b/app/components/Views/confirmations/legacy/SendFlow/Confirm/index.js
@@ -1054,8 +1054,6 @@ class Confirm extends PureComponent {
return;
}
- await KeyringController.resetQRKeyringState();
-
if (shouldUseSmartTransaction) {
await ApprovalController.accept(transactionMeta.id, undefined, {
waitForResult: false,
diff --git a/app/components/Views/confirmations/legacy/components/ApproveTransactionReview/index.js b/app/components/Views/confirmations/legacy/components/ApproveTransactionReview/index.js
index 98c8c0f0cd90..260197492192 100644
--- a/app/components/Views/confirmations/legacy/components/ApproveTransactionReview/index.js
+++ b/app/components/Views/confirmations/legacy/components/ApproveTransactionReview/index.js
@@ -236,7 +236,7 @@ class ApproveTransactionReview extends PureComponent {
*/
nicknameExists: PropTypes.bool,
isSigningQRObject: PropTypes.bool,
- QRState: PropTypes.object,
+ pendingScanRequest: PropTypes.object,
/**
* The selected gas value (low, medium, high). Gas value can be null when the advanced option is modified.
*/
@@ -1309,7 +1309,7 @@ class ApproveTransactionReview extends PureComponent {
const {
activeTabUrl,
transaction: { origin, from },
- QRState,
+ pendingScanRequest,
} = this.props;
const styles = this.getStyles();
return (
@@ -1323,7 +1323,7 @@ class ApproveTransactionReview extends PureComponent {
}}
/>
{
);
return {
context: {
- KeyringController: {
- getOrAddQRKeyring: async () => ({ subscribe: () => ({}) }),
- },
AssetsContractController: {
getERC20BalanceOf: jest.fn().mockResolvedValue(0x0186a0),
},
diff --git a/app/components/Views/confirmations/legacy/components/SignatureRequest/Root/Root.test.tsx b/app/components/Views/confirmations/legacy/components/SignatureRequest/Root/Root.test.tsx
index fc0edb808acd..d549fd308dee 100644
--- a/app/components/Views/confirmations/legacy/components/SignatureRequest/Root/Root.test.tsx
+++ b/app/components/Views/confirmations/legacy/components/SignatureRequest/Root/Root.test.tsx
@@ -23,7 +23,6 @@ jest.mock('../../../../../../../core/Engine', () => ({
getQRKeyringState: jest.fn(() =>
Promise.resolve({ subscribe: jest.fn(), unsubscribe: jest.fn() }),
),
- getOrAddQRKeyring: jest.fn(),
state: {
keyrings: [],
},
diff --git a/app/components/Views/confirmations/legacy/components/SignatureRequest/index.js b/app/components/Views/confirmations/legacy/components/SignatureRequest/index.js
index 944bcf6b0975..48d274e45b8e 100644
--- a/app/components/Views/confirmations/legacy/components/SignatureRequest/index.js
+++ b/app/components/Views/confirmations/legacy/components/SignatureRequest/index.js
@@ -164,7 +164,7 @@ class SignatureRequest extends PureComponent {
*/
fromAddress: PropTypes.string,
isSigningQRObject: PropTypes.bool,
- QRState: PropTypes.object,
+ pendingScanRequest: PropTypes.object,
testID: PropTypes.string,
securityAlertResponse: PropTypes.object,
/**
@@ -371,13 +371,13 @@ class SignatureRequest extends PureComponent {
}
renderQRDetails() {
- const { QRState, fromAddress } = this.props;
+ const { pendingScanRequest, fromAddress } = this.props;
const styles = this.getStyles();
return (
{
keyrings: [],
},
getAccountKeyringType: jest.fn(() => Promise.resolve({ data: {} })),
- getOrAddQRKeyring: jest.fn(),
},
SignatureController: {
hub: {
diff --git a/app/core/Engine/Engine.ts b/app/core/Engine/Engine.ts
index f0ba94657d2f..6710fe458e1e 100644
--- a/app/core/Engine/Engine.ts
+++ b/app/core/Engine/Engine.ts
@@ -68,7 +68,10 @@ import {
} from '@metamask/snaps-rpc-methods';
import type { EnumToUnion, DialogType } from '@metamask/snaps-sdk';
///: END:ONLY_INCLUDE_IF
-import { MetaMaskKeyring as QRHardwareKeyring } from '@keystonehq/metamask-airgapped-keyring';
+import {
+ QrKeyring,
+ QrKeyringDeferredPromiseBridge,
+} from '@metamask/eth-qr-keyring';
import { LoggingController } from '@metamask/logging-controller';
import { TokenSearchDiscoveryControllerMessenger } from '@metamask/token-search-discovery-controller';
import {
@@ -235,6 +238,7 @@ import { WebSocketServiceInit } from './controllers/snaps/websocket-service-init
import { networkEnablementControllerInit } from './controllers/network-enablement-controller/network-enablement-controller-init';
import { seedlessOnboardingControllerInit } from './controllers/seedless-onboarding-controller';
+import { scanCompleted, scanRequested } from '../redux/slices/qrKeyringScanner';
import { perpsControllerInit } from './controllers/perps-controller';
import { selectUseTokenDetection } from '../../selectors/preferencesController';
import { rewardsControllerInit } from './controllers/rewards-controller';
@@ -299,7 +303,21 @@ export class Engine {
smartTransactionsController: SmartTransactionsController;
transactionController: TransactionController;
multichainRouter: MultichainRouter;
+
+ readonly qrKeyringScanner = new QrKeyringDeferredPromiseBridge({
+ onScanRequested: (request) => {
+ store.dispatch(scanRequested(request));
+ },
+ onScanResolved: () => {
+ store.dispatch(scanCompleted());
+ },
+ onScanRejected: () => {
+ store.dispatch(scanCompleted());
+ },
+ });
+
rewardsDataService: RewardsDataService;
+
/**
* Creates a CoreController instance
*/
@@ -572,12 +590,12 @@ export class Engine {
const additionalKeyrings = [];
const qrKeyringBuilder = () => {
- const keyring = new QRHardwareKeyring();
+ const keyring = new QrKeyring({ bridge: this.qrKeyringScanner });
// to fix the bug in #9560, forgetDevice will reset all keyring properties to default.
keyring.forgetDevice();
return keyring;
};
- qrKeyringBuilder.type = QRHardwareKeyring.type;
+ qrKeyringBuilder.type = QrKeyring.type;
additionalKeyrings.push(qrKeyringBuilder);
@@ -2457,6 +2475,11 @@ export default {
instance.setAccountLabel(address, label);
},
+ getQrKeyringScanner: () => {
+ assertEngineExists(instance);
+ return instance.qrKeyringScanner;
+ },
+
///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps)
getSnapKeyring: () => {
assertEngineExists(instance);
diff --git a/app/core/QrKeyring/QrKeyring.ts b/app/core/QrKeyring/QrKeyring.ts
new file mode 100644
index 000000000000..077897912b75
--- /dev/null
+++ b/app/core/QrKeyring/QrKeyring.ts
@@ -0,0 +1,38 @@
+import { QrKeyring } from '@metamask/eth-qr-keyring';
+import { KeyringMetadata } from '@metamask/keyring-controller';
+import Engine from '../Engine';
+import ExtendedKeyringTypes from '../../constants/keyringTypes';
+
+/**
+ * Perform an operation with the QR keyring.
+ *
+ * If no QR keyring is found, one is created.
+ *
+ * Note that the `operation` function should only be used for interactions with the keyring.
+ * If you call KeyringController methods within this function, it could result in a deadlock.
+ *
+ * @param operation - The keyring operation to perform.
+ * @returns The stored Qr Keyring
+ * @throws If there is no QR keyring available or if the operation fails.
+ */
+export const withQrKeyring = async (
+ operation: (selectedKeyring: {
+ keyring: QrKeyring;
+ metadata: KeyringMetadata;
+ }) => Promise,
+): Promise =>
+ Engine.context.KeyringController.withKeyring(
+ { type: ExtendedKeyringTypes.qr },
+ operation,
+ // TODO: Refactor this to stop creating the keyring on-demand
+ // Instead create it only in response to an explicit user action, and do
+ // not allow interactions with Qr Keyring until after that has been done.
+ { createIfMissing: true },
+ );
+
+/**
+ * Forget the QR keyring device, removing all accounts and
+ * paired device information.
+ */
+export const forgetQrDevice = async () =>
+ withQrKeyring(async ({ keyring }) => keyring.forgetDevice());
diff --git a/app/core/redux/slices/qrKeyringScanner/index.ts b/app/core/redux/slices/qrKeyringScanner/index.ts
new file mode 100644
index 000000000000..c6abbaf04f82
--- /dev/null
+++ b/app/core/redux/slices/qrKeyringScanner/index.ts
@@ -0,0 +1,33 @@
+import { QrScanRequest } from '@metamask/eth-qr-keyring';
+import { PayloadAction, createSlice } from '@reduxjs/toolkit';
+
+export interface QrKeyringScannerState {
+ pendingScanRequest?: QrScanRequest;
+ isScanning: boolean;
+}
+
+export const initialState: QrKeyringScannerState = {
+ isScanning: false,
+};
+
+const name = 'qrKeyringScanner';
+
+const slice = createSlice({
+ name,
+ initialState,
+ reducers: {
+ scanRequested: (state, action: PayloadAction) => {
+ state.pendingScanRequest = action.payload;
+ state.isScanning = true;
+ },
+ scanCompleted: (state) => {
+ state.pendingScanRequest = undefined;
+ state.isScanning = false;
+ },
+ },
+});
+
+const { actions, reducer } = slice;
+
+export default reducer;
+export const { scanRequested, scanCompleted } = actions;
diff --git a/app/reducers/index.ts b/app/reducers/index.ts
index 79d7860a3466..7dbdaa621b6e 100644
--- a/app/reducers/index.ts
+++ b/app/reducers/index.ts
@@ -25,6 +25,7 @@ import rpcEventReducer from './rpcEvents';
import accountsReducer from './accounts';
import sdkReducer from './sdk';
import inpageProviderReducer from '../core/redux/slices/inpageProvider';
+import qrKeyringScannerReducer from '../core/redux/slices/qrKeyringScanner';
import confirmationMetricsReducer from '../core/redux/slices/confirmationMetrics';
import originThrottlingReducer from '../core/redux/slices/originThrottling';
import notificationsAccountsProvider from '../core/redux/slices/notifications';
@@ -124,6 +125,7 @@ export interface RootState {
originThrottling: StateFromReducer;
notifications: StateFromReducer;
bridge: StateFromReducer;
+ qrKeyringScanner: StateFromReducer;
banners: BannersState;
card: StateFromReducer;
performance?: PerformanceState;
@@ -165,6 +167,7 @@ const baseReducers = {
banners: bannersReducer,
card: cardReducer,
confirmationMetrics: confirmationMetricsReducer,
+ qrKeyringScanner: qrKeyringScannerReducer,
cronjobController: cronjobControllerReducer,
rewards: rewardsReducer,
};
diff --git a/app/util/test/initial-root-state.ts b/app/util/test/initial-root-state.ts
index b0785d7c80fb..9448d4c9529e 100644
--- a/app/util/test/initial-root-state.ts
+++ b/app/util/test/initial-root-state.ts
@@ -6,6 +6,7 @@ import { initialState as initialInpageProvider } from '../../core/redux/slices/i
import { initialState as confirmationMetrics } from '../../core/redux/slices/confirmationMetrics';
import { initialState as originThrottling } from '../../core/redux/slices/originThrottling';
import { initialState as initialBridgeState } from '../../core/redux/slices/bridge';
+import { initialState as initialQrKeyringScannerState } from '../../core/redux/slices/qrKeyringScanner';
import { initialState as initialCardState } from '../../core/redux/slices/card';
import initialBackgroundState from './initial-background-state.json';
import { userInitialState } from '../../reducers/user';
@@ -43,6 +44,7 @@ const initialRootState: RootState = {
networkOnboarded: undefined,
security: initialSecurityState,
signatureRequest: undefined,
+ qrKeyringScanner: initialQrKeyringScannerState,
sdk: {
connections: {},
approvedHosts: {},
diff --git a/package.json b/package.json
index 6be9767c00fb..476ead7ff37a 100644
--- a/package.json
+++ b/package.json
@@ -186,7 +186,6 @@
"**/@ethersproject/signing-key/elliptic": "^6.6.1",
"**/@walletconnect/utils/elliptic": "^6.6.1",
"@metamask/keyring-controller/@ethereumjs/tx": "npm:@ethereumjs/tx@5.4.0",
- "@keystonehq/metamask-airgapped-keyring": "^0.15.2",
"metro/image-size": "^1.2.1",
"content-hash/**/base-x": "3.0.11",
"multihashes/**/base-x": "3.0.11",
@@ -217,7 +216,6 @@
"@deeeed/hyperliquid-node20": "^0.23.1-node20.1",
"@ethersproject/abi": "^5.7.0",
"@keystonehq/bc-ur-registry-eth": "^0.21.0",
- "@keystonehq/metamask-airgapped-keyring": "^0.15.2",
"@keystonehq/ur-decoder": "^0.12.2",
"@lavamoat/react-native-lockdown": "^0.0.2",
"@ledgerhq/react-native-hw-transport-ble": "^6.34.1",
@@ -246,6 +244,7 @@
"@metamask/eth-json-rpc-filters": "^9.0.0",
"@metamask/eth-json-rpc-middleware": "^18.0.0",
"@metamask/eth-ledger-bridge-keyring": "11.1.0",
+ "@metamask/eth-qr-keyring": "^1.1.0",
"@metamask/eth-query": "^4.0.0",
"@metamask/eth-sig-util": "^8.0.0",
"@metamask/eth-snap-keyring": "^17.1.0",
@@ -259,7 +258,7 @@
"@metamask/json-rpc-middleware-stream": "^8.0.7",
"@metamask/key-tree": "^10.1.1",
"@metamask/keyring-api": "^21.0.0",
- "@metamask/keyring-controller": "^22.1.1",
+ "@metamask/keyring-controller": "^23.1.0",
"@metamask/keyring-internal-api": "^9.0.0",
"@metamask/keyring-snap-client": "^8.0.0",
"@metamask/logging-controller": "^6.0.4",
@@ -279,7 +278,7 @@
"@metamask/phishing-controller": "^13.1.0",
"@metamask/post-message-stream": "^10.0.0",
"@metamask/ppom-validator": "^0.38.0",
- "@metamask/preferences-controller": "^18.4.0",
+ "@metamask/preferences-controller": "^19.0.0",
"@metamask/preinstalled-example-snap": "^0.7.1",
"@metamask/profile-sync-controller": "^25.0.0",
"@metamask/react-native-acm": "^1.0.1",
@@ -295,7 +294,7 @@
"@metamask/selected-network-controller": "^24.0.0",
"@metamask/sdk-communication-layer": "0.33.1",
"@metamask/seedless-onboarding-controller": "^4.0.0",
- "@metamask/signature-controller": "^32.0.0",
+ "@metamask/signature-controller": "^33.0.0",
"@metamask/slip44": "^4.2.0",
"@metamask/smart-transactions-controller": "^18.1.0",
"@metamask/snaps-controllers": "^14.2.2",
diff --git a/patches/@metamask+keyring-controller+22.1.1.patch b/patches/@metamask+keyring-controller+22.1.1.patch
deleted file mode 100644
index a3843b98dc1b..000000000000
--- a/patches/@metamask+keyring-controller+22.1.1.patch
+++ /dev/null
@@ -1,36 +0,0 @@
-diff --git a/node_modules/@metamask/keyring-controller/dist/KeyringController.cjs b/node_modules/@metamask/keyring-controller/dist/KeyringController.cjs
-index f3d64eb..beb4ab6 100644
---- a/node_modules/@metamask/keyring-controller/dist/KeyringController.cjs
-+++ b/node_modules/@metamask/keyring-controller/dist/KeyringController.cjs
-@@ -1204,6 +1204,7 @@ _KeyringController_controllerOperationMutex = new WeakMap(), _KeyringController_
- this.messagingSystem.registerActionHandler(`${name}:signUserOperation`, this.signUserOperation.bind(this));
- this.messagingSystem.registerActionHandler(`${name}:addNewAccount`, this.addNewAccount.bind(this));
- this.messagingSystem.registerActionHandler(`${name}:withKeyring`, this.withKeyring.bind(this));
-+ this.messagingSystem.registerActionHandler(`${name}:addNewKeyring`, this.addNewKeyring.bind(this));
- }, _KeyringController_getKeyringById = function _KeyringController_getKeyringById(keyringId) {
- return __classPrivateFieldGet(this, _KeyringController_keyrings, "f").find(({ metadata }) => metadata.id === keyringId)
- ?.keyring;
-diff --git a/node_modules/@metamask/keyring-controller/dist/KeyringController.d.cts b/node_modules/@metamask/keyring-controller/dist/KeyringController.d.cts
-index ed58f0c..2315ae4 100644
---- a/node_modules/@metamask/keyring-controller/dist/KeyringController.d.cts
-+++ b/node_modules/@metamask/keyring-controller/dist/KeyringController.d.cts
-@@ -124,6 +124,10 @@ export type KeyringControllerWithKeyringAction = {
- type: `${typeof name}:withKeyring`;
- handler: KeyringController['withKeyring'];
- };
-+export type KeyringControllerAddNewKeyringAction = {
-+ type: `${typeof name}:addNewKeyring`;
-+ handler: KeyringController['addNewKeyring'];
-+};
- export type KeyringControllerStateChangeEvent = {
- type: `${typeof name}:stateChange`;
- payload: [KeyringControllerState, Patch[]];
-@@ -144,7 +148,7 @@ export type KeyringControllerQRKeyringStateChangeEvent = {
- type: `${typeof name}:qrKeyringStateChange`;
- payload: [ReturnType];
- };
--export type KeyringControllerActions = KeyringControllerGetStateAction | KeyringControllerSignMessageAction | KeyringControllerSignEip7702AuthorizationAction | KeyringControllerSignPersonalMessageAction | KeyringControllerSignTypedMessageAction | KeyringControllerDecryptMessageAction | KeyringControllerGetEncryptionPublicKeyAction | KeyringControllerGetAccountsAction | KeyringControllerGetKeyringsByTypeAction | KeyringControllerGetKeyringForAccountAction | KeyringControllerPersistAllKeyringsAction | KeyringControllerPrepareUserOperationAction | KeyringControllerPatchUserOperationAction | KeyringControllerSignUserOperationAction | KeyringControllerAddNewAccountAction | KeyringControllerWithKeyringAction;
-+export type KeyringControllerActions = KeyringControllerGetStateAction | KeyringControllerSignMessageAction | KeyringControllerSignEip7702AuthorizationAction | KeyringControllerSignPersonalMessageAction | KeyringControllerSignTypedMessageAction | KeyringControllerDecryptMessageAction | KeyringControllerGetEncryptionPublicKeyAction | KeyringControllerGetAccountsAction | KeyringControllerGetKeyringsByTypeAction | KeyringControllerGetKeyringForAccountAction | KeyringControllerPersistAllKeyringsAction | KeyringControllerPrepareUserOperationAction | KeyringControllerPatchUserOperationAction | KeyringControllerSignUserOperationAction | KeyringControllerAddNewAccountAction | KeyringControllerWithKeyringAction | KeyringControllerAddNewKeyringAction;
- export type KeyringControllerEvents = KeyringControllerStateChangeEvent | KeyringControllerLockEvent | KeyringControllerUnlockEvent | KeyringControllerAccountRemovedEvent | KeyringControllerQRKeyringStateChangeEvent;
- export type KeyringControllerMessenger = RestrictedMessenger;
- export type KeyringControllerOptions = {
diff --git a/patches/@metamask+preferences-controller+18.4.0.patch b/patches/@metamask+preferences-controller+19.0.0.patch
similarity index 90%
rename from patches/@metamask+preferences-controller+18.4.0.patch
rename to patches/@metamask+preferences-controller+19.0.0.patch
index 34ad1b377f7b..c7ea6ff88501 100644
--- a/patches/@metamask+preferences-controller+18.4.0.patch
+++ b/patches/@metamask+preferences-controller+19.0.0.patch
@@ -1,5 +1,5 @@
diff --git a/node_modules/@metamask/preferences-controller/dist/PreferencesController.cjs b/node_modules/@metamask/preferences-controller/dist/PreferencesController.cjs
-index 83eb804..6033da4 100644
+index 7881684..43daef4 100644
--- a/node_modules/@metamask/preferences-controller/dist/PreferencesController.cjs
+++ b/node_modules/@metamask/preferences-controller/dist/PreferencesController.cjs
@@ -17,7 +17,7 @@ const metadata = {
@@ -108,8 +108,8 @@ index 83eb804..6033da4 100644
}
});
}
-@@ -399,6 +401,16 @@ class PreferencesController extends base_controller_1.BaseController {
- state.smartAccountOptInForAccounts = accounts;
+@@ -388,6 +390,16 @@ class PreferencesController extends base_controller_1.BaseController {
+ state.smartAccountOptIn = smartAccountOptIn;
});
}
+ /**
@@ -122,11 +122,11 @@ index 83eb804..6033da4 100644
+ state.tokenNetworkFilter = tokenNetworkFilter;
+ });
+ }
- }
- exports.PreferencesController = PreferencesController;
- _PreferencesController_instances = new WeakSet(), _PreferencesController_syncIdentities = function _PreferencesController_syncIdentities(addresses) {
+ /**
+ * Add account to list of accounts for which user has optedin
+ * smart account upgrade.
diff --git a/node_modules/@metamask/preferences-controller/dist/PreferencesController.d.cts b/node_modules/@metamask/preferences-controller/dist/PreferencesController.d.cts
-index 7c24a04..892e601 100644
+index 4505e52..65c19d4 100644
--- a/node_modules/@metamask/preferences-controller/dist/PreferencesController.d.cts
+++ b/node_modules/@metamask/preferences-controller/dist/PreferencesController.d.cts
@@ -71,7 +71,7 @@ export type PreferencesState = {
@@ -147,8 +147,8 @@ index 7c24a04..892e601 100644
/**
* Controls whether to use the safe chains list validation
*/
-@@ -134,6 +134,10 @@ export type PreferencesState = {
- * User to opt in for smart account upgrade for specific accounts.
+@@ -136,6 +136,10 @@ export type PreferencesState = {
+ * @deprecated This preference is deprecated and will be removed in the future.
*/
smartAccountOptInForAccounts: Hex[];
+ /**
@@ -158,7 +158,7 @@ index 7c24a04..892e601 100644
};
declare const name = "PreferencesController";
export type PreferencesControllerGetStateAction = ControllerGetStateAction;
-@@ -215,11 +219,11 @@ export declare class PreferencesController extends BaseController