Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
66388aa
fix: switching networks from dapp permissions dapp icon has no effect…
ffmcgee725 Nov 14, 2025
6ef1fc7
feat(SDKConnectV2): add toasts for non-success states (#22610)
ffmcgee725 Nov 14, 2025
759b2f8
fix: Change to available fiat value text when fiat mode is enabled (#…
OGPoyraz Nov 14, 2025
cc9c67a
fix: remove browser navigation on account click when in initial conne…
ffmcgee725 Nov 14, 2025
18c3221
fix: cp-7.59.0 Fix submit loading for nonEVM send transactions (#22697)
OGPoyraz Nov 14, 2025
3e90034
fix: Remove raised amount error when value deleted on fiat mode (#22529)
OGPoyraz Nov 14, 2025
862f26e
feat: ui experience enhancements in createpassword screen (#22687)
grvgoel81 Nov 14, 2025
743353c
feat: [Trending] quick actions and restructuring (1/2) (#22700)
juanmigdr Nov 14, 2025
ca43970
fix(e2e): clean up Android adb reverse (#22693)
cmd-ob Nov 14, 2025
5e8e410
fix: onboarding rehydrate tracking (#22686)
ieow Nov 14, 2025
ded0b2d
fix: Fix Predict Navigation to Cash Out and Single Market (#22711)
andrepimenta Nov 14, 2025
47354bf
refactor: update multichain address sorting logic and tests (#22623)
PatrykLucka Nov 14, 2025
f15d604
fix(perps): update slippage configuration and error handling (#22615)
abretonc7s Nov 14, 2025
52e2d21
feat(ramps): use goToRamps for Aggregator and Deposit entry points (#…
AxelGes Nov 14, 2025
c722d72
refactor: remove dead code, and add/cleanup notification tests (#22681)
Prithpal-Sooriya Nov 14, 2025
97becda
fix: prevent concurrency for `createAccount` for Snap account provide…
ccharly Nov 14, 2025
e700d86
chore: refactor AI files - keeping same logic (#22706)
jvbriones Nov 14, 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
@@ -1,6 +1,6 @@
import { InternalAccount } from '@metamask/keyring-internal-api';
import { CHAIN_IDS } from '@metamask/transaction-controller';
import { SolScope } from '@metamask/keyring-api';
import { SolScope, BtcScope, TrxScope } from '@metamask/keyring-api';
import { CaipChainId } from '@metamask/utils';
import {
sortNetworkAddressItems,
Expand Down Expand Up @@ -62,7 +62,7 @@ describe('MultichainAddressRowsList Utils', () => {
expect(sorted[0].chainId).toBe(`eip155:${CHAIN_IDS.MAINNET}`);
});

it('sorts networks with Solana second after Ethereum', () => {
it('sorts networks with Bitcoin second and Solana third after Ethereum', () => {
const items: NetworkAddressItem[] = [
{ chainId: 'eip155:0x89', networkName: 'Polygon', address: '0x123' },
{ chainId: SolScope.Mainnet, networkName: 'Solana', address: '0x123' },
Expand All @@ -71,11 +71,59 @@ describe('MultichainAddressRowsList Utils', () => {
networkName: 'Ethereum',
address: '0x123',
},
{ chainId: BtcScope.Mainnet, networkName: 'Bitcoin', address: '0x123' },
];

const sorted = sortNetworkAddressItems(items);
expect(sorted[0].chainId).toBe(`eip155:${CHAIN_IDS.MAINNET}`);
expect(sorted[1].chainId).toBe(SolScope.Mainnet);
expect(sorted[1].chainId).toBe(BtcScope.Mainnet);
expect(sorted[2].chainId).toBe(SolScope.Mainnet);
});

it('sorts networks with Tron fourth after Ethereum, Bitcoin, and Solana', () => {
const items: NetworkAddressItem[] = [
{ chainId: TrxScope.Mainnet, networkName: 'Tron', address: '0x123' },
{ chainId: 'eip155:0x89', networkName: 'Polygon', address: '0x123' },
{ chainId: SolScope.Mainnet, networkName: 'Solana', address: '0x123' },
{
chainId: `eip155:${CHAIN_IDS.MAINNET}`,
networkName: 'Ethereum',
address: '0x123',
},
{ chainId: BtcScope.Mainnet, networkName: 'Bitcoin', address: '0x123' },
];

const sorted = sortNetworkAddressItems(items);
expect(sorted[0].chainId).toBe(`eip155:${CHAIN_IDS.MAINNET}`);
expect(sorted[1].chainId).toBe(BtcScope.Mainnet);
expect(sorted[2].chainId).toBe(SolScope.Mainnet);
expect(sorted[3].chainId).toBe(TrxScope.Mainnet);
});

it('sorts networks with Linea fifth after Ethereum, Bitcoin, Solana, and Tron', () => {
const items: NetworkAddressItem[] = [
{
chainId: `eip155:${CHAIN_IDS.LINEA_MAINNET}`,
networkName: 'Linea',
address: '0x123',
},
{ chainId: TrxScope.Mainnet, networkName: 'Tron', address: '0x123' },
{ chainId: 'eip155:0x89', networkName: 'Polygon', address: '0x123' },
{ chainId: SolScope.Mainnet, networkName: 'Solana', address: '0x123' },
{
chainId: `eip155:${CHAIN_IDS.MAINNET}`,
networkName: 'Ethereum',
address: '0x123',
},
{ chainId: BtcScope.Mainnet, networkName: 'Bitcoin', address: '0x123' },
];

const sorted = sortNetworkAddressItems(items);
expect(sorted[0].chainId).toBe(`eip155:${CHAIN_IDS.MAINNET}`);
expect(sorted[1].chainId).toBe(BtcScope.Mainnet);
expect(sorted[2].chainId).toBe(SolScope.Mainnet);
expect(sorted[3].chainId).toBe(TrxScope.Mainnet);
expect(sorted[4].chainId).toBe(`eip155:${CHAIN_IDS.LINEA_MAINNET}`);
});

it('sorts test networks last', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { InternalAccount } from '@metamask/keyring-internal-api';
import { CHAIN_IDS } from '@metamask/transaction-controller';
import { SolScope } from '@metamask/keyring-api';
import { SolScope, BtcScope, TrxScope } from '@metamask/keyring-api';
import { CaipChainId } from '@metamask/utils';
import { TEST_NETWORK_IDS } from '../../../../constants/network';
import { PopularList } from '../../../../util/networks/customNetworks';
Expand All @@ -18,7 +18,12 @@ export interface NetworkAddressItem {
*/
const extractHexChainId = (chainId: CaipChainId): string => {
if (chainId.startsWith('eip155:')) {
return chainId.split(':')[1];
const chainIdPart = chainId.split(':')[1];
// Convert decimal to hex format if needed (CAIP format uses decimal)
if (!chainIdPart.startsWith('0x')) {
return `0x${parseInt(chainIdPart, 10).toString(16)}`;
}
return chainIdPart;
}
return chainId;
};
Expand All @@ -33,30 +38,40 @@ const getNetworkPriority = (chainId: CaipChainId): number => {
// For EVM networks, extract hex chain ID for comparison
const hexChainId = extractHexChainId(chainId);

// Hardcoded order for top networks
if (hexChainId === CHAIN_IDS.MAINNET) {
return 0;
} // Ethereum first
if (chainId === SolScope.Mainnet) {
if (chainId === BtcScope.Mainnet) {
return 1;
} // Solana second
} // Bitcoin second
if (chainId === SolScope.Mainnet) {
return 2;
} // Solana third
if (chainId === TrxScope.Mainnet) {
return 3;
} // Tron fourth
if (hexChainId === CHAIN_IDS.LINEA_MAINNET) {
return 4;
} // Linea fifth
if (
TEST_NETWORK_IDS.includes(hexChainId as (typeof TEST_NETWORK_IDS)[number])
) {
return 4;
return 7;
} // Test networks last

// Featured networks (popular networks)
const popularChainIds = PopularList.map((network) => network.chainId);
if (popularChainIds.includes(hexChainId as `0x${string}`)) {
return 2;
return 5;
}

return 3; // Other custom networks
return 6; // Other custom networks
};

/**
* Sorts network address items according to priority:
* 1. Ethereum first, 2. Solana second, 3. Featured networks, 4. Other custom networks, 5. Test networks last
* 1. Ethereum, 2. Bitcoin, 3. Solana, 4. Tron, 5. Linea, 6. Featured networks, 7. Other custom networks, 8. Test networks last
*
* @param items - Array of NetworkAddressItem objects to sort
* @returns Sorted array of NetworkAddressItem objects
Expand Down
16 changes: 10 additions & 6 deletions app/components/UI/AssetOverview/AssetOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ import {
} from '../../../util/analytics/actionButtonTracking';
import { selectSelectedAccountGroup } from '../../../selectors/multichainAccounts/accountTreeController';
import { selectSelectedInternalAccountByScope } from '../../../selectors/multichainAccounts/accounts';
import { createBuyNavigationDetails } from '../Ramp/Aggregator/routes/utils';
import { useRampNavigation, RampMode } from '../Ramp/hooks/useRampNavigation';
import { RampType as AggregatorRampType } from '../Ramp/Aggregator/types';
import { TokenI } from '../Tokens/types';
import AssetDetailsActions from '../../../components/Views/AssetDetails/AssetDetailsActions';
import {
Expand Down Expand Up @@ -168,6 +169,7 @@ const AssetOverview: React.FC<AssetOverviewProps> = ({
///: END:ONLY_INCLUDE_IF

const currentAddress = asset.address as Hex;
const { goToRamps } = useRampNavigation();

const { data: prices = [], isLoading } = useTokenHistoricalPrices({
asset,
Expand Down Expand Up @@ -337,11 +339,13 @@ const AssetOverview: React.FC<AssetOverviewProps> = ({
assetId = undefined;
}

navigation.navigate(
...createBuyNavigationDetails({
assetId,
}),
);
goToRamps({
mode: RampMode.AGGREGATOR,
params: {
rampType: AggregatorRampType.BUY,
intent: assetId ? { assetId } : undefined,
},
});

trackEvent(
createEventBuilder(MetaMetricsEvents.BUY_BUTTON_CLICKED)
Expand Down
33 changes: 8 additions & 25 deletions app/components/UI/BalanceEmptyState/BalanceEmptyState.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,13 @@ import { backgroundState } from '../../../util/test/initial-root-state';
import BalanceEmptyState from './BalanceEmptyState';
import { BalanceEmptyStateProps } from './BalanceEmptyState.types';

// Mock navigation (component requires it)
const mockNavigate = jest.fn();
jest.mock('@react-navigation/native', () => ({
...jest.requireActual('@react-navigation/native'),
useNavigation: () => ({
navigate: mockNavigate,
}),
// Mock useRampNavigation hook
const mockGoToRamps = jest.fn();
jest.mock('../Ramp/hooks/useRampNavigation', () => ({
useRampNavigation: jest.fn(() => ({ goToRamps: mockGoToRamps })),
RampMode: { AGGREGATOR: 'AGGREGATOR', DEPOSIT: 'DEPOSIT' },
}));

// Mock buy navigation details
const mockBuyNavigationDetails = ['RampBuy', { screen: 'GetStarted' }];
jest.mock('../Ramp/Aggregator/routes/utils', () => ({
createBuyNavigationDetails: jest.fn(() => mockBuyNavigationDetails),
}));

// Get the mock function to verify calls
const { createBuyNavigationDetails } = jest.requireMock(
'../Ramp/Aggregator/routes/utils',
);

describe('BalanceEmptyState', () => {
beforeEach(() => {
jest.clearAllMocks();
Expand Down Expand Up @@ -60,15 +47,11 @@ describe('BalanceEmptyState', () => {

expect(actionButton).toBeDefined();

// Press the button
fireEvent.press(actionButton);

// Verify that buy navigation details are created
expect(createBuyNavigationDetails).toHaveBeenCalled();

// Verify that navigation was triggered with buy flow parameters
expect(mockNavigate).toHaveBeenCalledWith('RampBuy', {
screen: 'GetStarted',
expect(mockGoToRamps).toHaveBeenCalledWith({
mode: 'AGGREGATOR',
params: { rampType: expect.anything() },
});
});
});
11 changes: 7 additions & 4 deletions app/components/UI/BalanceEmptyState/BalanceEmptyState.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import { Image } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { useSelector } from 'react-redux';
import {
Box,
Expand All @@ -21,7 +20,8 @@ import { MetaMetricsEvents, useMetrics } from '../../hooks/useMetrics';
import { getDecimalChainId } from '../../../util/networks';
import { selectChainId } from '../../../selectors/networkController';
import { trace, TraceName } from '../../../util/trace';
import { createBuyNavigationDetails } from '../Ramp/Aggregator/routes/utils';
import { useRampNavigation, RampMode } from '../Ramp/hooks/useRampNavigation';
import { RampType } from '../Ramp/Aggregator/types';
import { BalanceEmptyStateProps } from './BalanceEmptyState.types';
import bankTransferImage from '../../../images/bank-transfer.png';
import { getDetectedGeolocation } from '../../../reducers/fiatOrders';
Expand All @@ -36,12 +36,15 @@ const BalanceEmptyState: React.FC<BalanceEmptyStateProps> = ({
}) => {
const tw = useTailwind();
const chainId = useSelector(selectChainId);
const navigation = useNavigation();
const { trackEvent, createEventBuilder } = useMetrics();
const rampGeodetectedRegion = useSelector(getDetectedGeolocation);
const { goToRamps } = useRampNavigation();

const handleAction = () => {
navigation.navigate(...createBuyNavigationDetails());
goToRamps({
mode: RampMode.AGGREGATOR,
params: { rampType: RampType.BUY },
});

trackEvent(
createEventBuilder(MetaMetricsEvents.BUY_BUTTON_CLICKED).build(),
Expand Down
12 changes: 12 additions & 0 deletions app/components/UI/Card/Views/CardHome/CardHome.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,11 @@ jest.mock('../../hooks/useOpenSwaps', () => ({
useOpenSwaps: jest.fn(),
}));

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

jest.mock('../../hooks/useIsSwapEnabledForPriorityToken', () => ({
useIsSwapEnabledForPriorityToken: jest.fn(),
}));
Expand Down Expand Up @@ -563,6 +568,13 @@ describe('CardHome Component', () => {
openSwaps: mockOpenSwaps,
});

const { useRampNavigation } = jest.requireMock(
'../../../Ramp/hooks/useRampNavigation',
);
(useRampNavigation as jest.Mock).mockReturnValue({
goToRamps: jest.fn(),
});

(useMetrics as jest.Mock).mockReturnValue({
trackEvent: mockTrackEvent,
createEventBuilder: mockCreateEventBuilder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,20 @@ import { trace, TraceName } from '../../../../../util/trace';
import { CardTokenAllowance, AllowanceState } from '../../types';
import { renderScreen } from '../../../../../util/test/renderWithProvider';
import { backgroundState } from '../../../../../util/test/initial-root-state';
import { createDepositNavigationDetails } from '../../../Ramp/Deposit/routes/utils';
import {
useRampNavigation,
RampMode,
} from '../../../Ramp/hooks/useRampNavigation';
import { CardHomeSelectors } from '../../../../../../e2e/selectors/Card/CardHome.selectors';

// Mock hooks first - must be hoisted before imports
const mockUseParams = jest.fn();
const mockGoBack = jest.fn();
const mockNavigate = jest.fn();
const mockGoToRamps = jest.fn();

// Mock dependencies
jest.mock('../../../Ramp/hooks/useRampNavigation');
jest.mock('../../hooks/useOpenSwaps', () => ({
useOpenSwaps: jest.fn(),
}));
Expand Down Expand Up @@ -135,6 +140,10 @@ describe('AddFundsBottomSheet', () => {
openSwaps: mockOpenSwaps,
});

(useRampNavigation as jest.Mock).mockReturnValue({
goToRamps: mockGoToRamps,
});

(useDepositEnabled as jest.Mock).mockReturnValue({
isDepositEnabled: true,
});
Expand Down Expand Up @@ -292,9 +301,7 @@ describe('AddFundsBottomSheet', () => {

fireEvent.press(getByText('Fund with cash'));

expect(mockNavigate).toHaveBeenCalledWith(
...createDepositNavigationDetails(),
);
expect(mockGoToRamps).toHaveBeenCalledWith({ mode: RampMode.DEPOSIT });
});

it('renders component correctly', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { useCallback, useRef } from 'react';
import { useSelector } from 'react-redux';
import { useNavigation } from '@react-navigation/native';
import BottomSheet, {
BottomSheetRef,
} from '../../../../../component-library/components/BottomSheets/BottomSheet';
Expand Down Expand Up @@ -31,7 +30,10 @@ import { useOpenSwaps } from '../../hooks/useOpenSwaps';
import { MetaMetricsEvents, useMetrics } from '../../../../hooks/useMetrics';
import { strings } from '../../../../../../locales/i18n';
import { CardHomeSelectors } from '../../../../../../e2e/selectors/Card/CardHome.selectors';
import { createDepositNavigationDetails } from '../../../Ramp/Deposit/routes/utils';
import {
useRampNavigation,
RampMode,
} from '../../../Ramp/hooks/useRampNavigation';
import { safeFormatChainIdToHex } from '../../util/safeFormatChainIdToHex';
import { getDetectedGeolocation } from '../../../../../reducers/fiatOrders';
import {
Expand All @@ -52,7 +54,6 @@ export const createAddFundsModalNavigationDetails =

const AddFundsBottomSheet: React.FC = () => {
const sheetRef = useRef<BottomSheetRef>(null);
const navigation = useNavigation();
const { priorityToken } = useParams<AddFundsModalNavigationDetails>();

const { isDepositEnabled } = useDepositEnabled();
Expand All @@ -63,6 +64,7 @@ const AddFundsBottomSheet: React.FC = () => {
});
const { trackEvent, createEventBuilder } = useMetrics();
const rampGeodetectedRegion = useSelector(getDetectedGeolocation);
const { goToRamps } = useRampNavigation();

const closeBottomSheetAndNavigate = useCallback(
(navigateFunc: () => void) => {
Expand All @@ -80,7 +82,7 @@ const AddFundsBottomSheet: React.FC = () => {

const openDeposit = useCallback(() => {
closeBottomSheetAndNavigate(() => {
navigation.navigate(...createDepositNavigationDetails());
goToRamps({ mode: RampMode.DEPOSIT });
});
trackEvent(
createEventBuilder(
Expand All @@ -106,7 +108,7 @@ const AddFundsBottomSheet: React.FC = () => {
}, [
rampGeodetectedRegion,
closeBottomSheetAndNavigate,
navigation,
goToRamps,
trackEvent,
createEventBuilder,
priorityToken,
Expand Down
Loading
Loading