Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
231f780
(wip) refactor: use `@metamask/eth-qr-keyring`
mikesposito Jul 14, 2025
556058a
fix Engine frozen
mikesposito Jul 15, 2025
0234346
move deferred promise to `QrKeyringScanner`
mikesposito Jul 15, 2025
99302b5
remove unused imports
mikesposito Jul 15, 2025
31505fe
remove unused redux action
mikesposito Jul 15, 2025
52cb8ab
use `QrKeyringDeferredPromiseBridge`
mikesposito Jul 15, 2025
d6971ac
remove test resolution
mikesposito Jul 15, 2025
8242f97
wip fix tests
mikesposito Jul 15, 2025
495c2cc
remove `getOrAddQRKeyring` usage
mikesposito Jul 15, 2025
454ec27
remove `resetQRKeyringState()` usage
mikesposito Jul 15, 2025
bac14c4
fix `qr-info.test.tsx`
mikesposito Jul 15, 2025
7108859
remove `QRState`
mikesposito Jul 15, 2025
e6537a7
remove `cancelQRSignRequest` usage
mikesposito Jul 15, 2025
3f48f57
remove `isQRSigningInProgress` from `useQRHardwareAwareness()`
mikesposito Jul 15, 2025
26a12e1
update tests and snapshots
mikesposito Jul 16, 2025
650a8eb
fix forget device
mikesposito Jul 19, 2025
6969679
Merge branch 'main' into mikesposito/feat/qr-keyring
mikesposito Aug 26, 2025
96d7f97
remove preview packages
mikesposito Aug 26, 2025
e43a33a
Merge branch 'main' into mikesposito/feat/qr-keyring
mikesposito Aug 26, 2025
281ef41
remove unused dependency
mikesposito Aug 26, 2025
995663c
remove unused imports
mikesposito Aug 26, 2025
67d8860
use valid addresses for unit tests
mikesposito Aug 27, 2025
542d9ea
fix `EvmAccountSelectorList` tests
mikesposito Aug 27, 2025
03c6bce
Merge branch 'main' into mikesposito/feat/qr-keyring
mikesposito Aug 27, 2025
6ab566d
update qr-keyring package to `^1.1.0`
mikesposito Aug 28, 2025
15246c2
Merge branch 'main' into mikesposito/feat/qr-keyring
mikesposito Sep 2, 2025
17d3bc5
Merge branch 'main' into mikesposito/feat/qr-keyring
mikesposito Sep 10, 2025
b6982b9
patch `@metamask/bridge-status-controller` to not require approval fo…
mikesposito Sep 11, 2025
590a04c
Merge branch 'main' into mikesposito/feat/qr-keyring
mikesposito Sep 12, 2025
5cd5a0c
Merge branch 'main' into mikesposito/feat/qr-keyring
mikesposito Sep 15, 2025
7b69073
Merge branch 'main' into mikesposito/feat/qr-keyring
mikesposito Sep 17, 2025
3591870
Merge branch 'main' into mikesposito/feat/qr-keyring
mikesposito Sep 17, 2025
60b9c8b
await promise instead of `bridge-status-controller` patch
mikesposito Sep 18, 2025
efaecfc
Merge branch 'main' into mikesposito/feat/qr-keyring
mikesposito Sep 18, 2025
f86f9f3
remove new `.resetQRKeyringState()` usage
mikesposito Sep 18, 2025
367ec3d
bump `@metamask/preferences-controller` to `^19.0.0`
mikesposito Sep 18, 2025
9b597ba
bump `@metamask/signature-controller` to `^33.0.0`
mikesposito Sep 18, 2025
5befb56
update `preferences-controller` patch
mikesposito Sep 19, 2025
cc1ed75
Merge branch 'main' into mikesposito/feat/qr-keyring
mikesposito Sep 19, 2025
8c969e3
fix comment
mikesposito Sep 22, 2025
6a8cfbc
Merge branch 'main' into mikesposito/feat/qr-keyring
mikesposito Sep 22, 2025
22a41d7
Merge branch 'main' into mikesposito/feat/qr-keyring
mikesposito Sep 23, 2025
898629e
Merge branch 'main' into mikesposito/feat/qr-keyring
mikesposito Sep 23, 2025
6bf1542
fix: check if signing with QR before rejecting
mikesposito Sep 24, 2025
b2c7b2d
Merge branch 'main' into mikesposito/feat/qr-keyring
mikesposito Sep 24, 2025
e3eaec0
revert change on `useConfirmActions.ts`
mikesposito Sep 24, 2025
2163cc9
Merge branch 'main' into mikesposito/feat/qr-keyring
mikesposito Sep 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,10 @@ describe('TransactionApproval', () => {
} as any);

const wrapper = shallow(
<TransactionApproval isSigningQRObject QRState={{ test: 'value' }} />,
<TransactionApproval
isSigningQRObject
pendingScanRequest={{ test: 'value' }}
/>,
);

