Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { useIsTransactionPayLoading } from '../../../hooks/pay/useTransactionPay
import { useConfirmActions } from '../../../hooks/useConfirmActions';
import { useAlerts } from '../../../context/alert-system-context';
import useFiatFormatter from '../../../../../UI/SimulationDetails/FiatDisplay/useFiatFormatter';
import { useMMPayHardwareAccountAlert } from '../../../hooks/alerts/useMMPayHardwareAccountAlert';
import { Severity } from '../../../types/alerts';

const mockToken: AssetType = {
Expand Down Expand Up @@ -90,20 +89,13 @@ jest.mock('../../rows/percentage-row', () => ({
PercentageRow: () => null,
}));

jest.mock('../../../hooks/alerts/useMMPayHardwareAccountAlert', () => ({
useMMPayHardwareAccountAlert: jest.fn(),
}));

const mockUseTransactionMetadataRequest = jest.mocked(
useTransactionMetadataRequest,
);
const mockUseIsTransactionPayLoading = jest.mocked(useIsTransactionPayLoading);
const mockUseConfirmActions = jest.mocked(useConfirmActions);
const mockUseAlerts = jest.mocked(useAlerts);
const mockUseFiatFormatter = jest.mocked(useFiatFormatter);
const mockUseMMPayHardwareAccountAlert = jest.mocked(
useMMPayHardwareAccountAlert,
);

function setupMocksForSuccessPath() {
mockUseParams.mockReturnValue({ token: mockToken });
Expand All @@ -121,7 +113,6 @@ function setupMocksForSuccessPath() {
mockUseFiatFormatter.mockReturnValue((value: { toString: () => string }) =>
value.toString(),
);
mockUseMMPayHardwareAccountAlert.mockReturnValue([]);
}

describe('MusdMaxConversionInfo', () => {
Expand Down Expand Up @@ -275,15 +266,17 @@ describe('MusdMaxConversionInfo', () => {
});

it('disables confirm button when hardware account alert is blocking', () => {
mockUseMMPayHardwareAccountAlert.mockReturnValue([
{
key: 'MMPayHardwareAccount',
message: 'Hardware wallet not supported',
title: 'Not Supported',
severity: Severity.Danger,
isBlocking: true,
},
]);
mockUseAlerts.mockReturnValue({
alerts: [
{
key: 'MMPayHardwareAccount',
message: 'Hardware wallet not supported',
title: 'Not Supported',
severity: Severity.Danger,
isBlocking: true,
},
],
} as unknown as ReturnType<typeof useAlerts>);

renderWithProvider(<MusdMaxConversionInfo />, { state: {} });

Expand All @@ -294,15 +287,17 @@ describe('MusdMaxConversionInfo', () => {
});

it('shows hardware account alert title as button label', () => {
mockUseMMPayHardwareAccountAlert.mockReturnValue([
{
key: 'MMPayHardwareAccount',
message: 'Hardware wallet not supported',
title: 'Not Supported',
severity: Severity.Danger,
isBlocking: true,
},
]);
mockUseAlerts.mockReturnValue({
alerts: [
{
key: 'MMPayHardwareAccount',
message: 'Hardware wallet not supported',
title: 'Not Supported',
severity: Severity.Danger,
isBlocking: true,
},
],
} as unknown as ReturnType<typeof useAlerts>);

renderWithProvider(<MusdMaxConversionInfo />, { state: {} });

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {
MUSD_TOKEN,
MUSD_TOKEN_ADDRESS,
} from '../../../../../UI/Earn/constants/musd';
import { useMMPayHardwareAccountAlert } from '../../../hooks/alerts/useMMPayHardwareAccountAlert';

/**
* Navigation params for MusdMaxConversionInfo
Expand All @@ -52,9 +51,8 @@ export const MusdMaxConversionInfo = () => {

const { onConfirm } = useConfirmActions();
const { alerts } = useAlerts();
const hardwareAccountAlerts = useMMPayHardwareAccountAlert();

const blockingAlert = [...alerts, ...hardwareAccountAlerts].find(
const blockingAlert = alerts.find(
(confirmationAlert) => confirmationAlert.isBlocking,
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { useHeadlessBuyErrorAlert } from './useHeadlessBuyErrorAlert';
import { useTokenContractAlert } from './useTokenContractAlert';
import { useAddressPoisoningAlert } from './useAddressPoisoningAlert';
import { useAccountNoFundsAlert } from './useAccountNoFundsAlert';
import { useMMPayHardwareAccountAlert } from './useMMPayHardwareAccountAlert';

jest.mock('./useBlockaidAlerts');
jest.mock('./useGasEstimateFailedAlert');
Expand All @@ -55,6 +56,7 @@ jest.mock('./useHeadlessBuyErrorAlert');
jest.mock('./useTokenContractAlert');
jest.mock('./useAddressPoisoningAlert');
jest.mock('./useAccountNoFundsAlert');
jest.mock('./useMMPayHardwareAccountAlert');

describe('useConfirmationAlerts', () => {
const ALERT_MESSAGE_MOCK = 'This is a test alert message.';
Expand Down Expand Up @@ -227,6 +229,7 @@ describe('useConfirmationAlerts', () => {
(useTokenContractAlert as jest.Mock).mockReturnValue([]);
(useAddressPoisoningAlert as jest.Mock).mockReturnValue([]);
(useAccountNoFundsAlert as jest.Mock).mockReturnValue([]);
(useMMPayHardwareAccountAlert as jest.Mock).mockReturnValue([]);
});

it('returns empty array if no alerts', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { useFirstTimeInteractionAlert } from './useFirstTimeInteractionAlert';
import { useAddressPoisoningAlert } from './useAddressPoisoningAlert';
import { useTokenContractAlert } from './useTokenContractAlert';
import { useAccountNoFundsAlert } from './useAccountNoFundsAlert';
import { useMMPayHardwareAccountAlert } from './useMMPayHardwareAccountAlert';

function useSignatureAlerts(): Alert[] {
const domainMismatchAlerts = useDomainMismatchAlerts();
Expand Down Expand Up @@ -53,6 +54,7 @@ function useTransactionAlerts(): Alert[] {
const addressPoisoningAlert = useAddressPoisoningAlert();
const tokenContractAlert = useTokenContractAlert();
const accountNoFundsAlert = useAccountNoFundsAlert();
const mmPayHardwareAccountAlert = useMMPayHardwareAccountAlert();

return useMemo(
() => [
Expand All @@ -75,6 +77,7 @@ function useTransactionAlerts(): Alert[] {
...addressPoisoningAlert,
...tokenContractAlert,
...accountNoFundsAlert,
...mmPayHardwareAccountAlert,
],
[
gasEstimateFailedAlert,
Expand All @@ -96,6 +99,7 @@ function useTransactionAlerts(): Alert[] {
addressPoisoningAlert,
tokenContractAlert,
accountNoFundsAlert,
mmPayHardwareAccountAlert,
],
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { AlertKeys } from '../../constants/alerts';
import { Severity } from '../../types/alerts';
import { strings } from '../../../../../../locales/i18n';
import { useTransactionMetadataRequest } from '../transactions/useTransactionMetadataRequest';
import { useTransactionAccountOverride } from '../transactions/useTransactionAccountOverride';
import { useTransactionPayFiatPayment } from '../pay/useTransactionPayData';
import {
TransactionMeta,
TransactionType,
Expand All @@ -18,10 +20,15 @@ jest.mock('react-redux', () => ({
useSelector: jest.fn(),
}));
jest.mock('../transactions/useTransactionMetadataRequest');
jest.mock('../transactions/useTransactionAccountOverride');
jest.mock('../pay/useTransactionPayData');
jest.mock('../../../../../util/address');

const useSelectorMock = jest.mocked(useSelector);

const HARDWARE_ADDRESS = '0xabc';
const OVERRIDE_ADDRESS = '0xdef';

function runHook() {
return renderHook(() => useMMPayHardwareAccountAlert());
}
Expand All @@ -41,15 +48,24 @@ describe('useMMPayHardwareAccountAlert', () => {
const useTransactionMetadataRequestMock = jest.mocked(
useTransactionMetadataRequest,
);
const useTransactionAccountOverrideMock = jest.mocked(
useTransactionAccountOverride,
);
const useTransactionPayFiatPaymentMock = jest.mocked(
useTransactionPayFiatPayment,
);

beforeEach(() => {
jest.resetAllMocks();

useSelectorMock.mockReturnValue({ enabled: false });
useTransactionAccountOverrideMock.mockReturnValue(undefined);
useTransactionPayFiatPaymentMock.mockReturnValue(undefined);

useTransactionMetadataRequestMock.mockReturnValue({
type: TransactionType.perpsDeposit,
txParams: {
from: '0xabc',
from: HARDWARE_ADDRESS,
},
} as TransactionMeta);

Expand All @@ -72,14 +88,74 @@ describe('useMMPayHardwareAccountAlert', () => {
expect(result.current).toStrictEqual([]);
});

it('returns no alert if transaction is not a pay transaction', () => {
isHardwareAccountMock.mockReturnValue(true);
useTransactionMetadataRequestMock.mockReturnValue({
type: TransactionType.simpleSend,
txParams: {
from: HARDWARE_ADDRESS,
},
} as TransactionMeta);

const { result } = runHook();

expect(result.current).toStrictEqual([]);
});

it('checks account override instead of from when set', () => {
isHardwareAccountMock.mockImplementation(
(address) => address === OVERRIDE_ADDRESS,
);
useTransactionAccountOverrideMock.mockReturnValue(OVERRIDE_ADDRESS);
useTransactionMetadataRequestMock.mockReturnValue({
type: TransactionType.moneyAccountDeposit,
txParams: {
from: '0x123',
},
} as TransactionMeta);

const { result } = runHook();

expect(result.current).toStrictEqual([EXPECTED_ALERT]);
expect(isHardwareAccountMock).toHaveBeenCalledWith(OVERRIDE_ADDRESS);
});

it('returns no alert on withdraw when account override is hardware account', () => {
isHardwareAccountMock.mockImplementation(
(address) => address === OVERRIDE_ADDRESS,
);
useTransactionAccountOverrideMock.mockReturnValue(OVERRIDE_ADDRESS);
useTransactionMetadataRequestMock.mockReturnValue({
type: TransactionType.moneyAccountWithdraw,
txParams: {
from: '0x123',
},
} as TransactionMeta);

const { result } = runHook();

expect(result.current).toStrictEqual([]);
});

it('returns no alert when fiat payment method is selected', () => {
isHardwareAccountMock.mockReturnValue(true);
useTransactionPayFiatPaymentMock.mockReturnValue({
selectedPaymentMethodId: 'payment-method-1',
} as ReturnType<typeof useTransactionPayFiatPayment>);

const { result } = runHook();

expect(result.current).toStrictEqual([]);
});

it('returns alert for Ledger wallet on mUSD conversion when feature flag is disabled', () => {
isHardwareAccountMock.mockReturnValue(true);
isQRHardwareAccountMock.mockReturnValue(false);
useSelectorMock.mockReturnValue({ enabled: false });
useTransactionMetadataRequestMock.mockReturnValue({
type: TransactionType.musdConversion,
txParams: {
from: '0xabc',
from: HARDWARE_ADDRESS,
},
} as TransactionMeta);

Expand All @@ -95,7 +171,7 @@ describe('useMMPayHardwareAccountAlert', () => {
useTransactionMetadataRequestMock.mockReturnValue({
type: TransactionType.musdConversion,
txParams: {
from: '0xabc',
from: HARDWARE_ADDRESS,
},
} as TransactionMeta);

Expand All @@ -111,7 +187,7 @@ describe('useMMPayHardwareAccountAlert', () => {
useTransactionMetadataRequestMock.mockReturnValue({
type: TransactionType.musdConversion,
txParams: {
from: '0xabc',
from: HARDWARE_ADDRESS,
},
} as TransactionMeta);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,23 @@ import { AlertKeys } from '../../constants/alerts';
import { Alert, Severity } from '../../types/alerts';
import { strings } from '../../../../../../locales/i18n';
import { useTransactionMetadataRequest } from '../transactions/useTransactionMetadataRequest';
import { hasTransactionType } from '../../utils/transaction';
import { useTransactionAccountOverride } from '../transactions/useTransactionAccountOverride';
import { useTransactionPayFiatPayment } from '../pay/useTransactionPayData';
import {
hasTransactionType,
isTransactionPayWithdraw,
} from '../../utils/transaction';
import {
isHardwareAccount,
isQRHardwareAccount,
} from '../../../../../util/address';
import { selectMetaMaskPayHardwareFlags } from '../../../../../selectors/featureFlagController/confirmations';
import { PAY_TRANSACTION_TYPES } from '../../constants/confirmations';

export function useMMPayHardwareAccountAlert(): Alert[] {
const transactionMeta = useTransactionMetadataRequest();
const accountOverride = useTransactionAccountOverride();
const fiatPayment = useTransactionPayFiatPayment();
const { enabled: isHardwarePayEnabled } = useSelector(
selectMetaMaskPayHardwareFlags,
);
Expand All @@ -22,15 +30,31 @@ export function useMMPayHardwareAccountAlert(): Alert[] {
txParams: { from },
} = transactionMeta ?? { txParams: {} };

const isPayTransaction = hasTransactionType(
transactionMeta,
PAY_TRANSACTION_TYPES,
);

const isMusdConversion = hasTransactionType(transactionMeta, [
TransactionType.musdConversion,
]);

const isHardwareWallet = isHardwareAccount(from ?? '');
const isQRWallet = isQRHardwareAccount(from ?? '');
// When set, accountOverride is the account paying for the transaction,
// except in withdraw (post-quote) flows where it is only the recipient
// and never signs.
const payingAccount = isTransactionPayWithdraw(transactionMeta)
? from
: (accountOverride ?? from);

const isHardwareWallet = isHardwareAccount(payingAccount ?? '');
const isQRWallet = isQRHardwareAccount(payingAccount ?? '');

// Fiat payments are bought directly to the destination, so the paying
// account never signs.
const isFiatPayment = Boolean(fiatPayment?.selectedPaymentMethodId);

return useMemo(() => {
if (!isHardwareWallet) {
if (!isPayTransaction || !isHardwareWallet || isFiatPayment) {
return [];
}

Expand All @@ -47,5 +71,12 @@ export function useMMPayHardwareAccountAlert(): Alert[] {
isBlocking: true,
},
];
}, [isHardwareWallet, isHardwarePayEnabled, isMusdConversion, isQRWallet]);
}, [
isFiatPayment,
isHardwareWallet,
isHardwarePayEnabled,
isMusdConversion,
isPayTransaction,
isQRWallet,
]);
}
Loading
Loading