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
7 changes: 1 addition & 6 deletions app/components/UI/Earn/constants/musd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* mUSD Conversion Constants for Earn namespace
*/

import { CHAIN_IDS, TransactionType } from '@metamask/transaction-controller';
import { CHAIN_IDS } from '@metamask/transaction-controller';
import { Hex } from '@metamask/utils';
import { NETWORKS_CHAIN_ID } from '../../../../constants/network';

Expand Down Expand Up @@ -52,8 +52,3 @@ export const CONVERTIBLE_STABLECOINS_BY_CHAIN: Record<Hex, Hex[]> = (() => {
}
return result;
})();

// TODO: Remove this once we add to TransactionType. Requires updating transaction-controller package.
// Similar to a swap except that output token is predetermined (e.g. mUSD) and the user cannot change it.
export const MUSD_CONVERSION_TRANSACTION_TYPE =
'mUSDConversion' as TransactionType;
5 changes: 3 additions & 2 deletions app/components/UI/Earn/hooks/useMusdConversion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import {
import Engine from '../../../../core/Engine';
import Logger from '../../../../util/Logger';
import { generateTransferData } from '../../../../util/transactions';
import { MUSD_CONVERSION_TRANSACTION_TYPE } from '../constants/musd';
import { MMM_ORIGIN } from '../../../Views/confirmations/constants/confirmations';
import Routes from '../../../../constants/navigation/Routes';
import { ConfirmationLoader } from '../../../Views/confirmations/components/confirm/confirm-component';
import { Hex } from '@metamask/utils';
import { useNavigation } from '@react-navigation/native';
import { useSelector } from 'react-redux';
import { TransactionType } from '@metamask/transaction-controller';

// Mock all external dependencies
jest.mock('../../../../core/Engine');
Expand Down Expand Up @@ -160,7 +160,8 @@ describe('useMusdConversion', () => {
{
networkClientId: 'mainnet',
origin: MMM_ORIGIN,
type: MUSD_CONVERSION_TRANSACTION_TYPE,
skipInitialGasEstimate: true,
type: TransactionType.musdConversion,
nestedTransactions: [
{
to: mockConfig.outputToken.address,
Expand Down
77 changes: 43 additions & 34 deletions app/components/UI/Earn/hooks/useMusdConversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { useSelector } from 'react-redux';
import Engine from '../../../../core/Engine';
import Logger from '../../../../util/Logger';
import { generateTransferData } from '../../../../util/transactions';
import { MUSD_CONVERSION_TRANSACTION_TYPE } from '../constants/musd';
import { MMM_ORIGIN } from '../../../Views/confirmations/constants/confirmations';
import { useNavigation } from '@react-navigation/native';
import Routes from '../../../../constants/navigation/Routes';
import { ConfirmationLoader } from '../../../Views/confirmations/components/confirm/confirm-component';
import { EVM_SCOPE } from '../constants/networks';
import { selectSelectedInternalAccountByScope } from '../../../../selectors/multichainAccounts/accounts';
import { TransactionType } from '@metamask/transaction-controller';

/**
* Type guard to validate allowedPaymentTokens structure.
Expand Down Expand Up @@ -161,45 +161,57 @@ export const useMusdConversion = () => {
},
});

const ZERO_HEX_VALUE = '0x0';
try {
const ZERO_HEX_VALUE = '0x0';

/**
* Create minimal transfer data with amount = 0
* The actual amount will be set by the user on the confirmation screen
*/
const transferData = generateTransferData('transfer', {
toAddress: selectedAddress,
amount: ZERO_HEX_VALUE,
});
/**
* Create minimal transfer data with amount = 0
* The actual amount will be set by the user on the confirmation screen
*/
const transferData = generateTransferData('transfer', {
toAddress: selectedAddress,
amount: ZERO_HEX_VALUE,
});

const { TransactionController } = Engine.context;
const { TransactionController } = Engine.context;

const { transactionMeta } = await TransactionController.addTransaction(
{
to: outputToken.address,
from: selectedAddress,
data: transferData,
value: ZERO_HEX_VALUE,
chainId: outputToken.chainId,
},
{
networkClientId,
origin: MMM_ORIGIN,
type: MUSD_CONVERSION_TRANSACTION_TYPE,
// Important: Nested transaction is required for Relay to work. This will be fixed in a future iteration.
nestedTransactions: [
const { transactionMeta } =
await TransactionController.addTransaction(
{
to: outputToken.address,
data: transferData as Hex,
from: selectedAddress,
data: transferData,
value: ZERO_HEX_VALUE,
chainId: outputToken.chainId,
},
],
},
);
{
/**
* Calculate gas estimate asynchronously.
* Enabling this reduces our first paint time on the mUSD conversion screen by ~500ms.
*/
skipInitialGasEstimate: true,
networkClientId,
origin: MMM_ORIGIN,
type: TransactionType.musdConversion,
// Important: Nested transaction is required for Relay to work. This will be fixed in a future iteration.
nestedTransactions: [
{
to: outputToken.address,
data: transferData as Hex,
value: ZERO_HEX_VALUE,
},
],
},
);

const newTransactionId = transactionMeta.id;
const newTransactionId = transactionMeta.id;

return newTransactionId;
return newTransactionId;
} catch (err) {
// Prevent the user from being stuck on the confirmation screen without a transaction.
navigation.goBack();
throw err;
}
} catch (err) {
const errorMessage =
err instanceof Error
Expand All @@ -213,9 +225,6 @@ export const useMusdConversion = () => {

setError(errorMessage);

// Prevent user from being stuck on confirmation screen without a transaction.
navigation.goBack();

throw err;
}
},
Expand Down
10 changes: 5 additions & 5 deletions app/components/UI/Earn/hooks/useMusdConversionStatus.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import {
TransactionMeta,
TransactionStatus,
TransactionType,
} from '@metamask/transaction-controller';
import { renderHook } from '@testing-library/react-hooks';
import Engine from '../../../../core/Engine';
import { useMusdConversionStatus } from './useMusdConversionStatus';
import useEarnToasts, { EarnToastOptionsConfig } from './useEarnToasts';
import { MUSD_CONVERSION_TRANSACTION_TYPE } from '../constants/musd';
import { ToastVariants } from '../../../../component-library/components/Toast/Toast.types';
import { IconName } from '../../../../component-library/components/Icons/Icon';
import { NotificationFeedbackType } from 'expo-haptics';
Expand Down Expand Up @@ -90,7 +90,7 @@ describe('useMusdConversionStatus', () => {
const createTransactionMeta = (
status: TransactionStatus,
transactionId = 'test-transaction-1',
type = MUSD_CONVERSION_TRANSACTION_TYPE,
type = TransactionType.musdConversion,
): TransactionMeta => ({
id: transactionId,
status,
Expand Down Expand Up @@ -356,7 +356,7 @@ describe('useMusdConversionStatus', () => {
const transactionMeta = createTransactionMeta(
TransactionStatus.submitted,
'test-transaction-5',
'contractInteraction' as typeof MUSD_CONVERSION_TRANSACTION_TYPE,
'contractInteraction' as typeof TransactionType.musdConversion,
);

handler({ transactionMeta });
Expand All @@ -371,7 +371,7 @@ describe('useMusdConversionStatus', () => {
const transactionMeta = createTransactionMeta(
TransactionStatus.confirmed,
'test-transaction-6',
'swap' as typeof MUSD_CONVERSION_TRANSACTION_TYPE,
'swap' as typeof TransactionType.musdConversion,
);

handler({ transactionMeta });
Expand All @@ -386,7 +386,7 @@ describe('useMusdConversionStatus', () => {
const transactionMeta = createTransactionMeta(
TransactionStatus.failed,
'test-transaction-7',
'simpleSend' as typeof MUSD_CONVERSION_TRANSACTION_TYPE,
'simpleSend' as typeof TransactionType.musdConversion,
);

handler({ transactionMeta });
Expand Down
5 changes: 2 additions & 3 deletions app/components/UI/Earn/hooks/useMusdConversionStatus.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import {
TransactionMeta,
TransactionStatus,
TransactionType,
} from '@metamask/transaction-controller';
import { useEffect, useRef } from 'react';
import Engine from '../../../../core/Engine';
import useEarnToasts from './useEarnToasts';
import { MUSD_CONVERSION_TRANSACTION_TYPE } from '../constants/musd';

/**
* Hook to monitor mUSD conversion transaction status and show appropriate toasts
*
Expand All @@ -33,7 +32,7 @@ export const useMusdConversionStatus = () => {
}: {
transactionMeta: TransactionMeta;
}) => {
if (transactionMeta.type !== MUSD_CONVERSION_TRANSACTION_TYPE) {
if (transactionMeta.type !== TransactionType.musdConversion) {
return;
}

Expand Down
3 changes: 1 addition & 2 deletions app/components/UI/Earn/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ const EarnScreenStack = () => (
name={Routes.FULL_SCREEN_CONFIRMATIONS.REDESIGNED_CONFIRMATIONS}
component={Confirm}
options={{
title: '',
headerShown: true,
headerShown: false,
}}
/>
</Stack.Navigator>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ interface TokenListItemProps {
setShowScamWarningModal: (arg: boolean) => void;
privacyMode: boolean;
showPercentageChange?: boolean;
isFullView?: boolean;
}

export const TokenListItemBip44 = React.memo(
Expand All @@ -60,6 +61,7 @@ export const TokenListItemBip44 = React.memo(
setShowScamWarningModal,
privacyMode,
showPercentageChange = true,
isFullView = false,
}: TokenListItemProps) => {
const { trackEvent, createEventBuilder } = useMetrics();
const navigation = useNavigation();
Expand Down Expand Up @@ -148,7 +150,7 @@ export const TokenListItemBip44 = React.memo(
trackEvent(
createEventBuilder(MetaMetricsEvents.TOKEN_DETAILS_OPENED)
.addProperties({
source: 'mobile-token-list',
source: isFullView ? 'mobile-token-list-page' : 'mobile-token-list',
chain_id: token.chainId,
token_symbol: token.symbol,
})
Expand Down
4 changes: 3 additions & 1 deletion app/components/UI/Tokens/TokenList/TokenListItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ interface TokenListItemProps {
setShowScamWarningModal: (arg: boolean) => void;
privacyMode: boolean;
showPercentageChange?: boolean;
isFullView?: boolean;
}

export const TokenListItem = React.memo(
Expand All @@ -98,6 +99,7 @@ export const TokenListItem = React.memo(
setShowScamWarningModal,
privacyMode,
showPercentageChange = true,
isFullView = false,
}: TokenListItemProps) => {
const { trackEvent, createEventBuilder } = useMetrics();
const navigation = useNavigation();
Expand Down Expand Up @@ -346,7 +348,7 @@ export const TokenListItem = React.memo(
trackEvent(
createEventBuilder(MetaMetricsEvents.TOKEN_DETAILS_OPENED)
.addProperties({
source: 'mobile-token-list',
source: isFullView ? 'mobile-token-list-page' : 'mobile-token-list',
chain_id: token.chainId,
token_symbol: token.symbol,
})
Expand Down
3 changes: 3 additions & 0 deletions app/components/UI/Tokens/TokenList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ const TokenListComponent = ({
setShowScamWarningModal={setShowScamWarningModal}
privacyMode={privacyMode}
showPercentageChange={showPercentageChange}
isFullView={isFullView}
/>
),
[
Expand All @@ -108,6 +109,7 @@ const TokenListComponent = ({
privacyMode,
showPercentageChange,
TokenListItemComponent,
isFullView,
],
);

Expand All @@ -125,6 +127,7 @@ const TokenListComponent = ({
setShowScamWarningModal={setShowScamWarningModal}
privacyMode={privacyMode}
showPercentageChange={showPercentageChange}
isFullView={isFullView}
/>
))}
{shouldShowViewAllButton && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { RootState } from '../../../../reducers';
import { MultichainAccountPermissions } from './MultichainAccountPermissions';
import Engine from '../../../../core/Engine';
import { MAINNET_DISPLAY_NAME } from '../../../../core/Engine/constants';
import { getNetworkImageSource } from '../../../../util/networks';

const mockedNavigate = jest.fn();
const mockedGoBack = jest.fn();
Expand Down Expand Up @@ -466,6 +467,47 @@ describe('MultichainAccountPermissions', () => {
});
});

describe('networkAvatars', () => {
it('renders successfully and filters wallet scopes when creating network avatars', () => {
// Arrange
const mockGetNetworkImageSource =
getNetworkImageSource as jest.MockedFunction<
typeof getNetworkImageSource
>;
mockGetNetworkImageSource.mockClear();

// Act - Render the component
const { getByTestId } = renderWithProvider(
<MultichainAccountPermissions
route={{
params: {
hostInfo: { metadata: { origin: 'test.com' } },
},
}}
/>,
{ state: mockInitialState() },
);

// Assert - Component renders successfully without crashing
expect(getByTestId('cancel-button')).toBeDefined();
expect(
getByTestId('navigate_to_edit_networks_permissions_button'),
).toBeDefined();

// If getNetworkImageSource was called, verify no wallet scopes were passed
const calls = mockGetNetworkImageSource.mock.calls;
calls.forEach((call) => {
const params = call[0];
if (params?.chainId) {
// The filter should exclude wallet scopes like 'wallet:eip155'
expect(params.chainId).not.toMatch(/^wallet:/);
// Should only be valid CAIP chain IDs
expect(params.chainId).toMatch(/^[a-z]+:\d+$/);
}
});
});
});

describe('screen navigation', () => {
it('should render correct screen based on current state', () => {
const { getByTestId } = renderWithProvider(
Expand Down
Loading
Loading