diff --git a/src/components/Modal/MapOperatorToStakingProviderConfirmationModal/index.tsx b/src/components/Modal/MapOperatorToStakingProviderConfirmationModal/index.tsx index dee189bd9..181c89deb 100644 --- a/src/components/Modal/MapOperatorToStakingProviderConfirmationModal/index.tsx +++ b/src/components/Modal/MapOperatorToStakingProviderConfirmationModal/index.tsx @@ -9,25 +9,19 @@ import { ModalHeader, } from "@chakra-ui/react" import { AddressZero } from "@ethersproject/constants" -import { - BodyLg, - BodyMd, - BodySm, - H5, - LabelSm, -} from "@threshold-network/components" +import { BodyLg, BodyMd, H5, LabelSm } from "@threshold-network/components" import { useWeb3React } from "@web3-react/core" import { ContractTransaction } from "ethers" import { FC, useCallback } from "react" -import { useDispatch } from "react-redux" import { ModalType } from "../../../enums" -import { useOperatorMappedtoStakingProviderHelpers } from "../../../hooks/staking-applications/useOperatorMappedToStakingProviderHelpers" import { useRegisterMultipleOperatorsTransaction } from "../../../hooks/staking-applications/useRegisterMultipleOperatorsTransaction" import { useRegisterOperatorTransaction } from "../../../hooks/staking-applications/useRegisterOperatorTransaction" +import { useAppDispatch } from "../../../hooks/store" import { useModal } from "../../../hooks/useModal" import StakeAddressInfo from "../../../pages/Staking/StakeCard/StakeAddressInfo" -import { mapOperatorToStakingProviderModalClosed } from "../../../store/modalQueue" +import { mapOperatorToStakingProviderModalClosed } from "../../../store/modal" import { BaseModalProps } from "../../../types" +import { isAddressZero } from "../../../web3/utils" import InfoBox from "../../InfoBox" import withBaseModal from "../withBaseModal" @@ -60,17 +54,27 @@ const OperatorMappingConfirmation: FC< const MapOperatorToStakingProviderConfirmationModal: FC< BaseModalProps & { operator: string + mappedOperatorTbtc: string + mappedOperatorRandomBeacon: string } -> = ({ operator, closeModal }) => { +> = ({ + operator, + mappedOperatorTbtc, + mappedOperatorRandomBeacon, + closeModal, +}) => { const { account } = useWeb3React() const { registerMultipleOperators } = useRegisterMultipleOperatorsTransaction() - const dispatch = useDispatch() + const dispatch = useAppDispatch() + + const isOperatorMappedOnlyInTbtc = + !isAddressZero(mappedOperatorTbtc) && + isAddressZero(mappedOperatorRandomBeacon) - const operatorMappedToStakingProviderHelpers = - useOperatorMappedtoStakingProviderHelpers() - const { isOperatorMappedOnlyInRandomBeacon, isOperatorMappedOnlyInTbtc } = - operatorMappedToStakingProviderHelpers + const isOperatorMappedOnlyInRandomBeacon = + isAddressZero(mappedOperatorTbtc) && + !isAddressZero(mappedOperatorRandomBeacon) const { openModal } = useModal() const onSuccess = useCallback( diff --git a/src/components/Modal/MapOperatorToStakingProviderModal/MapOperatorToStakingProviderForm.tsx b/src/components/Modal/MapOperatorToStakingProviderModal/MapOperatorToStakingProviderForm.tsx index dd95f4b4e..d87056062 100644 --- a/src/components/Modal/MapOperatorToStakingProviderModal/MapOperatorToStakingProviderForm.tsx +++ b/src/components/Modal/MapOperatorToStakingProviderModal/MapOperatorToStakingProviderForm.tsx @@ -2,7 +2,7 @@ import { FC, Ref } from "react" import { FormikProps, FormikErrors, withFormik } from "formik" import { Form, FormikInput } from "../../Forms" import { getErrorsObj, validateETHAddress } from "../../../utils/forms" -import { OperatorMappedToStakingProviderHelpers } from "../../../hooks/staking-applications/useOperatorMappedToStakingProviderHelpers" +import { isAddressZero, isSameETHAddress } from "../../../web3/utils" export interface MapOperatorToStakingProviderFormValues { operator: string @@ -26,10 +26,58 @@ const MapOperatorToStakingProviderFormBase: FC< ) } +const validateInputtedOperatorAddress = async ( + operator: string, + checkIfOperatorIsMappedToAnotherStakingProvider: ( + operator: string + ) => Promise, + mappedOperatorRandomBeacon: string, + mappedOperatorTbtc: string +): Promise => { + let validationMsg: string | undefined = "" + + const isOperatorMappedOnlyInTbtc = + !isAddressZero(mappedOperatorTbtc) && + isAddressZero(mappedOperatorRandomBeacon) + + const isOperatorMappedOnlyInRandomBeacon = + isAddressZero(mappedOperatorTbtc) && + !isAddressZero(mappedOperatorRandomBeacon) + + try { + const isOperatorMappedToAnotherStakingProvider = + await checkIfOperatorIsMappedToAnotherStakingProvider(operator) + validationMsg = undefined + if (isOperatorMappedToAnotherStakingProvider) { + validationMsg = "Operator is already mapped to another staking provider." + } + if ( + isOperatorMappedOnlyInRandomBeacon && + !isSameETHAddress(operator, mappedOperatorRandomBeacon) + ) { + validationMsg = + "The operator address doesn't match the one used in random beacon app" + } + if ( + isOperatorMappedOnlyInTbtc && + !isSameETHAddress(operator, mappedOperatorTbtc) + ) { + validationMsg = + "The operator address doesn't match the one used in tbtc app" + } + } catch (error) { + console.error("`MapOperatorToStakingProviderForm` validation error.", error) + validationMsg = (error as Error)?.message + } + + return validationMsg +} + type MapOperatorToStakingProviderFormProps = { initialAddress: string + mappedOperatorTbtc: string + mappedOperatorRandomBeacon: string innerRef: Ref> - operatorMappedToStakingProviderHelpers: OperatorMappedToStakingProviderHelpers checkIfOperatorIsMappedToAnotherStakingProvider: ( operator: string ) => Promise @@ -45,50 +93,20 @@ const MapOperatorToStakingProviderForm = withFormik< }), validate: async (values, props) => { const { + mappedOperatorTbtc, + mappedOperatorRandomBeacon, checkIfOperatorIsMappedToAnotherStakingProvider, - operatorMappedToStakingProviderHelpers, } = props - const { - operatorMappedRandomBeacon, - operatorMappedTbtc, - isOperatorMappedOnlyInRandomBeacon, - isOperatorMappedOnlyInTbtc, - } = operatorMappedToStakingProviderHelpers const errors: FormikErrors = {} errors.operator = validateETHAddress(values.operator) if (!errors.operator) { - let validationMsg: string | undefined = "" - try { - const isOperatorMappedToAnotherStakingProvider = - await checkIfOperatorIsMappedToAnotherStakingProvider(values.operator) - validationMsg = undefined - if (isOperatorMappedToAnotherStakingProvider) { - validationMsg = - "Operator is already mapped to another staking provider." - } - if ( - isOperatorMappedOnlyInRandomBeacon && - values.operator !== operatorMappedRandomBeacon - ) { - validationMsg = - "The operator address doesn't match the one used in tbtc app" - } - if ( - isOperatorMappedOnlyInTbtc && - values.operator !== operatorMappedTbtc - ) { - validationMsg = - "The operator address doesn't match the one used in random beacon app" - } - } catch (error) { - console.error( - "`MapOperatorToStakingProviderForm` validation error.", - error - ) - validationMsg = (error as Error)?.message - } - errors.operator = validationMsg + errors.operator = await validateInputtedOperatorAddress( + values.operator, + checkIfOperatorIsMappedToAnotherStakingProvider, + mappedOperatorRandomBeacon, + mappedOperatorTbtc + ) } return getErrorsObj(errors) diff --git a/src/components/Modal/MapOperatorToStakingProviderModal/index.tsx b/src/components/Modal/MapOperatorToStakingProviderModal/index.tsx index 8e88e6217..ed143c056 100644 --- a/src/components/Modal/MapOperatorToStakingProviderModal/index.tsx +++ b/src/components/Modal/MapOperatorToStakingProviderModal/index.tsx @@ -7,7 +7,6 @@ import { Box, Button, H5, - HStack, LabelSm, ModalBody, ModalCloseButton, @@ -25,32 +24,43 @@ import { ModalType } from "../../../enums" import { useModal } from "../../../hooks/useModal" import StakeAddressInfo from "../../../pages/Staking/StakeCard/StakeAddressInfo" import { useWeb3React } from "@web3-react/core" -import { AddressZero } from "@ethersproject/constants" import { useThreshold } from "../../../contexts/ThresholdContext" -import { isAddressZero, isSameETHAddress } from "../../../web3/utils" -import { useOperatorMappedtoStakingProviderHelpers } from "../../../hooks/staking-applications/useOperatorMappedToStakingProviderHelpers" +import { + isAddressZero, + isSameETHAddress, + AddressZero, +} from "../../../web3/utils" + +export interface MapOperatorToStakingProviderModalProps { + mappedOperatorTbtc: string + mappedOperatorRandomBeacon: string +} -const MapOperatorToStakingProviderModal: FC = () => { +const MapOperatorToStakingProviderModal: FC< + BaseModalProps & MapOperatorToStakingProviderModalProps +> = ({ mappedOperatorTbtc, mappedOperatorRandomBeacon }) => { const { account } = useWeb3React() - const operatorMappedToStakingProviderHelpers = - useOperatorMappedtoStakingProviderHelpers() - const { - isOperatorMappedOnlyInRandomBeacon, - isOperatorMappedOnlyInTbtc, - operatorMappedRandomBeacon, - operatorMappedTbtc, - } = operatorMappedToStakingProviderHelpers const formRef = useRef>(null) const { closeModal, openModal } = useModal() const threshold = useThreshold() + const isOperatorMappedOnlyInTbtc = + !isAddressZero(mappedOperatorTbtc) && + isAddressZero(mappedOperatorRandomBeacon) + + const isOperatorMappedOnlyInRandomBeacon = + isAddressZero(mappedOperatorTbtc) && + !isAddressZero(mappedOperatorRandomBeacon) + const onSubmit = async ({ operator, }: MapOperatorToStakingProviderFormValues) => { if (account) { openModal(ModalType.MapOperatorToStakingProviderConfirmation, { operator, + mappedOperatorTbtc, + mappedOperatorRandomBeacon, }) } } @@ -85,13 +95,14 @@ const MapOperatorToStakingProviderModal: FC = () => { ) : (
- We’ve noticed your wallet address is the same with your Provider + We’ve noticed your wallet address is the same as your Provider Address
)} - Would you like to map your Operator Address? Mapping an Operator - Address will require one transaction per application. + Map your Operator Address to your Provider Address to improve the + support of your hardware wallet. Mapping will require one + transaction per application. @@ -118,18 +129,17 @@ const MapOperatorToStakingProviderModal: FC = () => { formId="map-operator-to-staking-provider-form" initialAddress={ isOperatorMappedOnlyInRandomBeacon - ? operatorMappedRandomBeacon + ? mappedOperatorRandomBeacon : isOperatorMappedOnlyInTbtc - ? operatorMappedTbtc + ? mappedOperatorTbtc : "" } onSubmitForm={onSubmit} checkIfOperatorIsMappedToAnotherStakingProvider={ checkIfOperatorIsMappedToAnotherStakingProvider } - operatorMappedToStakingProviderHelpers={ - operatorMappedToStakingProviderHelpers - } + mappedOperatorTbtc={mappedOperatorTbtc} + mappedOperatorRandomBeacon={mappedOperatorRandomBeacon} /> - - + + - Operator + Provider Address - {shortenAddress(transactions[0].application.operator)} + {shortenAddress(transactions[0].application.stakingProvider)} - + - Staking Provider + Operator Address - {shortenAddress(transactions[0].application.stakingProvider)} + {shortenAddress(transactions[0].application.operator)} - {transactions.map((transaction, i) => { - const text = `${camelCaseToNormal( - transaction.application.appName - )} transaction` - return ( - - View{" "} + + {transactions.length === 1 ? ( + <> {" "} + transaction on Etherscan + + ) : ( + <> + View{" "} + {transactions.map((_, index) => ( + + + {index + 1 === transactions.length ? " " : " and "} + + ))} on Etherscan - - ) - })} + + )} + } /> diff --git a/src/hooks/staking-applications/index.ts b/src/hooks/staking-applications/index.ts index d242eaa98..8d2098884 100644 --- a/src/hooks/staking-applications/index.ts +++ b/src/hooks/staking-applications/index.ts @@ -1,4 +1,3 @@ -export * from "./useOperatorsMappedToStakingProvider" export * from "./useStakingAppContract" export * from "./useStakingAppDataByStakingProvider" export * from "./useStakingApplicationState" diff --git a/src/hooks/staking-applications/useOperatorMappedToStakingProviderHelpers.ts b/src/hooks/staking-applications/useOperatorMappedToStakingProviderHelpers.ts deleted file mode 100644 index 5234d8197..000000000 --- a/src/hooks/staking-applications/useOperatorMappedToStakingProviderHelpers.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { useWeb3React } from "@web3-react/core" -import { isAddressZero, isSameETHAddress } from "../../web3/utils" -import { useOperatorsMappedToStakingProvider } from "./useOperatorsMappedToStakingProvider" -import { useStakingApplicationState } from "./useStakingApplicationState" - -export interface OperatorMappedToStakingProviderHelpers { - operatorMappedRandomBeacon: string - operatorMappedTbtc: string - isOperatorMappedOnlyInTbtc: boolean - isOperatorMappedOnlyInRandomBeacon: boolean - isInitialFetchDone: boolean -} - -export const useOperatorMappedtoStakingProviderHelpers: () => OperatorMappedToStakingProviderHelpers = - () => { - const operatorMappedRandomBeacon = - useOperatorsMappedToStakingProvider("randomBeacon") - const operatorMappedTbtc = useOperatorsMappedToStakingProvider("tbtc") - const isInitiallyFetchedRandomBeacon = - useStakingApplicationState("randomBeacon").mappedOperator - .isInitialFetchDone - const isInitiallyFetchedTbtc = - useStakingApplicationState("tbtc").mappedOperator.isInitialFetchDone - - const isOperatorMappedOnlyInTbtc = - isAddressZero(operatorMappedRandomBeacon) && - !isAddressZero(operatorMappedTbtc) - const isOperatorMappedOnlyInRandomBeacon = - !isAddressZero(operatorMappedRandomBeacon) && - isAddressZero(operatorMappedTbtc) - - const isInitialFetchDone = - !!isInitiallyFetchedRandomBeacon && !!isInitiallyFetchedTbtc - - return { - operatorMappedRandomBeacon, - operatorMappedTbtc, - isOperatorMappedOnlyInTbtc, - isOperatorMappedOnlyInRandomBeacon, - isInitialFetchDone, - } - } diff --git a/src/hooks/staking-applications/useOperatorsMappedToStakingProvider.ts b/src/hooks/staking-applications/useOperatorsMappedToStakingProvider.ts deleted file mode 100644 index 29eeaa5aa..000000000 --- a/src/hooks/staking-applications/useOperatorsMappedToStakingProvider.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { StakingAppName } from "../../store/staking-applications" -import { useStakingApplicationState } from "./useStakingApplicationState" - -export const useOperatorsMappedToStakingProvider = ( - appName: StakingAppName -) => { - return useStakingApplicationState(appName).mappedOperator.data -} diff --git a/src/hooks/staking-applications/useRegisterMultipleOperatorsTransaction.ts b/src/hooks/staking-applications/useRegisterMultipleOperatorsTransaction.ts index b1f3d173c..5ea1cc39b 100644 --- a/src/hooks/staking-applications/useRegisterMultipleOperatorsTransaction.ts +++ b/src/hooks/staking-applications/useRegisterMultipleOperatorsTransaction.ts @@ -3,16 +3,21 @@ import { isAddressZero } from "../../web3/utils" import { useRegisterOperatorTransaction } from "./useRegisterOperatorTransaction" import { useModal } from "../useModal" import { ModalType } from "../../enums" -import { useOperatorMappedtoStakingProviderHelpers } from "./useOperatorMappedToStakingProviderHelpers" import { useWeb3React } from "@web3-react/core" import { OperatorMappedSuccessTx } from "../../components/Modal/MapOperatorToStakingProviderSuccessModal" -import { mapOperatorToStakingProviderModalClosed } from "../../store/modalQueue" -import { useDispatch } from "react-redux" +import { mapOperatorToStakingProviderModalClosed } from "../../store/modal" +import { useAppDispatch, useAppSelector } from "../store" export const useRegisterMultipleOperatorsTransaction = () => { + const mappedOperatorTbtc = useAppSelector( + (state) => state.account.operatorMapping.data.tbtc + ) + const mappedOperatorRandomBeacon = useAppSelector( + (state) => state.account.operatorMapping.data.randomBeacon + ) const { account } = useWeb3React() const { openModal, closeModal } = useModal() - const dispatch = useDispatch() + const dispatch = useAppDispatch() const { sendTransaction: sendRegisterOperatorTransactionTbtc, @@ -23,25 +28,27 @@ export const useRegisterMultipleOperatorsTransaction = () => { status: registerOperatorRandomBeaconStatus, } = useRegisterOperatorTransaction("randomBeacon") - const { - operatorMappedRandomBeacon, - operatorMappedTbtc, - isOperatorMappedOnlyInRandomBeacon, - isOperatorMappedOnlyInTbtc, - } = useOperatorMappedtoStakingProviderHelpers() - const registerMultipleOperators = useCallback( async (operator: string) => { try { if (!account) { throw new Error("Connect to the staking provider account first!") } + if ( - !isAddressZero(operatorMappedRandomBeacon) && - !isAddressZero(operatorMappedTbtc) + !isAddressZero(mappedOperatorRandomBeacon) && + !isAddressZero(mappedOperatorTbtc) ) throw new Error("Both apps already have mapped operator!") + const isOperatorMappedOnlyInTbtc = + !isAddressZero(mappedOperatorTbtc) && + isAddressZero(mappedOperatorRandomBeacon) + + const isOperatorMappedOnlyInRandomBeacon = + isAddressZero(mappedOperatorTbtc) && + !isAddressZero(mappedOperatorRandomBeacon) + if (isOperatorMappedOnlyInRandomBeacon) throw new Error("Random beacon app already has mapped operator!") @@ -106,6 +113,9 @@ export const useRegisterMultipleOperatorsTransaction = () => { } }, [ + account, + mappedOperatorRandomBeacon, + mappedOperatorTbtc, sendRegisterOperatorTransactionTbtc, sendRegisterOperatorTransactionRandomBeacon, openModal, diff --git a/src/hooks/staking-applications/useSubscribeToOperatorRegisteredEvent.ts b/src/hooks/staking-applications/useSubscribeToOperatorRegisteredEvent.ts index 2f5b7dedd..c9b6055e9 100644 --- a/src/hooks/staking-applications/useSubscribeToOperatorRegisteredEvent.ts +++ b/src/hooks/staking-applications/useSubscribeToOperatorRegisteredEvent.ts @@ -1,9 +1,8 @@ import { useWeb3React } from "@web3-react/core" -import { - operatorMapped, - StakingAppName, -} from "../../store/staking-applications" +import { operatorRegistered } from "../../store/account" +import { StakingAppName } from "../../store/staking-applications" import { useSubscribeToContractEvent } from "../../web3/hooks" +import { isSameETHAddress } from "../../web3/utils" import { useAppDispatch } from "../store" import { useStakingAppContract } from "./useStakingAppContract" @@ -19,12 +18,14 @@ export const useSubscribeToOperatorRegisteredEvent = ( "OperatorRegistered", //@ts-ignore async (stakingProvider: string, operator: string) => { - dispatch( - operatorMapped({ - appName, - operator, - }) - ) + if (account && isSameETHAddress(stakingProvider, account)) { + dispatch( + operatorRegistered({ + appName, + operator, + }) + ) + } }, [account] ) diff --git a/src/hooks/useModal.ts b/src/hooks/useModal.ts index 11360e51a..13d148183 100644 --- a/src/hooks/useModal.ts +++ b/src/hooks/useModal.ts @@ -2,14 +2,12 @@ import { useDispatch, useSelector } from "react-redux" import { UseModal } from "../types" import { closeModal as closeModalAction, + mapOperatorToStakingProviderModalClosed, openModal as openModalAction, + successfullLoginModalClosed, } from "../store/modal" import { RootState } from "../store" import { ModalType } from "../enums" -import { - mapOperatorToStakingProviderModalClosed, - successfullLoginModalClosed, -} from "../store/modalQueue" export const useModal: UseModal = () => { const modalType = useSelector((state: RootState) => state.modal.modalType) diff --git a/src/hooks/useRolesOf.ts b/src/hooks/useRolesOf.ts deleted file mode 100644 index d6074d72e..000000000 --- a/src/hooks/useRolesOf.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useSelector } from "react-redux" -import { RootState } from "../store" -import { RolesOf } from "../threshold-ts/staking" - -export const useRolesOf: () => RolesOf = () => { - const rolesOf = useSelector( - (state: RootState) => state.connectedAccount.rolesOf - ) - - return rolesOf -} diff --git a/src/hooks/useSaveConnectedAddressToStore.ts b/src/hooks/useSaveConnectedAddressToStore.ts index 827bfe13d..8bc50225a 100644 --- a/src/hooks/useSaveConnectedAddressToStore.ts +++ b/src/hooks/useSaveConnectedAddressToStore.ts @@ -1,7 +1,7 @@ import { useWeb3React } from "@web3-react/core" import { useEffect } from "react" import { useDispatch } from "react-redux" -import { setConnectedAccountAddress } from "../store/connected-account" +import { walletConnected } from "../store/account" export const useSaveConnectedAddressToStore = () => { const { account } = useWeb3React() @@ -9,6 +9,6 @@ export const useSaveConnectedAddressToStore = () => { useEffect(() => { const address = account ? account : "" - dispatch(setConnectedAccountAddress(address)) + dispatch(walletConnected(address)) }, [account]) } diff --git a/src/pages/Staking/OperatorAddressMappingCard.tsx b/src/pages/Staking/OperatorAddressMappingCard.tsx index 7caf47ce7..5bf14874d 100644 --- a/src/pages/Staking/OperatorAddressMappingCard.tsx +++ b/src/pages/Staking/OperatorAddressMappingCard.tsx @@ -6,21 +6,34 @@ import { Button, Card, HStack, + LabelSm, } from "@threshold-network/components" -import { LabelSm } from "@threshold-network/components" +import { FC } from "react" import { ModalType } from "../../enums" -import { useOperatorMappedtoStakingProviderHelpers } from "../../hooks/staking-applications/useOperatorMappedToStakingProviderHelpers" import { useModal } from "../../hooks/useModal" +import { isAddressZero } from "../../web3/utils" -const OperatorAddressMappingCard = () => { +const OperatorAddressMappingCard: FC<{ + mappedOperatorTbtc: string + mappedOperatorRandomBeacon: string +}> = ({ mappedOperatorTbtc, mappedOperatorRandomBeacon }) => { const { openModal } = useModal() - const { isOperatorMappedOnlyInRandomBeacon, isOperatorMappedOnlyInTbtc } = - useOperatorMappedtoStakingProviderHelpers() + const isOperatorMappedOnlyInTbtc = + !isAddressZero(mappedOperatorTbtc) && + isAddressZero(mappedOperatorRandomBeacon) + + const isOperatorMappedOnlyInRandomBeacon = + isAddressZero(mappedOperatorTbtc) && + !isAddressZero(mappedOperatorRandomBeacon) + const isOneOfTheAppsNotMapped = isOperatorMappedOnlyInRandomBeacon || isOperatorMappedOnlyInTbtc const onStartMappingClick = () => { - openModal(ModalType.MapOperatorToStakingProvider) + openModal(ModalType.MapOperatorToStakingProvider, { + mappedOperatorTbtc, + mappedOperatorRandomBeacon, + }) } return ( @@ -45,7 +58,7 @@ const OperatorAddressMappingCard = () => { ) diff --git a/src/pages/Staking/index.tsx b/src/pages/Staking/index.tsx index 3d8f6627a..3036210b1 100644 --- a/src/pages/Staking/index.tsx +++ b/src/pages/Staking/index.tsx @@ -1,5 +1,5 @@ import { useEffect, useMemo } from "react" -import { HStack, Stack, VStack } from "@chakra-ui/react" +import { HStack, VStack } from "@chakra-ui/react" import StakingTVLCard from "./StakingTVLCard" import StakedPortfolioCard from "./StakedPortfolioCard" import PageLayout from "../PageLayout" @@ -27,9 +27,8 @@ import { stakingApplicationsSlice } from "../../store/staking-applications/slice import StakeDetailsPage from "./StakeDetailsPage" import NewStakeCard from "./NewStakeCard" import OperatorAddressMappingCard from "./OperatorAddressMappingCard" -import { useRolesOf } from "../../hooks/useRolesOf" -import { useOperatorMappedtoStakingProviderHelpers } from "../../hooks/staking-applications/useOperatorMappedToStakingProviderHelpers" -import { isEmptyOrZeroAddress } from "../../web3/utils" +import { isAddressZero } from "../../web3/utils" +import { useAppSelector } from "../../hooks/store" const StakingPage: PageComponent = (props) => { const [data, fetchtTvlData] = useFetchTvl() @@ -47,32 +46,13 @@ const StakingPage: PageComponent = (props) => { const totalBonusBalance = useSelector(selectTotalBonusBalance) const hasStakes = stakes.length > 0 - const { owner, authorizer, beneficiary } = useRolesOf() - const { operatorMappedRandomBeacon, operatorMappedTbtc, isInitialFetchDone } = - useOperatorMappedtoStakingProviderHelpers() - - const shouldDisplayOperatorAddressMappingCard = useMemo(() => { - const isStakingProviderUsed = - !isEmptyOrZeroAddress(owner) || - !isEmptyOrZeroAddress(authorizer) || - !isEmptyOrZeroAddress(beneficiary) - - const isOperatorMappedInAllApps = - !isEmptyOrZeroAddress(operatorMappedRandomBeacon) && - !isEmptyOrZeroAddress(operatorMappedTbtc) - - return ( - isInitialFetchDone && isStakingProviderUsed && !isOperatorMappedInAllApps - ) - }, [ - owner, - authorizer, - beneficiary, - operatorMappedRandomBeacon, - operatorMappedTbtc, - isInitialFetchDone, - isEmptyOrZeroAddress, - ]) + const { + isStakingProvider, + operatorMapping: { + isInitialFetchDone: isOperatorMappingInitialFetchDone, + data: mappedOperators, + }, + } = useAppSelector((state) => state.account) return ( @@ -90,9 +70,15 @@ const StakingPage: PageComponent = (props) => { > Your Stake - {shouldDisplayOperatorAddressMappingCard && ( - - )} + {isStakingProvider && + isOperatorMappingInitialFetchDone && + (isAddressZero(mappedOperators.tbtc) || + isAddressZero(mappedOperators.randomBeacon)) && ( + + )} {hasStakes ? ( stakes.map((stake) => ( diff --git a/src/store/account/effects.ts b/src/store/account/effects.ts new file mode 100644 index 000000000..a4f23ecbc --- /dev/null +++ b/src/store/account/effects.ts @@ -0,0 +1,64 @@ +import { StakeData } from "../../types" +import { isAddressZero, isSameETHAddress } from "../../web3/utils" +import { AppListenerEffectAPI } from "../listener" +import { setStakes } from "../staking" +import { + accountUsedAsStakingProvider, + accountSlice, + fetchingOperatorMapping, + setMappedOperators, +} from "./slice" + +export const getStakingProviderOperatorInfo = async ( + action: ReturnType, + listenerApi: AppListenerEffectAPI +) => { + try { + const { account } = listenerApi.getState() + const { address } = account + const stakes = action.payload + + const stake = stakes.find((_: StakeData) => + isSameETHAddress(_.stakingProvider, address) + ) + + let isStakingProvider = false + + if (stake) { + isStakingProvider = true + } else { + const { owner, authorizer, beneficiary } = + await listenerApi.extra.threshold.staking.rolesOf(address) + + isStakingProvider = + !isAddressZero(owner) && + !isAddressZero(authorizer) && + !isAddressZero(beneficiary) + } + + if (!isStakingProvider) return + + listenerApi.dispatch(fetchingOperatorMapping()) + + listenerApi.dispatch(accountUsedAsStakingProvider()) + + const mappedOperators = + await listenerApi.extra.threshold.multiAppStaking.getMappedOperatorsForStakingProvider( + address + ) + + listenerApi.dispatch( + setMappedOperators({ + tbtc: mappedOperators.tbtc, + randomBeacon: mappedOperators.randomBeacon, + }) + ) + } catch (error: any) { + listenerApi.dispatch( + accountSlice.actions.setOperatorMappingError({ + error, + }) + ) + throw new Error("Could not load staking provider's operator info: " + error) + } +} diff --git a/src/store/modalQueue/index.ts b/src/store/account/index.ts similarity index 100% rename from src/store/modalQueue/index.ts rename to src/store/account/index.ts diff --git a/src/store/account/slice.ts b/src/store/account/slice.ts new file mode 100644 index 000000000..88cd21cf0 --- /dev/null +++ b/src/store/account/slice.ts @@ -0,0 +1,115 @@ +import { AddressZero } from "@ethersproject/constants" +import { AnyAction, createSlice, PayloadAction } from "@reduxjs/toolkit" +import { featureFlags } from "../../constants" +import { FetchingState } from "../../types" +import { isSameETHAddress } from "../../web3/utils" +import { startAppListening } from "../listener" +import { + providerStaked, + providerStakedForStakingProvider, + setStakes, +} from "../staking" +import { StakingAppName } from "../staking-applications" +import { getStakingProviderOperatorInfo } from "./effects" + +interface AccountState { + address: string + isStakingProvider: boolean + operatorMapping: FetchingState> +} + +export const accountSlice = createSlice({ + name: "account", + initialState: { + address: "", + isStakingProvider: false, + operatorMapping: { + data: { + tbtc: AddressZero, + randomBeacon: AddressZero, + }, + isFetching: false, + isInitialFetchDone: false, + }, + } as AccountState, + reducers: { + walletConnected: (state: AccountState, action: PayloadAction) => { + state.address = action.payload + }, + accountUsedAsStakingProvider: ( + state: AccountState, + action: PayloadAction + ) => { + state.isStakingProvider = true + }, + setMappedOperators: ( + state: AccountState, + action: PayloadAction<{ + tbtc: string + randomBeacon: string + }> + ) => { + const { tbtc, randomBeacon } = action.payload + state.operatorMapping.data.tbtc = tbtc + state.operatorMapping.data.randomBeacon = randomBeacon + state.operatorMapping.isFetching = false + state.operatorMapping.isInitialFetchDone = true + state.operatorMapping.error = "" + }, + fetchingOperatorMapping: (state: AccountState) => { + state.operatorMapping.isFetching = true + }, + setOperatorMappingError: ( + state: AccountState, + action: PayloadAction<{ error: string }> + ) => { + const { error } = action.payload + state.operatorMapping.isFetching = false + state.operatorMapping.error = error + }, + operatorRegistered: ( + state: AccountState, + action: PayloadAction<{ + appName: StakingAppName + operator: string + }> + ) => { + const { appName, operator } = action.payload + state.operatorMapping.data[appName] = operator + }, + }, + extraReducers: (builder) => { + builder.addMatcher( + (action: AnyAction) => + action.type.match(providerStakedForStakingProvider), + (state, action: ReturnType) => { + const { stakingProvider } = action.payload + + const { address } = state + + if (isSameETHAddress(stakingProvider, address)) { + state.isStakingProvider = true + } + } + ) + }, +}) + +export const registerAccountListeners = () => { + if (featureFlags.MULTI_APP_STAKING) { + startAppListening({ + actionCreator: setStakes, + effect: getStakingProviderOperatorInfo, + }) + } +} +registerAccountListeners() + +export const { + walletConnected, + accountUsedAsStakingProvider, + setMappedOperators, + fetchingOperatorMapping, + setOperatorMappingError, + operatorRegistered, +} = accountSlice.actions diff --git a/src/store/connected-account/connectedAccountSlice.ts b/src/store/connected-account/connectedAccountSlice.ts deleted file mode 100644 index 0a1dd0398..000000000 --- a/src/store/connected-account/connectedAccountSlice.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { AnyAction, createSlice, PayloadAction } from "@reduxjs/toolkit" -import { RolesOf } from "../../threshold-ts/staking" -import { isSameETHAddress } from "../../web3/utils" -import { startAppListening } from "../listener" -import { - providerStaked, - providerStakedForStakingProvider, - setStakes, -} from "../staking" -import { getRolesOf } from "./effects" - -interface ConnectedAccountState { - address: string - rolesOf: RolesOf -} - -export const connectedAccountSlice = createSlice({ - name: "connected-account", - initialState: { - address: "", - rolesOf: { - owner: "", - authorizer: "", - beneficiary: "", - }, - } as ConnectedAccountState, - reducers: { - setConnectedAccountAddress: ( - state: ConnectedAccountState, - action: PayloadAction - ) => { - state.address = action.payload - }, - setRolesOf: ( - state: ConnectedAccountState, - action: PayloadAction - ) => { - state.rolesOf.owner = action.payload.owner - state.rolesOf.authorizer = action.payload.authorizer - state.rolesOf.beneficiary = action.payload.beneficiary - }, - }, - extraReducers: (builder) => { - builder.addMatcher( - (action: AnyAction) => - action.type.match(providerStakedForStakingProvider), - (state, action: ReturnType) => { - const { owner, beneficiary, authorizer, stakingProvider } = - action.payload - - const { address } = state - - if (isSameETHAddress(stakingProvider, address)) { - state.rolesOf = { - owner, - beneficiary, - authorizer, - } - } - } - ) - }, -}) - -startAppListening({ - actionCreator: setStakes, - effect: getRolesOf, -}) - -export const { setConnectedAccountAddress, setRolesOf } = - connectedAccountSlice.actions diff --git a/src/store/connected-account/effects.ts b/src/store/connected-account/effects.ts deleted file mode 100644 index a53608546..000000000 --- a/src/store/connected-account/effects.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { AnyAction } from "@reduxjs/toolkit" -import { isAddressZero } from "../../web3/utils" -import { AppListenerEffectAPI } from "../listener" -import { mapOperatorToStakingProviderModalClosed } from "../modalQueue" -import { setRolesOf } from "./connectedAccountSlice" - -export const getRolesOf = async ( - action: AnyAction, - listenerApi: AppListenerEffectAPI -) => { - try { - const { connectedAccount } = listenerApi.getState() - const { address } = connectedAccount - - if (!address) return - listenerApi.unsubscribe() - const { owner, authorizer, beneficiary } = - await listenerApi.extra.threshold.staking.rolesOf(address) - if ( - isAddressZero(owner) && - isAddressZero(authorizer) && - isAddressZero(beneficiary) - ) { - listenerApi.dispatch(mapOperatorToStakingProviderModalClosed()) - } - listenerApi.dispatch( - setRolesOf({ - owner, - authorizer, - beneficiary, - }) - ) - } catch (error) { - console.log("Could not fetch roles for connected staking provider: ", error) - listenerApi.subscribe() - } -} diff --git a/src/store/connected-account/index.ts b/src/store/connected-account/index.ts deleted file mode 100644 index 65a027d83..000000000 --- a/src/store/connected-account/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./connectedAccountSlice" -export * from "./effects" diff --git a/src/store/index.ts b/src/store/index.ts index d387d84ad..f9f98f05d 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -17,11 +17,10 @@ import { stakingApplicationsSlice, } from "./staking-applications/slice" import { listenerMiddleware } from "./listener" -import { connectedAccountSlice } from "./connected-account" -import { modalQueueSlice } from "./modalQueue" +import { accountSlice, registerAccountListeners } from "./account" const combinedReducer = combineReducers({ - connectedAccount: connectedAccountSlice.reducer, + account: accountSlice.reducer, modal: modalSlice.reducer, token: tokenSlice.reducer, sidebar: sidebarSlice.reducer, @@ -31,7 +30,6 @@ const combinedReducer = combineReducers({ tbtc: tbtcSlice.reducer, rewards: rewardsSlice.reducer, applications: stakingApplicationsSlice.reducer, - modalQueue: modalQueueSlice.reducer, }) const APP_RESET_STORE = "app/reset_store" @@ -45,6 +43,7 @@ const rootReducer: Reducer = (state: RootState, action: AnyAction) => { listenerMiddleware.clearListeners() registerStakingListeners() registerStakingAppsListeners() + registerAccountListeners() state = { eth: { ...state.eth }, token: { @@ -53,6 +52,15 @@ const rootReducer: Reducer = (state: RootState, action: AnyAction) => { T: { ...state.token.T, balance: 0 }, TBTC: { ...state.token.TBTC, balance: 0 }, }, + // we don't display successful login modal when changin account so we are + // setting the isSuccessfulLoginModalClosed flag to true and also + // isMappingOperatorToStakingProviderModalClosed flag back to false + modal: { + modalQueue: { + isSuccessfulLoginModalClosed: true, + isMappingOperatorToStakingProviderModalClosed: false, + }, + }, } as RootState } diff --git a/src/store/modal/modalSlice.ts b/src/store/modal/modalSlice.ts index 38ff13384..91d1e00ad 100644 --- a/src/store/modal/modalSlice.ts +++ b/src/store/modal/modalSlice.ts @@ -1,9 +1,15 @@ -import { createSlice } from "@reduxjs/toolkit" +import { createSlice, PayloadAction } from "@reduxjs/toolkit" import { ModalType } from "../../enums" -export interface modalState { +export interface ModalQueueState { + isSuccessfulLoginModalClosed: boolean + isMappingOperatorToStakingProviderModalClosed: boolean +} + +export interface ModalState { modalType: ModalType | null props: any + modalQueue: ModalQueueState } export const modalSlice = createSlice({ @@ -11,17 +17,35 @@ export const modalSlice = createSlice({ initialState: { modalType: null, props: {}, - } as modalState, + modalQueue: { + isSuccessfulLoginModalClosed: false, + isMappingOperatorToStakingProviderModalClosed: false, + }, + } as ModalState, reducers: { - openModal: (state, action) => { + openModal: ( + state: ModalState, + action: PayloadAction<{ modalType: ModalType; props?: any }> + ) => { state.modalType = action.payload.modalType state.props = action.payload.props }, - closeModal: (state) => { + closeModal: (state: ModalState) => { state.modalType = null state.props = {} }, + successfullLoginModalClosed: (state: ModalState) => { + state.modalQueue.isSuccessfulLoginModalClosed = true + }, + mapOperatorToStakingProviderModalClosed: (state: ModalState) => { + state.modalQueue.isMappingOperatorToStakingProviderModalClosed = true + }, }, }) -export const { openModal, closeModal } = modalSlice.actions +export const { + openModal, + closeModal, + successfullLoginModalClosed, + mapOperatorToStakingProviderModalClosed, +} = modalSlice.actions diff --git a/src/store/modalQueue/slice.ts b/src/store/modalQueue/slice.ts deleted file mode 100644 index 92883f5ba..000000000 --- a/src/store/modalQueue/slice.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { createSlice, PayloadAction } from "@reduxjs/toolkit" -import { featureFlags } from "../../constants" - -export interface ModalQueueState { - isSuccessfullLoginModalClosed: boolean - isMappingOperatorToStakingProviderModalClosed: boolean -} - -/* - Simplpified modal queue, we should think how to do it properly whenever we have more time -*/ -export const modalQueueSlice = createSlice({ - name: "modal-queue", - initialState: { - isSuccessfullLoginModalClosed: false, - isMappingOperatorToStakingProviderModalClosed: false, - } as ModalQueueState, - reducers: { - successfullLoginModalClosed: ( - state: ModalQueueState, - action: PayloadAction - ) => { - state.isSuccessfullLoginModalClosed = true - }, - mapOperatorToStakingProviderModalClosed: ( - state: ModalQueueState, - action: PayloadAction - ) => { - state.isMappingOperatorToStakingProviderModalClosed = true - }, - }, -}) - -export const { - successfullLoginModalClosed, - mapOperatorToStakingProviderModalClosed, -} = modalQueueSlice.actions diff --git a/src/store/staking-applications/effects.ts b/src/store/staking-applications/effects.ts index fadafbd91..86ba37261 100644 --- a/src/store/staking-applications/effects.ts +++ b/src/store/staking-applications/effects.ts @@ -6,7 +6,11 @@ import { selectStakingProviders, setStakes, } from "../staking" -import { modalSlice, openModal } from "../modal" +import { + mapOperatorToStakingProviderModalClosed, + modalSlice, + openModal, +} from "../modal" import { IApplication, StakingProviderAppInfo, @@ -18,8 +22,6 @@ import { selectStakingAppStateByAppName, } from "./selectors" import { isAddressZero } from "../../web3/utils" -import { featureFlags } from "../../constants" -import { mapOperatorToStakingProviderModalClosed } from "../modalQueue" export const getSupportedAppsEffect = async ( action: ReturnType, @@ -168,114 +170,58 @@ const getKeepStakingAppStakingProvidersData = async ( } } -export const getMappedOperatorsEffect = async ( +export const displayMapOperatorToStakingProviderModalEffect = async ( action: AnyAction, listenerApi: AppListenerEffectAPI ) => { - try { - const { connectedAccount } = listenerApi.getState() - const { address } = connectedAccount - - if (address) { - listenerApi.unsubscribe() - getMappedOperatorEffect(address, "randomBeacon", listenerApi) - getMappedOperatorEffect(address, "tbtc", listenerApi) - } - } catch (error) { - console.log( - "Could not fetch mapped operator for connected staking provider: ", - error - ) - listenerApi.subscribe() + const { + modal: { modalQueue }, + } = listenerApi.getState() + const { account } = listenerApi.getState() + if (!modalQueue.isSuccessfulLoginModalClosed) { + await listenerApi.condition((action, currentState) => { + return currentState.modal.modalQueue.isSuccessfulLoginModalClosed + }) } -} + const { address } = account + if (!address) return -const getMappedOperatorEffect = async ( - stakingProvider: string, - appName: StakingAppName, - listenerApi: AppListenerEffectAPI -) => { + listenerApi.unsubscribe() try { - listenerApi.dispatch( - stakingApplicationsSlice.actions.fetchingMappedOperator({ - appName, - }) - ) - const appNameProp = appName === "tbtc" ? "ecdsa" : appName - if (stakingProvider) { - const operatorMapped = await listenerApi.extra.threshold.multiAppStaking[ - appNameProp - ].stakingProviderToOperator(stakingProvider) - listenerApi.dispatch( - stakingApplicationsSlice.actions.setMappedOperator({ - appName: appName, - operator: operatorMapped, - }) - ) + const { isStakingProvider } = account + + const { + tbtc: mappedOperatorTbtc, + randomBeacon: mappedOperatorRandomBeacon, + } = action.payload + + if ( + isStakingProvider && + (isAddressZero(mappedOperatorTbtc) || + isAddressZero(mappedOperatorRandomBeacon)) + ) { listenerApi.dispatch( - stakingApplicationsSlice.actions.setMappedOperatorInitialFetch({ - appName: appName, - value: true, + openModal({ + modalType: ModalType.MapOperatorToStakingProvider, + props: { + address, + mappedOperatorTbtc: mappedOperatorTbtc, + mappedOperatorRandomBeacon: mappedOperatorRandomBeacon, + }, }) ) + } else { + listenerApi.dispatch(mapOperatorToStakingProviderModalClosed()) } } catch (error) { - listenerApi.dispatch( - stakingApplicationsSlice.actions.setStakingProvidersAppDataError({ - appName, - error: (error as Error).toString(), - }) + console.log( + "Could not fetch info about mapped operators for given staking provider:", + error ) - throw error - } -} - -export const displayMapOperatorToStakingProviderModalEffect = async ( - action: AnyAction, - listenerApi: AppListenerEffectAPI -) => { - const { connectedAccount } = listenerApi.getState() - const { address } = connectedAccount - if (address) { - // check if the current connected address is used somewhere as a staking - // provider - listenerApi.unsubscribe() - const { owner, authorizer, beneficiary } = - await listenerApi.extra.threshold.staking.rolesOf(address) - - if ( - !isAddressZero(owner) || - !isAddressZero(authorizer) || - !isAddressZero(beneficiary) - ) { - if (featureFlags.MULTI_APP_STAKING) { - listenerApi.dispatch( - openModal({ modalType: ModalType.MapOperatorToStakingProvider }) - ) - } - } + listenerApi.subscribe() } } -export const shouldDisplayMapOperatorToStakingProviderModal = ( - action: AnyAction, - currentState: RootState, - previousState: RootState -) => { - return ( - !!currentState.connectedAccount.address && - currentState.modalQueue.isSuccessfullLoginModalClosed && - (currentState.applications.randomBeacon.mappedOperator - .isInitialFetchDone as boolean) && - (currentState.applications.tbtc.mappedOperator - .isInitialFetchDone as boolean) && - (isAddressZero( - currentState.applications.randomBeacon.mappedOperator.data - ) || - isAddressZero(currentState.applications.tbtc.mappedOperator.data)) - ) -} - export const displayNewAppsToAuthorizeModalEffect = async ( action: AnyAction, listenerApi: AppListenerEffectAPI @@ -308,12 +254,9 @@ export const shouldDisplayNewAppsToAuthorizeModal = ( previousState: RootState ) => { return ( - currentState.modalQueue.isSuccessfullLoginModalClosed && - (currentState.modalQueue.isMappingOperatorToStakingProviderModalClosed || - (!isAddressZero( - currentState.applications.randomBeacon.mappedOperator.data - ) && - !isAddressZero(currentState.applications.tbtc.mappedOperator.data))) && + currentState.modal.modalQueue.isSuccessfulLoginModalClosed && + currentState.modal.modalQueue + .isMappingOperatorToStakingProviderModalClosed && Object.values( currentState.applications.randomBeacon.stakingProviders.data ?? {} ).length > 0 && diff --git a/src/store/staking-applications/slice.ts b/src/store/staking-applications/slice.ts index aa0c8d393..4fdeb67d1 100644 --- a/src/store/staking-applications/slice.ts +++ b/src/store/staking-applications/slice.ts @@ -1,6 +1,5 @@ import { AnyAction, createSlice, PayloadAction } from "@reduxjs/toolkit" import { BigNumber } from "ethers" -import { AddressZero } from "@ethersproject/constants" import { featureFlags } from "../../constants" import { StakingProviderAppInfo, @@ -13,14 +12,13 @@ import { providerStaked, setStakes } from "../staking" import { getSupportedAppsStakingProvidersData, getSupportedAppsEffect, - getMappedOperatorsEffect, - shouldDisplayMapOperatorToStakingProviderModal, displayMapOperatorToStakingProviderModalEffect, shouldDisplayNewAppsToAuthorizeModal, displayNewAppsToAuthorizeModalEffect, displayDeauthrizationCompletedModalEffect, displayDeauthrizationInitiatedModalEffect, } from "./effects" +import { setMappedOperators } from "../account" type StakingApplicationDataByStakingProvider = { [stakingProvider: string]: StakingProviderAppInfo @@ -29,7 +27,6 @@ type StakingApplicationDataByStakingProvider = { export type StakingApplicationState = { parameters: FetchingState> stakingProviders: FetchingState - mappedOperator: FetchingState } export interface StakingApplicationsState { @@ -57,12 +54,6 @@ export const stakingApplicationsSlice = createSlice({ error: "", data: {}, }, - mappedOperator: { - isInitialFetchDone: false, - isFetching: false, - error: "", - data: AddressZero, - }, }, randomBeacon: { parameters: { @@ -79,12 +70,6 @@ export const stakingApplicationsSlice = createSlice({ error: "", data: {}, }, - mappedOperator: { - isInitialFetchDone: false, - isFetching: false, - error: "", - data: AddressZero, - }, }, } as StakingApplicationsState, reducers: { @@ -152,62 +137,6 @@ export const stakingApplicationsSlice = createSlice({ state[appName].stakingProviders.isFetching = false state[appName].stakingProviders.error = error }, - fetchMappedOperators: (state: StakingApplicationsState, action) => {}, - setMappedOperator: ( - state: StakingApplicationsState, - action: PayloadAction<{ - appName: StakingAppName - operator: string - }> - ) => { - const { appName, operator } = action.payload - state[appName].mappedOperator = { - ...state[appName].mappedOperator, - isFetching: false, - error: "", - data: operator, - } - }, - setMappedOperatorInitialFetch: ( - state: StakingApplicationsState, - action: PayloadAction<{ - appName: StakingAppName - value: boolean - }> - ) => { - const { appName, value } = action.payload - state[appName].mappedOperator.isInitialFetchDone = value - }, - fetchingMappedOperator: ( - state: StakingApplicationsState, - action: PayloadAction<{ - appName: StakingAppName - }> - ) => { - const { appName } = action.payload - state[appName].mappedOperator.isFetching = true - }, - setMappedOperatorError: ( - state: StakingApplicationsState, - action: PayloadAction<{ - appName: StakingAppName - error: string - }> - ) => { - const { appName, error } = action.payload - state[appName].mappedOperator.isFetching = false - state[appName].mappedOperator.error = error - }, - operatorMapped: ( - state: StakingApplicationsState, - action: PayloadAction<{ - appName: StakingAppName - operator: string - }> - ) => { - const { appName, operator } = action.payload - state[appName].mappedOperator.data = operator - }, authorizationIncreased: ( state: StakingApplicationsState, action: PayloadAction<{ @@ -348,21 +277,9 @@ export const registerStakingAppsListeners = () => { }) startAppListening({ - actionCreator: setStakes, - effect: getMappedOperatorsEffect, - }) - - startAppListening({ - actionCreator: stakingApplicationsSlice.actions.fetchMappedOperators, - effect: getMappedOperatorsEffect, - }) - - startAppListening({ - predicate: shouldDisplayMapOperatorToStakingProviderModal, + actionCreator: setMappedOperators, effect: displayMapOperatorToStakingProviderModalEffect, }) } } registerStakingAppsListeners() - -export const { operatorMapped } = stakingApplicationsSlice.actions diff --git a/src/threshold-ts/mas/__test__/mas.test.ts b/src/threshold-ts/mas/__test__/mas.test.ts index 0391219c9..88760890e 100644 --- a/src/threshold-ts/mas/__test__/mas.test.ts +++ b/src/threshold-ts/mas/__test__/mas.test.ts @@ -99,4 +99,42 @@ describe("Multi app staking test", () => { randomBeacon: randomBeaconAuthParams, }) }) + + test("should return mapped operators for given staking provider", async () => { + const mockStakingProvider = "0x3" + const mockOperator = "0x4" + const mappedOperatorTbtc = mockOperator + const mappedOperatorRandomBeacon = mockOperator + + const mulitcallMockResult = [ + [mappedOperatorTbtc], + [mappedOperatorRandomBeacon], + ] + const spyOnMulticall = jest + .spyOn(multicall, "aggregate") + .mockResolvedValue(mulitcallMockResult) + + const result = await mas.getMappedOperatorsForStakingProvider( + mockStakingProvider + ) + + expect(spyOnMulticall).toHaveBeenCalledWith([ + { + interface: mas.ecdsa.contract.interface, + address: mas.ecdsa.address, + method: "stakingProviderToOperator", + args: [mockStakingProvider], + }, + { + interface: mas.randomBeacon.contract.interface, + address: mas.randomBeacon.address, + method: "stakingProviderToOperator", + args: [mockStakingProvider], + }, + ]) + expect(result).toEqual({ + tbtc: mappedOperatorTbtc, + randomBeacon: mappedOperatorRandomBeacon, + }) + }) }) diff --git a/src/threshold-ts/mas/index.ts b/src/threshold-ts/mas/index.ts index 6eb324eac..f63a84e9c 100644 --- a/src/threshold-ts/mas/index.ts +++ b/src/threshold-ts/mas/index.ts @@ -14,6 +14,11 @@ export interface SupportedAppAuthorizationParameters { randomBeacon: AuthorizationParameters } +export interface MappedOperatorsForStakingProvider { + tbtc: string + randomBeacon: string +} + export class MultiAppStaking { private _staking: IStaking private _multicall: IMulticall @@ -63,4 +68,31 @@ export class MultiAppStaking { randomBeacon: randomBeaconMinAuthorizationParams, } } + + async getMappedOperatorsForStakingProvider( + stakingProvider: string + ): Promise { + const calls: ContractCall[] = [ + { + interface: this.ecdsa.contract.interface, + address: this.ecdsa.address, + method: "stakingProviderToOperator", + args: [stakingProvider], + }, + { + interface: this.randomBeacon.contract.interface, + address: this.randomBeacon.address, + method: "stakingProviderToOperator", + args: [stakingProvider], + }, + ] + + const [mappedOperatorTbtc, mappedOperatorRandomBeacon] = + await this._multicall.aggregate(calls) + + return { + tbtc: mappedOperatorTbtc.toString(), + randomBeacon: mappedOperatorRandomBeacon.toString(), + } + } }