diff --git a/app/components/UI/Money/components/MoneyAddMoneySheet/MoneyAddMoneySheet.test.tsx b/app/components/UI/Money/components/MoneyAddMoneySheet/MoneyAddMoneySheet.test.tsx
index 532aebe5d835..36d8ff5a6953 100644
--- a/app/components/UI/Money/components/MoneyAddMoneySheet/MoneyAddMoneySheet.test.tsx
+++ b/app/components/UI/Money/components/MoneyAddMoneySheet/MoneyAddMoneySheet.test.tsx
@@ -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', () => {
@@ -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(),
}));
@@ -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');
@@ -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',
@@ -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', () => {
@@ -226,7 +217,7 @@ 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();
fireEvent.press(
@@ -234,8 +225,8 @@ describe('MoneyAddMoneySheet', () => {
);
expect(mockOnCloseBottomSheet).toHaveBeenCalledTimes(1);
- expect(mockGoToBuy).toHaveBeenCalledWith({
- assetId: MUSD_TOKEN_ASSET_ID_BY_CHAIN[MUSD_CONVERSION_DEFAULT_CHAIN_ID],
+ expect(mockInitiateDeposit).toHaveBeenCalledWith({
+ autoSelectFiatPayment: true,
});
});
@@ -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();
+
+ 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',
diff --git a/app/components/UI/Money/components/MoneyAddMoneySheet/MoneyAddMoneySheet.tsx b/app/components/UI/Money/components/MoneyAddMoneySheet/MoneyAddMoneySheet.tsx
index 7b08fb74d50c..02d6b9118866 100644
--- a/app/components/UI/Money/components/MoneyAddMoneySheet/MoneyAddMoneySheet.tsx
+++ b/app/components/UI/Money/components/MoneyAddMoneySheet/MoneyAddMoneySheet.tsx
@@ -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,
@@ -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';
@@ -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);
@@ -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;
@@ -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
diff --git a/app/components/UI/Money/hooks/useMoneyAccount.test.ts b/app/components/UI/Money/hooks/useMoneyAccount.test.ts
index 18dd647f357d..aa4ef92059a0 100644
--- a/app/components/UI/Money/hooks/useMoneyAccount.test.ts
+++ b/app/components/UI/Money/hooks/useMoneyAccount.test.ts
@@ -201,6 +201,7 @@ describe('useMoneyAccountDeposit', () => {
loader: ConfirmationLoader.CustomAmount,
stack: Routes.MONEY.CONFIRMATIONS_ROOT,
preferredPaymentToken: undefined,
+ autoSelectFiatPayment: undefined,
});
expect(mockAddTransactionBatch).toHaveBeenCalledWith(
@@ -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,
@@ -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');
diff --git a/app/components/UI/Money/hooks/useMoneyAccount.ts b/app/components/UI/Money/hooks/useMoneyAccount.ts
index bab89f948cb6..47b193f4e21d 100644
--- a/app/components/UI/Money/hooks/useMoneyAccount.ts
+++ b/app/components/UI/Money/hooks/useMoneyAccount.ts
@@ -45,6 +45,7 @@ export interface InitiateDepositOptions {
chainId: Hex;
};
intent?: MoneyAccountDepositIntent;
+ autoSelectFiatPayment?: boolean;
}
function resolveNetworkClientId(chainId: Hex): string {
@@ -108,6 +109,7 @@ export function useMoneyAccountDeposit() {
loader: ConfirmationLoader.CustomAmount,
stack: Routes.MONEY.CONFIRMATIONS_ROOT,
preferredPaymentToken,
+ autoSelectFiatPayment: options?.autoSelectFiatPayment,
});
try {
diff --git a/app/components/Views/confirmations/components/confirm/confirm-component.tsx b/app/components/Views/confirmations/components/confirm/confirm-component.tsx
index 381aaf66e46e..11e82c4b014c 100755
--- a/app/components/Views/confirmations/components/confirm/confirm-component.tsx
+++ b/app/components/Views/confirmations/components/confirm/confirm-component.tsx
@@ -58,6 +58,7 @@ export enum ConfirmationLoader {
}
export interface ConfirmationParams {
+ autoSelectFiatPayment?: boolean;
loader?: ConfirmationLoader;
maxValueMode?: boolean;
forceBottomSheet?: boolean;
diff --git a/app/components/Views/confirmations/components/info/custom-amount-info/custom-amount-info.tsx b/app/components/Views/confirmations/components/info/custom-amount-info/custom-amount-info.tsx
index 0942f562b983..93d8f1592925 100644
--- a/app/components/Views/confirmations/components/info/custom-amount-info/custom-amount-info.tsx
+++ b/app/components/Views/confirmations/components/info/custom-amount-info/custom-amount-info.tsx
@@ -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';
@@ -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;
/**
@@ -104,12 +106,14 @@ export interface CustomAmountInfoProps {
export const CustomAmountInfo: React.FC = memo(
({
+ autoSelectFiatPayment,
children,
currency,
disableConfirm,
disablePay,
hasMax,
hasExtraBottomPadding,
+ hideAccountSelector,
onAmountSubmit,
hidePayTokenAmount,
preferredToken,
@@ -121,6 +125,7 @@ export const CustomAmountInfo: React.FC = memo(
const { canSelectWithdrawToken } = useTransactionPayWithdraw();
useAutomaticTransactionPayToken({
+ autoSelectFiatPayment,
disable: disablePay,
preferredToken,
});
@@ -133,6 +138,12 @@ export const CustomAmountInfo: React.FC = 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();
@@ -239,17 +250,19 @@ export const CustomAmountInfo: React.FC = memo(
{!isResultReady && (
<>
- {supportAccountSelection && !selectedFiatPaymentMethodId && (
-
- )}
+ {supportAccountSelection &&
+ !selectedFiatPaymentMethodId &&
+ !shouldHideAccountSelector && (
+
+ )}
{disablePay !== true && hasTokens && }
>
)}
{isResultReady && (
- {supportAccountSelection && !selectedFiatPaymentMethodId && (
-
- )}
+ {supportAccountSelection &&
+ !selectedFiatPaymentMethodId &&
+ !shouldHideAccountSelector && }
{disablePay !== true && hasTokens && }
{showPaymentDetails && (
<>
@@ -276,7 +289,10 @@ export const CustomAmountInfo: React.FC = memo(
)}
{isKeyboardVisible && hasTokens && (
({
+ useParams: () => mockUseParams(),
+}));
+
const mockUseMoneyAccountDepositNavbar = jest.fn();
jest.mock('../../../../../UI/Money/hooks/useMoneyAccountDepositNavbar', () => ({
useMoneyAccountDepositNavbar: () => mockUseMoneyAccountDepositNavbar(),
@@ -29,16 +34,12 @@ jest.mock('../../../../../../../locales/i18n', () => ({
({ 'confirm.title.money_account_add_money': 'Add funds' })[key] ?? key,
}));
-const mockUseParams = jest.fn(() => ({}));
-jest.mock('../../../../../../util/navigation/navUtils', () => ({
- useParams: () => mockUseParams(),
-}));
-
describe('MoneyAccountDepositInfo', () => {
beforeEach(() => {
jest.clearAllMocks();
mockCustomAmountInfo.mockClear();
mockUseMoneyAccountDepositNavbar.mockReturnValue(undefined);
+ mockUseParams.mockReturnValue({});
});
it('renders CustomAmountInfo with usd currency', () => {
@@ -80,6 +81,19 @@ describe('MoneyAccountDepositInfo', () => {
expect(lastCall.hasMax).toBe(true);
});
+ it('passes autoSelectFiatPayment and hideAccountSelector from route params', () => {
+ mockUseParams.mockReturnValue({ autoSelectFiatPayment: true });
+
+ render();
+
+ const lastCall =
+ mockCustomAmountInfo.mock.calls[
+ mockCustomAmountInfo.mock.calls.length - 1
+ ][0];
+ expect(lastCall.autoSelectFiatPayment).toBe(true);
+ expect(lastCall.hideAccountSelector).toBe(true);
+ });
+
it('forwards preferredPaymentToken from route params to CustomAmountInfo', () => {
const preferredPaymentToken = {
address: '0xaca92e438df0b2401ff60da7e4337b687a2435da',
@@ -96,13 +110,16 @@ describe('MoneyAccountDepositInfo', () => {
expect(lastCall.preferredToken).toEqual(preferredPaymentToken);
});
- it('passes undefined preferredToken when no preferredPaymentToken in params', () => {
+ it('does not pass autoSelectFiatPayment when route param is absent', () => {
+ mockUseParams.mockReturnValue({});
+
render();
const lastCall =
mockCustomAmountInfo.mock.calls[
mockCustomAmountInfo.mock.calls.length - 1
][0];
- expect(lastCall.preferredToken).toBeUndefined();
+ expect(lastCall.autoSelectFiatPayment).toBeUndefined();
+ expect(lastCall.hideAccountSelector).toBeUndefined();
});
});
diff --git a/app/components/Views/confirmations/components/info/money-account-deposit-info/money-account-deposit-info.tsx b/app/components/Views/confirmations/components/info/money-account-deposit-info/money-account-deposit-info.tsx
index 8fb5e6ecc902..566064fa18d0 100644
--- a/app/components/Views/confirmations/components/info/money-account-deposit-info/money-account-deposit-info.tsx
+++ b/app/components/Views/confirmations/components/info/money-account-deposit-info/money-account-deposit-info.tsx
@@ -10,10 +10,15 @@ export function MoneyAccountDepositInfo() {
useMoneyAccountDepositNavbar();
const { preferredPaymentToken } = useParams({});
+ const params = useParams();
+ const autoFiat = params?.autoSelectFiatPayment;
+
return (
diff --git a/app/components/Views/confirmations/hooks/pay/useAutomaticTransactionPayToken.test.ts b/app/components/Views/confirmations/hooks/pay/useAutomaticTransactionPayToken.test.ts
index 36c82e828d64..7abd5e6d30c3 100644
--- a/app/components/Views/confirmations/hooks/pay/useAutomaticTransactionPayToken.test.ts
+++ b/app/components/Views/confirmations/hooks/pay/useAutomaticTransactionPayToken.test.ts
@@ -13,6 +13,7 @@ import { transactionApprovalControllerMock } from '../../__mocks__/controllers/a
import {
MetaMaskPayTokensFlags,
selectMetaMaskPayTokensFlags,
+ selectMetaMaskPayFiatFlags,
} from '../../../../../selectors/featureFlagController/confirmations';
import {
isHardwareAccount,
@@ -24,10 +25,14 @@ import {
TransactionPayRequiredToken,
} from '@metamask/transaction-pay-controller';
import { Hex } from '@metamask/utils';
-import { useTransactionPayRequiredTokens } from './useTransactionPayData';
+import {
+ useTransactionPayFiatPayment,
+ useTransactionPayRequiredTokens,
+} from './useTransactionPayData';
import { useTransactionPayAvailableTokens } from './useTransactionPayAvailableTokens';
import { AssetType } from '../../types/token';
import { useWithdrawTokenFilter } from './useWithdrawTokenFilter';
+import { useRampsPaymentMethods } from '../../../../UI/Ramp/hooks/useRampsPaymentMethods';
import { useTransactionMetadataRequest } from '../transactions/useTransactionMetadataRequest';
import { useTransactionAccountOverride } from '../transactions/useTransactionAccountOverride';
import { MUSD_TOKEN_ADDRESS } from '../../../../UI/Earn/constants/musd';
@@ -42,6 +47,7 @@ jest.mock('../../../../../selectors/transactionPayController');
jest.mock('./useTransactionPayData');
jest.mock('./useTransactionPayAvailableTokens');
jest.mock('./useWithdrawTokenFilter');
+jest.mock('../../../../UI/Ramp/hooks/useRampsPaymentMethods');
jest.mock('../../../../../selectors/transactionController', () => ({
...jest.requireActual('../../../../../selectors/transactionController'),
selectLastWithdrawTokenByType: jest.fn(),
@@ -53,6 +59,7 @@ jest.mock(
'../../../../../selectors/featureFlagController/confirmations',
),
selectMetaMaskPayTokensFlags: jest.fn(),
+ selectMetaMaskPayFiatFlags: jest.fn(),
}),
);
@@ -101,6 +108,9 @@ function runHook({
describe('useAutomaticTransactionPayToken', () => {
const useTransactionPayTokenMock = jest.mocked(useTransactionPayToken);
+ const useTransactionPayFiatPaymentMock = jest.mocked(
+ useTransactionPayFiatPayment,
+ );
const useTransactionPayAvailableTokensMock = jest.mocked(
useTransactionPayAvailableTokens,
);
@@ -112,6 +122,9 @@ describe('useAutomaticTransactionPayToken', () => {
const selectMetaMaskPayTokensFlagsMock = jest.mocked(
selectMetaMaskPayTokensFlags,
);
+ const selectMetaMaskPayFiatFlagsMock = jest.mocked(
+ selectMetaMaskPayFiatFlags,
+ );
const useTransactionMetadataRequestMock = jest.mocked(
useTransactionMetadataRequest,
);
@@ -160,6 +173,24 @@ describe('useAutomaticTransactionPayToken', () => {
} as never);
useTransactionAccountOverrideMock.mockReturnValue(undefined);
+
+ useTransactionPayFiatPaymentMock.mockReturnValue(undefined);
+
+ jest.mocked(useRampsPaymentMethods).mockReturnValue({
+ paymentMethods: [],
+ selectedPaymentMethod: null,
+ setSelectedPaymentMethod: jest.fn(),
+ isLoading: false,
+ isFetching: false,
+ status: 'success',
+ isSuccess: true,
+ error: null,
+ });
+
+ selectMetaMaskPayFiatFlagsMock.mockReturnValue({
+ enabledTransactionTypes: [],
+ maxDelayMinutesForPaymentMethods: 10,
+ });
});
it('selects first token', () => {
diff --git a/app/components/Views/confirmations/hooks/pay/useAutomaticTransactionPayToken.ts b/app/components/Views/confirmations/hooks/pay/useAutomaticTransactionPayToken.ts
index ea4caf2e72da..e4bd814a15b5 100644
--- a/app/components/Views/confirmations/hooks/pay/useAutomaticTransactionPayToken.ts
+++ b/app/components/Views/confirmations/hooks/pay/useAutomaticTransactionPayToken.ts
@@ -2,6 +2,7 @@ import { useTransactionMetadataRequest } from '../transactions/useTransactionMet
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { Hex } from 'viem';
import { createProjectLogger } from '@metamask/utils';
+import Engine from '../../../../../core/Engine';
import { useTransactionPayToken } from './useTransactionPayToken';
import {
isHardwareAccount,
@@ -13,7 +14,10 @@ import {
TransactionType,
} from '@metamask/transaction-controller';
import { PaymentOverride } from '@metamask/transaction-pay-controller';
-import { useTransactionPayRequiredTokens } from './useTransactionPayData';
+import {
+ useTransactionPayFiatPayment,
+ useTransactionPayRequiredTokens,
+} from './useTransactionPayData';
import { useTransactionPayAvailableTokens } from './useTransactionPayAvailableTokens';
import { AssetType } from '../../types/token';
import {
@@ -24,6 +28,7 @@ import {
import { useSelector } from 'react-redux';
import {
selectMetaMaskPayTokensFlags,
+ selectMetaMaskPayFiatFlags,
PreferredToken,
getPreferredTokensForTransactionType,
} from '../../../../../selectors/featureFlagController/confirmations';
@@ -33,6 +38,7 @@ import { selectPaymentOverrideByTransactionId } from '../../../../../selectors/t
import { MUSD_TOKEN_ADDRESS } from '../../../../UI/Earn/constants/musd';
import { useWithdrawTokenFilter } from './useWithdrawTokenFilter';
import { useTransactionAccountOverride } from '../transactions/useTransactionAccountOverride';
+import { useRampsPaymentMethods } from '../../../../UI/Ramp/hooks/useRampsPaymentMethods';
export interface SetPayTokenRequest {
address: Hex;
@@ -42,14 +48,18 @@ export interface SetPayTokenRequest {
const log = createProjectLogger('transaction-pay');
export function useAutomaticTransactionPayToken({
+ autoSelectFiatPayment = false,
disable = false,
preferredToken,
}: {
+ autoSelectFiatPayment?: boolean;
disable?: boolean;
preferredToken?: SetPayTokenRequest;
} = {}) {
const isUpdated = useRef(undefined);
const { payToken, setPayToken } = useTransactionPayToken();
+ const fiatPayment = useTransactionPayFiatPayment();
+ const hasFiatPaymentSelected = Boolean(fiatPayment?.selectedPaymentMethodId);
const requiredTokens = useTransactionPayRequiredTokens();
const { availableTokens } = useTransactionPayAvailableTokens();
const payTokensFlags = useSelector(selectMetaMaskPayTokensFlags);
@@ -149,10 +159,18 @@ export function useAutomaticTransactionPayToken({
const automaticToken = useMemo(() => selectBestToken(), [selectBestToken]);
+ const { paymentMethods } = useRampsPaymentMethods();
+ const fiatFlags = useSelector(selectMetaMaskPayFiatFlags);
+ const isFiatEnabled = hasTransactionType(
+ transactionMeta,
+ fiatFlags.enabledTransactionTypes,
+ );
+
useEffect(() => {
if (
disable ||
payToken ||
+ hasFiatPaymentSelected ||
!transactionId ||
isUpdated.current === transactionId
) {
@@ -164,6 +182,31 @@ export function useAutomaticTransactionPayToken({
return;
}
+ if (autoSelectFiatPayment) {
+ if (!isFiatEnabled || paymentMethods.length === 0) {
+ return;
+ }
+
+ const eligibleMethod = paymentMethods.find(
+ (pm) =>
+ !pm.delay ||
+ pm.delay[1] <= fiatFlags.maxDelayMinutesForPaymentMethods,
+ );
+
+ if (eligibleMethod) {
+ Engine.context.TransactionPayController.updateFiatPayment({
+ transactionId,
+ callback: (fp) => {
+ fp.selectedPaymentMethodId = eligibleMethod.id;
+ },
+ });
+ }
+
+ isUpdated.current = transactionId;
+ log('Auto-selected fiat payment method', eligibleMethod?.name);
+ return;
+ }
+
setPayToken({
address: automaticToken.address,
chainId: automaticToken.chainId,
@@ -173,9 +216,14 @@ export function useAutomaticTransactionPayToken({
log('Automatically selected pay token', automaticToken);
}, [
+ autoSelectFiatPayment,
automaticToken,
disable,
+ fiatFlags,
+ hasFiatPaymentSelected,
+ isFiatEnabled,
payToken,
+ paymentMethods,
requiredTokens,
setPayToken,
tokens,
@@ -191,6 +239,7 @@ export function useAutomaticTransactionPayToken({
const accountKey = `${from ?? ''}:${accountOverride ?? ''}`;
if (
disable ||
+ hasFiatPaymentSelected ||
!from ||
prevAccountKeyRef.current === accountKey ||
postQuoteTransactionType
@@ -211,6 +260,7 @@ export function useAutomaticTransactionPayToken({
automaticToken,
disable,
from,
+ hasFiatPaymentSelected,
postQuoteTransactionType,
setPayToken,
]);
@@ -221,6 +271,7 @@ export function useAutomaticTransactionPayToken({
useEffect(() => {
if (
disable ||
+ hasFiatPaymentSelected ||
!from ||
isMoneyPaymentOverride === previsMoneyPaymentOverrideRef.current ||
postQuoteTransactionType
@@ -240,6 +291,7 @@ export function useAutomaticTransactionPayToken({
automaticToken,
disable,
from,
+ hasFiatPaymentSelected,
postQuoteTransactionType,
setPayToken,
isMoneyPaymentOverride,