expect(wrapper).toMatchSnapshot();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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();
Expand Down Expand Up @@ -64,9 +71,7 @@ const TransactionApprovalInternal = (props: TransactionApprovalProps) => {
return (
<QRSigningModal
isVisible
// TODO: Replace "any" with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
QRState={props.QRState as any}
pendingScanRequest={pendingScanRequest}
onSuccess={onComplete}
onCancel={onComplete}
onFailure={onComplete}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`TransactionApproval renders QR signing modal if signing QR object is exists 1`] = `
<QRSigningModal
QRState={
{
"test": "value",
}
}
isVisible={true}
onCancel={[Function]}
onFailure={[Function]}
onSuccess={[Function]}
/>
`;
exports[`TransactionApproval renders QR signing modal if signing QR object is exists 1`] = `""`;

exports[`TransactionApproval renders approval component if transaction type is dapp 1`] = `
<Component
Expand Down
7 changes: 5 additions & 2 deletions app/components/Nav/Main/RootRPCMethodsUI.js
Original file line number Diff line number Diff line change
Expand Up @@ -326,13 +326,16 @@ const RootRPCMethodsUI = (props) => {
// 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ const EvmAccountSelectorList = ({
} = item.data;

const internalAccount = internalAccountsById[id];

const shortAddress = formatAddress(address, 'short');
const tagLabel = isMultichainAccountsState1Enabled
? undefined
Expand Down
9 changes: 5 additions & 4 deletions app/components/UI/QRHardware/AnimatedQRScanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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;
Expand All @@ -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,
Expand All @@ -152,7 +153,7 @@ const AnimatedQRScannerModal = (props: AnimatedQRScannerProps) => {
{strings('connect_qr_hardware.hint_text')}
<Text style={styles.bold}>
{strings(
purpose === 'sync'
purpose === QrScanRequestType.PAIR
? 'connect_qr_hardware.purpose_connect'
: 'connect_qr_hardware.purpose_sign',
)}
Expand Down Expand Up @@ -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({
Expand Down
60 changes: 27 additions & 33 deletions app/components/UI/QRHardware/QRSigningDetails.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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';
Expand All @@ -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;
Expand Down Expand Up @@ -117,7 +111,7 @@ const createStyles = (colors: any) =>
});

const QRSigningDetails = ({
QRState,
pendingScanRequest,
successCallback,
failureCallback,
cancelCallback,
Expand All @@ -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);
Expand Down Expand Up @@ -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('');
Expand All @@ -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) => {
Expand All @@ -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;
Expand All @@ -250,8 +245,7 @@ const QRSigningDetails = ({
failureCallback?.(strings('transaction.mismatched_qr_request_id'));
},
[
KeyringController,
QRState.sign.request?.requestId,
pendingScanRequest?.request?.requestId,
failureCallback,
successCallback,
trackEvent,
Expand Down Expand Up @@ -287,7 +281,7 @@ const QRSigningDetails = ({

return (
<Fragment>
{QRState?.sign?.request && (
{pendingScanRequest?.request && (
<ScrollView contentContainerStyle={styles.wrapper}>
<ActionView
confirmDisabled={!hasCameraPermission}
Expand Down Expand Up @@ -326,8 +320,8 @@ const QRSigningDetails = ({
</Text>
</View>
<AnimatedQRCode
cbor={QRState.sign.request.payload.cbor}
type={QRState.sign.request.payload.type}
cbor={pendingScanRequest.request.payload.cbor}
type={pendingScanRequest.request.payload.type}
shouldPause={
scannerVisible || !shouldStartAnimated || shouldPause
}
Expand Down Expand Up @@ -368,7 +362,7 @@ const QRSigningDetails = ({
<AnimatedQRScannerModal
pauseQRCode={setShouldPause}
visible={scannerVisible}
purpose={'sign'}
purpose={QrScanRequestType.SIGN}
onScanSuccess={onScanSuccess}
onScanError={onScanError}
hideModal={hideScanner}
Expand Down
8 changes: 4 additions & 4 deletions app/components/UI/QRHardware/QRSigningModal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import React, { useState } from 'react';
import Modal from 'react-native-modal';
import { IQRState } from '../types';
import { StyleSheet, View } from 'react-native';
import QRSigningDetails from '../QRSigningDetails';
import { useTheme } from '../../../../util/theme';
import { useDispatch, useSelector } from 'react-redux';
import { getNormalizedTxState } from '../../../../util/transactions';
import { resetTransaction } from '../../../../actions/transaction';
import { selectSelectedInternalAccount } from '../../../../selectors/accountsController';
import { QrScanRequest } from '@metamask/eth-qr-keyring';

interface IQRSigningModalProps {
isVisible: boolean;
QRState: IQRState;
pendingScanRequest: QrScanRequest;
onSuccess?: () => void;
onCancel?: () => void;
onFailure?: (error: string) => void;
Expand All @@ -37,7 +37,7 @@ const createStyles = (colors: any) =>

const QRSigningModal = ({
isVisible,
QRState,
pendingScanRequest,
onSuccess,
onCancel,
onFailure,
Expand Down Expand Up @@ -85,7 +85,7 @@ const QRSigningModal = ({
>
<View style={styles.contentWrapper}>
<QRSigningDetails
QRState={QRState}
pendingScanRequest={pendingScanRequest}
showCancelButton
tighten
showHint
Expand Down
14 changes: 0 additions & 14 deletions app/components/UI/QRHardware/types.tsx

This file was deleted.

Loading
Loading