Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
@@ -1,24 +1,20 @@
import React from 'react';
import { fireEvent } from '@testing-library/react-native';
import { TransactionType, CHAIN_IDS } from '@metamask/transaction-controller';
import renderWithProvider from '../../../../../util/test/renderWithProvider';
import MoneyAddMoneySheet from './MoneyAddMoneySheet';
import { MoneyAddMoneySheetTestIds } from './MoneyAddMoneySheet.testIds';
import { useMusdConversionFlowData } from '../../../Earn/hooks/useMusdConversionFlowData';
import { useRampNavigation } from '../../../Ramp/hooks/useRampNavigation';
import { useMusdBalance } from '../../../Earn/hooks/useMusdBalance';
import { useMoneyAccountDeposit } from '../../hooks/useMoneyAccount';
import { useMMPayFiatConfig } from '../../../../Views/confirmations/hooks/pay/useMMPayFiatConfig';
import {
MUSD_CONVERSION_DEFAULT_CHAIN_ID,
MUSD_TOKEN_ADDRESS_BY_CHAIN,
MUSD_TOKEN_ASSET_ID_BY_CHAIN,
} from '../../../Earn/constants/musd';
import { CHAIN_IDS } from '@metamask/transaction-controller';

const mockOnCloseBottomSheet = jest.fn((cb?: () => void) => cb?.());
const mockNavigate = jest.fn();
const mockGoBack = jest.fn();
const mockGetChainIdForBuyFlow = jest.fn();
const mockGoToBuy = jest.fn();
const mockInitiateDeposit = jest.fn(() => Promise.resolve());

