Skip to content

Commit fb7b81d

Browse files
authored
feat(deposit): deeplink initial support (MetaMask#19404)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** This PR adds initial support for the Deposit flow to be exposed to the Deeplinks by using `/deposit` URL ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: Added `/deposit` action for deeplinking into Deposit. ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/TRAM-2653 ## **Manual testing steps** ```gherkin Feature: Deposit Scenario: user deeplinks into deposit feature Given app is in the background When user visits /deposit deeplink Then app handles it correctly and navigates to Deposit ``` ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/user-attachments/assets/b01da70a-186a-4322-963a-932311453612 ## **Pre-merge author checklist** - [ ] 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). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] 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.
1 parent faabf54 commit fb7b81d

24 files changed

Lines changed: 252 additions & 21 deletions

app/components/UI/Card/components/AddFundsBottomSheet/AddFundsBottomSheet.test.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import { isSwapsAllowed } from '../../../Swaps/utils';
88
import { MetaMetricsEvents, useMetrics } from '../../../../hooks/useMetrics';
99
import { getDecimalChainId } from '../../../../../util/networks';
1010
import { trace, TraceName } from '../../../../../util/trace';
11-
import Routes from '../../../../../constants/navigation/Routes';
1211
import { CardTokenAllowance, AllowanceState } from '../../types';
1312
import { BottomSheetRef } from '../../../../../component-library/components/BottomSheets/BottomSheet';
1413
import { renderScreen } from '../../../../../util/test/renderWithProvider';
1514
import { backgroundState } from '../../../../../util/test/initial-root-state';
15+
import { createDepositNavigationDetails } from '../../../Ramp/Deposit/routes/utils';
1616

1717
// Mock dependencies
1818
jest.mock('../../hooks/useOpenSwaps', () => ({
@@ -297,7 +297,9 @@ describe('AddFundsBottomSheet', () => {
297297

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

300-
expect(mockNavigate).toHaveBeenCalledWith(Routes.DEPOSIT.ID);
300+
expect(mockNavigate).toHaveBeenCalledWith(
301+
...createDepositNavigationDetails(),
302+
);
301303
});
302304

303305
it('handles ref prop correctly', () => {

app/components/UI/Card/components/AddFundsBottomSheet/AddFundsBottomSheet.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { useCallback } from 'react';
2+
import { NavigationProp, ParamListBase } from '@react-navigation/native';
23
import BottomSheet, {
34
BottomSheetRef,
45
} from '../../../../../component-library/components/BottomSheets/BottomSheet';
@@ -23,20 +24,20 @@ import { CardTokenAllowance } from '../../types';
2324
import AppConstants from '../../../../../core/AppConstants';
2425
import { isSwapsAllowed } from '../../../Swaps/utils';
2526
import useDepositEnabled from '../../../Ramp/Deposit/hooks/useDepositEnabled';
26-
import Routes from '../../../../../constants/navigation/Routes';
2727
import { getDecimalChainId } from '../../../../../util/networks';
2828
import { trace, TraceName } from '../../../../../util/trace';
2929
import { useOpenSwaps } from '../../hooks/useOpenSwaps';
3030
import { MetaMetricsEvents, useMetrics } from '../../../../hooks/useMetrics';
3131
import { strings } from '../../../../../../locales/i18n';
3232
import { CardHomeSelectors } from '../../../../../../e2e/selectors/Card/CardHome.selectors';
33+
import { createDepositNavigationDetails } from '../../../Ramp/Deposit/routes/utils';
3334

3435
export interface AddFundsBottomSheetProps {
3536
setOpenAddFundsBottomSheet: (open: boolean) => void;
3637
sheetRef: React.RefObject<BottomSheetRef>;
3738
priorityToken?: CardTokenAllowance;
3839
chainId: string;
39-
navigate: (route: string) => void;
40+
navigate: NavigationProp<ParamListBase>['navigate'];
4041
}
4142

4243
const AddFundsBottomSheet: React.FC<AddFundsBottomSheetProps> = ({
@@ -71,7 +72,7 @@ const AddFundsBottomSheet: React.FC<AddFundsBottomSheetProps> = ({
7172

7273
const openDeposit = useCallback(() => {
7374
closeBottomSheetAndNavigate(() => {
74-
navigate(Routes.DEPOSIT.ID);
75+
navigate(...createDepositNavigationDetails());
7576
});
7677
trackEvent(
7778
createEventBuilder(

app/components/UI/FundActionMenu/FundActionMenu.test.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { useSelector } from 'react-redux';
88
import { MetaMetricsEvents } from '../../../core/Analytics';
99
import { WalletActionsBottomSheetSelectorsIDs } from '../../../../e2e/selectors/wallet/WalletActionsBottomSheet.selectors';
1010
import { RampType } from '../../../reducers/fiatOrders/types';
11-
import Routes from '../../../constants/navigation/Routes';
11+
import { createDepositNavigationDetails } from '../Ramp/Deposit/routes/utils';
1212

1313
// Internal dependencies.
1414
import { useMetrics } from '../../hooks/useMetrics';
@@ -244,7 +244,9 @@ describe('FundActionMenu', () => {
244244
);
245245

246246
await waitFor(() => {
247-
expect(mockNavigate).toHaveBeenCalledWith(Routes.DEPOSIT.ID);
247+
expect(mockNavigate).toHaveBeenCalledWith(
248+
...createDepositNavigationDetails(),
249+
);
248250
});
249251
});
250252

app/components/UI/FundActionMenu/FundActionMenu.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { trace, TraceName } from '../../../util/trace';
2626
import { selectCanSignTransactions } from '../../../selectors/accountsController';
2727
import { RampType } from '../../../reducers/fiatOrders/types';
2828
import useDepositEnabled from '../Ramp/Deposit/hooks/useDepositEnabled';
29-
import Routes from '../../../constants/navigation/Routes';
29+
import { createDepositNavigationDetails } from '../Ramp/Deposit/routes/utils';
3030

3131
// Types
3232
import type {
@@ -115,7 +115,7 @@ const FundActionMenu = () => {
115115
ramp_type: 'DEPOSIT',
116116
},
117117
traceName: TraceName.LoadDepositExperience,
118-
navigationAction: () => navigate(Routes.DEPOSIT.ID),
118+
navigationAction: () => navigate(...createDepositNavigationDetails()),
119119
},
120120
{
121121
type: 'buy',

app/components/UI/Ramp/Aggregator/Views/OrdersList/OrdersList.test.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -291,9 +291,6 @@ describe('OrdersList', () => {
291291
],
292292
[
293293
"Deposit",
294-
{
295-
"screen": "DepositRoot",
296-
},
297294
],
298295
]
299296
`);

app/components/UI/Ramp/Aggregator/Views/OrdersList/OrdersList.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useSelector } from 'react-redux';
66
import { OrderOrderTypeEnum } from '@consensys/on-ramp-sdk/dist/API';
77

88
import { createOrderDetailsNavDetails } from '../OrderDetails/OrderDetails';
9+
import { createDepositNavigationDetails } from '../../../Deposit/routes/utils';
910
import OrderListItem from '../../components/OrderListItem';
1011
import Row from '../../components/Row';
1112
import createStyles from './OrdersList.styles';
@@ -28,7 +29,6 @@ import { FiatOrder, getOrders } from '../../../../../../reducers/fiatOrders';
2829
import { strings } from '../../../../../../../locales/i18n';
2930
import { useTheme } from '../../../../../../util/theme';
3031
import { createDepositOrderDetailsNavDetails } from '../../../Deposit/Views/DepositOrderDetails/DepositOrderDetails';
31-
import Routes from '../../../../../../constants/navigation/Routes';
3232

3333
type filterType = 'ALL' | 'PURCHASE' | 'SELL';
3434

@@ -83,9 +83,7 @@ function OrdersList() {
8383
const order = orders.find((o) => o.id === orderId);
8484

8585
if (order?.state === FIAT_ORDER_STATES.CREATED) {
86-
navigation.navigate(Routes.DEPOSIT.ID, {
87-
screen: Routes.DEPOSIT.ROOT,
88-
});
86+
navigation.navigate(...createDepositNavigationDetails());
8987
} else {
9088
navigation.navigate(
9189
...createDepositOrderDetailsNavDetails({

app/components/UI/Ramp/Aggregator/deeplink/handleRampUrl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { NavigationProp, ParamListBase } from '@react-navigation/native';
22
import handleRedirection from './handleRedirection';
3-
import getRedirectPathsAndParams from './utils/getRedirectPathAndParams';
3+
import getRedirectPathsAndParams from '../../utils/getRedirectPathAndParams';
44
import { RampType } from '../types';
55
import parseRampIntent from '../utils/parseRampIntent';
66
import {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { NavigationProp, ParamListBase } from '@react-navigation/native';
2+
import Routes from '../../../../../constants/navigation/Routes';
3+
import handleDepositUrl from './handleDepositUrl';
4+
5+
jest.mock('@react-navigation/native');
6+
7+
describe('handleDepositUrl', () => {
8+
let navigation: NavigationProp<ParamListBase>;
9+
10+
beforeEach(() => {
11+
navigation = {
12+
navigate: jest.fn(),
13+
} as unknown as NavigationProp<ParamListBase>;
14+
});
15+
16+
it('navigates to Deposit route when query params do not have allowed fields', () => {
17+
handleDepositUrl({
18+
depositPath: '?as=example',
19+
navigation,
20+
});
21+
expect(navigation.navigate).toHaveBeenCalledWith(Routes.DEPOSIT.ID);
22+
});
23+
24+
it('navigates to Deposit route when query params are allowed', () => {
25+
handleDepositUrl({
26+
depositPath:
27+
'?assetId=eip155:1/erc20:0xacA92E438df0B2401fF60dA7E4337B687a2435DA',
28+
navigation,
29+
});
30+
expect(navigation.navigate).toHaveBeenCalledWith(Routes.DEPOSIT.ID, {
31+
screen: Routes.DEPOSIT.ID,
32+
params: {
33+
assetId: 'eip155:1/erc20:0xacA92E438df0B2401fF60dA7E4337B687a2435DA',
34+
},
35+
});
36+
});
37+
38+
it('navigates to Deposit route when query params are encoded and allowed', () => {
39+
handleDepositUrl({
40+
depositPath:
41+
'?assetId=eip155%3A1%2Ferc20%3A0xacA92E438df0B2401fF60dA7E4337B687a2435DA',
42+
navigation,
43+
});
44+
expect(navigation.navigate).toHaveBeenCalledWith(Routes.DEPOSIT.ID, {
45+
screen: Routes.DEPOSIT.ID,
46+
params: {
47+
assetId: 'eip155:1/erc20:0xacA92E438df0B2401fF60dA7E4337B687a2435DA',
48+
},
49+
});
50+
});
51+
});
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { NavigationProp, ParamListBase } from '@react-navigation/native';
2+
import Logger from '../../../../../util/Logger';
3+
import getRedirectPathsAndParams from '../../utils/getRedirectPathAndParams';
4+
import { createDepositNavigationDetails } from '../routes/utils';
5+
import parseDepositParams from './utils/parseDepositParams';
6+
7+
interface DepositUrlOptions {
8+
depositPath: string;
9+
navigation: NavigationProp<ParamListBase>;
10+
}
11+
12+
export default function handleDepositUrl({
13+
depositPath,
14+
navigation,
15+
}: DepositUrlOptions) {
16+
try {
17+
const [, pathParams] = getRedirectPathsAndParams(depositPath);
18+
19+
let depositParams;
20+
if (pathParams) {
21+
depositParams = parseDepositParams(pathParams);
22+
}
23+
navigation.navigate(...createDepositNavigationDetails(depositParams));
24+
} catch (error) {
25+
Logger.error(
26+
error as Error,
27+
`Error in handleDepositUrl. depositPath: ${depositPath}`,
28+
);
29+
}
30+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import parseDepositParams from './parseDepositParams';
2+
3+
describe('parseDepositParams', () => {
4+
it('returns undefined if depositParams do not contain necessary fields', () => {
5+
const depositParams = {};
6+
const result = parseDepositParams(depositParams);
7+
expect(result).toBeUndefined();
8+
});
9+
10+
it('returns a DepositNavigationParams object if depositParams contain necessary fields', () => {
11+
const depositParams = {
12+
assetId: 'eip155:1/erc20:0xacA92E438df0B2401fF60dA7E4337B687a2435DA',
13+
amount: '10',
14+
};
15+
const result = parseDepositParams(depositParams);
16+
expect(result).toEqual({
17+
assetId: 'eip155:1/erc20:0xacA92E438df0B2401fF60dA7E4337B687a2435DA',
18+
amount: '10',
19+
});
20+
});
21+
22+
it('returns a DepositNavigationParams object with only defined fields', () => {
23+
const depositParams = {
24+
assetId: 'eip155:1/erc20:0xacA92E438df0B2401fF60dA7E4337B687a2435DA',
25+
amount: '10',
26+
extraneaous: 'field',
27+
};
28+
const result = parseDepositParams(depositParams);
29+
expect(result).toEqual({
30+
assetId: 'eip155:1/erc20:0xacA92E438df0B2401fF60dA7E4337B687a2435DA',
31+
amount: '10',
32+
});
33+
});
34+
35+
it('returns a DepositNavigationParams object with only defined values', () => {
36+
const depositParams = {
37+
assetId: 'eip155:1/erc20:0xacA92E438df0B2401fF60dA7E4337B687a2435DA',
38+
amount: undefined,
39+
};
40+
const result = parseDepositParams(depositParams);
41+
expect(result).toEqual({
42+
assetId: 'eip155:1/erc20:0xacA92E438df0B2401fF60dA7E4337B687a2435DA',
43+
});
44+
});
45+
});

0 commit comments

Comments
 (0)