Skip to content

Commit 402fccb

Browse files
feat: metamask pay with send picker (#22363)
## **Description** Use asset picker from send flow in MetaMask Pay. Also display disabled entries in token list, including associated message. ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: [#6174](MetaMask/MetaMask-planning#6174) ## **Manual testing steps** ## **Screenshots/Recordings** ### **Before** ### **After** <img width="300" alt="Asset Picker" src="https://github.com/user-attachments/assets/dffb1e98-32c5-43f7-afd7-8bfadbff07e6" /> ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Replaces the Pay With modal’s token selector with the Send flow’s Asset picker, adds disabled-token handling/no-gas messaging, drops the network selector modal, and updates hooks/types/tests accordingly. > > - **UI/UX**: > - **PayWithModal**: Refactored to BottomSheet using `send/asset` picker; filters to EVM `ERC20` tokens, shows zero-balance tokens if required/selected, and disables tokens lacking native gas with `pay_with_modal.no_gas` message. > - **Token**: Supports `disabled` state (press disabled, opacity) and optional `disabledMessage` display. > - **Asset/TokenList**: New props `hideNfts`, `includeNoBalance`, `onTokenSelect`, `tokenFilter`; adaptive search placeholder; NFT section hidden when `hideNfts`. > - **Navigation**: > - Removed `Routes.CONFIRMATION_PAY_WITH_NETWORK_MODAL` screen and references. > - **Data/Hooks/Types**: > - `useAccountTokens` accepts `{ includeNoBalance }` to optionally include zero-balance assets. > - Added `disabled`/`disabledMessage` to `AssetType`. > - Removed `useTransactionPayAvailableTokens` hook and its tests. > - **Localization**: > - Added strings: `send.search_tokens`, `pay_with_modal.no_gas`. > - **Tests**: > - Added/updated tests for PayWithModal filtering/selection, token disabled behavior, Asset/TokenList props and behavior; removed obsolete network modal and available-tokens tests. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit c264c90. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 7a3407e commit 402fccb

File tree

17 files changed

+435
-712
lines changed

17 files changed

+435
-712
lines changed

app/components/Nav/App/App.tsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,6 @@ import { Duration } from '@metamask/utils';
148148
import { selectSeedlessOnboardingLoginFlow } from '../../../selectors/seedlessOnboardingController';
149149
import { SmartAccountUpdateModal } from '../../Views/confirmations/components/smart-account-update-modal';
150150
import { PayWithModal } from '../../Views/confirmations/components/modals/pay-with-modal/pay-with-modal';
151-
import { PayWithNetworkModal } from '../../Views/confirmations/components/modals/pay-with-network-modal/pay-with-network-modal';
152151
import { useMetrics } from '../../hooks/useMetrics';
153152
import { State2AccountConnectWrapper } from '../../Views/MultichainAccounts/MultichainAccountConnect/State2AccountConnectWrapper';
154153
import { SmartAccountModal } from '../../Views/MultichainAccounts/AccountDetails/components/SmartAccountModal/SmartAccountModal';
@@ -1052,10 +1051,6 @@ const AppFlow = () => {
10521051
name={Routes.CONFIRMATION_PAY_WITH_MODAL}
10531052
component={PayWithModal}
10541053
/>
1055-
<Stack.Screen
1056-
name={Routes.CONFIRMATION_PAY_WITH_NETWORK_MODAL}
1057-
component={PayWithNetworkModal}
1058-
/>
10591054
</Stack.Navigator>
10601055
</>
10611056
);

app/components/Views/confirmations/components/UI/token/token.test.tsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { BtcAccountType } from '@metamask/keyring-api';
44
import renderWithProvider from '../../../../../../util/test/renderWithProvider';
55
import { AssetType } from '../../../types/token';
66
import { Token } from './token';
7+
import { act, fireEvent } from '@testing-library/react-native';
78

89
describe('Token', () => {
910
const createMockToken = (overrides: Partial<AssetType> = {}): AssetType => ({
@@ -129,4 +130,48 @@ describe('Token', () => {
129130

130131
expect(getByText('Native SegWit')).toBeOnTheScreen();
131132
});
133+
134+
it('renders disabled message when token is disabled', () => {
135+
const mockToken = createMockToken({
136+
disabled: true,
137+
disabledMessage: 'Disabled Test',
138+
});
139+
140+
const { getByText } = renderWithProvider(
141+
<Token asset={mockToken} onPress={mockOnPress} />,
142+
);
143+
144+
expect(getByText('Disabled Test')).toBeOnTheScreen();
145+
});
146+
147+
it('calls onPress when token is pressed', async () => {
148+
const mockToken = createMockToken();
149+
150+
const { getByText } = renderWithProvider(
151+
<Token asset={mockToken} onPress={mockOnPress} />,
152+
);
153+
154+
await act(() => {
155+
fireEvent.press(getByText('ETH'));
156+
});
157+
158+
expect(mockOnPress).toHaveBeenCalledTimes(1);
159+
expect(mockOnPress).toHaveBeenCalledWith(mockToken);
160+
});
161+
162+
it('does not call onPress when token is disabled and pressed', async () => {
163+
const mockToken = createMockToken({
164+
disabled: true,
165+
});
166+
167+
const { getByText } = renderWithProvider(
168+
<Token asset={mockToken} onPress={mockOnPress} />,
169+
);
170+
171+
await act(() => {
172+
fireEvent.press(getByText('ETH'));
173+
});
174+
175+
expect(mockOnPress).not.toHaveBeenCalled();
176+
});
132177
});

app/components/Views/confirmations/components/UI/token/token.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,12 @@ export function Token({ asset, onPress }: TokenProps) {
4141

4242
return (
4343
<Pressable
44+
disabled={asset.disabled}
4445
style={({ pressed }) =>
4546
tw.style(
4647
'w-full flex-row items-center justify-between py-2 max-w-full',
4748
pressed || asset.isSelected ? 'bg-pressed' : 'bg-transparent',
49+
asset.disabled && 'opacity-50',
4850
)
4951
}
5052
onPress={handlePress}
@@ -98,7 +100,9 @@ export function Token({ asset, onPress }: TokenProps) {
98100
color={TextColor.TextAlternative}
99101
numberOfLines={1}
100102
>
101-
{asset.symbol}
103+
{!asset.disabled || !asset.disabledMessage
104+
? asset.symbol
105+
: asset.disabledMessage}
102106
</Text>
103107
</Box>
104108
</Box>

0 commit comments

Comments
 (0)