From f996de194b885948f1706a25ace5d293798375fe Mon Sep 17 00:00:00 2001
From: Matthew Grainger <46547583+Matt561@users.noreply.github.com>
Date: Tue, 15 Oct 2024 20:05:05 -0400
Subject: [PATCH] feat: STAKE-824: [FE] build staking input confirmation screen
(#11605)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## **Description**
This PR adds the staking confirmation screen with some mock data being
used temporarily.
### Change List
- Add staking confirmation screen.
- Connect existing `` to staking confirmation screen
when user enters valid amount to stake.
## **Related issues**
Ticket: [FE] Build staking input confirmation screen -
([link](https://consensyssoftware.atlassian.net/browse/STAKE-824))
Figma Designs -
[link](https://www.figma.com/design/1c0Y9jDJe6p0j82jydJDcs/Mobile-Staking?node-id=2979-22435&m=dev)
## **Manual testing steps**
1. Add `export MM_POOLED_STAKING_UI_ENABLED=true` to your `.js.env`
file.
2. Click on Ethereum In the token list page
3. Scroll down a bit and click "Stake more". This should open the stake
input view (not related to this PR)
4. Enter a valid amount to stake and click "Confirm"
5. You should be redirected to a staking confirmation screen. The screen
should display the amount to stake in `wETH` and Fiat.
## **Screenshots/Recordings**
### **Before**
Nothing would happen after clicking "Confirm" on the stake input view.
This screen is new.
### **After**
https://github.com/user-attachments/assets/84ea4c52-50c5-48c3-8077-2c2e8a92bf21
## **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
- [ ] 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.
---
app/components/UI/Navbar/index.js | 63 +-
.../StakeConfirmationView.styles.ts | 23 +
.../StakeConfirmationView.test.tsx | 74 +
.../StakeConfirmationView.tsx | 60 +
.../StakeConfirmationView.types.ts | 10 +
.../StakeConfirmationView.test.tsx.snap | 1424 +++++++++++++++++
.../Views/StakeInputView/StakeInputView.tsx | 16 +-
.../StakeInputView.test.tsx.snap | 100 +-
.../UnstakeInputView/UnstakeInputView.tsx | 6 +-
.../UnstakeInputView.test.tsx.snap | 100 +-
.../StakingBalance/StakingBalance.test.tsx | 40 +-
.../StakingBalance/StakingCta/StakingCta.tsx | 6 +-
.../__snapshots__/StakingCta.test.tsx.snap | 2 +-
.../StakingBalance.test.tsx.snap | 1385 +++++++---------
.../AccountHeaderCard.styles.ts | 40 +
.../AccountHeaderCard.test.tsx | 72 +
.../AccountHeaderCard/AccountHeaderCard.tsx | 74 +
.../AccountHeaderCard.types.ts | 3 +
.../AccountHeaderCard.test.tsx.snap | 623 ++++++++
.../AccountTag/AccountTag.test.tsx | 31 +
.../AccountTag/AccountTag.tsx | 38 +
.../AccountTag/AccountTag.types.ts | 5 +
.../__snapshots__/AccountTag.test.tsx.snap | 395 +++++
.../ConfirmationFooter.styles.ts | 114 ++
.../ConfirmationFooter.test.tsx | 19 +
.../ConfirmationFooter/ConfirmationFooter.tsx | 19 +
.../FooterButtonGroup.styles.ts | 18 +
.../FooterButtonGroup.test.tsx | 49 +
.../FooterButtonGroup/FooterButtonGroup.tsx | 59 +
.../FooterButtonGroup.test.tsx.snap | 189 +++
.../LegalLinks/LegalLinks.styles.ts | 14 +
.../LegalLinks/LegalLinks.test.tsx | 61 +
.../LegalLinks/LegalLinks.tsx | 52 +
.../__snapshots__/LegalLinks.test.tsx.snap | 187 +++
.../ConfirmationFooter.test.tsx.snap | 162 ++
.../ContractTag/ContractTag.test.tsx | 17 +
.../ContractTag/ContractTag.tsx | 23 +
.../ContractTag/ContractTag.types.ts | 3 +
.../__snapshots__/ContractTag.test.tsx.snap | 69 +
.../EstimatedGasCard.styles.ts | 39 +
.../EstimatedGasCard.test.tsx | 67 +
.../EstimatedGasCard/EstimatedGasCard.tsx | 69 +
.../EstimatedGasCard.types.ts | 4 +
.../EstimatedGasCard.test.tsx.snap | 419 +++++
.../EstimatedGasFeeTooltipContent.styles.ts | 13 +
.../EstimatedGasFeeTooltipContent.test.tsx | 53 +
.../EstimatedGasFeeTooltipContent.tsx | 43 +
...stimatedGasFeeTooltipContent.test.tsx.snap | 67 +
.../RewardsCard/RewardsCard.styles.ts | 16 +
.../RewardsCard/RewardsCard.test.tsx | 98 ++
.../RewardsCard/RewardsCard.tsx | 74 +
.../RewardsCard/RewardsCard.types.ts | 5 +
.../__snapshots__/RewardsCard.test.tsx.snap | 1255 +++++++++++++++
.../TokenValueStack/TokenValueStack.styles.ts | 23 +
.../TokenValueStack/TokenValueStack.test.tsx | 33 +
.../TokenValueStack/TokenValueStack.tsx | 54 +
.../TokenValueStack/TokenValueStack.types.ts | 7 +
.../TokenValueStack.test.tsx.snap | 212 +++
app/components/UI/Stake/routes/index.tsx | 5 +
.../Views/TooltipModal/ToolTipModal.styles.ts | 1 -
app/constants/navigation/Routes.ts | 1 +
app/core/AppConstants.ts | 1 +
locales/languages/en.json | 19 +-
63 files changed, 7149 insertions(+), 1074 deletions(-)
create mode 100644 app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.styles.ts
create mode 100644 app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.test.tsx
create mode 100644 app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.tsx
create mode 100644 app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.types.ts
create mode 100644 app/components/UI/Stake/Views/StakeConfirmationView/__snapshots__/StakeConfirmationView.test.tsx.snap
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/AccountHeaderCard/AccountHeaderCard.styles.ts
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/AccountHeaderCard/AccountHeaderCard.test.tsx
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/AccountHeaderCard/AccountHeaderCard.tsx
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/AccountHeaderCard/AccountHeaderCard.types.ts
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/AccountHeaderCard/__snapshots__/AccountHeaderCard.test.tsx.snap
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/AccountTag/AccountTag.test.tsx
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/AccountTag/AccountTag.tsx
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/AccountTag/AccountTag.types.ts
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/AccountTag/__snapshots__/AccountTag.test.tsx.snap
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/ConfirmationFooter.styles.ts
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/ConfirmationFooter.test.tsx
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/ConfirmationFooter.tsx
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.styles.ts
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.test.tsx
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.tsx
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/__snapshots__/FooterButtonGroup.test.tsx.snap
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/LegalLinks/LegalLinks.styles.ts
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/LegalLinks/LegalLinks.test.tsx
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/LegalLinks/LegalLinks.tsx
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/LegalLinks/__snapshots__/LegalLinks.test.tsx.snap
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/__snapshots__/ConfirmationFooter.test.tsx.snap
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/ContractTag/ContractTag.test.tsx
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/ContractTag/ContractTag.tsx
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/ContractTag/ContractTag.types.ts
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/ContractTag/__snapshots__/ContractTag.test.tsx.snap
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/EstimatedGasCard/EstimatedGasCard.styles.ts
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/EstimatedGasCard/EstimatedGasCard.test.tsx
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/EstimatedGasCard/EstimatedGasCard.tsx
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/EstimatedGasCard/EstimatedGasCard.types.ts
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/EstimatedGasCard/__snapshots__/EstimatedGasCard.test.tsx.snap
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/EstimatedGasFeeTooltipContent/EstimatedGasFeeTooltipContent.styles.ts
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/EstimatedGasFeeTooltipContent/EstimatedGasFeeTooltipContent.test.tsx
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/EstimatedGasFeeTooltipContent/EstimatedGasFeeTooltipContent.tsx
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/EstimatedGasFeeTooltipContent/__snapshots__/EstimatedGasFeeTooltipContent.test.tsx.snap
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/RewardsCard/RewardsCard.styles.ts
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/RewardsCard/RewardsCard.test.tsx
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/RewardsCard/RewardsCard.tsx
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/RewardsCard/RewardsCard.types.ts
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/RewardsCard/__snapshots__/RewardsCard.test.tsx.snap
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/TokenValueStack/TokenValueStack.styles.ts
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/TokenValueStack/TokenValueStack.test.tsx
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/TokenValueStack/TokenValueStack.tsx
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/TokenValueStack/TokenValueStack.types.ts
create mode 100644 app/components/UI/Stake/components/StakingConfirmation/TokenValueStack/__snapshots__/TokenValueStack.test.tsx.snap
diff --git a/app/components/UI/Navbar/index.js b/app/components/UI/Navbar/index.js
index 8127d67f23c..fd8aa98b1f8 100644
--- a/app/components/UI/Navbar/index.js
+++ b/app/components/UI/Navbar/index.js
@@ -1824,34 +1824,61 @@ export const getSettingsNavigationOptions = (title, themeColors) => {
};
};
-export function getStakingNavbar(title, navigation, themeColors) {
+/**
+ *
+ * @param {String} title - Navbar Title.
+ * @param {NavigationProp} navigation Navigation object returned from useNavigation hook.
+ * @param {ThemeColors} themeColors theme.colors returned from useStyles hook.
+ * @param {{ backgroundColor?: string, hasCancelButton?: boolean, hasBackButton?: boolean }} [options] - Optional options for navbar.
+ * @returns Staking Navbar Component.
+ */
+export function getStakingNavbar(title, navigation, themeColors, options) {
+ const { hasBackButton = true, hasCancelButton = true } = options ?? {};
+
const innerStyles = StyleSheet.create({
+ headerStyle: {
+ backgroundColor:
+ options?.backgroundColor ?? themeColors.background.default,
+ shadowOffset: null,
+ },
+ headerLeft: {
+ marginHorizontal: 16,
+ },
headerButtonText: {
color: themeColors.primary.default,
fontSize: 14,
...fontStyles.normal,
},
- headerStyle: {
- backgroundColor: themeColors.background.default,
- shadowColor: importedColors.transparent,
- elevation: 0,
- },
});
+
+ function navigationPop() {
+ navigation.goBack();
+ }
+
return {
headerTitle: () => (
-
- ),
- headerLeft: () => ,
- headerRight: () => (
- navigation.dangerouslyGetParent()?.pop()}
- style={styles.closeButton}
- >
-
- {strings('navigation.cancel')}
-
-
+ {title}
),
headerStyle: innerStyles.headerStyle,
+ headerLeft: () =>
+ hasBackButton ? (
+
+ ) : null,
+ headerRight: () =>
+ hasCancelButton ? (
+ navigation.dangerouslyGetParent()?.pop()}
+ style={styles.closeButton}
+ >
+
+ {strings('navigation.cancel')}
+
+
+ ) : null,
};
}
diff --git a/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.styles.ts b/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.styles.ts
new file mode 100644
index 00000000000..d351fc7302a
--- /dev/null
+++ b/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.styles.ts
@@ -0,0 +1,23 @@
+import type { Theme } from '../../../../../util/theme/models';
+import { StyleSheet } from 'react-native';
+
+const stylesSheet = (params: { theme: Theme }) => {
+ const { theme } = params;
+ const { colors } = theme;
+
+ return StyleSheet.create({
+ mainContainer: {
+ flex: 1,
+ paddingTop: 8,
+ paddingHorizontal: 16,
+ backgroundColor: colors.background.alternative,
+ justifyContent: 'space-between',
+ },
+ cardsContainer: {
+ paddingTop: 16,
+ gap: 8,
+ },
+ });
+};
+
+export default stylesSheet;
diff --git a/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.test.tsx b/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.test.tsx
new file mode 100644
index 00000000000..109fe3e7fac
--- /dev/null
+++ b/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.test.tsx
@@ -0,0 +1,74 @@
+import React from 'react';
+import renderWithProvider from '../../../../../util/test/renderWithProvider';
+import StakeConfirmationView from './StakeConfirmationView';
+import { Image } from 'react-native';
+import { createMockAccountsControllerState } from '../../../../../util/test/accountsControllerTestUtils';
+import { backgroundState } from '../../../../../util/test/initial-root-state';
+import configureMockStore from 'redux-mock-store';
+import { Provider } from 'react-redux';
+import { StakeConfirmationViewProps } from './StakeConfirmationView.types';
+
+jest.mock('../../../../hooks/useIpfsGateway', () => jest.fn());
+
+Image.getSize = jest.fn((_uri, success) => {
+ success(100, 100); // Mock successful response for ETH native Icon Image
+});
+
+const MOCK_ADDRESS_1 = '0x0';
+const MOCK_ADDRESS_2 = '0x1';
+
+const MOCK_ACCOUNTS_CONTROLLER_STATE = createMockAccountsControllerState([
+ MOCK_ADDRESS_1,
+ MOCK_ADDRESS_2,
+]);
+
+const mockStore = configureMockStore();
+
+const mockInitialState = {
+ settings: {},
+ engine: {
+ backgroundState: {
+ ...backgroundState,
+ AccountsController: MOCK_ACCOUNTS_CONTROLLER_STATE,
+ },
+ },
+};
+const store = mockStore(mockInitialState);
+
+jest.mock('react-redux', () => ({
+ ...jest.requireActual('react-redux'),
+ useSelector: jest
+ .fn()
+ .mockImplementation((callback) => callback(mockInitialState)),
+}));
+
+jest.mock('@react-navigation/native', () => {
+ const actualNav = jest.requireActual('@react-navigation/native');
+ return {
+ ...actualNav,
+ useNavigation: () => ({
+ navigate: jest.fn(),
+ setOptions: jest.fn(),
+ }),
+ };
+});
+
+describe('StakeConfirmationView', () => {
+ it('render matches snapshot', () => {
+ const props: StakeConfirmationViewProps = {
+ route: {
+ key: '1',
+ params: { amountWei: '3210000000000000', amountFiat: '7.46' },
+ name: 'params',
+ },
+ };
+
+ const { toJSON } = renderWithProvider(
+
+
+ ,
+ );
+
+ expect(toJSON()).toMatchSnapshot();
+ });
+});
diff --git a/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.tsx b/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.tsx
new file mode 100644
index 00000000000..2f1a4890286
--- /dev/null
+++ b/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.tsx
@@ -0,0 +1,60 @@
+import React, { useEffect } from 'react';
+import { View } from 'react-native';
+import { useNavigation } from '@react-navigation/native';
+import { useStyles } from '../../../../hooks/useStyles';
+import { getStakingNavbar } from '../../../Navbar';
+import styleSheet from './StakeConfirmationView.styles';
+import TokenValueStack from '../../components/StakingConfirmation/TokenValueStack/TokenValueStack';
+import AccountHeaderCard from '../../components/StakingConfirmation/AccountHeaderCard/AccountHeaderCard';
+import RewardsCard from '../../components/StakingConfirmation/RewardsCard/RewardsCard';
+import ConfirmationFooter from '../../components/StakingConfirmation/ConfirmationFooter/ConfirmationFooter';
+import { StakeConfirmationViewProps } from './StakeConfirmationView.types';
+import { MOCK_GET_VAULT_RESPONSE } from '../../components/StakingBalance/mockData';
+import { strings } from '../../../../../../locales/i18n';
+
+const MOCK_REWARD_DATA = {
+ REWARDS: {
+ ETH: '0.13 ETH',
+ FIAT: '$334.93',
+ },
+};
+
+const MOCK_STAKING_CONTRACT_NAME = 'MM Pooled Staking';
+
+const StakeConfirmationView = ({ route }: StakeConfirmationViewProps) => {
+ const navigation = useNavigation();
+
+ const { styles, theme } = useStyles(styleSheet, {});
+
+ useEffect(() => {
+ navigation.setOptions(
+ getStakingNavbar(strings('stake.stake'), navigation, theme.colors, {
+ backgroundColor: theme.colors.background.alternative,
+ hasCancelButton: false,
+ }),
+ );
+ }, [navigation, theme.colors]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default StakeConfirmationView;
diff --git a/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.types.ts b/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.types.ts
new file mode 100644
index 00000000000..8c723135f4f
--- /dev/null
+++ b/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.types.ts
@@ -0,0 +1,10 @@
+import { RouteProp } from '@react-navigation/native';
+
+interface StakeConfirmationViewRouteParams {
+ amountWei: string;
+ amountFiat: string;
+}
+
+export interface StakeConfirmationViewProps {
+ route: RouteProp<{ params: StakeConfirmationViewRouteParams }, 'params'>;
+}
diff --git a/app/components/UI/Stake/Views/StakeConfirmationView/__snapshots__/StakeConfirmationView.test.tsx.snap b/app/components/UI/Stake/Views/StakeConfirmationView/__snapshots__/StakeConfirmationView.test.tsx.snap
new file mode 100644
index 00000000000..9d14c100f63
--- /dev/null
+++ b/app/components/UI/Stake/Views/StakeConfirmationView/__snapshots__/StakeConfirmationView.test.tsx.snap
@@ -0,0 +1,1424 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`StakeConfirmationView render matches snapshot 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.00321
+
+ ETH
+
+
+ $7.46
+
+
+
+
+
+
+
+
+
+
+
+ Staking from
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Account 1
+
+
+
+
+
+
+
+
+
+
+
+
+ Interacting with
+
+
+
+
+
+
+
+
+
+
+
+
+ MM Pooled Staking
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Network
+
+
+
+
+
+
+
+
+
+
+
+
+ Ethereum Main Network
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reward rate
+
+
+
+
+
+
+
+
+
+
+
+ 2.8%
+
+
+
+
+
+
+
+
+
+
+ Estimated annual rewards
+
+
+
+
+
+
+
+
+
+ $334.93
+
+
+ 0.13 ETH
+
+
+
+
+
+
+
+
+
+
+
+ Reward frequency
+
+
+
+
+
+
+
+
+
+
+
+ 12 hours
+
+
+
+
+
+
+
+
+
+
+
+
+ Terms of service
+
+
+
+
+ Risk disclosure
+
+
+
+
+
+
+ Cancel
+
+
+
+
+ Confirm
+
+
+
+
+
+`;
diff --git a/app/components/UI/Stake/Views/StakeInputView/StakeInputView.tsx b/app/components/UI/Stake/Views/StakeInputView/StakeInputView.tsx
index 1ca6560d7f4..0431e67a77f 100644
--- a/app/components/UI/Stake/Views/StakeInputView/StakeInputView.tsx
+++ b/app/components/UI/Stake/Views/StakeInputView/StakeInputView.tsx
@@ -50,8 +50,14 @@ const StakeInputView = () => {
};
const handleStakePress = useCallback(() => {
- // TODO: Display the Review bottom sheet: STAKE-824
- }, []);
+ navigation.navigate('StakeScreens', {
+ screen: Routes.STAKING.STAKE_CONFIRMATION,
+ params: {
+ amountWei: amountWei.toString(),
+ amountFiat: fiatAmount,
+ },
+ });
+ }, [amountWei, fiatAmount, navigation]);
const balanceText = strings('stake.balance');
@@ -66,7 +72,11 @@ const StakeInputView = () => {
: `${balanceFiatNumber?.toString()} ${currentCurrency.toUpperCase()}`;
useEffect(() => {
- navigation.setOptions(getStakingNavbar(title, navigation, theme.colors));
+ navigation.setOptions(
+ getStakingNavbar(title, navigation, theme.colors, {
+ hasBackButton: false,
+ }),
+ );
}, [navigation, theme.colors, title]);
useEffect(() => {
diff --git a/app/components/UI/Stake/Views/StakeInputView/__snapshots__/StakeInputView.test.tsx.snap b/app/components/UI/Stake/Views/StakeInputView/__snapshots__/StakeInputView.test.tsx.snap
index 5085ab0c15e..4a35aa2b83e 100644
--- a/app/components/UI/Stake/Views/StakeInputView/__snapshots__/StakeInputView.test.tsx.snap
+++ b/app/components/UI/Stake/Views/StakeInputView/__snapshots__/StakeInputView.test.tsx.snap
@@ -56,13 +56,9 @@ exports[`StakeInputView render matches snapshot 1`] = `
{
"backgroundColor": "#ffffff",
"borderBottomColor": "rgb(216, 216, 216)",
- "elevation": 0,
"flex": 1,
- "shadowColor": "transparent",
- "shadowOffset": {
- "height": 0.5,
- "width": 0,
- },
+ "shadowColor": "rgb(216, 216, 216)",
+ "shadowOffset": null,
"shadowOpacity": 0.85,
"shadowRadius": 0,
}
@@ -106,96 +102,26 @@ exports[`StakeInputView render matches snapshot 1`] = `
pointerEvents="box-none"
style={
{
- "alignItems": "flex-start",
- "bottom": 0,
- "justifyContent": "center",
- "left": 0,
- "opacity": 1,
- "position": "absolute",
- "top": 0,
- }
- }
- >
-
-
-
-
-
- Stake ETH
-
-
-
-
- Ethereum Main Network
-
-
-
+ Stake ETH
+
{
: strings('stake.review');
useEffect(() => {
- navigation.setOptions(getStakingNavbar(title, navigation, theme.colors));
+ navigation.setOptions(
+ getStakingNavbar(title, navigation, theme.colors, {
+ hasBackButton: false,
+ }),
+ );
}, [navigation, theme.colors, title]);
const handleUnstakePress = useCallback(() => {
diff --git a/app/components/UI/Stake/Views/UnstakeInputView/__snapshots__/UnstakeInputView.test.tsx.snap b/app/components/UI/Stake/Views/UnstakeInputView/__snapshots__/UnstakeInputView.test.tsx.snap
index 15e289f23e7..5e7927b0b5c 100644
--- a/app/components/UI/Stake/Views/UnstakeInputView/__snapshots__/UnstakeInputView.test.tsx.snap
+++ b/app/components/UI/Stake/Views/UnstakeInputView/__snapshots__/UnstakeInputView.test.tsx.snap
@@ -56,13 +56,9 @@ exports[`UnstakeInputView render matches snapshot 1`] = `
{
"backgroundColor": "#ffffff",
"borderBottomColor": "rgb(216, 216, 216)",
- "elevation": 0,
"flex": 1,
- "shadowColor": "transparent",
- "shadowOffset": {
- "height": 0.5,
- "width": 0,
- },
+ "shadowColor": "rgb(216, 216, 216)",
+ "shadowOffset": null,
"shadowOpacity": 0.85,
"shadowRadius": 0,
}
@@ -106,96 +102,26 @@ exports[`UnstakeInputView render matches snapshot 1`] = `
pointerEvents="box-none"
style={
{
- "alignItems": "flex-start",
- "bottom": 0,
- "justifyContent": "center",
- "left": 0,
- "opacity": 1,
- "position": "absolute",
- "top": 0,
- }
- }
- >
-
-
-
-
-
- Unstake ETH
-
-
-
-
- Ethereum Main Network
-
-
-
+ Unstake ETH
+
jest.fn());
+
+Image.getSize = jest.fn((_uri, success) => {
+ success(100, 100); // Mock successful response for ETH native Icon Image
+});
const mockNavigate = jest.fn();
@@ -39,15 +29,17 @@ afterEach(() => {
});
describe('StakingBalance', () => {
+ beforeEach(() => jest.resetAllMocks());
+
it('render matches snapshot', () => {
- render(StakingBalance);
- expect(screen.toJSON()).toMatchSnapshot();
+ const { toJSON } = renderWithProvider();
+ expect(toJSON()).toMatchSnapshot();
});
it('redirects to StakeInputView on stake button click', () => {
- render(StakingBalance);
+ const { getByText } = renderWithProvider();
- fireEvent.press(screen.getByText(strings('stake.stake_more')));
+ fireEvent.press(getByText(strings('stake.stake_more')));
expect(mockNavigate).toHaveBeenCalledTimes(1);
expect(mockNavigate).toHaveBeenCalledWith('StakeScreens', {
@@ -56,9 +48,9 @@ describe('StakingBalance', () => {
});
it('redirects to UnstakeInputView on unstake button click', () => {
- render(StakingBalance);
+ const { getByText } = renderWithProvider();
- fireEvent.press(screen.getByText(strings('stake.unstake')));
+ fireEvent.press(getByText(strings('stake.unstake')));
expect(mockNavigate).toHaveBeenCalledTimes(1);
expect(mockNavigate).toHaveBeenCalledWith('StakeScreens', {
diff --git a/app/components/UI/Stake/components/StakingBalance/StakingCta/StakingCta.tsx b/app/components/UI/Stake/components/StakingBalance/StakingCta/StakingCta.tsx
index 1ab816b649d..13b3d2c8629 100644
--- a/app/components/UI/Stake/components/StakingBalance/StakingCta/StakingCta.tsx
+++ b/app/components/UI/Stake/components/StakingBalance/StakingCta/StakingCta.tsx
@@ -36,10 +36,10 @@ const StakingCta = ({ estimatedRewardRate, style }: StakingCtaProps) => {
{strings('stake.stake_your_eth_cta.base')}
- {estimatedRewardRate}
-
- {strings('stake.stake_your_eth_cta.annually')}
+
+ {estimatedRewardRate}
+ {strings('stake.stake_your_eth_cta.annually')}
+
-
+ $13,292.20
+
+
-
+
+
+
+
+
+
-
+
+
+ Unstaking 0.0010 ETH in progress. Come back in 11 days to claim it.
+
+
+
+
+
+
+
+
+
-
+
+
-
+
+
+
+
+
+ Stake ETH and earn
+
+
+
+ Stake your ETH with MetaMask Pool and earn
+
+
+ 2.9%
+
+
+ annually.
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Staked Ethereum
-
-
-
- $13,292.20
-
-
- 4.9999 ETH
-
-
-
-
-
-
-
-
-
-
- Unstaking 0.0010 ETH in progress. Come back in 11 days to claim it.
-
-
-
-
-
-
-
-
-
- You can claim 0.00214 ETH. Once claimed, you'll get ETH back in your wallet.
-
-
-
- Claim
- ETH
-
-
-
-
-
-
- Stake ETH and earn
-
-
-
- Stake your ETH with MetaMask Pool and earn
-
-
- 2.9%
-
-
- annually.
-
-
-
- Learn more.
-
-
-
-
-
-
-
- Unstake
-
-
-
-
- Stake more
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ }
+ >
+ Learn more.
+
+
+
+
+
+
+
+ Unstake
+
+
+
+
+ Stake more
+
+
+
+
`;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/AccountHeaderCard/AccountHeaderCard.styles.ts b/app/components/UI/Stake/components/StakingConfirmation/AccountHeaderCard/AccountHeaderCard.styles.ts
new file mode 100644
index 00000000000..3688107b7e1
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/AccountHeaderCard/AccountHeaderCard.styles.ts
@@ -0,0 +1,40 @@
+import type { Theme } from '../../../../../../util/theme/models';
+import { StyleSheet } from 'react-native';
+
+const stylesSheet = (params: { theme: Theme }) => {
+ const { theme } = params;
+ const { colors } = theme;
+
+ return StyleSheet.create({
+ cardGroupTop: {
+ borderWidth: 0,
+ gap: 16,
+ borderRadius: 8,
+ borderBottomLeftRadius: 0,
+ borderBottomRightRadius: 0,
+ },
+ cardGroupBottom: {
+ borderLeftWidth: 0,
+ borderRightWidth: 0,
+ borderBottomWidth: 0,
+ borderTopLeftRadius: 0,
+ borderTopRightRadius: 0,
+ borderBottomLeftRadius: 8,
+ borderBottomRightRadius: 8,
+ borderColor: colors.border.muted,
+ },
+ networkKeyValueRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 8,
+ },
+ tagMinimalPadding: {
+ paddingLeft: 0,
+ paddingRight: 8,
+ paddingTop: 0,
+ paddingBottom: 0,
+ },
+ });
+};
+
+export default stylesSheet;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/AccountHeaderCard/AccountHeaderCard.test.tsx b/app/components/UI/Stake/components/StakingConfirmation/AccountHeaderCard/AccountHeaderCard.test.tsx
new file mode 100644
index 00000000000..c8aa996b8f9
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/AccountHeaderCard/AccountHeaderCard.test.tsx
@@ -0,0 +1,72 @@
+import React from 'react';
+import renderWithProvider from '../../../../../../util/test/renderWithProvider';
+import AccountHeaderCard from './AccountHeaderCard';
+import { strings } from '../../../../../../../locales/i18n';
+import { createMockAccountsControllerState } from '../../../../../../util/test/accountsControllerTestUtils';
+import configureMockStore from 'redux-mock-store';
+import { backgroundState } from '../../../../../../util/test/initial-root-state';
+import { Provider } from 'react-redux';
+import { AccountHeaderCardProps } from './AccountHeaderCard.types';
+
+const MOCK_STAKING_CONTRACT_NAME = 'MM Pooled Staking';
+
+const MOCK_ADDRESS_1 = '0x0';
+const MOCK_ADDRESS_2 = '0x1';
+
+const MOCK_ACCOUNTS_CONTROLLER_STATE = createMockAccountsControllerState([
+ MOCK_ADDRESS_1,
+ MOCK_ADDRESS_2,
+]);
+
+const mockStore = configureMockStore();
+
+const mockInitialState = {
+ settings: {},
+ engine: {
+ backgroundState: {
+ ...backgroundState,
+ AccountsController: MOCK_ACCOUNTS_CONTROLLER_STATE,
+ },
+ },
+};
+const store = mockStore(mockInitialState);
+
+jest.mock('react-redux', () => ({
+ ...jest.requireActual('react-redux'),
+ useSelector: jest
+ .fn()
+ .mockImplementation((callback) => callback(mockInitialState)),
+}));
+
+const mockNavigate = jest.fn();
+
+jest.mock('@react-navigation/native', () => {
+ const actualReactNavigation = jest.requireActual('@react-navigation/native');
+ return {
+ ...actualReactNavigation,
+ useNavigation: () => ({
+ navigate: mockNavigate,
+ }),
+ };
+});
+
+describe('AccountHeaderCard', () => {
+ it('render matches snapshot', () => {
+ const props: AccountHeaderCardProps = {
+ contractName: MOCK_STAKING_CONTRACT_NAME,
+ };
+
+ const { getByText, toJSON } = renderWithProvider(
+
+ ,
+ ,
+ );
+
+ expect(getByText(strings('stake.staking_from'))).toBeDefined();
+ expect(getByText(strings('stake.interacting_with'))).toBeDefined();
+ expect(getByText(strings('asset_details.network'))).toBeDefined();
+ expect(getByText(props.contractName)).toBeDefined();
+
+ expect(toJSON()).toMatchSnapshot();
+ });
+});
diff --git a/app/components/UI/Stake/components/StakingConfirmation/AccountHeaderCard/AccountHeaderCard.tsx b/app/components/UI/Stake/components/StakingConfirmation/AccountHeaderCard/AccountHeaderCard.tsx
new file mode 100644
index 00000000000..9d4e2cb19ee
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/AccountHeaderCard/AccountHeaderCard.tsx
@@ -0,0 +1,74 @@
+import React from 'react';
+import { View } from 'react-native';
+import { useSelector } from 'react-redux';
+import { strings } from '../../../../../../../locales/i18n';
+import KeyValueRow from '../../../../../../component-library/components-temp/KeyValueRow';
+import Avatar, {
+ AvatarVariant,
+ AvatarSize,
+} from '../../../../../../component-library/components/Avatars/Avatar';
+import Text from '../../../../../../component-library/components/Texts/Text';
+import { selectSelectedInternalAccount } from '../../../../../../selectors/accountsController';
+import { useStyles } from '../../../../../hooks/useStyles';
+import Card from '../../../../../../component-library/components/Cards/Card';
+import styleSheet from './AccountHeaderCard.styles';
+import images from '../../../../../../images/image-icons';
+import AccountTag from '../AccountTag/AccountTag';
+import { selectNetworkName } from '../../../../../../selectors/networkInfos';
+import { AccountHeaderCardProps } from './AccountHeaderCard.types';
+import ContractTag from '../ContractTag/ContractTag';
+
+const AccountHeaderCard = ({ contractName }: AccountHeaderCardProps) => {
+ const { styles } = useStyles(styleSheet, {});
+
+ const account = useSelector(selectSelectedInternalAccount);
+
+ const networkName = useSelector(selectNetworkName);
+
+ return (
+
+
+ {account && (
+
+ ),
+ }}
+ />
+ )}
+ ,
+ }}
+ />
+
+
+
+
+ {networkName}
+
+ ),
+ }}
+ />
+
+
+ );
+};
+
+export default AccountHeaderCard;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/AccountHeaderCard/AccountHeaderCard.types.ts b/app/components/UI/Stake/components/StakingConfirmation/AccountHeaderCard/AccountHeaderCard.types.ts
new file mode 100644
index 00000000000..3775cc7f14a
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/AccountHeaderCard/AccountHeaderCard.types.ts
@@ -0,0 +1,3 @@
+export interface AccountHeaderCardProps {
+ contractName: string;
+}
diff --git a/app/components/UI/Stake/components/StakingConfirmation/AccountHeaderCard/__snapshots__/AccountHeaderCard.test.tsx.snap b/app/components/UI/Stake/components/StakingConfirmation/AccountHeaderCard/__snapshots__/AccountHeaderCard.test.tsx.snap
new file mode 100644
index 00000000000..261f776ab14
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/AccountHeaderCard/__snapshots__/AccountHeaderCard.test.tsx.snap
@@ -0,0 +1,623 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`AccountHeaderCard render matches snapshot 1`] = `
+[
+
+
+
+
+
+
+
+ Staking from
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Account 1
+
+
+
+
+
+
+
+
+
+
+
+
+ Interacting with
+
+
+
+
+
+
+
+
+
+
+
+
+ MM Pooled Staking
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Network
+
+
+
+
+
+
+
+
+
+
+
+
+ Ethereum Main Network
+
+
+
+
+
+
+
+ ,
+ ",",
+]
+`;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/AccountTag/AccountTag.test.tsx b/app/components/UI/Stake/components/StakingConfirmation/AccountTag/AccountTag.test.tsx
new file mode 100644
index 00000000000..901690c1847
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/AccountTag/AccountTag.test.tsx
@@ -0,0 +1,31 @@
+import React from 'react';
+import renderWithProvider from '../../../../../../util/test/renderWithProvider';
+import AccountTag from './AccountTag';
+import { AccountTagProps } from './AccountTag.types';
+
+describe('AccountTag', () => {
+ it('render matches snapshot when name prop is defined', () => {
+ const props: AccountTagProps = {
+ accountAddress: '0x1',
+ accountName: 'Sample Contract',
+ };
+
+ const { getByText, toJSON } = renderWithProvider();
+
+ expect(getByText(props.accountName as string)).toBeDefined();
+
+ expect(toJSON()).toMatchSnapshot();
+ });
+
+ it("render matches snapshot when name prop isn't defined", () => {
+ const props: AccountTagProps = {
+ accountAddress: '0x1',
+ };
+
+ const { getByText, toJSON } = renderWithProvider();
+
+ expect(getByText(props.accountAddress)).toBeDefined();
+
+ expect(toJSON()).toMatchSnapshot();
+ });
+});
diff --git a/app/components/UI/Stake/components/StakingConfirmation/AccountTag/AccountTag.tsx b/app/components/UI/Stake/components/StakingConfirmation/AccountTag/AccountTag.tsx
new file mode 100644
index 00000000000..70a8986edd0
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/AccountTag/AccountTag.tsx
@@ -0,0 +1,38 @@
+import React from 'react';
+import TagBase, {
+ TagShape,
+ TagSeverity,
+} from '../../../../../../component-library/base-components/TagBase';
+import Avatar, {
+ AvatarVariant,
+ AvatarSize,
+ AvatarAccountType,
+} from '../../../../../../component-library/components/Avatars/Avatar';
+import { AccountTagProps } from './AccountTag.types';
+
+const AccountTag = ({
+ accountAddress,
+ accountName,
+ useBlockieIcon = false,
+}: AccountTagProps) => (
+
+ }
+ shape={TagShape.Pill}
+ severity={TagSeverity.Info}
+ >
+ {accountName ?? accountAddress}
+
+);
+
+export default AccountTag;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/AccountTag/AccountTag.types.ts b/app/components/UI/Stake/components/StakingConfirmation/AccountTag/AccountTag.types.ts
new file mode 100644
index 00000000000..24866d2b492
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/AccountTag/AccountTag.types.ts
@@ -0,0 +1,5 @@
+export interface AccountTagProps {
+ accountAddress: string;
+ accountName?: string;
+ useBlockieIcon?: boolean;
+}
diff --git a/app/components/UI/Stake/components/StakingConfirmation/AccountTag/__snapshots__/AccountTag.test.tsx.snap b/app/components/UI/Stake/components/StakingConfirmation/AccountTag/__snapshots__/AccountTag.test.tsx.snap
new file mode 100644
index 00000000000..ca2dc96db44
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/AccountTag/__snapshots__/AccountTag.test.tsx.snap
@@ -0,0 +1,395 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`AccountTag render matches snapshot when name prop is defined 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sample Contract
+
+
+
+`;
+
+exports[`AccountTag render matches snapshot when name prop isn't defined 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0x1
+
+
+
+`;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/ConfirmationFooter.styles.ts b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/ConfirmationFooter.styles.ts
new file mode 100644
index 00000000000..de5fe494916
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/ConfirmationFooter.styles.ts
@@ -0,0 +1,114 @@
+import type { Theme } from '../../../../../../util/theme/models';
+import { StyleSheet } from 'react-native';
+
+const stylesSheet = (params: { theme: Theme }) => {
+ const { theme } = params;
+ const { colors } = theme;
+
+ return StyleSheet.create({
+ mainContainer: {
+ paddingTop: 8,
+ paddingHorizontal: 16,
+ backgroundColor: colors.background.alternative,
+ height: '100%',
+ justifyContent: 'space-between',
+ },
+ // Card styles
+ cardsContainer: {
+ paddingTop: 8,
+ gap: 8,
+ },
+ card: {
+ borderWidth: 0,
+ gap: 16,
+ borderRadius: 8,
+ },
+ estGasFeeCard: {
+ borderWidth: 0,
+ gap: 16,
+ borderRadius: 8,
+ justifyContent: 'center',
+ },
+ cardGroupTop: {
+ borderWidth: 0,
+ gap: 16,
+ borderRadius: 8,
+ borderBottomLeftRadius: 0,
+ borderBottomRightRadius: 0,
+ },
+ cardGroupBottom: {
+ borderLeftWidth: 0,
+ borderRightWidth: 0,
+ borderBottomWidth: 0,
+ borderTopLeftRadius: 0,
+ borderTopRightRadius: 0,
+ borderBottomLeftRadius: 8,
+ borderBottomRightRadius: 8,
+ borderColor: colors.border.muted,
+ },
+ // Network
+ networkKeyValueRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 8,
+ },
+ // Estimated Gas Fee
+ estGasFeeValue: {
+ flexDirection: 'row',
+ paddingTop: 1,
+ },
+ foxIcon: {
+ paddingRight: 8,
+ },
+ fiatText: {
+ paddingRight: 4,
+ },
+ ethText: {
+ borderBottomWidth: 1,
+ borderBottomColor: theme.colors.primary.default,
+ },
+ estimatedGasTooltipContent: {
+ gap: 16,
+ },
+ gasLearnMoreLink: {
+ alignSelf: 'flex-start',
+ },
+ // Est. Annual Reward
+ estAnnualRewardValue: {
+ flexDirection: 'row',
+ gap: 8,
+ },
+ // Tags
+ tagMinimalPadding: {
+ paddingLeft: 0,
+ paddingRight: 8,
+ paddingTop: 0,
+ paddingBottom: 0,
+ },
+ // Terms of Service / Risk Disclosure Button Group
+ termsOfServiceButtonGroup: {
+ flexDirection: 'row',
+ justifyContent: 'center',
+ },
+ legalLink: {
+ padding: 16,
+ },
+ // Footer Button Group
+ footerButtonGroup: {
+ flexDirection: 'row',
+ justifyContent: 'center',
+ gap: 16,
+ paddingTop: 24,
+ },
+ footerButton: {
+ flexGrow: 1,
+ flexShrink: 0,
+ flexBasis: 0,
+ },
+ footerContainer: {
+ paddingBottom: 40,
+ },
+ });
+};
+
+export default stylesSheet;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/ConfirmationFooter.test.tsx b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/ConfirmationFooter.test.tsx
new file mode 100644
index 00000000000..36db2a000b1
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/ConfirmationFooter.test.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import renderWithProvider from '../../../../../../util/test/renderWithProvider';
+import ConfirmationFooter from './ConfirmationFooter';
+
+jest.mock('@react-navigation/native', () => {
+ const actualReactNavigation = jest.requireActual('@react-navigation/native');
+ return {
+ ...actualReactNavigation,
+ useNavigation: jest.fn(),
+ };
+});
+
+describe('ConfirmationFooter', () => {
+ it('render matches snapshot', () => {
+ const { toJSON } = renderWithProvider();
+
+ expect(toJSON()).toMatchSnapshot();
+ });
+});
diff --git a/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/ConfirmationFooter.tsx b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/ConfirmationFooter.tsx
new file mode 100644
index 00000000000..6505cef13f7
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/ConfirmationFooter.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import { useStyles } from '../../../../../hooks/useStyles';
+import styleSheet from './ConfirmationFooter.styles';
+import { View } from 'react-native';
+import FooterLegalLinks from './LegalLinks/LegalLinks';
+import FooterButtonGroup from './FooterButtonGroup/FooterButtonGroup';
+
+const ConfirmationFooter = () => {
+ const { styles } = useStyles(styleSheet, {});
+
+ return (
+
+
+
+
+ );
+};
+
+export default ConfirmationFooter;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.styles.ts b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.styles.ts
new file mode 100644
index 00000000000..c50133567a7
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.styles.ts
@@ -0,0 +1,18 @@
+import { StyleSheet } from 'react-native';
+
+const stylesSheet = () =>
+ StyleSheet.create({
+ footerContainer: {
+ flexDirection: 'row',
+ justifyContent: 'center',
+ gap: 16,
+ paddingTop: 24,
+ },
+ button: {
+ flexGrow: 1,
+ flexShrink: 0,
+ flexBasis: 0,
+ },
+ });
+
+export default stylesSheet;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.test.tsx b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.test.tsx
new file mode 100644
index 00000000000..9f756063fa6
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.test.tsx
@@ -0,0 +1,49 @@
+import React from 'react';
+import renderWithProvider from '../../../../../../../util/test/renderWithProvider';
+import { strings } from '../../../../../../../../locales/i18n';
+import FooterButtonGroup from './FooterButtonGroup';
+import { fireEvent } from '@testing-library/react-native';
+
+const mockCanGoBack = jest.fn();
+const mockGoBack = jest.fn();
+
+jest.mock('@react-navigation/native', () => {
+ const actualReactNavigation = jest.requireActual('@react-navigation/native');
+ return {
+ ...actualReactNavigation,
+ useNavigation: () => ({
+ canGoBack: mockCanGoBack,
+ goBack: mockGoBack,
+ }),
+ };
+});
+
+describe('FooterButtonGroup', () => {
+ beforeEach(() => {
+ jest.resetAllMocks();
+ });
+
+ it('render matches snapshot', () => {
+ const { getByText, toJSON } = renderWithProvider();
+
+ expect(getByText(strings('stake.cancel'))).toBeDefined();
+ expect(getByText(strings('stake.confirm'))).toBeDefined();
+
+ expect(toJSON()).toMatchSnapshot();
+ });
+
+ it('navigates to previous page when cancel button is pressed', () => {
+ mockCanGoBack.mockImplementationOnce(() => true);
+
+ const { getByText, toJSON } = renderWithProvider();
+
+ fireEvent.press(getByText(strings('stake.cancel')));
+
+ expect(mockCanGoBack).toHaveBeenCalledTimes(1);
+ expect(mockGoBack).toHaveBeenCalledTimes(1);
+
+ expect(toJSON()).toMatchSnapshot();
+ });
+
+ it.todo('confirms stake when confirm button is pressed');
+});
diff --git a/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.tsx b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.tsx
new file mode 100644
index 00000000000..bd781bfc9a8
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.tsx
@@ -0,0 +1,59 @@
+import React from 'react';
+import { useNavigation } from '@react-navigation/native';
+import { View } from 'react-native';
+import { strings } from '../../../../../../../../locales/i18n';
+import Button, {
+ ButtonVariants,
+ ButtonWidthTypes,
+ ButtonSize,
+} from '../../../../../../../component-library/components/Buttons/Button';
+import Text, {
+ TextVariant,
+ TextColor,
+} from '../../../../../../../component-library/components/Texts/Text';
+import { useStyles } from '../../../../../../hooks/useStyles';
+import styleSheet from './FooterButtonGroup.styles';
+
+const FooterButtonGroup = () => {
+ const { styles } = useStyles(styleSheet, {});
+
+ const navigation = useNavigation();
+
+ const handleGoBack = () => {
+ if (navigation.canGoBack()) {
+ navigation.goBack();
+ }
+ };
+
+ return (
+
+
+ );
+};
+
+export default FooterButtonGroup;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/__snapshots__/FooterButtonGroup.test.tsx.snap b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/__snapshots__/FooterButtonGroup.test.tsx.snap
new file mode 100644
index 00000000000..df88108ac02
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/__snapshots__/FooterButtonGroup.test.tsx.snap
@@ -0,0 +1,189 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`FooterButtonGroup navigates to previous page when cancel button is pressed 1`] = `
+
+
+
+ Cancel
+
+
+
+
+ Confirm
+
+
+
+`;
+
+exports[`FooterButtonGroup render matches snapshot 1`] = `
+
+
+
+ Cancel
+
+
+
+
+ Confirm
+
+
+
+`;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/LegalLinks/LegalLinks.styles.ts b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/LegalLinks/LegalLinks.styles.ts
new file mode 100644
index 00000000000..a5ac5b5ff78
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/LegalLinks/LegalLinks.styles.ts
@@ -0,0 +1,14 @@
+import { StyleSheet } from 'react-native';
+
+const stylesSheet = () =>
+ StyleSheet.create({
+ termsOfServiceButtonGroup: {
+ flexDirection: 'row',
+ justifyContent: 'center',
+ },
+ legalLink: {
+ padding: 16,
+ },
+ });
+
+export default stylesSheet;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/LegalLinks/LegalLinks.test.tsx b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/LegalLinks/LegalLinks.test.tsx
new file mode 100644
index 00000000000..5338556c843
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/LegalLinks/LegalLinks.test.tsx
@@ -0,0 +1,61 @@
+import React from 'react';
+import renderWithProvider from '../../../../../../../util/test/renderWithProvider';
+import FooterLegalLinks from './LegalLinks';
+import { strings } from '../../../../../../../../locales/i18n';
+import { fireEvent } from '@testing-library/react-native';
+import AppConstants from '../../../../../../../core/AppConstants';
+
+const mockNavigate = jest.fn();
+
+jest.mock('@react-navigation/native', () => {
+ const actualReactNavigation = jest.requireActual('@react-navigation/native');
+ return {
+ ...actualReactNavigation,
+ useNavigation: () => ({
+ navigate: mockNavigate,
+ }),
+ };
+});
+
+describe('FooterLegalLinks', () => {
+ beforeEach(() => {
+ jest.resetAllMocks();
+ });
+
+ it('render matches snapshot', () => {
+ const { getByText, toJSON } = renderWithProvider();
+
+ expect(getByText(strings('stake.terms_of_service'))).toBeDefined();
+ expect(getByText(strings('stake.risk_disclosure'))).toBeDefined();
+
+ expect(toJSON()).toMatchSnapshot();
+ });
+
+ it('navigates to terms of use web page', () => {
+ const { getByText, toJSON } = renderWithProvider();
+
+ fireEvent.press(getByText(strings('stake.terms_of_service')));
+
+ expect(mockNavigate).toHaveBeenCalledTimes(1);
+ expect(mockNavigate).toHaveBeenCalledWith('Webview', {
+ params: { url: AppConstants.URLS.TERMS_AND_CONDITIONS },
+ screen: 'SimpleWebview',
+ });
+
+ expect(toJSON()).toMatchSnapshot();
+ });
+
+ it('navigates to risk disclosure web page', () => {
+ const { getByText, toJSON } = renderWithProvider();
+
+ fireEvent.press(getByText(strings('stake.risk_disclosure')));
+
+ expect(mockNavigate).toHaveBeenCalledTimes(1);
+ expect(mockNavigate).toHaveBeenCalledWith('Webview', {
+ params: { url: AppConstants.URLS.STAKING_RISK_DISCLOSURE },
+ screen: 'SimpleWebview',
+ });
+
+ expect(toJSON()).toMatchSnapshot();
+ });
+});
diff --git a/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/LegalLinks/LegalLinks.tsx b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/LegalLinks/LegalLinks.tsx
new file mode 100644
index 00000000000..4b74efd0bcc
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/LegalLinks/LegalLinks.tsx
@@ -0,0 +1,52 @@
+import React from 'react';
+import { useNavigation } from '@react-navigation/native';
+import { View, TouchableOpacity } from 'react-native';
+import { strings } from '../../../../../../../../locales/i18n';
+import Text, {
+ TextVariant,
+ TextColor,
+} from '../../../../../../../component-library/components/Texts/Text';
+import AppConstants from '../../../../../../../core/AppConstants';
+import { useStyles } from '../../../../../../hooks/useStyles';
+import styleSheet from './LegalLinks.styles';
+
+const FooterLegalLinks = () => {
+ const { styles } = useStyles(styleSheet, {});
+
+ const navigation = useNavigation();
+
+ const handleNavigateToWebView = (url: string) =>
+ navigation.navigate('Webview', {
+ screen: 'SimpleWebview',
+ params: { url },
+ });
+
+ return (
+
+
+ handleNavigateToWebView(AppConstants.URLS.TERMS_AND_CONDITIONS)
+ }
+ style={styles.legalLink}
+ >
+
+ {strings('stake.terms_of_service')}
+
+
+
+ handleNavigateToWebView(AppConstants.URLS.STAKING_RISK_DISCLOSURE)
+ }
+ style={styles.legalLink}
+ >
+
+ {strings('stake.risk_disclosure')}
+
+
+
+ );
+};
+
+export default FooterLegalLinks;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/LegalLinks/__snapshots__/LegalLinks.test.tsx.snap b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/LegalLinks/__snapshots__/LegalLinks.test.tsx.snap
new file mode 100644
index 00000000000..2fc6328066f
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/LegalLinks/__snapshots__/LegalLinks.test.tsx.snap
@@ -0,0 +1,187 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`FooterLegalLinks navigates to risk disclosure web page 1`] = `
+
+
+
+ Terms of service
+
+
+
+
+ Risk disclosure
+
+
+
+`;
+
+exports[`FooterLegalLinks navigates to terms of use web page 1`] = `
+
+
+
+ Terms of service
+
+
+
+
+ Risk disclosure
+
+
+
+`;
+
+exports[`FooterLegalLinks render matches snapshot 1`] = `
+
+
+
+ Terms of service
+
+
+
+
+ Risk disclosure
+
+
+
+`;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/__snapshots__/ConfirmationFooter.test.tsx.snap b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/__snapshots__/ConfirmationFooter.test.tsx.snap
new file mode 100644
index 00000000000..e29a06f61c9
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/__snapshots__/ConfirmationFooter.test.tsx.snap
@@ -0,0 +1,162 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ConfirmationFooter render matches snapshot 1`] = `
+
+
+
+
+ Terms of service
+
+
+
+
+ Risk disclosure
+
+
+
+
+
+
+ Cancel
+
+
+
+
+ Confirm
+
+
+
+
+`;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/ContractTag/ContractTag.test.tsx b/app/components/UI/Stake/components/StakingConfirmation/ContractTag/ContractTag.test.tsx
new file mode 100644
index 00000000000..c88f586fb98
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/ContractTag/ContractTag.test.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import renderWithProvider from '../../../../../../util/test/renderWithProvider';
+import ContractTag from './ContractTag';
+
+const MOCK_STAKING_CONTRACT_NAME = 'MM Pooled Staking';
+
+describe('ContractTag', () => {
+ it('render matches snapshot', () => {
+ const { getByText, toJSON } = renderWithProvider(
+ ,
+ );
+
+ expect(getByText(MOCK_STAKING_CONTRACT_NAME)).toBeDefined();
+
+ expect(toJSON()).toMatchSnapshot();
+ });
+});
diff --git a/app/components/UI/Stake/components/StakingConfirmation/ContractTag/ContractTag.tsx b/app/components/UI/Stake/components/StakingConfirmation/ContractTag/ContractTag.tsx
new file mode 100644
index 00000000000..4b3eb3cee57
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/ContractTag/ContractTag.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import TagBase, {
+ TagSeverity,
+ TagShape,
+} from '../../../../../../component-library/base-components/TagBase';
+import Icon, {
+ IconName,
+ IconSize,
+} from '../../../../../../component-library/components/Icons/Icon';
+import Text from '../../../../../../component-library/components/Texts/Text';
+import { ContractTagProps } from './ContractTag.types';
+
+const ContractTag = ({ contractName }: ContractTagProps) => (
+ }
+ shape={TagShape.Pill}
+ severity={TagSeverity.Neutral}
+ >
+ {contractName}
+
+);
+
+export default ContractTag;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/ContractTag/ContractTag.types.ts b/app/components/UI/Stake/components/StakingConfirmation/ContractTag/ContractTag.types.ts
new file mode 100644
index 00000000000..082888ac193
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/ContractTag/ContractTag.types.ts
@@ -0,0 +1,3 @@
+export interface ContractTagProps {
+ contractName: string;
+}
diff --git a/app/components/UI/Stake/components/StakingConfirmation/ContractTag/__snapshots__/ContractTag.test.tsx.snap b/app/components/UI/Stake/components/StakingConfirmation/ContractTag/__snapshots__/ContractTag.test.tsx.snap
new file mode 100644
index 00000000000..bd7e17af2f8
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/ContractTag/__snapshots__/ContractTag.test.tsx.snap
@@ -0,0 +1,69 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ContractTag render matches snapshot 1`] = `
+
+
+
+
+
+ MM Pooled Staking
+
+
+
+`;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasCard/EstimatedGasCard.styles.ts b/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasCard/EstimatedGasCard.styles.ts
new file mode 100644
index 00000000000..33c5ad186e8
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasCard/EstimatedGasCard.styles.ts
@@ -0,0 +1,39 @@
+import type { Theme } from '../../../../../../util/theme/models';
+import { StyleSheet } from 'react-native';
+
+const stylesSheet = (params: { theme: Theme }) => {
+ const { theme } = params;
+ const { colors } = theme;
+
+ return StyleSheet.create({
+ estGasFeeCard: {
+ borderWidth: 0,
+ gap: 16,
+ borderRadius: 8,
+ justifyContent: 'center',
+ },
+ // Estimated Gas Fee
+ estGasFeeValue: {
+ flexDirection: 'row',
+ paddingTop: 1,
+ },
+ foxIcon: {
+ paddingRight: 8,
+ },
+ fiatText: {
+ paddingRight: 4,
+ },
+ ethText: {
+ borderBottomWidth: 1,
+ borderBottomColor: colors.primary.default,
+ },
+ estimatedGasTooltipContent: {
+ gap: 16,
+ },
+ gasLearnMoreLink: {
+ alignSelf: 'flex-start',
+ },
+ });
+};
+
+export default stylesSheet;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasCard/EstimatedGasCard.test.tsx b/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasCard/EstimatedGasCard.test.tsx
new file mode 100644
index 00000000000..97a31da76f3
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasCard/EstimatedGasCard.test.tsx
@@ -0,0 +1,67 @@
+import React from 'react';
+import renderWithProvider from '../../../../../../util/test/renderWithProvider';
+import EstimatedGasCard from './EstimatedGasCard';
+import { strings } from '../../../../../../../locales/i18n';
+import { fireEvent } from '@testing-library/react-native';
+
+const mockNavigate = jest.fn();
+
+jest.mock('@react-navigation/native', () => {
+ const actualReactNavigation = jest.requireActual('@react-navigation/native');
+ return {
+ ...actualReactNavigation,
+ useNavigation: () => ({
+ navigate: mockNavigate,
+ }),
+ };
+});
+
+describe('EstimatedGasCard', () => {
+ it('render matches snapshot', () => {
+ const props = {
+ gasCostEth: '0.0884 ETH',
+ gasCostFiat: '$43.56',
+ };
+
+ const { getByText, toJSON } = renderWithProvider(
+ ,
+ );
+
+ expect(
+ getByText(strings('tooltip_modal.estimated_gas_fee.title')),
+ ).toBeDefined();
+ expect(getByText(props.gasCostEth)).toBeDefined();
+ expect(getByText(props.gasCostFiat)).toBeDefined();
+
+ expect(toJSON()).toMatchSnapshot();
+ });
+
+ it('displays the estimated gas cost tooltip when pressed', () => {
+ const props = {
+ gasCostEth: '0.0884 ETH',
+ gasCostFiat: '$43.56',
+ };
+
+ const { getByLabelText, toJSON } = renderWithProvider(
+ ,
+ );
+
+ fireEvent.press(
+ getByLabelText(
+ `${strings('tooltip_modal.estimated_gas_fee.title')} tooltip`,
+ ),
+ );
+
+ expect(mockNavigate).toHaveBeenCalledTimes(1);
+ expect(mockNavigate).toHaveBeenCalledWith('RootModalFlow', {
+ params: {
+ title: strings('tooltip_modal.estimated_gas_fee.title'),
+ // difficult to directly compare ReactNodes
+ tooltip: expect.any(Object),
+ },
+ screen: 'tooltipModal',
+ });
+
+ expect(toJSON()).toMatchSnapshot();
+ });
+});
diff --git a/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasCard/EstimatedGasCard.tsx b/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasCard/EstimatedGasCard.tsx
new file mode 100644
index 00000000000..d8d9c83130e
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasCard/EstimatedGasCard.tsx
@@ -0,0 +1,69 @@
+import React from 'react';
+import { View, TouchableOpacity } from 'react-native';
+import { strings } from '../../../../../../../locales/i18n';
+import KeyValueRow, {
+ TooltipSizes,
+} from '../../../../../../component-library/components-temp/KeyValueRow';
+import Text, {
+ TextColor,
+ TextVariant,
+} from '../../../../../../component-library/components/Texts/Text';
+import Card from '../../../../../../component-library/components/Cards/Card';
+import { useStyles } from '../../../../../hooks/useStyles';
+import useTooltipModal from '../../../../../hooks/useTooltipModal';
+import styleSheet from './EstimatedGasCard.styles';
+import { EstimatedGasCardProps } from './EstimatedGasCard.types';
+import EstimatedGasFeeTooltipContent from '../EstimatedGasFeeTooltipContent/EstimatedGasFeeTooltipContent';
+
+const EstimatedGasCard = ({
+ gasCostEth,
+ gasCostFiat,
+}: EstimatedGasCardProps) => {
+ const { styles } = useStyles(styleSheet, {});
+
+ const { openTooltipModal } = useTooltipModal();
+
+ // TODO: Navigate to the edit gas bottom sheet
+ const handleNavigateToEditGas = () =>
+ openTooltipModal('TODO', 'Navigate to gas customization component');
+
+ return (
+
+ ,
+ size: TooltipSizes.Sm,
+ },
+ }}
+ value={{
+ label: (
+
+ 🦊
+
+ {gasCostFiat}
+
+
+
+
+ {gasCostEth}
+
+
+
+
+ ),
+ }}
+ />
+
+ );
+};
+
+export default EstimatedGasCard;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasCard/EstimatedGasCard.types.ts b/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasCard/EstimatedGasCard.types.ts
new file mode 100644
index 00000000000..58cd5f1aed7
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasCard/EstimatedGasCard.types.ts
@@ -0,0 +1,4 @@
+export interface EstimatedGasCardProps {
+ gasCostEth: string;
+ gasCostFiat: string;
+}
diff --git a/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasCard/__snapshots__/EstimatedGasCard.test.tsx.snap b/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasCard/__snapshots__/EstimatedGasCard.test.tsx.snap
new file mode 100644
index 00000000000..3bb66a34c5c
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasCard/__snapshots__/EstimatedGasCard.test.tsx.snap
@@ -0,0 +1,419 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`EstimatedGasCard displays the estimated gas cost tooltip when pressed 1`] = `
+
+
+
+
+
+
+ Estimated gas fee
+
+
+
+
+
+
+
+
+
+
+
+
+ 🦊
+
+
+ $43.56
+
+
+
+
+ 0.0884 ETH
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`EstimatedGasCard render matches snapshot 1`] = `
+
+
+
+
+
+
+ Estimated gas fee
+
+
+
+
+
+
+
+
+
+
+
+
+ 🦊
+
+
+ $43.56
+
+
+
+
+ 0.0884 ETH
+
+
+
+
+
+
+
+
+
+`;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasFeeTooltipContent/EstimatedGasFeeTooltipContent.styles.ts b/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasFeeTooltipContent/EstimatedGasFeeTooltipContent.styles.ts
new file mode 100644
index 00000000000..de81ef5a0bd
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasFeeTooltipContent/EstimatedGasFeeTooltipContent.styles.ts
@@ -0,0 +1,13 @@
+import { StyleSheet } from 'react-native';
+
+const stylesSheet = () =>
+ StyleSheet.create({
+ estimatedGasTooltipContent: {
+ gap: 16,
+ },
+ gasLearnMoreLink: {
+ alignSelf: 'flex-start',
+ },
+ });
+
+export default stylesSheet;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasFeeTooltipContent/EstimatedGasFeeTooltipContent.test.tsx b/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasFeeTooltipContent/EstimatedGasFeeTooltipContent.test.tsx
new file mode 100644
index 00000000000..440fb7f7f3b
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasFeeTooltipContent/EstimatedGasFeeTooltipContent.test.tsx
@@ -0,0 +1,53 @@
+import React from 'react';
+import renderWithProvider from '../../../../../../util/test/renderWithProvider';
+import EstimatedGasFeeTooltipContent from './EstimatedGasFeeTooltipContent';
+import { strings } from '../../../../../../../locales/i18n';
+import { fireEvent } from '@testing-library/react-native';
+
+const mockNavigate = jest.fn();
+
+jest.mock('@react-navigation/native', () => {
+ const actualReactNavigation = jest.requireActual('@react-navigation/native');
+ return {
+ ...actualReactNavigation,
+ useNavigation: () => ({
+ navigate: mockNavigate,
+ }),
+ };
+});
+
+describe('EstimatedGasFeeTooltipContent', () => {
+ it('render matches snapshot', () => {
+ const { getByText, toJSON } = renderWithProvider(
+ ,
+ );
+
+ expect(
+ getByText(strings('tooltip_modal.estimated_gas_fee.gas_recipient')),
+ ).toBeDefined();
+ expect(
+ getByText(strings('tooltip_modal.estimated_gas_fee.gas_fluctuation')),
+ ).toBeDefined();
+ expect(
+ getByText(strings('tooltip_modal.estimated_gas_fee.gas_learn_more')),
+ ).toBeDefined();
+
+ expect(toJSON()).toMatchSnapshot();
+ });
+
+ it('redirects to "learn more about gas fees" web view when learn more pressed', () => {
+ const { getByText } = renderWithProvider();
+
+ fireEvent.press(
+ getByText(strings('tooltip_modal.estimated_gas_fee.gas_learn_more')),
+ );
+
+ expect(mockNavigate).toHaveBeenCalledTimes(1);
+ expect(mockNavigate).toHaveBeenCalledWith('Webview', {
+ params: {
+ url: 'https://support.metamask.io/transactions-and-gas/gas-fees/why-are-my-gas-fees-so-high/',
+ },
+ screen: 'SimpleWebview',
+ });
+ });
+});
diff --git a/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasFeeTooltipContent/EstimatedGasFeeTooltipContent.tsx b/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasFeeTooltipContent/EstimatedGasFeeTooltipContent.tsx
new file mode 100644
index 00000000000..65a52044848
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasFeeTooltipContent/EstimatedGasFeeTooltipContent.tsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import { useNavigation } from '@react-navigation/native';
+import { View, TouchableOpacity } from 'react-native';
+import { strings } from '../../../../../../../locales/i18n';
+import Text, {
+ TextVariant,
+ TextColor,
+} from '../../../../../../component-library/components/Texts/Text';
+import AppConstants from '../../../../../../core/AppConstants';
+import { useStyles } from '../../../../../hooks/useStyles';
+import styleSheet from './EstimatedGasFeeTooltipContent.styles';
+
+export const EstimatedGasFeeTooltipContent = () => {
+ const { styles } = useStyles(styleSheet, {});
+
+ const navigation = useNavigation();
+
+ const handleNavigateToGasLearnMore = () =>
+ navigation.navigate('Webview', {
+ screen: 'SimpleWebview',
+ params: {
+ url: AppConstants.REVIEW_PROMPT.HIGH_GAS_FEES,
+ },
+ });
+
+ return (
+
+ {strings('tooltip_modal.estimated_gas_fee.gas_recipient')}
+ {strings('tooltip_modal.estimated_gas_fee.gas_fluctuation')}
+
+
+ {strings('tooltip_modal.estimated_gas_fee.gas_learn_more')}
+
+
+
+ );
+};
+
+export default EstimatedGasFeeTooltipContent;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasFeeTooltipContent/__snapshots__/EstimatedGasFeeTooltipContent.test.tsx.snap b/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasFeeTooltipContent/__snapshots__/EstimatedGasFeeTooltipContent.test.tsx.snap
new file mode 100644
index 00000000000..bd50772cb00
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/EstimatedGasFeeTooltipContent/__snapshots__/EstimatedGasFeeTooltipContent.test.tsx.snap
@@ -0,0 +1,67 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`EstimatedGasFeeTooltipContent render matches snapshot 1`] = `
+
+
+ Gas fees are paid to crypto miners who process transactions on Ethereum network. Metamask does not profit from gas fees.
+
+
+ Gas fees are estimated and will fluctuate based on network traffic and transaction complexity.
+
+
+
+ Learn more about gas fees
+
+
+
+`;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/RewardsCard/RewardsCard.styles.ts b/app/components/UI/Stake/components/StakingConfirmation/RewardsCard/RewardsCard.styles.ts
new file mode 100644
index 00000000000..85e2cd913df
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/RewardsCard/RewardsCard.styles.ts
@@ -0,0 +1,16 @@
+import { StyleSheet } from 'react-native';
+
+const stylesSheet = () =>
+ StyleSheet.create({
+ card: {
+ borderWidth: 0,
+ gap: 16,
+ borderRadius: 8,
+ },
+ estAnnualRewardValue: {
+ flexDirection: 'row',
+ gap: 8,
+ },
+ });
+
+export default stylesSheet;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/RewardsCard/RewardsCard.test.tsx b/app/components/UI/Stake/components/StakingConfirmation/RewardsCard/RewardsCard.test.tsx
new file mode 100644
index 00000000000..ae8bebfe465
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/RewardsCard/RewardsCard.test.tsx
@@ -0,0 +1,98 @@
+import React from 'react';
+import renderWithProvider from '../../../../../../util/test/renderWithProvider';
+import RewardsCard from './RewardsCard';
+import { RewardsCardProps } from './RewardsCard.types';
+import { fireEvent } from '@testing-library/react-native';
+import { strings } from '../../../../../../../locales/i18n';
+
+const mockNavigate = jest.fn();
+
+jest.mock('@react-navigation/native', () => {
+ const actualReactNavigation = jest.requireActual('@react-navigation/native');
+ return {
+ ...actualReactNavigation,
+ useNavigation: () => ({
+ navigate: mockNavigate,
+ }),
+ };
+});
+
+describe('RewardsCard', () => {
+ beforeEach(() => {
+ jest.resetAllMocks();
+ });
+
+ it('render matches snapshot', () => {
+ const props: RewardsCardProps = {
+ rewardRate: '2.6',
+ rewardsEth: '0.13 ETH',
+ rewardsFiat: '$334.93',
+ };
+
+ const { getByText, toJSON } = renderWithProvider(
+ ,
+ );
+
+ expect(getByText(`${props.rewardRate}%`)).toBeDefined();
+ expect(getByText(props.rewardsEth)).toBeDefined();
+ expect(getByText(props.rewardsFiat)).toBeDefined();
+
+ expect(toJSON()).toMatchSnapshot();
+ });
+
+ it('reward rate tooltip displayed when pressed', () => {
+ const props: RewardsCardProps = {
+ rewardRate: '2.6',
+ rewardsEth: '0.13 ETH',
+ rewardsFiat: '$334.93',
+ };
+
+ const { toJSON, getByLabelText } = renderWithProvider(
+ ,
+ );
+
+ fireEvent.press(
+ getByLabelText(`${strings('tooltip_modal.reward_rate.title')} tooltip`),
+ );
+
+ expect(mockNavigate).toHaveBeenCalledTimes(1);
+ expect(mockNavigate).toHaveBeenCalledWith('RootModalFlow', {
+ params: {
+ title: strings('tooltip_modal.reward_rate.title'),
+ tooltip: strings('tooltip_modal.reward_rate.tooltip'),
+ },
+ screen: 'tooltipModal',
+ });
+
+ expect(toJSON()).toMatchSnapshot();
+ });
+
+ it('reward frequency tooltip displayed when pressed', () => {
+ const props: RewardsCardProps = {
+ rewardRate: '2.6',
+ rewardsEth: '0.13 ETH',
+ rewardsFiat: '$334.93',
+ };
+
+ const { toJSON, getByLabelText } = renderWithProvider(
+ ,
+ );
+
+ fireEvent.press(
+ getByLabelText(
+ `${strings('tooltip_modal.reward_frequency.title')} tooltip`,
+ ),
+ );
+
+ expect(mockNavigate).toHaveBeenCalledTimes(1);
+ expect(mockNavigate).toHaveBeenCalledWith('RootModalFlow', {
+ params: {
+ title: strings('tooltip_modal.reward_frequency.title'),
+ tooltip: strings('tooltip_modal.reward_frequency.tooltip'),
+ },
+ screen: 'tooltipModal',
+ });
+
+ expect(toJSON()).toMatchSnapshot();
+ });
+});
diff --git a/app/components/UI/Stake/components/StakingConfirmation/RewardsCard/RewardsCard.tsx b/app/components/UI/Stake/components/StakingConfirmation/RewardsCard/RewardsCard.tsx
new file mode 100644
index 00000000000..66b6eacedf5
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/RewardsCard/RewardsCard.tsx
@@ -0,0 +1,74 @@
+import React from 'react';
+import { View } from 'react-native';
+import { strings } from '../../../../../../../locales/i18n';
+import KeyValueRow, {
+ TooltipSizes,
+} from '../../../../../../component-library/components-temp/KeyValueRow';
+import Text, {
+ TextColor,
+ TextVariant,
+} from '../../../../../../component-library/components/Texts/Text';
+import { useStyles } from '../../../../../hooks/useStyles';
+import Card from '../../../../../../component-library/components/Cards/Card';
+import styleSheet from './RewardsCard.styles';
+import { RewardsCardProps } from './RewardsCard.types';
+import { fixDisplayAmount } from '../../../utils/value';
+
+const RewardsCard = ({
+ rewardRate,
+ rewardsEth,
+ rewardsFiat,
+}: RewardsCardProps) => {
+ const { styles } = useStyles(styleSheet, {});
+
+ return (
+
+
+
+ {rewardsFiat}
+ {rewardsEth}
+
+ ),
+ }}
+ />
+
+
+ );
+};
+
+export default RewardsCard;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/RewardsCard/RewardsCard.types.ts b/app/components/UI/Stake/components/StakingConfirmation/RewardsCard/RewardsCard.types.ts
new file mode 100644
index 00000000000..3d60d99f371
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/RewardsCard/RewardsCard.types.ts
@@ -0,0 +1,5 @@
+export interface RewardsCardProps {
+ rewardRate: string;
+ rewardsEth: string;
+ rewardsFiat: string;
+}
diff --git a/app/components/UI/Stake/components/StakingConfirmation/RewardsCard/__snapshots__/RewardsCard.test.tsx.snap b/app/components/UI/Stake/components/StakingConfirmation/RewardsCard/__snapshots__/RewardsCard.test.tsx.snap
new file mode 100644
index 00000000000..e2ace2593d4
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/RewardsCard/__snapshots__/RewardsCard.test.tsx.snap
@@ -0,0 +1,1255 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`RewardsCard render matches snapshot 1`] = `
+
+
+
+
+
+
+ Reward rate
+
+
+
+
+
+
+
+
+
+
+
+ 2.6%
+
+
+
+
+
+
+
+
+
+
+ Estimated annual rewards
+
+
+
+
+
+
+
+
+
+ $334.93
+
+
+ 0.13 ETH
+
+
+
+
+
+
+
+
+
+
+
+ Reward frequency
+
+
+
+
+
+
+
+
+
+
+
+ 12 hours
+
+
+
+
+
+
+`;
+
+exports[`RewardsCard reward frequency tooltip displayed when pressed 1`] = `
+
+
+
+
+
+
+ Reward rate
+
+
+
+
+
+
+
+
+
+
+
+ 2.6%
+
+
+
+
+
+
+
+
+
+
+ Estimated annual rewards
+
+
+
+
+
+
+
+
+
+ $334.93
+
+
+ 0.13 ETH
+
+
+
+
+
+
+
+
+
+
+
+ Reward frequency
+
+
+
+
+
+
+
+
+
+
+
+ 12 hours
+
+
+
+
+
+
+`;
+
+exports[`RewardsCard reward rate tooltip displayed when pressed 1`] = `
+
+
+
+
+
+
+ Reward rate
+
+
+
+
+
+
+
+
+
+
+
+ 2.6%
+
+
+
+
+
+
+
+
+
+
+ Estimated annual rewards
+
+
+
+
+
+
+
+
+
+ $334.93
+
+
+ 0.13 ETH
+
+
+
+
+
+
+
+
+
+
+
+ Reward frequency
+
+
+
+
+
+
+
+
+
+
+
+ 12 hours
+
+
+
+
+
+
+`;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/TokenValueStack/TokenValueStack.styles.ts b/app/components/UI/Stake/components/StakingConfirmation/TokenValueStack/TokenValueStack.styles.ts
new file mode 100644
index 00000000000..36e6e22593b
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/TokenValueStack/TokenValueStack.styles.ts
@@ -0,0 +1,23 @@
+import { StyleSheet } from 'react-native';
+
+const stylesSheet = () =>
+ StyleSheet.create({
+ tokenValueStackContainer: {
+ alignItems: 'center',
+ paddingVertical: 8,
+ gap: 8,
+ },
+ badgeWrapper: {
+ alignSelf: 'center',
+ },
+ ethLogo: {
+ width: 48,
+ height: 48,
+ borderRadius: 24,
+ },
+ balancesContainer: {
+ alignItems: 'center',
+ },
+ });
+
+export default stylesSheet;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/TokenValueStack/TokenValueStack.test.tsx b/app/components/UI/Stake/components/StakingConfirmation/TokenValueStack/TokenValueStack.test.tsx
new file mode 100644
index 00000000000..bcc0060c555
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/TokenValueStack/TokenValueStack.test.tsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import renderWithProvider from '../../../../../../util/test/renderWithProvider';
+import TokenValueStack from './TokenValueStack';
+import { Image } from 'react-native';
+import { TokenValueStackProps } from './TokenValueStack.types';
+import { renderFromWei } from '../../../../../../util/number';
+
+jest.mock('../../../../../hooks/useIpfsGateway', () => jest.fn());
+
+Image.getSize = jest.fn((_uri, success) => {
+ success(100, 100); // Mock successful response for ETH native Icon Image
+});
+
+describe('TokenValueStack', () => {
+ it('render matches snapshot', () => {
+ const props: TokenValueStackProps = {
+ amountWei: '3210000000000000',
+ amountFiat: '7.46',
+ tokenSymbol: 'wETH',
+ };
+
+ const { getByText, toJSON } = renderWithProvider(
+ ,
+ );
+
+ expect(
+ getByText(`${renderFromWei(props.amountWei)} ${props.tokenSymbol}`),
+ ).toBeDefined(); // 0.00321 wETH
+ expect(getByText(props.amountFiat)).toBeDefined();
+
+ expect(toJSON()).toMatchSnapshot();
+ });
+});
diff --git a/app/components/UI/Stake/components/StakingConfirmation/TokenValueStack/TokenValueStack.tsx b/app/components/UI/Stake/components/StakingConfirmation/TokenValueStack/TokenValueStack.tsx
new file mode 100644
index 00000000000..8d98a2a05fb
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/TokenValueStack/TokenValueStack.tsx
@@ -0,0 +1,54 @@
+import React from 'react';
+import { View } from 'react-native';
+import { useSelector } from 'react-redux';
+import Badge, {
+ BadgeVariant,
+} from '../../../../../../component-library/components/Badges/Badge';
+import BadgeWrapper from '../../../../../../component-library/components/Badges/BadgeWrapper';
+import Text, {
+ TextVariant,
+ TextColor,
+} from '../../../../../../component-library/components/Texts/Text';
+import { selectNetworkName } from '../../../../../../selectors/networkInfos';
+import { useStyles } from '../../../../../hooks/useStyles';
+import NetworkMainAssetLogo from '../../../../NetworkMainAssetLogo';
+import styleSheet from './TokenValueStack.styles';
+import images from '../../../../../../images/image-icons';
+import { TokenValueStackProps } from './TokenValueStack.types';
+import { renderFromWei } from '../../../../../../util/number';
+
+const TokenValueStack = ({
+ amountWei,
+ amountFiat,
+ tokenSymbol,
+ style,
+}: TokenValueStackProps) => {
+ const { styles } = useStyles(styleSheet, {});
+
+ const networkName = useSelector(selectNetworkName);
+
+ return (
+
+
+ }
+ >
+
+
+
+
+ {renderFromWei(amountWei)} {tokenSymbol}
+
+ {amountFiat}
+
+
+ );
+};
+
+export default TokenValueStack;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/TokenValueStack/TokenValueStack.types.ts b/app/components/UI/Stake/components/StakingConfirmation/TokenValueStack/TokenValueStack.types.ts
new file mode 100644
index 00000000000..add4a7ba379
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/TokenValueStack/TokenValueStack.types.ts
@@ -0,0 +1,7 @@
+import { ViewProps } from 'react-native';
+
+export interface TokenValueStackProps extends Pick {
+ amountWei: string;
+ amountFiat: string;
+ tokenSymbol: string;
+}
diff --git a/app/components/UI/Stake/components/StakingConfirmation/TokenValueStack/__snapshots__/TokenValueStack.test.tsx.snap b/app/components/UI/Stake/components/StakingConfirmation/TokenValueStack/__snapshots__/TokenValueStack.test.tsx.snap
new file mode 100644
index 00000000000..32f3cf0ccfc
--- /dev/null
+++ b/app/components/UI/Stake/components/StakingConfirmation/TokenValueStack/__snapshots__/TokenValueStack.test.tsx.snap
@@ -0,0 +1,212 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`TokenValueStack render matches snapshot 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.00321
+
+ wETH
+
+
+ 7.46
+
+
+
+`;
diff --git a/app/components/UI/Stake/routes/index.tsx b/app/components/UI/Stake/routes/index.tsx
index 321d0eb7f1c..14993705ca5 100644
--- a/app/components/UI/Stake/routes/index.tsx
+++ b/app/components/UI/Stake/routes/index.tsx
@@ -3,6 +3,7 @@ import { createStackNavigator } from '@react-navigation/stack';
import StakeInputView from '../Views/StakeInputView/StakeInputView';
import LearnMoreModal from '../components/LearnMoreModal';
import Routes from '../../../../constants/navigation/Routes';
+import StakeConfirmationView from '../Views/StakeConfirmationView/StakeConfirmationView';
import UnstakeInputView from '../Views/UnstakeInputView/UnstakeInputView';
const Stack = createStackNavigator();
const ModalStack = createStackNavigator();
@@ -20,6 +21,10 @@ const StakeScreenStack = () => (
+
);
diff --git a/app/components/Views/TooltipModal/ToolTipModal.styles.ts b/app/components/Views/TooltipModal/ToolTipModal.styles.ts
index ff89c01b839..15b9645e681 100644
--- a/app/components/Views/TooltipModal/ToolTipModal.styles.ts
+++ b/app/components/Views/TooltipModal/ToolTipModal.styles.ts
@@ -3,7 +3,6 @@ import { StyleSheet } from 'react-native';
const styleSheet = () =>
StyleSheet.create({
content: {
- paddingBottom: 16,
paddingHorizontal: 32,
flexDirection: 'row',
justifyContent: 'center',
diff --git a/app/constants/navigation/Routes.ts b/app/constants/navigation/Routes.ts
index 0becb6358b7..6bccce37031 100644
--- a/app/constants/navigation/Routes.ts
+++ b/app/constants/navigation/Routes.ts
@@ -144,6 +144,7 @@ const Routes = {
},
STAKING: {
STAKE: 'Stake',
+ STAKE_CONFIRMATION: 'StakeConfirmation',
UNSTAKE: 'Unstake',
CLAIM: 'Claim',
MODALS: {
diff --git a/app/core/AppConstants.ts b/app/core/AppConstants.ts
index 4999b670957..8aea9a7cb72 100644
--- a/app/core/AppConstants.ts
+++ b/app/core/AppConstants.ts
@@ -129,6 +129,7 @@ export default {
'https://support.metamask.io/privacy-and-security/privacy-best-practices',
SMART_TXS:
'https://support.metamask.io/transactions-and-gas/transactions/smart-transactions/',
+ STAKING_RISK_DISCLOSURE: 'https://consensys.io/staking-risk-disclosures',
},
ERRORS: {
INFURA_BLOCKED_MESSAGE:
diff --git a/locales/languages/en.json b/locales/languages/en.json
index e4c5e637b3e..e2bbea8e5c3 100644
--- a/locales/languages/en.json
+++ b/locales/languages/en.json
@@ -3351,7 +3351,14 @@
"approximately": "approximately"
},
"unstake_input_banner_description":"On average, it takes less than 3 days for the unstaked ETH to be claimable, but can take up to 11 days.",
- "max": "Max"
+ "max": "Max",
+ "staking_from": "Staking from",
+ "interacting_with": "Interacting with",
+ "12_hours": "12 hours",
+ "terms_of_service": "Terms of service",
+ "risk_disclosure": "Risk disclosure",
+ "cancel": "Cancel",
+ "confirm": "Confirm"
},
"default_settings": {
"title": "Your Wallet is ready",
@@ -3416,6 +3423,16 @@
"reward_rate": {
"title": "Reward rate",
"tooltip": "Expected yearly increase in the value of your stake, based on the reward rate over the past week."
+ },
+ "estimated_gas_fee": {
+ "title": "Estimated gas fee",
+ "gas_recipient": "Gas fees are paid to crypto miners who process transactions on Ethereum network. Metamask does not profit from gas fees.",
+ "gas_fluctuation": "Gas fees are estimated and will fluctuate based on network traffic and transaction complexity.",
+ "gas_learn_more": "Learn more about gas fees"
+ },
+ "reward_frequency": {
+ "title": "Reward frequency",
+ "tooltip": "Your staked balance updates every 12 hours to account for new rewards."
}
},
"confirm": {