From e721d23e06dbfb0427d654434e7abd6446f5e6e2 Mon Sep 17 00:00:00 2001
From: Matthew Grainger <46547583+Matt561@users.noreply.github.com>
Date: Thu, 24 Oct 2024 13:32:41 -0400
Subject: [PATCH] feat: STAKE-803 integrate unstake method from sdk (#11962)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## **Description**
This PR adds the ability to unstake natively within the MetaMask mobile
app.
## **Related issues**
Jira Ticket: [FE] [Unstake flow] Integrate unstake method from sdk -
([link](https://consensyssoftware.atlassian.net/browse/STAKE-803))
## **Manual testing steps**
prerequisite: if you don't have any Ethereum staked, you'll need to
stake some in order to unstake.
1. Add `export MM_POOLED_STAKING_UI_ENABLED=true` to `.js.env` file.
2. Click on Ethereum from the asset list page
3. Click on "Unstake"
4. Enter a valid amount to unstake and click "Review"
5. Click "Continue" to initiate the transaction-controller flow.
## Figma Designs
- Staking UX
([link](https://www.figma.com/design/1c0Y9jDJe6p0j82jydJDcs/Staking-UX?node-id=2979-19504&m=dev))
## **Screenshots/Recordings**
### **Before**
https://github.com/user-attachments/assets/bd1919d3-dcc6-4443-b08c-7944e183914c
### **After**
https://github.com/user-attachments/assets/924905b1-405a-49f9-aa9e-eb0cbeaf872e
## **Pre-merge author checklist**
- [x] I’ve followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
---
.../StakeConfirmationView.test.tsx | 6 +
.../StakeConfirmationView.test.tsx.snap | 143 ++-
.../StakeInputView.test.tsx.snap | 8 +-
.../UnstakeConfirmationView.test.tsx | 6 +
.../UnstakeConfirmationView.test.tsx.snap | 143 ++-
app/components/UI/Stake/__mocks__/mockData.ts | 40 +-
.../components/EstimatedAnnualRewardsCard.tsx | 7 +-
.../AccountCard/AccountCard.test.tsx | 16 +-
.../AccountCard/AccountCard.tsx | 23 +-
.../__snapshots__/AccountCard.test.tsx.snap | 907 ++++++++++--------
.../ConfirmationFooter.test.tsx | 17 +-
.../FooterButtonGroup.test.tsx | 60 +-
.../FooterButtonGroup/FooterButtonGroup.tsx | 106 +-
.../FooterButtonGroup.test.tsx.snap | 178 ++++
.../ConfirmationFooter.test.tsx.snap | 3 +
.../ContractTag/ContractTag.test.tsx | 7 +-
.../ContractTag/ContractTag.tsx | 28 +-
.../ContractTag/ContractTag.types.ts | 2 +
.../__snapshots__/ContractTag.test.tsx.snap | 140 ++-
.../Stake/hooks/usePoolStakedUnstake/index.ts | 73 ++
.../usePoolStakedUnstake.test.tsx | 133 +++
.../UI/Stake/sdk/stakeSdkProvider.test.tsx | 42 +-
.../UI/Stake/sdk/stakeSdkProvider.tsx | 25 +-
23 files changed, 1586 insertions(+), 527 deletions(-)
create mode 100644 app/components/UI/Stake/hooks/usePoolStakedUnstake/index.ts
create mode 100644 app/components/UI/Stake/hooks/usePoolStakedUnstake/usePoolStakedUnstake.test.tsx
diff --git a/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.test.tsx b/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.test.tsx
index 720b5242f61..90bdb658e9a 100644
--- a/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.test.tsx
+++ b/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.test.tsx
@@ -7,6 +7,7 @@ import { backgroundState } from '../../../../../util/test/initial-root-state';
import configureMockStore from 'redux-mock-store';
import { Provider } from 'react-redux';
import { StakeConfirmationViewProps } from './StakeConfirmationView.types';
+import { MOCK_POOL_STAKING_SDK } from '../../__mocks__/mockData';
jest.mock('../../../../hooks/useIpfsGateway', () => jest.fn());
@@ -60,6 +61,11 @@ jest.mock('../../hooks/usePoolStakedDeposit', () => ({
}),
}));
+jest.mock('../../hooks/useStakeContext', () => ({
+ __esModule: true,
+ useStakeContext: jest.fn(() => MOCK_POOL_STAKING_SDK),
+}));
+
jest.mock('../../hooks/usePooledStakes', () => ({
__esModule: true,
default: () => ({
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
index eedfba7fe99..ab1c216fd7a 100644
--- a/app/components/UI/Stake/Views/StakeConfirmationView/__snapshots__/StakeConfirmationView.test.tsx.snap
+++ b/app/components/UI/Stake/Views/StakeConfirmationView/__snapshots__/StakeConfirmationView.test.tsx.snap
@@ -638,18 +638,146 @@ exports[`StakeConfirmationView render matches snapshot 1`] = `
}
}
>
-
+ >
+
+
+
+
+
+
+
+
+
+
diff --git a/app/components/UI/Stake/Views/UnstakeConfirmationView/UnstakeConfirmationView.test.tsx b/app/components/UI/Stake/Views/UnstakeConfirmationView/UnstakeConfirmationView.test.tsx
index ada7220b852..735c23b5be7 100644
--- a/app/components/UI/Stake/Views/UnstakeConfirmationView/UnstakeConfirmationView.test.tsx
+++ b/app/components/UI/Stake/Views/UnstakeConfirmationView/UnstakeConfirmationView.test.tsx
@@ -5,6 +5,7 @@ import { Image } from 'react-native';
import { createMockAccountsControllerState } from '../../../../../util/test/accountsControllerTestUtils';
import { backgroundState } from '../../../../../util/test/initial-root-state';
import { UnstakeConfirmationViewProps } from './UnstakeConfirmationView.types';
+import { MOCK_POOL_STAKING_SDK } from '../../__mocks__/mockData';
const MOCK_ADDRESS_1 = '0x0';
const MOCK_ADDRESS_2 = '0x1';
@@ -58,6 +59,11 @@ jest.mock('../../hooks/usePooledStakes', () => ({
}),
}));
+jest.mock('../../hooks/useStakeContext', () => ({
+ __esModule: true,
+ useStakeContext: jest.fn(() => MOCK_POOL_STAKING_SDK),
+}));
+
describe('UnstakeConfirmationView', () => {
it('render matches snapshot', () => {
const props: UnstakeConfirmationViewProps = {
diff --git a/app/components/UI/Stake/Views/UnstakeConfirmationView/__snapshots__/UnstakeConfirmationView.test.tsx.snap b/app/components/UI/Stake/Views/UnstakeConfirmationView/__snapshots__/UnstakeConfirmationView.test.tsx.snap
index c0b65def7af..2b82484bc70 100644
--- a/app/components/UI/Stake/Views/UnstakeConfirmationView/__snapshots__/UnstakeConfirmationView.test.tsx.snap
+++ b/app/components/UI/Stake/Views/UnstakeConfirmationView/__snapshots__/UnstakeConfirmationView.test.tsx.snap
@@ -863,18 +863,146 @@ exports[`UnstakeConfirmationView render matches snapshot 1`] = `
}
}
>
-
+ >
+
+
+
+
+
+
+
+
+
+
= {
+ fetchFromApi: jest.fn(),
+ getPooledStakes: jest.fn(),
+ getVaultData: jest.fn(),
+ getPooledStakingEligibility: jest.fn(),
+ baseUrl: 'https://staking.api.com',
+};
+
+const MOCK_POOLED_STAKING_CONTRACT_SERVICE = {
+ chainId: ChainId.ETHEREUM,
+ connectSignerOrProvider: jest.fn(),
+ contract: new Contract('0x0000000000000000000000000000000000000000', []),
+ convertToShares: jest.fn(),
+ encodeClaimExitedAssetsTransactionData: jest.fn(),
+ encodeDepositTransactionData: jest.fn(),
+ encodeEnterExitQueueTransactionData: jest.fn(),
+ encodeMulticallTransactionData: jest.fn(),
+ estimateClaimExitedAssetsGas: jest.fn(),
+ estimateDepositGas: jest.fn(),
+ estimateEnterExitQueueGas: jest.fn(),
+ estimateMulticallGas: jest.fn(),
+};
+
+export const MOCK_POOL_STAKING_SDK: Stake = {
+ stakingContract: MOCK_POOLED_STAKING_CONTRACT_SERVICE,
+ stakingApiService: MOCK_STAKING_API_SERVICE as StakingApiService,
+ sdkType: StakingType.POOLED,
+ setSdkType: jest.fn(),
+};
diff --git a/app/components/UI/Stake/components/EstimatedAnnualRewardsCard.tsx b/app/components/UI/Stake/components/EstimatedAnnualRewardsCard.tsx
index 35d39695c4f..7c2b306cef3 100644
--- a/app/components/UI/Stake/components/EstimatedAnnualRewardsCard.tsx
+++ b/app/components/UI/Stake/components/EstimatedAnnualRewardsCard.tsx
@@ -4,6 +4,7 @@ import { strings } from '../../../../../locales/i18n';
import Icon, {
IconColor,
IconName,
+ IconSize,
} from '../../../../component-library/components/Icons/Icon';
import Text, {
TextColor,
@@ -66,7 +67,11 @@ const EstimatedAnnualRewardsCard = ({
onPress={onIconPress}
accessibilityLabel="Learn More"
>
-
+
diff --git a/app/components/UI/Stake/components/StakingConfirmation/AccountCard/AccountCard.test.tsx b/app/components/UI/Stake/components/StakingConfirmation/AccountCard/AccountCard.test.tsx
index b1e81d21976..4b7aab92d6c 100644
--- a/app/components/UI/Stake/components/StakingConfirmation/AccountCard/AccountCard.test.tsx
+++ b/app/components/UI/Stake/components/StakingConfirmation/AccountCard/AccountCard.test.tsx
@@ -3,10 +3,9 @@ import renderWithProvider from '../../../../../../util/test/renderWithProvider';
import AccountCard from './AccountCard';
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 { AccountCardProps } from './AccountCard.types';
+import { MOCK_POOL_STAKING_SDK } from '../../../__mocks__/mockData';
const MOCK_STAKING_CONTRACT_NAME = 'MM Pooled Staking';
@@ -18,8 +17,6 @@ const MOCK_ACCOUNTS_CONTROLLER_STATE = createMockAccountsControllerState([
MOCK_ADDRESS_2,
]);
-const mockStore = configureMockStore();
-
const mockInitialState = {
settings: {},
engine: {
@@ -29,7 +26,6 @@ const mockInitialState = {
},
},
};
-const store = mockStore(mockInitialState);
jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
@@ -50,6 +46,11 @@ jest.mock('@react-navigation/native', () => {
};
});
+jest.mock('../../../hooks/useStakeContext', () => ({
+ __esModule: true,
+ useStakeContext: jest.fn(() => MOCK_POOL_STAKING_SDK),
+}));
+
describe('AccountCard', () => {
it('render matches snapshot', () => {
const props: AccountCardProps = {
@@ -59,9 +60,8 @@ describe('AccountCard', () => {
};
const { getByText, toJSON } = renderWithProvider(
-
- ,
- ,
+ ,
+ { state: mockInitialState },
);
expect(getByText(strings('stake.staking_from'))).toBeDefined();
diff --git a/app/components/UI/Stake/components/StakingConfirmation/AccountCard/AccountCard.tsx b/app/components/UI/Stake/components/StakingConfirmation/AccountCard/AccountCard.tsx
index fdd805b94ea..41f5d883cff 100644
--- a/app/components/UI/Stake/components/StakingConfirmation/AccountCard/AccountCard.tsx
+++ b/app/components/UI/Stake/components/StakingConfirmation/AccountCard/AccountCard.tsx
@@ -16,9 +16,11 @@ import images from '../../../../../../images/image-icons';
import AccountTag from '../AccountTag/AccountTag';
import { selectNetworkName } from '../../../../../../selectors/networkInfos';
import { AccountCardProps } from './AccountCard.types';
+import { useStakeContext } from '../../../hooks/useStakeContext';
import ContractTag from '../ContractTag/ContractTag';
+import { RootState } from '../../../../BasicFunctionality/BasicFunctionalityModal/BasicFunctionalityModal.test';
-const AccountHeaderCard = ({
+const AccountCard = ({
contractName,
primaryLabel,
secondaryLabel,
@@ -29,6 +31,12 @@ const AccountHeaderCard = ({
const networkName = useSelector(selectNetworkName);
+ const useBlockieIcon = useSelector(
+ (state: RootState) => state.settings.useBlockieIcon,
+ );
+
+ const { stakingContract } = useStakeContext();
+
return (
@@ -40,6 +48,7 @@ const AccountHeaderCard = ({
),
}}
@@ -50,7 +59,15 @@ const AccountHeaderCard = ({
label: { text: secondaryLabel },
}}
value={{
- label: ,
+ label: (
+
+ ),
}}
/>
@@ -75,4 +92,4 @@ const AccountHeaderCard = ({
);
};
-export default AccountHeaderCard;
+export default AccountCard;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/AccountCard/__snapshots__/AccountCard.test.tsx.snap b/app/components/UI/Stake/components/StakingConfirmation/AccountCard/__snapshots__/AccountCard.test.tsx.snap
index 6e485b602ef..696e3ad7e8b 100644
--- a/app/components/UI/Stake/components/StakingConfirmation/AccountCard/__snapshots__/AccountCard.test.tsx.snap
+++ b/app/components/UI/Stake/components/StakingConfirmation/AccountCard/__snapshots__/AccountCard.test.tsx.snap
@@ -1,43 +1,51 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AccountCard render matches snapshot 1`] = `
-[
-
-
+
+
@@ -46,42 +54,42 @@ exports[`AccountCard render matches snapshot 1`] = `
{
"alignItems": "center",
"flexDirection": "row",
- "gap": 8,
}
}
>
-
-
- Staking from
-
-
+ Staking from
+
+
+
@@ -90,236 +98,236 @@ exports[`AccountCard render matches snapshot 1`] = `
{
"alignItems": "center",
"flexDirection": "row",
- "gap": 8,
}
}
>
-
-
-
-
-
-
+
+
-
-
-
+ }
+ height={16}
+ matrix={
+ [
+ -0.458649554484315,
+ 0.8886172326549487,
+ -0.8886172326549487,
+ -0.458649554484315,
+ 14.2400072574634,
+ 19.300266514976617,
+ ]
+ }
+ propList={
+ [
+ "fill",
+ ]
+ }
+ width={16}
+ x={0}
+ y={0}
+ />
+
+
-
+
-
+
- Account 1
-
-
+ }
+ testID="tagbase-text"
+ >
+ Account 1
+
+
+
@@ -328,42 +336,42 @@ exports[`AccountCard render matches snapshot 1`] = `
{
"alignItems": "center",
"flexDirection": "row",
- "gap": 8,
}
}
>
-
-
- Interacting with
-
-
+ Interacting with
+
+
+
@@ -372,127 +380,255 @@ exports[`AccountCard render matches snapshot 1`] = `
{
"alignItems": "center",
"flexDirection": "row",
- "gap": 8,
}
}
>
-
-
- MM Pooled Staking
-
+
+
+
+
+
+
+
+
+
+
+ MM Pooled Staking
+
-
-
+
+
+
@@ -501,42 +637,42 @@ exports[`AccountCard render matches snapshot 1`] = `
{
"alignItems": "center",
"flexDirection": "row",
- "gap": 8,
}
}
>
-
-
- Network
-
-
+ Network
+
+
+
@@ -545,7 +681,6 @@ exports[`AccountCard render matches snapshot 1`] = `
{
"alignItems": "center",
"flexDirection": "row",
- "gap": 8,
}
}
>
@@ -554,6 +689,7 @@ exports[`AccountCard render matches snapshot 1`] = `
{
"alignItems": "center",
"flexDirection": "row",
+ "gap": 8,
}
}
>
@@ -561,63 +697,52 @@ exports[`AccountCard render matches snapshot 1`] = `
style={
{
"alignItems": "center",
- "flexDirection": "row",
- "gap": 8,
+ "backgroundColor": "#ffffff",
+ "borderRadius": 8,
+ "height": 16,
+ "justifyContent": "center",
+ "overflow": "hidden",
+ "width": 16,
}
}
>
-
-
-
-
- Ethereum Main Network
-
+ testID="network-avatar-image"
+ />
+
+ Ethereum Main Network
+
-
- ,
- ",",
-]
+
+
+
`;
diff --git a/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/ConfirmationFooter.test.tsx b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/ConfirmationFooter.test.tsx
index f9a045f68bf..b7447a17bc9 100644
--- a/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/ConfirmationFooter.test.tsx
+++ b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/ConfirmationFooter.test.tsx
@@ -5,6 +5,7 @@ import { ConfirmationFooterProps } from './ConfirmationFooter.types';
import { createMockAccountsControllerState } from '../../../../../../util/test/accountsControllerTestUtils';
import { backgroundState } from '../../../../../../util/test/initial-root-state';
import { FooterButtonGroupActions } from './FooterButtonGroup/FooterButtonGroup.types';
+import { MOCK_POOL_STAKING_SDK } from '../../../__mocks__/mockData';
const MOCK_ADDRESS_1 = '0x0';
const MOCK_ADDRESS_2 = '0x1';
@@ -34,20 +35,8 @@ jest.mock('@react-navigation/native', () => {
};
});
-jest.mock('../../../hooks/usePoolStakedDeposit', () => ({
- __esModule: true,
- default: () => ({
- poolStakingContract: {},
- estimateDepositGas: jest.fn(),
- attemptDepositTransaction: jest.fn(),
- }),
-}));
-
-jest.mock('../../../hooks/usePooledStakes', () => ({
- __esModule: true,
- default: () => ({
- refreshPooledStakes: jest.fn(),
- }),
+jest.mock('../../../hooks/useStakeContext', () => ({
+ useStakeContext: () => MOCK_POOL_STAKING_SDK,
}));
describe('ConfirmationFooter', () => {
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
index 509c2054dcb..b67a2a409e8 100644
--- a/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.test.tsx
+++ b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.test.tsx
@@ -9,21 +9,25 @@ import {
} from './FooterButtonGroup.types';
import { createMockAccountsControllerState } from '../../../../../../../util/test/accountsControllerTestUtils';
import { backgroundState } from '../../../../../../../util/test/initial-root-state';
+import { MOCK_POOL_STAKING_SDK } from '../../../../__mocks__/mockData';
const MOCK_ADDRESS_1 = '0x0';
-const MOCK_ADDRESS_2 = '0x1';
const MOCK_ACCOUNTS_CONTROLLER_STATE = createMockAccountsControllerState([
MOCK_ADDRESS_1,
- MOCK_ADDRESS_2,
]);
+const mockSubscribeOnceIf = jest.fn();
+
const mockInitialState = {
settings: {},
engine: {
backgroundState: {
...backgroundState,
AccountsController: MOCK_ACCOUNTS_CONTROLLER_STATE,
+ controllerMessenger: {
+ subscribeOnceIf: mockSubscribeOnceIf,
+ },
},
},
};
@@ -44,12 +48,24 @@ jest.mock('@react-navigation/native', () => {
};
});
+jest.mock('../../../../hooks/useStakeContext', () => ({
+ useStakeContext: () => MOCK_POOL_STAKING_SDK,
+}));
+
+const mockAttemptDepositTransaction = jest.fn();
+const mockAttemptUnstakeTransaction = jest.fn();
+
jest.mock('../../../../hooks/usePoolStakedDeposit', () => ({
__esModule: true,
default: () => ({
- poolStakingContract: {},
- estimateDepositGas: jest.fn(),
- attemptDepositTransaction: jest.fn(),
+ attemptDepositTransaction: mockAttemptDepositTransaction,
+ }),
+}));
+
+jest.mock('../../../../hooks/usePoolStakedUnstake', () => ({
+ __esModule: true,
+ default: () => ({
+ attemptUnstakeTransaction: mockAttemptUnstakeTransaction,
}),
}));
@@ -101,5 +117,37 @@ describe('FooterButtonGroup', () => {
expect(toJSON()).toMatchSnapshot();
});
- it.todo('confirms stake when confirm button is pressed');
+ it('attempts stake transaction on continue click', () => {
+ const props: FooterButtonGroupProps = {
+ valueWei: '3210000000000000',
+ action: FooterButtonGroupActions.STAKE,
+ };
+
+ const { getByText, toJSON } = renderWithProvider(
+ ,
+ { state: mockInitialState },
+ );
+
+ fireEvent.press(getByText(strings('stake.continue')));
+
+ expect(toJSON()).toMatchSnapshot();
+ expect(mockAttemptDepositTransaction).toHaveBeenCalledTimes(1);
+ });
+
+ it('attempts unstake transaction on continue click', () => {
+ const props: FooterButtonGroupProps = {
+ valueWei: '3210000000000000',
+ action: FooterButtonGroupActions.UNSTAKE,
+ };
+
+ const { getByText, toJSON } = renderWithProvider(
+ ,
+ { state: mockInitialState },
+ );
+
+ fireEvent.press(getByText(strings('stake.continue')));
+
+ expect(toJSON()).toMatchSnapshot();
+ expect(mockAttemptUnstakeTransaction).toHaveBeenCalledTimes(1);
+ });
});
diff --git a/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.tsx b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.tsx
index 5a98d66c48b..c60c393fb18 100644
--- a/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.tsx
+++ b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useCallback, useState } from 'react';
import { useNavigation } from '@react-navigation/native';
import { View } from 'react-native';
import { strings } from '../../../../../../../../locales/i18n';
@@ -22,6 +22,7 @@ import {
FooterButtonGroupProps,
} from './FooterButtonGroup.types';
import Routes from '../../../../../../../constants/navigation/Routes';
+import usePoolStakedUnstake from '../../../../hooks/usePoolStakedUnstake';
import usePooledStakes from '../../../../hooks/usePooledStakes';
const FooterButtonGroup = ({ valueWei, action }: FooterButtonGroupProps) => {
@@ -35,36 +36,78 @@ const FooterButtonGroup = ({ valueWei, action }: FooterButtonGroupProps) => {
const { attemptDepositTransaction } = usePoolStakedDeposit();
const { refreshPooledStakes } = usePooledStakes();
- const handleStake = async () => {
- if (!activeAccount?.address) return;
-
- const txRes = await attemptDepositTransaction(
- valueWei,
- activeAccount.address,
- );
-
- const transactionId = txRes?.transactionMeta?.id;
-
- // Listening for confirmation
- Engine.controllerMessenger.subscribeOnceIf(
- 'TransactionController:transactionSubmitted',
- () => {
- navigate(Routes.TRANSACTIONS_VIEW);
- },
- ({ transactionMeta }) => transactionMeta.id === transactionId,
- );
-
- Engine.controllerMessenger.subscribeOnceIf(
- 'TransactionController:transactionConfirmed',
- () => {
- refreshPooledStakes();
- },
- (transactionMeta) => transactionMeta.id === transactionId,
- );
- };
+ const { attemptUnstakeTransaction } = usePoolStakedUnstake();
+
+ const [didSubmitTransaction, setDidSubmitTransaction] = useState(false);
+
+ const listenForTransactionEvents = useCallback(
+ (transactionId?: string) => {
+ if (!transactionId) return;
+
+ Engine.controllerMessenger.subscribeOnceIf(
+ 'TransactionController:transactionSubmitted',
+ () => {
+ setDidSubmitTransaction(false);
+ navigate(Routes.TRANSACTIONS_VIEW);
+ },
+ ({ transactionMeta }) => transactionMeta.id === transactionId,
+ );
+
+ Engine.controllerMessenger.subscribeOnceIf(
+ 'TransactionController:transactionFailed',
+ () => {
+ setDidSubmitTransaction(false);
+ },
+ ({ transactionMeta }) => transactionMeta.id === transactionId,
+ );
+
+ Engine.controllerMessenger.subscribeOnceIf(
+ 'TransactionController:transactionRejected',
+ () => {
+ setDidSubmitTransaction(false);
+ },
+ ({ transactionMeta }) => transactionMeta.id === transactionId,
+ );
+
+ Engine.controllerMessenger.subscribeOnceIf(
+ 'TransactionController:transactionConfirmed',
+ () => {
+ refreshPooledStakes();
+ },
+ (transactionMeta) => transactionMeta.id === transactionId,
+ );
+ },
+ [navigate, refreshPooledStakes],
+ );
+
+ const handleConfirmation = async () => {
+ try {
+ if (!activeAccount?.address) return;
+
+ setDidSubmitTransaction(true);
+
+ let transactionId: string | undefined;
+
+ if (action === FooterButtonGroupActions.STAKE) {
+ const txRes = await attemptDepositTransaction(
+ valueWei,
+ activeAccount.address,
+ );
+ transactionId = txRes?.transactionMeta?.id;
+ }
+
+ if (action === FooterButtonGroupActions.UNSTAKE) {
+ const txRes = await attemptUnstakeTransaction(
+ valueWei,
+ activeAccount.address,
+ );
+ transactionId = txRes?.transactionMeta?.id;
+ }
- const handleConfirmation = () => {
- if (action === FooterButtonGroupActions.STAKE) return handleStake();
+ listenForTransactionEvents(transactionId);
+ } catch (e) {
+ setDidSubmitTransaction(false);
+ }
};
return (
@@ -82,6 +125,7 @@ const FooterButtonGroup = ({ valueWei, action }: FooterButtonGroupProps) => {
onPress={() => {
navigation.goBack();
}}
+ disabled={didSubmitTransaction}
/>