jest.mock('@react-navigation/native', () => {
Expand All @@ -32,14 +28,6 @@ jest.mock('@react-navigation/native', () => {
};
});

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

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

jest.mock('../../../Earn/hooks/useMusdBalance', () => ({
useMusdBalance: jest.fn(),
}));
Expand All @@ -48,6 +36,13 @@ jest.mock('../../hooks/useMoneyAccount', () => ({
useMoneyAccountDeposit: jest.fn(),
}));

jest.mock(
'../../../../Views/confirmations/hooks/pay/useMMPayFiatConfig',
() => ({
useMMPayFiatConfig: jest.fn(),
}),
);

jest.mock('@metamask/design-system-react-native', () => {
const actual = jest.requireActual('@metamask/design-system-react-native');
const { forwardRef, useImperativeHandle } = jest.requireActual('react');
Expand Down Expand Up @@ -80,14 +75,6 @@ describe('MoneyAddMoneySheet', () => {
beforeEach(() => {
jest.clearAllMocks();

mockGetChainIdForBuyFlow.mockReturnValue(MUSD_CONVERSION_DEFAULT_CHAIN_ID);

(useMusdConversionFlowData as jest.Mock).mockReturnValue({
getChainIdForBuyFlow: mockGetChainIdForBuyFlow,
});
(useRampNavigation as jest.Mock).mockReturnValue({
goToBuy: mockGoToBuy,
});
(useMusdBalance as jest.Mock).mockReturnValue({
fiatBalanceAggregated: '1203.89',
fiatBalanceAggregatedFormatted: '$1,203.89',
Expand All @@ -98,6 +85,10 @@ describe('MoneyAddMoneySheet', () => {
(useMoneyAccountDeposit as jest.Mock).mockReturnValue({
initiateDeposit: mockInitiateDeposit,
});
(useMMPayFiatConfig as jest.Mock).mockReturnValue({
enabledTransactionTypes: [TransactionType.moneyAccountDeposit],
maxDelayMinutesForPaymentMethods: 10,
});
});

it('renders all four options', () => {
Expand Down Expand Up @@ -226,16 +217,16 @@ describe('MoneyAddMoneySheet', () => {
expect(getByText('Add your 42.50 mUSD')).toBeOnTheScreen();
});

it('navigates to the Ramps buy flow with mUSD pre-selected when Deposit funds is pressed', () => {
it('initiates a deposit with autoSelectFiatPayment when Deposit funds is pressed', () => {
const { getByTestId } = renderWithProvider(<MoneyAddMoneySheet />);

fireEvent.press(
getByTestId(MoneyAddMoneySheetTestIds.DEPOSIT_FUNDS_OPTION),
);

expect(mockOnCloseBottomSheet).toHaveBeenCalledTimes(1);
expect(mockGoToBuy).toHaveBeenCalledWith({
assetId: MUSD_TOKEN_ASSET_ID_BY_CHAIN[MUSD_CONVERSION_DEFAULT_CHAIN_ID],
expect(mockInitiateDeposit).toHaveBeenCalledWith({
autoSelectFiatPayment: true,
});
});

Expand All @@ -250,6 +241,19 @@ describe('MoneyAddMoneySheet', () => {
expect(mockInitiateDeposit).toHaveBeenCalledWith();
});

it('hides the Deposit funds option when moneyAccountDeposit is not in enabledTransactionTypes', () => {
(useMMPayFiatConfig as jest.Mock).mockReturnValue({
enabledTransactionTypes: [],
maxDelayMinutesForPaymentMethods: 10,
});

const { queryByTestId } = renderWithProvider(<MoneyAddMoneySheet />);

expect(
queryByTestId(MoneyAddMoneySheetTestIds.DEPOSIT_FUNDS_OPTION),
).toBeNull();
});

it('initiates a deposit pre-selecting mUSD on the highest-balance chain when Move mUSD is pressed', () => {
(useMusdBalance as jest.Mock).mockReturnValue({
fiatBalanceAggregated: '1500.00',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useCallback, useRef } from 'react';
import React, { useCallback, useMemo, useRef } from 'react';
import { TouchableOpacity, View } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import BigNumber from 'bignumber.js';
import { TransactionType } from '@metamask/transaction-controller';
import {
BottomSheet,
BottomSheetHeader,
Expand All @@ -18,16 +19,14 @@ import {
import Tag from '../../../../../component-library/components/Tags/Tag';
import { strings } from '../../../../../../locales/i18n';
import { useStyles } from '../../../../../component-library/hooks';
import { useMusdConversionFlowData } from '../../../Earn/hooks/useMusdConversionFlowData';
import { useMusdBalance } from '../../../Earn/hooks/useMusdBalance';
import {
MUSD_CONVERSION_DEFAULT_CHAIN_ID,
MUSD_TOKEN_ADDRESS_BY_CHAIN,
MUSD_TOKEN_ASSET_ID_BY_CHAIN,
} from '../../../Earn/constants/musd';
import { Hex } from '@metamask/utils';
import { useRampNavigation } from '../../../Ramp/hooks/useRampNavigation';
import { useMoneyAccountDeposit } from '../../hooks/useMoneyAccount';
import { useMMPayFiatConfig } from '../../../../Views/confirmations/hooks/pay/useMMPayFiatConfig';
import { useElevatedSurface } from '../../../../../util/theme/themeUtils';
import styleSheet from './MoneyAddMoneySheet.styles';
import { MoneyAddMoneySheetTestIds } from './MoneyAddMoneySheet.testIds';
Expand All @@ -54,9 +53,12 @@ const MoneyAddMoneySheet: React.FC = () => {
tokenBalanceAggregated,
tokenBalanceByChain,
} = useMusdBalance();
const { getChainIdForBuyFlow } = useMusdConversionFlowData();
const { goToBuy } = useRampNavigation();
const { initiateDeposit } = useMoneyAccountDeposit();
const { enabledTransactionTypes } = useMMPayFiatConfig();
const isFiatDepositEnabled = useMemo(
() => enabledTransactionTypes.includes(TransactionType.moneyAccountDeposit),
[enabledTransactionTypes],
);

const closeAndNavigate = useCallback((navigateFn: () => void) => {
sheetRef.current?.onCloseBottomSheet(navigateFn);
Expand All @@ -72,17 +74,11 @@ const MoneyAddMoneySheet: React.FC = () => {
});
}, [closeAndNavigate, initiateDeposit]);

// TODO(MUSD-479): point to the Ramps "Add funds" amount-entry screen
// (Figma 2547:8780). Interim: unified smart-routed Buy flow with mUSD
// pre-selected so the destination matches the Money Hub experience.
const handleDepositFunds = useCallback(() => {
const chainId = getChainIdForBuyFlow
? getChainIdForBuyFlow()
: MUSD_CONVERSION_DEFAULT_CHAIN_ID;
closeAndNavigate(() => {
goToBuy({ assetId: MUSD_TOKEN_ASSET_ID_BY_CHAIN[chainId] });
initiateDeposit({ autoSelectFiatPayment: true }).catch(() => undefined);
});
}, [closeAndNavigate, getChainIdForBuyFlow, goToBuy]);
}, [closeAndNavigate, initiateDeposit]);

const handleMoveMusd = useCallback(() => {
let sourceChainId: Hex = MUSD_CONVERSION_DEFAULT_CHAIN_ID;
Expand Down Expand Up @@ -129,14 +125,21 @@ const MoneyAddMoneySheet: React.FC = () => {
onPress: handleConvertCrypto,
testID: MoneyAddMoneySheetTestIds.CONVERT_CRYPTO_OPTION,
},
{
label: strings('money.add_money_sheet.deposit_funds'),
description: strings('money.add_money_sheet.deposit_funds_description'),
descriptionTestID: MoneyAddMoneySheetTestIds.DEPOSIT_FUNDS_DESCRIPTION,
icon: IconName.AttachMoney,
onPress: handleDepositFunds,
testID: MoneyAddMoneySheetTestIds.DEPOSIT_FUNDS_OPTION,
},
...(isFiatDepositEnabled
? [
{
label: strings('money.add_money_sheet.deposit_funds'),
description: strings(
'money.add_money_sheet.deposit_funds_description',
),
descriptionTestID:
MoneyAddMoneySheetTestIds.DEPOSIT_FUNDS_DESCRIPTION,
icon: IconName.AttachMoney,
onPress: handleDepositFunds,
testID: MoneyAddMoneySheetTestIds.DEPOSIT_FUNDS_OPTION,
},
]
: []),
];

const options: Option[] = hasMusdBalance
Expand Down
17 changes: 17 additions & 0 deletions app/components/UI/Money/hooks/useMoneyAccount.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ describe('useMoneyAccountDeposit', () => {
loader: ConfirmationLoader.CustomAmount,
stack: Routes.MONEY.CONFIRMATIONS_ROOT,
preferredPaymentToken: undefined,
autoSelectFiatPayment: undefined,
});

expect(mockAddTransactionBatch).toHaveBeenCalledWith(
Expand All @@ -214,6 +215,21 @@ describe('useMoneyAccountDeposit', () => {
);
});

it('passes autoSelectFiatPayment to navigateToConfirmation', async () => {
const { result } = renderHook(() => useMoneyAccountDeposit());

await act(async () => {
await result.current.initiateDeposit({ autoSelectFiatPayment: true });
});

expect(getNavigateToConfirmation()).toHaveBeenCalledWith({
loader: ConfirmationLoader.CustomAmount,
stack: Routes.MONEY.CONFIRMATIONS_ROOT,
preferredPaymentToken: undefined,
autoSelectFiatPayment: true,
});
});

it('pre-generates a batchId, registers intent before the await, and forwards preferredPaymentToken', async () => {
const preferredPaymentToken = {
address: '0xaca92e438df0b2401ff60da7e4337b687a2435da' as Hex,
Expand Down Expand Up @@ -241,6 +257,7 @@ describe('useMoneyAccountDeposit', () => {
loader: ConfirmationLoader.CustomAmount,
stack: Routes.MONEY.CONFIRMATIONS_ROOT,
preferredPaymentToken,
autoSelectFiatPayment: undefined,
});
expect(observedBatchId).toMatch(/^0x[0-9a-f]+$/);
expect(intentAtCallTime).toBe('addMusd');
Expand Down
2 changes: 2 additions & 0 deletions app/components/UI/Money/hooks/useMoneyAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export interface InitiateDepositOptions {
chainId: Hex;
};
intent?: MoneyAccountDepositIntent;
autoSelectFiatPayment?: boolean;
}

function resolveNetworkClientId(chainId: Hex): string {
Expand Down Expand Up @@ -108,6 +109,7 @@ export function useMoneyAccountDeposit() {
loader: ConfirmationLoader.CustomAmount,
stack: Routes.MONEY.CONFIRMATIONS_ROOT,
preferredPaymentToken,
autoSelectFiatPayment: options?.autoSelectFiatPayment,
});

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export enum ConfirmationLoader {
}

export interface ConfirmationParams {
autoSelectFiatPayment?: boolean;
loader?: ConfirmationLoader;
maxValueMode?: boolean;
forceBottomSheet?: boolean;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { ReactNode, memo, useCallback, useState } from 'react';
import React, { ReactNode, memo, useCallback, useRef, useState } from 'react';
import { toCaipAssetType } from '@metamask/utils';
import { TransactionType } from '@metamask/transaction-controller';
import { PayTokenAmount, PayTokenAmountSkeleton } from '../../pay-token-amount';
Expand Down Expand Up @@ -72,10 +72,12 @@ import { CustomAmountInfoTestIds } from './custom-amount-info.testIds';
import { useConfirmationContext } from '../../../context/confirmation-context';

export interface CustomAmountInfoProps {
autoSelectFiatPayment?: boolean;
children?: ReactNode;
currency?: string;
disablePay?: boolean;
hasMax?: boolean;
hideAccountSelector?: boolean;
preferredToken?: SetPayTokenRequest;
footerText?: string;
/**
Expand Down Expand Up @@ -104,12 +106,14 @@ export interface CustomAmountInfoProps {

export const CustomAmountInfo: React.FC<CustomAmountInfoProps> = memo(
({
autoSelectFiatPayment,
children,
currency,
disableConfirm,
disablePay,
hasMax,
hasExtraBottomPadding,
hideAccountSelector,
onAmountSubmit,
hidePayTokenAmount,
preferredToken,
Expand All @@ -121,6 +125,7 @@ export const CustomAmountInfo: React.FC<CustomAmountInfoProps> = memo(
const { canSelectWithdrawToken } = useTransactionPayWithdraw();

useAutomaticTransactionPayToken({
autoSelectFiatPayment,
disable: disablePay,
preferredToken,
});
Expand All @@ -133,6 +138,12 @@ export const CustomAmountInfo: React.FC<CustomAmountInfoProps> = memo(
const { hasTokens } = useTransactionPayAvailableTokens();
const fiatPayment = useTransactionPayFiatPayment();
const selectedFiatPaymentMethodId = fiatPayment?.selectedPaymentMethodId;
const fiatEverSelectedRef = useRef(false);
if (selectedFiatPaymentMethodId) {
fiatEverSelectedRef.current = true;
}
const shouldHideAccountSelector =
hideAccountSelector && !fiatEverSelectedRef.current;
const transactionMeta = useTransactionMetadataRequest();
const transactionId = transactionMeta?.id;
const accountOverride = useTransactionAccountOverride();
Expand Down Expand Up @@ -239,17 +250,19 @@ export const CustomAmountInfo: React.FC<CustomAmountInfoProps> = memo(
<AlertMessage alertMessage={alertMessage ?? headlessBuyError} />
{!isResultReady && (
<>
{supportAccountSelection && !selectedFiatPaymentMethodId && (
<PayAccountSelector style={styles.separator} />
)}
{supportAccountSelection &&
!selectedFiatPaymentMethodId &&
!shouldHideAccountSelector && (
<PayAccountSelector style={styles.separator} />
)}
{disablePay !== true && hasTokens && <PayWithRow />}
</>
)}
{isResultReady && (
<Box>
{supportAccountSelection && !selectedFiatPaymentMethodId && (
<PayAccountSelector />
)}
{supportAccountSelection &&
!selectedFiatPaymentMethodId &&
!shouldHideAccountSelector && <PayAccountSelector />}
{disablePay !== true && hasTokens && <PayWithRow />}
{showPaymentDetails && (
<>
Expand All @@ -276,7 +289,10 @@ export const CustomAmountInfo: React.FC<CustomAmountInfoProps> = memo(
)}
{isKeyboardVisible && hasTokens && (
<DepositKeyboard
hidePercentageButtons={Boolean(selectedFiatPaymentMethodId)}
hidePercentageButtons={
Boolean(selectedFiatPaymentMethodId) ||
shouldHideAccountSelector
}
alertMessage={alertTitle}
value={amountFiat}
onChange={updatePendingAmount}
Expand Down
Loading
Loading