diff --git a/app/components/UI/Stake/Views/StakeInputView/StakeInputView.test.tsx b/app/components/UI/Stake/Views/StakeInputView/StakeInputView.test.tsx
index 10ac002beb1..9648fba601e 100644
--- a/app/components/UI/Stake/Views/StakeInputView/StakeInputView.test.tsx
+++ b/app/components/UI/Stake/Views/StakeInputView/StakeInputView.test.tsx
@@ -5,6 +5,7 @@ import { renderScreen } from '../../../../../util/test/renderWithProvider';
import Routes from '../../../../../constants/navigation/Routes';
import { backgroundState } from '../../../../../util/test/initial-root-state';
import { BN } from 'ethereumjs-util';
+import { Stake } from '../../sdk/stakeSdkProvider';
function render(Component: React.ComponentType) {
return renderScreen(
@@ -51,6 +52,17 @@ jest.mock('../../../../../selectors/currencyRateController.ts', () => ({
}));
const mockBalanceBN = new BN('1500000000000000000');
+
+jest.mock('../../hooks/useStakeContext.ts', () => ({
+ useStakeContext: jest.fn(() => {
+ const stakeContext: Stake = {
+ setSdkType: jest.fn(),
+ sdkService: undefined
+ }
+ return stakeContext
+ })
+}))
+
jest.mock('../../hooks/useBalance', () => ({
__esModule: true,
default: () => ({
diff --git a/app/components/UI/Stake/Views/StakeInputView/StakeInputView.tsx b/app/components/UI/Stake/Views/StakeInputView/StakeInputView.tsx
index 0431e67a77f..782e4d5ab3c 100644
--- a/app/components/UI/Stake/Views/StakeInputView/StakeInputView.tsx
+++ b/app/components/UI/Stake/Views/StakeInputView/StakeInputView.tsx
@@ -19,6 +19,7 @@ import styleSheet from './StakeInputView.styles';
import useStakingInputHandlers from '../../hooks/useStakingInput';
import useBalance from '../../hooks/useBalance';
import InputDisplay from '../../components/InputDisplay';
+import { useStakeContext } from '../../hooks/useStakeContext';
const StakeInputView = () => {
const title = strings('stake.stake_eth');
@@ -43,6 +44,9 @@ const StakeInputView = () => {
estimatedAnnualRewards,
} = useStakingInputHandlers(balanceWei);
+
+ const { sdkService } = useStakeContext();
+
const navigateToLearnMoreModal = () => {
navigation.navigate('StakeModals', {
screen: Routes.STAKING.MODALS.LEARN_MORE,
@@ -57,7 +61,8 @@ const StakeInputView = () => {
amountFiat: fiatAmount,
},
});
- }, [amountWei, fiatAmount, navigation]);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [amountWei, fiatAmount, navigation, sdkService]);
const balanceText = strings('stake.balance');
diff --git a/app/components/UI/Stake/hooks/useBalance.ts b/app/components/UI/Stake/hooks/useBalance.ts
index cbe6db124d3..2ce1dfc6af4 100644
--- a/app/components/UI/Stake/hooks/useBalance.ts
+++ b/app/components/UI/Stake/hooks/useBalance.ts
@@ -48,7 +48,7 @@ const useBalance = () => {
[balanceWei, conversionRate],
);
- return { balance, balanceFiat, balanceWei, balanceFiatNumber };
+ return { balance, balanceFiat, balanceWei, balanceFiatNumber, conversionRate, currentCurrency };
};
export default useBalance;
diff --git a/app/components/UI/Stake/hooks/useStakeContext.ts b/app/components/UI/Stake/hooks/useStakeContext.ts
new file mode 100644
index 00000000000..0fc280593da
--- /dev/null
+++ b/app/components/UI/Stake/hooks/useStakeContext.ts
@@ -0,0 +1,7 @@
+import { useContext } from 'react';
+import { Stake, StakeContext } from '../sdk/stakeSdkProvider';
+
+export const useStakeContext = () => {
+ const context = useContext(StakeContext);
+ return context as Stake;
+};
diff --git a/app/components/UI/Stake/routes/index.tsx b/app/components/UI/Stake/routes/index.tsx
index 14993705ca5..73960d4a7af 100644
--- a/app/components/UI/Stake/routes/index.tsx
+++ b/app/components/UI/Stake/routes/index.tsx
@@ -5,6 +5,7 @@ import LearnMoreModal from '../components/LearnMoreModal';
import Routes from '../../../../constants/navigation/Routes';
import StakeConfirmationView from '../Views/StakeConfirmationView/StakeConfirmationView';
import UnstakeInputView from '../Views/UnstakeInputView/UnstakeInputView';
+import { StakeSDKProvider } from '../sdk/stakeSdkProvider';
const Stack = createStackNavigator();
const ModalStack = createStackNavigator();
@@ -18,28 +19,35 @@ const clearStackNavigatorOptions = {
// Regular Stack for Screens
const StakeScreenStack = () => (
-
-
-
-
-
+
+
+
+
+
+
+
);
// Modal Stack for Modals
const StakeModalStack = () => (
-
-
-
+
+
+
+
+
);
export { StakeScreenStack, StakeModalStack };
diff --git a/app/components/UI/Stake/sdk/__snapshots__/stakeSdkProvider.test.tsx.snap b/app/components/UI/Stake/sdk/__snapshots__/stakeSdkProvider.test.tsx.snap
new file mode 100644
index 00000000000..12944c1a6ce
--- /dev/null
+++ b/app/components/UI/Stake/sdk/__snapshots__/stakeSdkProvider.test.tsx.snap
@@ -0,0 +1,2224 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Stake Modals With Stake Sdk Provider should render correctly stake modal with stake sdk provider and resolve the stake context 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Stake ETH and earn
+
+
+
+
+ Stake any amount of ETH
+
+
+ No minimum required.
+
+
+
+
+ Earn ETH rewards
+
+
+ Start earning as soon as you stake. Rewards compound automatically.
+
+
+
+
+ Flexible unstaking
+
+
+ Unstake anytime. Typically takes up to 11 days to process.
+
+
+
+
+ Staking does not guarantee rewards, and involves risks including a loss of funds.
+
+
+
+
+
+
+
+ Learn more
+
+
+
+
+
+
+ Got it
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Stake Modals With Stake Sdk Provider should render correctly stake screen with stake sdk provider and resolve the stake context 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+ Stake ETH
+
+
+
+
+
+ Cancel
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Balance
+ :
+ 0 ETH
+
+
+
+
+ 0
+
+
+ ETH
+
+
+
+
+
+ 0 USD
+
+
+
+
+
+
+
+
+
+
+ MetaMask Pool
+
+
+
+
+
+
+
+ 2.6%
+
+
+ Estimated annual rewards
+
+
+
+
+
+
+
+
+ 25%
+
+
+
+
+ 50%
+
+
+
+
+ 75%
+
+
+
+
+
+ Max
+
+
+
+
+
+
+
+ 1
+
+
+
+
+ 2
+
+
+
+
+ 3
+
+
+
+
+
+
+ 4
+
+
+
+
+ 5
+
+
+
+
+ 6
+
+
+
+
+
+
+ 7
+
+
+
+
+ 8
+
+
+
+
+ 9
+
+
+
+
+
+
+ .
+
+
+
+
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+ Enter amount
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/app/components/UI/Stake/sdk/stakeSdkProvider.test.tsx b/app/components/UI/Stake/sdk/stakeSdkProvider.test.tsx
new file mode 100644
index 00000000000..6c47a20406c
--- /dev/null
+++ b/app/components/UI/Stake/sdk/stakeSdkProvider.test.tsx
@@ -0,0 +1,71 @@
+import {
+ ChainId,
+ PooledStakingContract,
+ StakingType,
+} from '@metamask/stake-sdk';
+import renderWithProvider from '../../../../util/test/renderWithProvider';
+import { backgroundState } from '../../../../util/test/initial-root-state';
+import { Stake } from '../sdk/stakeSdkProvider';
+// eslint-disable-next-line import/no-namespace
+import * as useStakeContextHook from '../hooks/useStakeContext';
+import { Contract } from '@ethersproject/contracts';
+import { StakeModalStack, StakeScreenStack } from '../routes';
+
+const mockPooledStakingContractService: PooledStakingContract = {
+ 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(),
+};
+
+const mockSDK: Stake = {
+ sdkService: mockPooledStakingContractService,
+ sdkType: StakingType.POOLED,
+ setSdkType: jest.fn(),
+};
+
+jest.mock('../../Stake/constants', () => ({
+ isPooledStakingFeatureEnabled: jest.fn().mockReturnValue(true),
+}));
+
+describe('Stake Modals With Stake Sdk Provider', () => {
+ const initialState = {
+ engine: {
+ backgroundState,
+ },
+ };
+ it('should render correctly stake screen with stake sdk provider and resolve the stake context', () => {
+ const useStakeContextSpy = jest
+ .spyOn(useStakeContextHook, 'useStakeContext')
+ .mockReturnValue(mockSDK);
+
+ const { toJSON } = renderWithProvider(StakeScreenStack(), {
+ state: initialState,
+ });
+
+ expect(toJSON()).toMatchSnapshot();
+ expect(useStakeContextSpy).toHaveBeenCalled();
+ });
+
+ it('should render correctly stake modal with stake sdk provider and resolve the stake context', () => {
+ const useStakeContextSpy = jest
+ .spyOn(useStakeContextHook, 'useStakeContext')
+ .mockReturnValue(mockSDK);
+
+ const { toJSON } = renderWithProvider(StakeModalStack(), {
+ state: initialState,
+ });
+
+ expect(toJSON()).toMatchSnapshot();
+ expect(useStakeContextSpy).toHaveBeenCalledTimes(0);
+
+ });
+});
diff --git a/app/components/UI/Stake/sdk/stakeSdkProvider.tsx b/app/components/UI/Stake/sdk/stakeSdkProvider.tsx
new file mode 100644
index 00000000000..19a6769949b
--- /dev/null
+++ b/app/components/UI/Stake/sdk/stakeSdkProvider.tsx
@@ -0,0 +1,71 @@
+import { StakingType, StakeSdk, PooledStakingContract } from '@metamask/stake-sdk';
+import Logger from '../../../../util/Logger';
+import React, {
+ useState,
+ useEffect,
+ createContext,
+ useMemo,
+ PropsWithChildren,
+} from 'react';
+
+export const SDK = StakeSdk.create({ stakingType: StakingType.POOLED });
+
+export interface Stake {
+ sdkError?: Error;
+ sdkService?: PooledStakingContract; // to do : facade it for other services implementation
+
+ sdkType?: StakingType;
+ setSdkType: (stakeType: StakingType) => void;
+}
+
+export const StakeContext = createContext(undefined);
+
+export interface StakeProviderProps {
+ stakingType?: StakingType;
+}
+export const StakeSDKProvider: React.FC> = ({
+ children,
+}) => {
+ const [sdkService, setSdkService] = useState();
+ const [sdkError, setSdkError] = useState();
+ const [sdkType, setSdkType] = useState(StakingType.POOLED);
+
+ useEffect(() => {
+ (async () => {
+ try {
+ if (sdkType === StakingType?.POOLED) {
+ setSdkService(SDK.pooledStakingContractService);
+ } else {
+ const notImplementedError = new Error(
+ `StakeSDKProvider SDK.StakingType ${sdkType} not implemented yet`,
+ );
+ Logger.error(notImplementedError);
+ setSdkError(notImplementedError);
+ }
+ } catch (error) {
+ Logger.error(error as Error, `StakeSDKProvider SDK.service failed`);
+ setSdkError(error as Error);
+ }
+ })();
+ }, [sdkType]);
+
+ const stakeContextValue = useMemo(
+ (): Stake => ({
+ sdkError,
+ sdkService,
+ sdkType,
+ setSdkType,
+ }),
+ [
+ sdkError,
+ sdkService,
+ sdkType,
+ setSdkType,
+ ],
+ );
+ return (
+
+ {children}
+
+ );
+};
\ No newline at end of file
diff --git a/package.json b/package.json
index 2d747bf2cf6..286f8ccd663 100644
--- a/package.json
+++ b/package.json
@@ -183,6 +183,7 @@
"@metamask/snaps-rpc-methods": "^9.1.4",
"@metamask/snaps-sdk": "^6.5.0",
"@metamask/snaps-utils": "^8.1.1",
+ "@metamask/stake-sdk": "^0.2.11",
"@metamask/swappable-obj-proxy": "^2.1.0",
"@metamask/swaps-controller": "^9.0.12",
"@metamask/transaction-controller": "^37.1.0",
diff --git a/yarn.lock b/yarn.lock
index 76ef5733ba8..c0dbd10e276 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5532,6 +5532,13 @@
ses "^1.1.0"
validate-npm-package-name "^5.0.0"
+"@metamask/stake-sdk@^0.2.11":
+ version "0.2.11"
+ resolved "https://registry.yarnpkg.com/@metamask/stake-sdk/-/stake-sdk-0.2.11.tgz#70b003a7b7f5208fad0d5a986aedd84b0987979f"
+ integrity sha512-l2novyUK7oVKO2vZDd2tCSyQ8e468hWp0ZB3ed2FoR61HGlZDMqv3hDtXPAfOeA0+cQwsZM861yoUdSXNo0WPA==
+ dependencies:
+ axios "^1.7.7"
+
"@metamask/superstruct@^3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@metamask/superstruct/-/superstruct-3.1.0.tgz#148f786a674fba3ac885c1093ab718515bf7f648"
@@ -12962,7 +12969,7 @@ axios-retry@^3.1.2:
"@babel/runtime" "^7.15.4"
is-retry-allowed "^2.2.0"
-axios@1.4.0, axios@^0.26.0, axios@^0.28.0, axios@^0.x, axios@^1.6.7, axios@^1.6.8, axios@^1.7.4, axios@~1.6.8:
+axios@1.4.0, axios@^0.26.0, axios@^0.28.0, axios@^0.x, axios@^1.6.7, axios@^1.6.8, axios@^1.7.4, axios@^1.7.7, axios@~1.6.8:
version "1.7.4"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.4.tgz#4c8ded1b43683c8dd362973c393f3ede24052aa2"
integrity sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==