diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 75b4cf336b9..6f13d4efd5a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,7 +18,6 @@ jobs:
- uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 #v1
with:
ruby-version: '3.1.6'
- bundler-cache: true
env:
BUNDLE_GEMFILE: ios/Gemfile
- run: yarn setup
@@ -181,7 +180,6 @@ jobs:
name: ios-bundle
path: ios/main.jsbundle
-
- name: Push bundle size to mobile_bundlesize_stats repo
run: ./scripts/push-bundle-size.sh
env:
diff --git a/app/components/UI/Stake/Views/StakeInputView/StakeInputView.test.tsx b/app/components/UI/Stake/Views/StakeInputView/StakeInputView.test.tsx
index c7c3005ad89..6507156a447 100644
--- a/app/components/UI/Stake/Views/StakeInputView/StakeInputView.test.tsx
+++ b/app/components/UI/Stake/Views/StakeInputView/StakeInputView.test.tsx
@@ -9,6 +9,9 @@ import { ChainId, PooledStakingContract } from '@metamask/stake-sdk';
import { Contract } from 'ethers';
import { MOCK_GET_VAULT_RESPONSE } from '../../__mocks__/mockData';
import { toWei } from '../../../../../util/number';
+import { strings } from '../../../../../../locales/i18n';
+// eslint-disable-next-line import/no-namespace
+import * as useStakingGasFee from '../../hooks/useStakingGasFee';
function render(Component: React.ComponentType) {
return renderScreen(
@@ -96,7 +99,6 @@ jest.mock('../../hooks/useStakingGasFee', () => ({
__esModule: true,
default: () => ({
estimatedGasFeeWei: mockGasFee,
- gasLimit: 70122,
isLoadingStakingGasFee: false,
isStakingGasFeeError: false,
refreshGasValues: jest.fn(),
@@ -203,5 +205,31 @@ describe('StakeInputView', () => {
screen: Routes.STAKING.MODALS.LEARN_MORE,
});
});
+
+ it('navigates to gas impact modal when gas cost is 30% or more of deposit amount', () => {
+ jest.spyOn(useStakingGasFee, 'default').mockReturnValue({
+ estimatedGasFeeWei: toWei('0.25'),
+ isLoadingStakingGasFee: false,
+ isStakingGasFeeError: false,
+ refreshGasValues: jest.fn(),
+ });
+
+ render(StakeInputView);
+
+ fireEvent.press(screen.getByText('25%'));
+
+ fireEvent.press(screen.getByText(strings('stake.review')));
+
+ expect(mockNavigate).toHaveBeenLastCalledWith('StakeModals', {
+ screen: Routes.STAKING.MODALS.GAS_IMPACT,
+ params: {
+ amountFiat: '750',
+ amountWei: '375000000000000000',
+ annualRewardRate: '2.5%',
+ annualRewardsETH: '0.00938 ETH',
+ annualRewardsFiat: '18.75 USD',
+ },
+ });
+ });
});
});
diff --git a/app/components/UI/Stake/Views/StakeInputView/StakeInputView.tsx b/app/components/UI/Stake/Views/StakeInputView/StakeInputView.tsx
index 06fdb4ab6ba..56c3cc749ae 100644
--- a/app/components/UI/Stake/Views/StakeInputView/StakeInputView.tsx
+++ b/app/components/UI/Stake/Views/StakeInputView/StakeInputView.tsx
@@ -48,6 +48,8 @@ const StakeInputView = () => {
isLoadingVaultData,
handleMax,
balanceValue,
+ isHighGasCostImpact,
+ isLoadingStakingGasFee,
} = useStakingInputHandlers();
const navigateToLearnMoreModal = () => {
@@ -57,6 +59,20 @@ const StakeInputView = () => {
};
const handleStakePress = useCallback(() => {
+ if (isHighGasCostImpact()) {
+ navigation.navigate('StakeModals', {
+ screen: Routes.STAKING.MODALS.GAS_IMPACT,
+ params: {
+ amountWei: amountWei.toString(),
+ amountFiat: fiatAmount,
+ annualRewardsETH,
+ annualRewardsFiat,
+ annualRewardRate,
+ },
+ });
+ return;
+ }
+
navigation.navigate('StakeScreens', {
screen: Routes.STAKING.STAKE_CONFIRMATION,
params: {
@@ -77,7 +93,7 @@ const StakeInputView = () => {
.build(),
);
}, [
- amountEth,
+ isHighGasCostImpact,
navigation,
amountWei,
fiatAmount,
@@ -86,6 +102,7 @@ const StakeInputView = () => {
annualRewardRate,
trackEvent,
createEventBuilder,
+ amountEth,
]);
const handleMaxButtonPress = () => {
@@ -173,7 +190,9 @@ const StakeInputView = () => {
size={ButtonSize.Lg}
labelTextVariant={TextVariant.BodyMDMedium}
variant={ButtonVariants.Primary}
- isDisabled={isOverMaximum || !isNonZeroAmount}
+ isDisabled={
+ isOverMaximum || !isNonZeroAmount || isLoadingStakingGasFee
+ }
width={ButtonWidthTypes.Full}
onPress={handleStakePress}
/>
diff --git a/app/components/UI/Stake/components/GasImpactModal/GasImpactModal.styles.ts b/app/components/UI/Stake/components/GasImpactModal/GasImpactModal.styles.ts
new file mode 100644
index 00000000000..54b53fd9e90
--- /dev/null
+++ b/app/components/UI/Stake/components/GasImpactModal/GasImpactModal.styles.ts
@@ -0,0 +1,16 @@
+import { StyleSheet } from 'react-native';
+
+const styleSheet = () =>
+ StyleSheet.create({
+ container: {
+ paddingHorizontal: 16,
+ },
+ content: {
+ paddingBottom: 16,
+ },
+ footer: {
+ paddingBottom: 16,
+ },
+ });
+
+export default styleSheet;
diff --git a/app/components/UI/Stake/components/GasImpactModal/GasImpactModal.test.tsx b/app/components/UI/Stake/components/GasImpactModal/GasImpactModal.test.tsx
new file mode 100644
index 00000000000..7018981373c
--- /dev/null
+++ b/app/components/UI/Stake/components/GasImpactModal/GasImpactModal.test.tsx
@@ -0,0 +1,75 @@
+import React from 'react';
+import renderWithProvider from '../../../../../util/test/renderWithProvider';
+import { GasImpactModalProps } from './GasImpactModal.types';
+import GasImpactModal from './index';
+import { Metrics, SafeAreaProvider } from 'react-native-safe-area-context';
+import { fireEvent } from '@testing-library/react-native';
+import { strings } from '../../../../../../locales/i18n';
+
+const mockNavigate = jest.fn();
+const mockGoBack = jest.fn();
+
+jest.mock('@react-navigation/native', () => {
+ const actualReactNavigation = jest.requireActual('@react-navigation/native');
+ return {
+ ...actualReactNavigation,
+ useNavigation: () => ({
+ navigate: mockNavigate,
+ goBack: mockGoBack,
+ }),
+ };
+});
+
+const props: GasImpactModalProps = {
+ route: {
+ key: '1',
+ params: {
+ amountWei: '3210000000000000',
+ amountFiat: '7.46',
+ annualRewardRate: '2.5%',
+ annualRewardsETH: '2.5 ETH',
+ annualRewardsFiat: '$5000',
+ },
+ name: 'params',
+ },
+};
+
+const initialMetrics: Metrics = {
+ frame: { x: 0, y: 0, width: 320, height: 640 },
+ insets: { top: 0, left: 0, right: 0, bottom: 0 },
+};
+
+const renderGasImpactModal = () =>
+ renderWithProvider(
+
+ ,
+ ,
+ );
+
+describe('GasImpactModal', () => {
+ it('render matches snapshot', () => {
+ const { toJSON } = renderGasImpactModal();
+
+ expect(toJSON()).toMatchSnapshot();
+ });
+
+ it('navigates to StakeConfirmationView on approval', () => {
+ const { getByText } = renderGasImpactModal();
+
+ const proceedAnywayButton = getByText(strings('stake.proceed_anyway'));
+
+ fireEvent.press(proceedAnywayButton);
+
+ expect(mockNavigate).toHaveBeenCalledTimes(1);
+ });
+
+ it('closes gas impact modal on cancel', () => {
+ const { getByText } = renderGasImpactModal();
+
+ const proceedAnywayButton = getByText(strings('stake.cancel'));
+
+ fireEvent.press(proceedAnywayButton);
+
+ expect(mockGoBack).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/app/components/UI/Stake/components/GasImpactModal/GasImpactModal.types.ts b/app/components/UI/Stake/components/GasImpactModal/GasImpactModal.types.ts
new file mode 100644
index 00000000000..a00204cfbee
--- /dev/null
+++ b/app/components/UI/Stake/components/GasImpactModal/GasImpactModal.types.ts
@@ -0,0 +1,13 @@
+import { RouteProp } from '@react-navigation/native';
+
+interface GasImpactModalRouteParams {
+ amountWei: string;
+ amountFiat: string;
+ annualRewardsETH: string;
+ annualRewardsFiat: string;
+ annualRewardRate: string;
+}
+
+export interface GasImpactModalProps {
+ route: RouteProp<{ params: GasImpactModalRouteParams }, 'params'>;
+}
diff --git a/app/components/UI/Stake/components/GasImpactModal/__snapshots__/GasImpactModal.test.tsx.snap b/app/components/UI/Stake/components/GasImpactModal/__snapshots__/GasImpactModal.test.tsx.snap
new file mode 100644
index 00000000000..4050753f14d
--- /dev/null
+++ b/app/components/UI/Stake/components/GasImpactModal/__snapshots__/GasImpactModal.test.tsx.snap
@@ -0,0 +1,342 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`GasImpactModal render matches snapshot 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Gas cost impact
+
+
+
+
+
+
+
+
+
+
+
+ Warning: the transaction gas cost will account for more than 30% of your deposit.
+
+
+
+
+ Cancel
+
+
+
+
+ Proceed anyway
+
+
+
+
+
+
+
+ ,
+
+`;
diff --git a/app/components/UI/Stake/components/GasImpactModal/index.tsx b/app/components/UI/Stake/components/GasImpactModal/index.tsx
new file mode 100644
index 00000000000..4e348f75426
--- /dev/null
+++ b/app/components/UI/Stake/components/GasImpactModal/index.tsx
@@ -0,0 +1,103 @@
+import React, { useRef } from 'react';
+import BottomSheet, {
+ BottomSheetRef,
+} from '../../../../../component-library/components/BottomSheets/BottomSheet';
+import Text, {
+ TextColor,
+ TextVariant,
+} from '../../../../../component-library/components/Texts/Text';
+import { View } from 'react-native';
+import BottomSheetHeader from '../../../../../component-library/components/BottomSheets/BottomSheetHeader';
+import BottomSheetFooter, {
+ ButtonsAlignment,
+} from '../../../../../component-library/components/BottomSheets/BottomSheetFooter';
+import {
+ ButtonProps,
+ ButtonSize,
+ ButtonVariants,
+} from '../../../../../component-library/components/Buttons/Button/Button.types';
+import styleSheet from './GasImpactModal.styles';
+import { useStyles } from '../../../../hooks/useStyles';
+import { useNavigation } from '@react-navigation/native';
+import Routes from '../../../../../constants/navigation/Routes';
+import { GasImpactModalProps } from './GasImpactModal.types';
+import { strings } from '../../../../../../locales/i18n';
+
+const GasImpactModal = ({ route }: GasImpactModalProps) => {
+ const { styles } = useStyles(styleSheet, {});
+
+ const { navigate } = useNavigation();
+
+ const sheetRef = useRef(null);
+
+ const handleClose = () => {
+ sheetRef.current?.onCloseBottomSheet();
+ };
+
+ const handleNavigateToStakeReviewScreen = () => {
+ const {
+ amountWei,
+ annualRewardRate,
+ annualRewardsFiat,
+ annualRewardsETH,
+ amountFiat,
+ } = route.params;
+
+ navigate('StakeScreens', {
+ screen: Routes.STAKING.STAKE_CONFIRMATION,
+ params: {
+ amountWei: amountWei.toString(),
+ amountFiat,
+ annualRewardsETH,
+ annualRewardsFiat,
+ annualRewardRate,
+ },
+ });
+ };
+
+ const footerButtons: ButtonProps[] = [
+ {
+ variant: ButtonVariants.Secondary,
+ label: (
+
+ {strings('stake.cancel')}
+
+ ),
+ size: ButtonSize.Lg,
+ onPress: handleClose,
+ },
+ {
+ variant: ButtonVariants.Primary,
+ label: (
+
+ {strings('stake.proceed_anyway')}
+
+ ),
+ labelTextVariant: TextVariant.BodyMDMedium,
+ size: ButtonSize.Lg,
+ onPress: handleNavigateToStakeReviewScreen,
+ },
+ ];
+
+ return (
+
+
+
+
+ {strings('stake.gas_cost_impact')}
+
+
+
+ {strings('stake.gas_cost_impact_warning')}
+
+
+
+
+ );
+};
+
+export default GasImpactModal;
diff --git a/app/components/UI/Stake/components/StakingBalance/StakingBalance.tsx b/app/components/UI/Stake/components/StakingBalance/StakingBalance.tsx
index 9aafb72b6e9..a014fca6425 100644
--- a/app/components/UI/Stake/components/StakingBalance/StakingBalance.tsx
+++ b/app/components/UI/Stake/components/StakingBalance/StakingBalance.tsx
@@ -43,6 +43,7 @@ import type { TokenI } from '../../../Tokens/types';
import useBalance from '../../hooks/useBalance';
import { NetworkBadgeSource } from '../../../AssetOverview/Balance/Balance';
import { selectChainId } from '../../../../../selectors/networkController';
+import SkeletonPlaceholder from 'react-native-skeleton-placeholder';
export interface StakingBalanceProps {
asset: TokenI;
@@ -62,6 +63,7 @@ const StakingBalanceContent = ({ asset }: StakingBalanceProps) => {
exchangeRate,
hasStakedPositions,
hasEthToUnstake,
+ isLoadingPooledStakesData,
} = usePooledStakes();
const { vaultData } = useVaultData();
const annualRewardRate = vaultData?.apy || '';
@@ -94,6 +96,74 @@ const StakingBalanceContent = ({ asset }: StakingBalanceProps) => {
return <>>;
}
+ const renderStakingContent = () => {
+ if (isLoadingPooledStakesData) {
+ return (
+
+
+
+ );
+ }
+
+ if (!isEligibleForPooledStaking) {
+ return (
+
+ );
+ }
+
+ return (
+ <>
+ {unstakingRequests.map(
+ ({ positionTicket, withdrawalTimestamp, assetsToDisplay }) =>
+ assetsToDisplay && (
+
+ ),
+ )}
+
+ {hasClaimableEth && (
+
+ )}
+
+ {!hasStakedPositions && (
+
+ )}
+
+
+ >
+ );
+ };
+
return (
{hasStakedPositions && (
@@ -120,61 +190,7 @@ const StakingBalanceContent = ({ asset }: StakingBalanceProps) => {
)}
-
- {!isEligibleForPooledStaking ? (
-
- ) : (
- <>
- {unstakingRequests.map(
- ({ positionTicket, withdrawalTimestamp, assetsToDisplay }) =>
- assetsToDisplay && (
-
- ),
- )}
-
- {hasClaimableEth && (
-
- )}
-
- {!hasStakedPositions && (
-
- )}
-
-
- >
- )}
-
+ {renderStakingContent()}
);
};
diff --git a/app/components/UI/Stake/hooks/usePoolStakedUnstake/index.ts b/app/components/UI/Stake/hooks/usePoolStakedUnstake/index.ts
index 04e8802c5c7..641a2fd9c49 100644
--- a/app/components/UI/Stake/hooks/usePoolStakedUnstake/index.ts
+++ b/app/components/UI/Stake/hooks/usePoolStakedUnstake/index.ts
@@ -1,3 +1,4 @@
+import { ethers } from 'ethers';
import { PooledStakingContract, ChainId } from '@metamask/stake-sdk';
import { useStakeContext } from '../useStakeContext';
import {
@@ -8,6 +9,7 @@ import {
import { addTransaction } from '../../../../../util/transaction-controller';
import { ORIGIN_METAMASK } from '@metamask/controller-utils';
import trackErrorAsAnalytics from '../../../../../util/metrics/TrackError/trackErrorAsAnalytics';
+import useBalance from '../useBalance';
const generateUnstakeTxParams = (
activeAccountAddress: string,
@@ -23,11 +25,33 @@ const generateUnstakeTxParams = (
});
const attemptUnstakeTransaction =
- (pooledStakingContract: PooledStakingContract) =>
+ (pooledStakingContract: PooledStakingContract, stakedBalanceWei: string) =>
// Note: receiver is the user address attempting to unstake.
async (valueWei: string, receiver: string) => {
try {
- const shares = await pooledStakingContract.convertToShares(valueWei);
+ // STAKE-867: This is temporary logic for the unstake all action
+ // if we are unstaking the total assets we send the total shares
+ // the user has in the vault through getShares contract method
+ // this is a quick fix for mobile only and will be refactored to cover
+ // portfolio in the future. We avoid the case where contract level rounding
+ // error causes 1 wei dust to be left when converting assets to shares
+ // and attempting to unstake all assets
+ let shares;
+ if (valueWei === stakedBalanceWei) {
+ // create the interface for the getShares method and call getShares to get user shares
+ const tempInterface = new ethers.utils.Interface([
+ 'function getShares(address) returns (uint256)',
+ ]);
+ const data = tempInterface.encodeFunctionData('getShares', [receiver]);
+ const sharesResult = await pooledStakingContract?.contract.provider.call({
+ to: pooledStakingContract?.contract.address,
+ data,
+ });
+ const [sharesBN] = tempInterface.decodeFunctionResult('getShares', sharesResult);
+ shares = sharesBN.toString();
+ } else {
+ shares = await pooledStakingContract.convertToShares(valueWei);
+ }
const gasLimit = await pooledStakingContract.estimateEnterExitQueueGas(
shares.toString(),
@@ -64,11 +88,12 @@ const attemptUnstakeTransaction =
const usePoolStakedUnstake = () => {
const stakeContext = useStakeContext();
+ const { stakedBalanceWei } = useBalance();
const stakingContract = stakeContext.stakingContract as PooledStakingContract;
return {
- attemptUnstakeTransaction: attemptUnstakeTransaction(stakingContract),
+ attemptUnstakeTransaction: attemptUnstakeTransaction(stakingContract, stakedBalanceWei),
};
};
diff --git a/app/components/UI/Stake/hooks/usePoolStakedUnstake/usePoolStakedUnstake.test.tsx b/app/components/UI/Stake/hooks/usePoolStakedUnstake/usePoolStakedUnstake.test.tsx
index eb546d23f64..780791f1d0c 100644
--- a/app/components/UI/Stake/hooks/usePoolStakedUnstake/usePoolStakedUnstake.test.tsx
+++ b/app/components/UI/Stake/hooks/usePoolStakedUnstake/usePoolStakedUnstake.test.tsx
@@ -7,8 +7,9 @@ import {
StakingType,
ChainId,
} from '@metamask/stake-sdk';
-import { Contract } from 'ethers';
+import { BigNumber, Contract, ethers } from 'ethers';
import { Stake } from '../../sdk/stakeSdkProvider';
+import useBalance from '../useBalance';
const MOCK_ADDRESS_1 = '0x0';
@@ -32,6 +33,8 @@ const MOCK_DEPOSIT_CONTRACT_ADDRESS =
const MOCK_RECEIVER_ADDRESS = '0x316bde155acd07609872a56bc32ccfb0b13201fa';
const MOCK_UNSTAKE_GAS_LIMIT = 73135;
const MOCK_UNSTAKE_VALUE_WEI = '10000000000000000'; // 0.01 ETH
+const MOCK_STAKED_BALANCE_VALUE_WEI = '20000000000000000'; // 0.02 ETH
+const MOCK_UNSTAKE_ALL_VALUE_WEI = MOCK_STAKED_BALANCE_VALUE_WEI;
const ENCODED_TX_UNSTAKE_DATA = {
chainId: 1,
@@ -82,7 +85,12 @@ jest.mock('../../../../../core/Engine', () => {
const mockPooledStakingContractService: PooledStakingContract = {
chainId: ChainId.ETHEREUM,
connectSignerOrProvider: mockConnectSignerOrProvider,
- contract: new Contract('0x0000000000000000000000000000000000000000', []),
+ contract: {
+ ...new Contract('0x0000000000000000000000000000000000000000', []),
+ provider: {
+ call: jest.fn(),
+ },
+ } as unknown as Contract,
convertToShares: mockConvertToShares,
encodeClaimExitedAssetsTransactionData: jest.fn(),
encodeDepositTransactionData: jest.fn(),
@@ -100,10 +108,19 @@ const mockSdkContext: Stake = {
setSdkType: jest.fn(),
};
+const mockBalance: Pick, 'stakedBalanceWei'> = {
+ stakedBalanceWei: MOCK_STAKED_BALANCE_VALUE_WEI,
+};
+
jest.mock('../useStakeContext', () => ({
useStakeContext: () => mockSdkContext,
}));
+jest.mock('../useBalance', () => ({
+ __esModule: true,
+ default: () => mockBalance,
+}));
+
describe('usePoolStakedUnstake', () => {
afterEach(() => {
jest.clearAllMocks();
@@ -129,5 +146,27 @@ describe('usePoolStakedUnstake', () => {
expect(mockEncodeEnterExitQueueTransactionData).toHaveBeenCalledTimes(1);
expect(mockAddTransaction).toHaveBeenCalledTimes(1);
});
+
+ it('attempts to create and submit an unstake all transaction', async () => {
+ jest.spyOn(ethers.utils, 'Interface').mockImplementation(() => ({
+ encodeFunctionData: jest.fn(),
+ decodeFunctionResult: jest.fn().mockReturnValue([BigNumber.from(MOCK_UNSTAKE_ALL_VALUE_WEI)]),
+ } as unknown as ethers.utils.Interface));
+
+ const { result } = renderHookWithProvider(() => usePoolStakedUnstake(), {
+ state: mockInitialState,
+ });
+
+ await result.current.attemptUnstakeTransaction(
+ MOCK_UNSTAKE_ALL_VALUE_WEI,
+ MOCK_RECEIVER_ADDRESS,
+ );
+
+ expect(mockConvertToShares).toHaveBeenCalledTimes(0);
+ expect(mockEstimateEnterExitQueueGas).toHaveBeenCalledTimes(1);
+ expect(mockEncodeEnterExitQueueTransactionData).toHaveBeenCalledTimes(1);
+ expect(mockEncodeEnterExitQueueTransactionData).toHaveBeenCalledWith(BigNumber.from(MOCK_UNSTAKE_ALL_VALUE_WEI).toString(), MOCK_RECEIVER_ADDRESS, { gasLimit: MOCK_UNSTAKE_GAS_LIMIT });
+ expect(mockAddTransaction).toHaveBeenCalledTimes(1);
+ });
});
});
diff --git a/app/components/UI/Stake/hooks/useStakingInput.ts b/app/components/UI/Stake/hooks/useStakingInput.ts
index 3193c1a7002..5920a3f633c 100644
--- a/app/components/UI/Stake/hooks/useStakingInput.ts
+++ b/app/components/UI/Stake/hooks/useStakingInput.ts
@@ -97,6 +97,17 @@ const useStakingInputHandlers = () => {
? `${balanceETH} ETH`
: `${balanceFiatNumber?.toString()} ${currentCurrency.toUpperCase()}`;
+ const getDepositTxGasPercentage = useCallback(
+ () => estimatedGasFeeWei.mul(new BN(100)).div(amountWei).toString(),
+ [amountWei, estimatedGasFeeWei],
+ );
+
+ // Gas fee make up 30% or more of the deposit amount.
+ const isHighGasCostImpact = useCallback(
+ () => new BN(getDepositTxGasPercentage()).gt(new BN(30)),
+ [getDepositTxGasPercentage],
+ );
+
return {
amountEth,
amountWei,
@@ -122,6 +133,8 @@ const useStakingInputHandlers = () => {
handleMax,
isLoadingStakingGasFee,
balanceValue,
+ getDepositTxGasPercentage,
+ isHighGasCostImpact,
};
};
diff --git a/app/components/UI/Stake/routes/index.tsx b/app/components/UI/Stake/routes/index.tsx
index c6b98f24d2e..cf1ae55b3cb 100644
--- a/app/components/UI/Stake/routes/index.tsx
+++ b/app/components/UI/Stake/routes/index.tsx
@@ -8,6 +8,7 @@ import UnstakeInputView from '../Views/UnstakeInputView/UnstakeInputView';
import UnstakeConfirmationView from '../Views/UnstakeConfirmationView/UnstakeConfirmationView';
import { StakeSDKProvider } from '../sdk/stakeSdkProvider';
import MaxInputModal from '../components/MaxInputModal';
+import GasImpactModal from '../components/GasImpactModal';
const Stack = createStackNavigator();
const ModalStack = createStackNavigator();
@@ -57,6 +58,11 @@ const StakeModalStack = () => (
component={MaxInputModal}
options={{ headerShown: false }}
/>
+
);
diff --git a/app/components/Views/Wallet/index.test.tsx b/app/components/Views/Wallet/index.test.tsx
index a7baa2653b3..a145d9d1c8e 100644
--- a/app/components/Views/Wallet/index.test.tsx
+++ b/app/components/Views/Wallet/index.test.tsx
@@ -1,14 +1,12 @@
import React from 'react';
import Wallet from './';
import { renderScreen } from '../../../util/test/renderWithProvider';
-import { act, screen } from '@testing-library/react-native';
+import { screen } from '@testing-library/react-native';
import ScrollableTabView from 'react-native-scrollable-tab-view';
import Routes from '../../../constants/navigation/Routes';
import { backgroundState } from '../../../util/test/initial-root-state';
import { MOCK_ACCOUNTS_CONTROLLER_STATE } from '../../../util/test/accountsControllerTestUtils';
import { WalletViewSelectorsIDs } from '../../../../e2e/selectors/wallet/WalletView.selectors';
-import { useAccountSyncing } from '../../../util/notifications/hooks/useAccountSyncing';
-import { AppState } from 'react-native';
const MOCK_ADDRESS = '0xc4955c0d639d99699bfd7ec54d9fafee40e4d272';
@@ -186,40 +184,4 @@ describe('Wallet', () => {
);
expect(accountPicker).toBeDefined();
});
- it('dispatches account syncing on mount', () => {
- jest.clearAllMocks();
- //@ts-expect-error we are ignoring the navigation params on purpose because we do not want to mock setOptions to test the navbar
- render(Wallet);
- expect(useAccountSyncing().dispatchAccountSyncing).toHaveBeenCalledTimes(1);
- });
- it('dispatches account syncing when appState switches from inactive|background to active', () => {
- jest.clearAllMocks();
-
- const addEventListener = jest.spyOn(AppState, 'addEventListener');
-
- //@ts-expect-error we are ignoring the navigation params on purpose because we do not want to mock setOptions to test the navbar
- render(Wallet);
-
- expect(addEventListener).toHaveBeenCalledWith(
- 'change',
- expect.any(Function),
- );
- const handleAppStateChange = (
- addEventListener as jest.Mock
- ).mock.calls.find(([event]) => event === 'change')[1];
-
- act(() => {
- handleAppStateChange('background');
- handleAppStateChange('active');
- });
-
- expect(useAccountSyncing().dispatchAccountSyncing).toHaveBeenCalledTimes(2);
-
- act(() => {
- handleAppStateChange('inactive');
- handleAppStateChange('active');
- });
-
- expect(useAccountSyncing().dispatchAccountSyncing).toHaveBeenCalledTimes(3);
- });
});
diff --git a/app/components/Views/Wallet/index.tsx b/app/components/Views/Wallet/index.tsx
index 2036f343925..d06231fc9b9 100644
--- a/app/components/Views/Wallet/index.tsx
+++ b/app/components/Views/Wallet/index.tsx
@@ -1,10 +1,4 @@
-import React, {
- useEffect,
- useRef,
- useCallback,
- useContext,
- useLayoutEffect,
-} from 'react';
+import React, { useEffect, useRef, useCallback, useContext } from 'react';
import {
ActivityIndicator,
StyleSheet,
@@ -12,8 +6,6 @@ import {
TextStyle,
InteractionManager,
Linking,
- AppState,
- AppStateStatus,
} from 'react-native';
import type { Theme } from '@metamask/design-tokens';
import { connect, useSelector } from 'react-redux';
@@ -93,9 +85,7 @@ import {
selectIsProfileSyncingEnabled,
} from '../../../selectors/notifications';
import { ButtonVariants } from '../../../component-library/components/Buttons/Button';
-import { useListNotifications } from '../../../util/notifications/hooks/useNotifications';
import { useAccountName } from '../../hooks/useAccountName';
-import { useAccountSyncing } from '../../../util/notifications/hooks/useAccountSyncing';
import { PortfolioBalance } from '../../UI/Tokens/TokenList/PortfolioBalance';
import useCheckNftAutoDetectionModal from '../../hooks/useCheckNftAutoDetectionModal';
@@ -162,10 +152,7 @@ const Wallet = ({
showNftFetchingLoadingIndicator,
hideNftFetchingLoadingIndicator,
}: WalletProps) => {
- const appState = useRef(AppState.currentState);
const { navigate } = useNavigation();
- const { listNotifications } = useListNotifications();
- const { dispatchAccountSyncing } = useAccountSyncing();
const walletRef = useRef(null);
const theme = useTheme();
const { toastRef } = useContext(ToastContext);
@@ -415,35 +402,6 @@ const Wallet = ({
[navigation, providerConfig.chainId],
);
- // Layout effect when component/view is visible
- // - fetches notifications
- // - dispatches account syncing
- useLayoutEffect(() => {
- const handleAppStateChange = (nextAppState: AppStateStatus) => {
- if (
- appState.current?.match(/inactive|background/) &&
- nextAppState === 'active'
- ) {
- listNotifications();
- dispatchAccountSyncing();
- }
-
- appState.current = nextAppState;
- };
-
- const subscription = AppState.addEventListener(
- 'change',
- handleAppStateChange,
- );
-
- listNotifications();
- dispatchAccountSyncing();
-
- return () => {
- subscription.remove();
- };
- }, [listNotifications, dispatchAccountSyncing]);
-
useEffect(() => {
navigation.setOptions(
getWalletNavbarOptions(
@@ -533,12 +491,9 @@ const Wallet = ({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let stakedBalance: any = 0;
- const assets = [
- ...(tokens || []),
- ];
+ const assets = [...(tokens || [])];
if (accountBalanceByChainId) {
-
balance = renderFromWei(accountBalanceByChainId.balance);
const nativeAsset = {
// TODO: Add name property to Token interface in controllers.
@@ -575,8 +530,8 @@ const Wallet = ({
conversionRate,
currentCurrency,
),
- // TODO: Replace "any" with type
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ // TODO: Replace "any" with type
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any;
assets.push(stakedAsset);
}
diff --git a/app/constants/navigation/Routes.ts b/app/constants/navigation/Routes.ts
index 008fb429b5d..3b3dfcc7ced 100644
--- a/app/constants/navigation/Routes.ts
+++ b/app/constants/navigation/Routes.ts
@@ -155,6 +155,7 @@ const Routes = {
MODALS: {
LEARN_MORE: 'LearnMore',
MAX_INPUT: 'MaxInput',
+ GAS_IMPACT: 'GasImpact',
},
},
///: BEGIN:ONLY_INCLUDE_IF(external-snaps)
diff --git a/app/util/sentry/utils.js b/app/util/sentry/utils.js
index 06937e5d3bc..5241f5fa41e 100644
--- a/app/util/sentry/utils.js
+++ b/app/util/sentry/utils.js
@@ -10,6 +10,7 @@ import { store } from '../../store';
import { Performance } from '../../core/Performance';
import Device from '../device';
import { TraceName } from '../trace';
+import { getTraceTags } from './tags';
/**
* This symbol matches all object properties when used in a mask
*/
@@ -402,15 +403,19 @@ function rewriteReport(report) {
*/
export function excludeEvents(event) {
// This is needed because store starts to initialise before performance observers completes to measure app start time
- if (event?.transaction === TraceName.UIStartup && Device.isAndroid()) {
- const appLaunchTime = Performance.appLaunchTime;
- const formattedAppLaunchTime = (event.start_timestamp = Number(
- `${appLaunchTime.toString().slice(0, 10)}.${appLaunchTime
- .toString()
- .slice(10)}`,
- ));
- if (event.start_timestamp !== formattedAppLaunchTime) {
- event.start_timestamp = formattedAppLaunchTime;
+ if (event?.transaction === TraceName.UIStartup) {
+ event.tags = getTraceTags(store.getState());
+
+ if (Device.isAndroid()) {
+ const appLaunchTime = Performance.appLaunchTime;
+ const formattedAppLaunchTime = (event.start_timestamp = Number(
+ `${appLaunchTime.toString().slice(0, 10)}.${appLaunchTime
+ .toString()
+ .slice(10)}`,
+ ));
+ if (event.start_timestamp !== formattedAppLaunchTime) {
+ event.start_timestamp = formattedAppLaunchTime;
+ }
}
}
//Modify or drop event here
diff --git a/bitrise.yml b/bitrise.yml
index 124b3e2daf6..eb1a69d7959 100644
--- a/bitrise.yml
+++ b/bitrise.yml
@@ -135,8 +135,8 @@ stages:
- run_ios_api_specs: {}
- run_tag_smoke_accounts_ios: {}
- run_tag_smoke_accounts_android: {}
- - run_tag_smoke_notifications_ios: {}
- - run_tag_smoke_notifications_android: {}
+ # - run_tag_smoke_notifications_ios: {}
+ # - run_tag_smoke_notifications_android: {}
# - run_tag_smoke_assets_ios: {}
- run_tag_smoke_assets_android: {}
- run_tag_smoke_confirmations_ios: {}
diff --git a/ios/Gemfile b/ios/Gemfile
index 4d245c9f943..e05db0cadde 100644
--- a/ios/Gemfile
+++ b/ios/Gemfile
@@ -1,10 +1,10 @@
source 'https://rubygems.org'
# Recommended to use http://rbenv.org/ to install and use this version
-ruby '>= 3.1.6'
+ruby '3.1.6'
# Allow minor version updates up to but excluding 2.0.0
-gem 'cocoapods', '~> 1.12'
+gem 'cocoapods', '1.16.2'
# Allow all version updates up to but excluding 7.1.0
gem 'activesupport', '>= 6.1.7.3', '< 7.1.0'
diff --git a/ios/Gemfile.lock b/ios/Gemfile.lock
index ed3f678b6d1..0f857ed10f5 100644
--- a/ios/Gemfile.lock
+++ b/ios/Gemfile.lock
@@ -10,18 +10,18 @@ GEM
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
- addressable (2.8.6)
- public_suffix (>= 2.0.2, < 6.0)
+ addressable (2.8.7)
+ public_suffix (>= 2.0.2, < 7.0)
algoliasearch (1.27.5)
httpclient (~> 2.8, >= 2.8.3)
json (>= 1.5.1)
atomos (0.1.3)
base64 (0.2.0)
claide (1.1.0)
- cocoapods (1.15.2)
+ cocoapods (1.16.2)
addressable (~> 2.8)
claide (>= 1.0.2, < 2.0)
- cocoapods-core (= 1.15.2)
+ cocoapods-core (= 1.16.2)
cocoapods-deintegrate (>= 1.0.3, < 2.0)
cocoapods-downloader (>= 2.1, < 3.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
@@ -35,8 +35,8 @@ GEM
molinillo (~> 0.8.0)
nap (~> 1.0)
ruby-macho (>= 2.3.0, < 3.0)
- xcodeproj (>= 1.23.0, < 2.0)
- cocoapods-core (1.15.2)
+ xcodeproj (>= 1.27.0, < 2.0)
+ cocoapods-core (1.16.2)
activesupport (>= 5.0, < 8)
addressable (~> 2.8)
algoliasearch (~> 1.0)
@@ -60,36 +60,36 @@ GEM
escape (0.0.4)
ethon (0.16.0)
ffi (>= 1.15.0)
- ffi (1.16.3)
+ ffi (1.17.0)
+ ffi (1.17.0-arm64-darwin)
+ ffi (1.17.0-x86_64-linux-gnu)
fourflusher (2.3.1)
fuzzy_match (2.0.4)
gh_inspector (1.1.3)
httpclient (2.8.3)
i18n (1.14.4)
concurrent-ruby (~> 1.0)
- json (2.7.2)
+ json (2.8.1)
minitest (5.22.3)
molinillo (0.8.0)
- nanaimo (0.3.0)
+ nanaimo (0.4.0)
nap (1.1.0)
netrc (0.11.0)
nkf (0.2.0)
public_suffix (4.0.7)
- rexml (3.3.6)
- strscan
+ rexml (3.3.9)
ruby-macho (2.5.1)
- strscan (3.1.0)
typhoeus (1.4.1)
ethon (>= 0.9.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
- xcodeproj (1.25.0)
+ xcodeproj (1.27.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
- nanaimo (~> 0.3.0)
- rexml (>= 3.3.2, < 4.0)
+ nanaimo (~> 0.4.0)
+ rexml (>= 3.3.6, < 4.0)
PLATFORMS
arm64-darwin-22
@@ -98,7 +98,7 @@ PLATFORMS
DEPENDENCIES
activesupport (>= 6.1.7.3, < 7.1.0)
- cocoapods (~> 1.12)
+ cocoapods (= 1.16.2)
RUBY VERSION
ruby 3.1.6p260
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index d2a95947052..84b23bf4f32 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -200,9 +200,9 @@ PODS:
- lottie-react-native (5.1.5):
- lottie-ios (~> 3.4.0)
- React-Core
- - MMKV (1.3.9):
- - MMKVCore (~> 1.3.9)
- - MMKVCore (1.3.9)
+ - MMKV (2.0.0):
+ - MMKVCore (~> 2.0.0)
+ - MMKVCore (2.0.0)
- MultiplatformBleAdapter (0.2.0)
- nanopb (2.30910.0):
- nanopb/decode (= 2.30910.0)
@@ -1165,7 +1165,7 @@ SPEC CHECKSUMS:
BEMCheckBox: 5ba6e37ade3d3657b36caecc35c8b75c6c2b1a4e
boost: 7dcd2de282d72e344012f7d6564d024930a6a440
Branch: 4ac024cb3c29b0ef628048694db3c4cfa679beb0
- BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872
+ BVLinearGradient: 44fd36b87f318e7933c4c91a6991442a5e3f5bcf
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
FBLazyVector: 25cbffbaec517695d376ab4bc428948cd0f08088
@@ -1194,63 +1194,63 @@ SPEC CHECKSUMS:
GzipSwift: 893f3e48e597a1a4f62fafcb6514220fcf8287fa
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
lottie-ios: 016449b5d8be0c3dcbcfa0a9988469999cd04c5d
- lottie-react-native: 3e722c63015fdb9c27638b0a77969fc412067c18
- MMKV: 817ba1eea17421547e01e087285606eb270a8dcb
- MMKVCore: af055b00e27d88cd92fad301c5fecd1ff9b26dd9
+ lottie-react-native: b6776287d7fd31be4fd865059cd890f744242ffd
+ MMKV: f7d1d5945c8765f97f39c3d121f353d46735d801
+ MMKVCore: c04b296010fcb1d1638f2c69405096aac12f6390
MultiplatformBleAdapter: b1fddd0d499b96b607e00f0faa8e60648343dc1d
nanopb: 438bc412db1928dac798aa6fd75726007be04262
OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c
- Permission-BluetoothPeripheral: 247e379c9ecb4b1af2b87f73e4a15a00a5bc0c1f
+ Permission-BluetoothPeripheral: 34ab829f159c6cf400c57bac05f5ba1b0af7a86e
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
- RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1
+ RCT-Folly: 8dc08ca5a393b48b1c523ab6220dfdcc0fe000ad
RCTRequired: fb207f74935626041e7308c9e88dcdda680f1073
- RCTSearchApi: d2d38a5a7bffbfb144e2c770fbb30f51b1053067
+ RCTSearchApi: 5fc36140c598a74fd831dca924a28ed53bc7aa18
RCTTypeSafety: 146fd11361680250b7580dd1f7f601995cfad1b1
React: f3712351445cc96ba507425675a0cd8d31321d0c
React-callinvoker: dcc51a66e02d20a70aeca2abbb1388d4d3011bf8
- React-Codegen: 04b7e88a7f5d3933d058ffb9cea7b0268666de79
- React-Core: ed3aeebf41aeb621de2ab4b58216a2fd5a5fd141
- React-CoreModules: 9d1e6f44bf658431a3b99561c8058b54b5959190
- React-cxxreact: d2d14fc0c0782bd9ed7a556892769b4034ae027c
+ React-Codegen: ef431087b06572288cd0f789c9cf1d22b37c3019
+ React-Core: 88bf9e0d862195fda28723fd95aef3111025f300
+ React-CoreModules: 96a557c45f6be644a82d63066c4ac79173bba0ff
+ React-cxxreact: 3db957f2a0db039b95c1103ea2274e36815b8009
React-debug: 4e90d08c78aa207c064a3860e1540ff252695585
React-jsc: 9ffa4c837c5286366d27c892b6c7c34da3cd5f3d
- React-jsi: 020729f637b93456de0018061d44ce36f33c2d8a
- React-jsiexecutor: ce8ecfcd3b7dbc9cb65a661110be17f5afd18aa3
+ React-jsi: 08cb162e1d192bf197bc0693270ab65d8e9d4d5c
+ React-jsiexecutor: b71b576b4447d9fed6f2f1b146550de70d49a75a
React-jsinspector: b86a8abae760c28d69366bbc1d991561e51341ed
- React-logger: ed7c9e01e58529065e7da6bf8318baf15024283e
- react-native-aes: 0143040f4e0cb19296b69b4acc7ddd8d3df9d62d
- react-native-background-timer: 1b6e6b4e10f1b74c367a1fdc3c72b67c619b222b
- react-native-ble-plx: f0557dbb6bd1f26cca75a67b5f33cfc7f7f9abed
- react-native-blob-jsi-helper: 13c10135af4614dbc0712afba5960784cd44f043
- react-native-blob-util: 18b510205c080a453574a7d2344d64673d0ad9af
- react-native-blur: 507cf3dd4434eb9d5ca5f183e49d8bcccdd66826
- react-native-branch: 4e42fda662d96893afbbd02839806931398e3d2e
- react-native-camera: b8cc03e2feec0c04403d0998e37cf519d8fd4c6f
- react-native-compat: 8b6a38155e778a20a008aea837efd00e099b6fe8
- react-native-cookies: f54fcded06bb0cda05c11d86788020b43528a26c
- react-native-fast-crypto: 5943c42466b86ad70be60d3a5f64bd22251e5d9e
- react-native-flipper: 6cfd5991388121f7f96fc5171b93380f97ebb3c6
- react-native-get-random-values: a6ea6a8a65dc93e96e24a11105b1a9c8cfe1d72a
- react-native-gzip: c5e87ee9e359f02350e3a2ee52eb35eddc398868
- react-native-in-app-review: db8bb167a5f238e7ceca5c242d6b36ce8c4404a4
- react-native-launch-arguments: 4e0fd58e56dcc7f52eedef9dc8eff81eb73ced7a
- react-native-mmkv: e97c0c79403fb94577e5d902ab1ebd42b0715b43
- react-native-netinfo: 48c5f79a84fbc3ba1d28a8b0d04adeda72885fa8
- react-native-performance: ff93f8af3b2ee9519fd7879896aa9b8b8272691d
- react-native-quick-base64: 777057ea4286f806b00259ede65dc79c7c706320
- react-native-quick-crypto: 455c1b411db006dba1026a30681ececb19180187
- react-native-randombytes: 421f1c7d48c0af8dbcd471b0324393ebf8fe7846
- react-native-render-html: 984dfe2294163d04bf5fe25d7c9f122e60e05ebe
- react-native-safe-area-context: 9e40fb181dac02619414ba1294d6c2a807056ab9
- react-native-slider: f266dd860064138a659a42714e6da47a52a51107
- react-native-video: c26780b224543c62d5e1b2a7244a5cd1b50e8253
- react-native-view-shot: 4475fde003fe8a210053d1f98fb9e06c1d834e1c
- react-native-webview-mm: c518409c962c1f0f95c08bb6a700b9f97aff131b
- React-NativeModulesApple: 7bab439cb5de9a76299210ed1127698170777a7f
+ React-logger: 8c0f8173197ad28ac3212c18f8141690209dfe52
+ react-native-aes: e8b2e113d532b0efb6449754492aee9c218dd502
+ react-native-background-timer: 007ff829f79644caf2ed013e22f0563560336f86
+ react-native-ble-plx: c08c34c162509ec466c68a7cdc86b69c12e6efdd
+ react-native-blob-jsi-helper: bd7509e50b0f906044c53ad7ab767786054424c9
+ react-native-blob-util: 6560d6fc4b940ec140f9c3ebe21c8669b1df789b
+ react-native-blur: 7c03644c321696ccec9778447180e0f9339b3604
+ react-native-branch: 76e1f947b40597727e6faa5cba5824d7ecf6c6b0
+ react-native-camera: 1e6fefa515d3af8b4aeaca3a8bffa2925252c4ea
+ react-native-compat: 8050db8973090f2c764807e7fa74f163f78e4c32
+ react-native-cookies: d648ab7025833b977c0b19e142503034f5f29411
+ react-native-fast-crypto: 6b448866f5310cf203714a21147ef67f735bea8e
+ react-native-flipper: ca4382a2b6cfd319b6e212539bc1fe7aafe36879
+ react-native-get-random-values: 0fd2b6a3129988d701d10e30f0622d5f039531bc
+ react-native-gzip: 8d602277c2564591f04dd1cec4043acc8350dcc3
+ react-native-in-app-review: b3d1eed3d1596ebf6539804778272c4c65e4a400
+ react-native-launch-arguments: 7eb321ed3f3ef19b3ec4a2eca71c4f9baee76b41
+ react-native-mmkv: 5a46c73e3e12aa872c4485ae0e4414b4040af79a
+ react-native-netinfo: 26560022f28c06d8ef00a9ff1e03beefbbb60c2d
+ react-native-performance: 125a96c145e29918b55b45ce25cbba54f1e24dcd
+ react-native-quick-base64: daf67f19ee076b77f0755bf4056f3425f164e1d8
+ react-native-quick-crypto: eff065b704d3f1c6e336cfc612dce63228ab3482
+ react-native-randombytes: 3c8f3e89d12487fd03a2f966c288d495415fc116
+ react-native-render-html: 5afc4751f1a98621b3009432ef84c47019dcb2bd
+ react-native-safe-area-context: 667324e20fb3dd9c39c12d6036675ed90099bcd5
+ react-native-slider: 6a25a7398addb8478798315a58504efce744009d
+ react-native-video: 2aad0d963bf3952bd9ebb2f53fab799338e8e202
+ react-native-view-shot: bb8934cb93bf8ec740c81ed94f93244778797b6c
+ react-native-webview-mm: d5f16bf95d45db97b53851ab87c79b2e1d964a13
+ React-NativeModulesApple: ee6c836571c874dc879cf87603edff00d8dded46
React-perflogger: 6acc671f527e69c0cd93b8e62821d33d3ddf25ca
React-RCTActionSheet: 569bb9db46d85565d14697e15ecf2166e035eb07
React-RCTAnimation: 0eea98143c2938a8751a33722623d3e8a38fe1e4
- React-RCTAppDelegate: 74d38dbb3d8691f72e6dda670006e85d9ea21c91
+ React-RCTAppDelegate: 11e6d38c00a34e1025b9ef26bb13968f6d9ed902
React-RCTBlob: 9b3b60e806ce5c9fe5a8ee449f3e41087617441c
React-RCTImage: 0220975422a367e784dfd794adfc6454fab23c1f
React-RCTLinking: 1abf9834017e080ecbd5b6a28b4fb15eb843a3dd
@@ -1261,42 +1261,42 @@ SPEC CHECKSUMS:
React-RCTVibration: 372a12b697a170aaee792f8a9999c40e1f2692d0
React-rncore: d1ccbd5adaf4a67703790838b7c62f140e72d32a
React-runtimeexecutor: d4f7ff5073fcf87e14dbf89541d434218630246e
- React-runtimescheduler: b360635f6f804ec42fa875500620882a6b97d2f5
- React-utils: 8eb3c12fd4a4da6df3824f7d9a961d73a6ed6e5d
- ReactCommon: 317bddf4a70fca9e542343e942a504285282971c
- ReactNativePayments: db62ee22a825e9e9c3e19c276d8d020881dd0630
- RNCAsyncStorage: 826b603ae9c0f88b5ac4e956801f755109fa4d5c
- RNCCheckbox: a3ca9978cb0846b981d28da4e9914bd437403d77
- RNCClipboard: ddd4d291537f1667209c9c405aaa4307297e252e
- RNCMaskedView: 090213d32d8b3bb83a4dcb7d12c18f0152591906
- RNDateTimePicker: 4f3c4dbd4f908be32ec8c93f086e8924bd4a2e07
- RNDefaultPreference: 2f8d6d54230edbd78708ada8d63bb275e5a8415b
- RNDeviceInfo: 1e3f62b9ec32f7754fac60bd06b8f8a27124e7f0
- RNFBApp: 5f87753a8d8b37d229adf85cd0ff37709ffdf008
- RNFBMessaging: 3fa1114c0868dd21f20dfe186adf42297ea316b1
- RNFS: 4ac0f0ea233904cb798630b3c077808c06931688
- RNGestureHandler: a479ebd5ed4221a810967000735517df0d2db211
- RNI18n: e2f7e76389fcc6e84f2c8733ea89b92502351fd8
- RNInAppBrowser: e36d6935517101ccba0e875bac8ad7b0cb655364
- RNKeychain: 4f63aada75ebafd26f4bc2c670199461eab85d94
- RNNotifee: 2b7df6e32a9cc24b9af6b410fa7db1cd2f411d6d
- RNOS: 6f2f9a70895bbbfbdad7196abd952e7b01d45027
- RNPermissions: 4e3714e18afe7141d000beae3755e5b5fb2f5e05
- RNReanimated: f8379347f71248607d530a21e31e4140c5910c25
- RNScreens: 68fd1060f57dd1023880bf4c05d74784b5392789
- RNSensors: c363d486c879e181905dea84a2535e49af1c2d25
- RNSentry: ae79ba7d46cfdf501be85ef72af1b7c8b1d80a79
- RNShare: f116bbb04f310c665ca483d0bd1e88cf59b3b334
- RNSVG: a48668fd382115bc89761ce291a81c4ca5f2fd2e
- RNVectorIcons: 6607bd3a30291d0edb56f9bbe7ae411ee2b928b0
- segment-analytics-react-native: dbdd08d96fec78132e96bda092562e41c2ce0ce0
+ React-runtimescheduler: 06b060b5b022f4cdb6bd9fd3405396372179cd9b
+ React-utils: e50991349b1b749744f35ff93d943343886deb24
+ ReactCommon: 394d4d2b27d88bb8ae15fa7f864a4a7525f467f0
+ ReactNativePayments: 47056cd9f1dc32dbdd716974de5df700c44f12db
+ RNCAsyncStorage: aa75595c1aefa18f868452091fa0c411a516ce11
+ RNCCheckbox: 450ce156f3e29e25efa0315c96cfbabe5a39ded1
+ RNCClipboard: ba13782f62310ffd4377332497241a1051f6870b
+ RNCMaskedView: de80352547bd4f0d607bf6bab363d826822bd126
+ RNDateTimePicker: 590f2000e4272050b98689cee6c8abc66c25bb22
+ RNDefaultPreference: 36fe31684af1f2d14e0664aa9a816d0ec6149cc1
+ RNDeviceInfo: e5219d380b51ddb7f97e650ab99a518476b90203
+ RNFBApp: 0e66b9f844efdf2ac3fa2b30e64c9db41a263b3d
+ RNFBMessaging: 70b12c9f22c7c9d5011ac9b12ac2bafbfb081267
+ RNFS: 89de7d7f4c0f6bafa05343c578f61118c8282ed8
+ RNGestureHandler: 6572a5f44759900730562b418da289c373de8d06
+ RNI18n: 11ec5086508673ef71b5b567da3e8bcca8a926e1
+ RNInAppBrowser: 6d3eb68d471b9834335c664704719b8be1bfdb20
+ RNKeychain: 3194f1c9d8599f39e570b4b5ecbcdd8cd610e771
+ RNNotifee: 5165d37aaf980031837be3caece2eae5a6d73ae8
+ RNOS: d07e5090b5060c6f2b83116d740a32cfdb33afe3
+ RNPermissions: bd0d9ca7969ff7b999aa605ee2e5919c12522bfe
+ RNReanimated: 7a85cf61cf3849efb530a9de2204a119f426636a
+ RNScreens: f112bc5442a9a0e468809c107a43f55882d6cd98
+ RNSensors: 4690be00931bc60be7c7bd457701edefaff965e3
+ RNSentry: 984bb0495abef6c419697bef208c581f127891d1
+ RNShare: d03cdc71e750246a48b81dcd62bd792bf57a758e
+ RNSVG: e77adf5edb2302f0f10dd03a09e92bb9420d914e
+ RNVectorIcons: 24be0b504ce32d5bea38bde6c645f08b9c736392
+ segment-analytics-react-native: 885c1703579dc7964b97e7ae11d857669aa9b015
Sentry: f8374b5415bc38dfb5645941b3ae31230fbeae57
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
- sovran-react-native: 791f2f726b4d57ece59676eda58d6da9dc95ad4e
- TcpSockets: a8eb6b5867fe643e6cfed5db2e4de62f4d1e8fd0
+ sovran-react-native: e4721a564ee6ef5b5a0d901bc677018cf371ea01
+ TcpSockets: 48866ffcb39d7114741919d21069fc90189e474a
Yoga: 6f5ab94cd8b1ecd04b6e973d0bc583ede2a598cc
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
PODFILE CHECKSUM: e0bcc4eb12d48746028cd4f4161a292fa9ddc627
-COCOAPODS: 1.15.2
+COCOAPODS: 1.16.2
diff --git a/locales/languages/en.json b/locales/languages/en.json
index 1345810012d..9c371ef2bde 100644
--- a/locales/languages/en.json
+++ b/locales/languages/en.json
@@ -3503,7 +3503,10 @@
"description": "Max is the total amount of ETH you have, minus the gas fee required to stake. It’s a good idea to keep some extra ETH in your wallet for future transactions."
},
"use_max": "Use max",
- "estimated_unstaking_time": "1 to 11 days"
+ "estimated_unstaking_time": "1 to 11 days",
+ "proceed_anyway": "Proceed anyway",
+ "gas_cost_impact": "Gas cost impact",
+ "gas_cost_impact_warning": "Warning: the transaction gas cost will account for more than 30% of your deposit."
},
"default_settings": {
"title": "Your Wallet is ready",