diff --git a/src/App.tsx b/src/App.tsx index 0bf199c06..16bf28bd2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -39,6 +39,7 @@ import { useCheckBonusEligibility } from "./hooks/useCheckBonusEligibility" import { useFetchStakingRewards } from "./hooks/useFetchStakingRewards" import { isSameETHAddress } from "./web3/utils" import { ThresholdProvider } from "./contexts/ThresholdContext" +import { useSubscribeToAuthorizationIncreasedEvent } from "./hooks/staking-applications" const Web3EventHandlerComponent = () => { useSubscribeToVendingMachineContractEvents() @@ -48,6 +49,8 @@ const Web3EventHandlerComponent = () => { useSubscribeToStakedEvent() useSubscribeToUnstakedEvent() useSubscribeToToppedUpEvent() + useSubscribeToAuthorizationIncreasedEvent("tbtc") + useSubscribeToAuthorizationIncreasedEvent("randomBeacon") return <> } diff --git a/src/contexts/ThresholdContext.tsx b/src/contexts/ThresholdContext.tsx index 3fb20f9c7..f2881e743 100644 --- a/src/contexts/ThresholdContext.tsx +++ b/src/contexts/ThresholdContext.tsx @@ -1,34 +1,43 @@ -import { createContext, FC, useContext, useMemo } from "react" +import { createContext, FC, useContext, useEffect, useRef } from "react" import { useWeb3React } from "@web3-react/core" -import { JsonRpcProvider, Provider } from "@ethersproject/providers" -import { Signer } from "ethers" -import { Threshold } from "../threshold-ts" -import { EnvVariable } from "../enums" -import { getEnvVariable, supportedChainId } from "../utils/getEnvVariable" +import { + getDefaultThresholdLibProvider, + threshold, +} from "../utils/getThresholdLib" +import { supportedChainId } from "../utils/getEnvVariable" -const getThresholdLib = (providerOrSigner?: Provider | Signer) => { - return new Threshold({ - ethereum: { - chainId: supportedChainId, - providerOrSigner: - providerOrSigner || - new JsonRpcProvider(getEnvVariable(EnvVariable.ETH_HOSTNAME_HTTP)), - }, - }) -} - -const ThresholdContext = createContext(getThresholdLib()) +const ThresholdContext = createContext(threshold) export const useThreshold = () => { return useContext(ThresholdContext) } export const ThresholdProvider: FC = ({ children }) => { - const { library, active } = useWeb3React() + const { library, active, account } = useWeb3React() + const hasThresholdLibConfigBeenUpdated = useRef(false) + + useEffect(() => { + if (active && library && account) { + threshold.updateConfig({ + ethereum: { + chainId: supportedChainId, + providerOrSigner: library, + account, + }, + }) + hasThresholdLibConfigBeenUpdated.current = true + } - const threshold = useMemo(() => { - return getThresholdLib(active ? library : undefined) - }, [library, active]) + if (!active && !account && hasThresholdLibConfigBeenUpdated.current) { + threshold.updateConfig({ + ethereum: { + chainId: supportedChainId, + providerOrSigner: getDefaultThresholdLibProvider(), + }, + }) + hasThresholdLibConfigBeenUpdated.current = false + } + }, [library, active, account]) return ( diff --git a/src/hooks/staking-applications/index.ts b/src/hooks/staking-applications/index.ts new file mode 100644 index 000000000..50bcb755b --- /dev/null +++ b/src/hooks/staking-applications/index.ts @@ -0,0 +1,6 @@ +export * from "./useStakingAppContract" +export * from "./useStakingAppDataByStakingProvider" +export * from "./useStakingApplicationState" +export * from "./useStakingAppMinAuthorizationAmount" +export * from "./useStakingAppParameters" +export * from "./useSubscribeToAuthorizationIncreasedEvent" diff --git a/src/hooks/staking-applications/useStakingAppContract.ts b/src/hooks/staking-applications/useStakingAppContract.ts new file mode 100644 index 000000000..c30ed14c7 --- /dev/null +++ b/src/hooks/staking-applications/useStakingAppContract.ts @@ -0,0 +1,11 @@ +import { StakingAppName } from "../../store/staking-applications" +import { useThreshold } from "../../contexts/ThresholdContext" + +const appNameToAppService: Record = { + tbtc: "ecdsa", + randomBeacon: "randomBeacon", +} + +export const useStakingAppContract = (appName: StakingAppName) => { + return useThreshold().multiAppStaking[appNameToAppService[appName]].contract +} diff --git a/src/hooks/staking-applications/useStakingAppDataByStakingProvider.ts b/src/hooks/staking-applications/useStakingAppDataByStakingProvider.ts new file mode 100644 index 000000000..a13a049f2 --- /dev/null +++ b/src/hooks/staking-applications/useStakingAppDataByStakingProvider.ts @@ -0,0 +1,14 @@ +import { + selectStakingAppByStakingProvider, + StakingAppName, +} from "../../store/staking-applications" +import { useAppSelector } from "../store" + +export const useStakingAppDataByStakingProvider = ( + appName: StakingAppName, + stakingProvider: string +) => { + return useAppSelector((state) => + selectStakingAppByStakingProvider(state, appName, stakingProvider) + ) +} diff --git a/src/hooks/staking-applications/useStakingAppMinAuthorizationAmount.ts b/src/hooks/staking-applications/useStakingAppMinAuthorizationAmount.ts new file mode 100644 index 000000000..84a462580 --- /dev/null +++ b/src/hooks/staking-applications/useStakingAppMinAuthorizationAmount.ts @@ -0,0 +1,8 @@ +import { StakingAppName } from "../../store/staking-applications" +import { useStakingAppParameters } from "./useStakingAppParameters" + +export const useStakingAppMinAuthorizationAmount = ( + appName: StakingAppName +) => { + return useStakingAppParameters(appName).data.minimumAuthorization +} diff --git a/src/hooks/staking-applications/useStakingAppParameters.ts b/src/hooks/staking-applications/useStakingAppParameters.ts new file mode 100644 index 000000000..2867156b3 --- /dev/null +++ b/src/hooks/staking-applications/useStakingAppParameters.ts @@ -0,0 +1,6 @@ +import { StakingAppName } from "../../store/staking-applications" +import { useStakingApplicationState } from "./useStakingApplicationState" + +export const useStakingAppParameters = (appName: StakingAppName) => { + return useStakingApplicationState(appName).parameters +} diff --git a/src/hooks/staking-applications/useStakingApplicationState.ts b/src/hooks/staking-applications/useStakingApplicationState.ts new file mode 100644 index 000000000..065b9ed31 --- /dev/null +++ b/src/hooks/staking-applications/useStakingApplicationState.ts @@ -0,0 +1,11 @@ +import { + StakingAppName, + selectStakingAppStateByAppName, +} from "../../store/staking-applications" +import { useAppSelector } from "../store" + +export const useStakingApplicationState = (appName: StakingAppName) => { + return useAppSelector((state) => + selectStakingAppStateByAppName(state, appName) + ) +} diff --git a/src/hooks/staking-applications/useSubscribeToAuthorizationIncreasedEvent.ts b/src/hooks/staking-applications/useSubscribeToAuthorizationIncreasedEvent.ts new file mode 100644 index 000000000..5eca9e0a8 --- /dev/null +++ b/src/hooks/staking-applications/useSubscribeToAuthorizationIncreasedEvent.ts @@ -0,0 +1,29 @@ +import { + stakingApplicationsSlice, + StakingAppName, +} from "../../store/staking-applications" +import { useSubscribeToContractEvent } from "../../web3/hooks" +import { useAppDispatch } from "../store" +import { useStakingAppContract } from "./useStakingAppContract" + +export const useSubscribeToAuthorizationIncreasedEvent = ( + appName: StakingAppName +) => { + const contract = useStakingAppContract(appName) + const dispatch = useAppDispatch() + + useSubscribeToContractEvent( + contract, + "AuthorizationIncreased", + // @ts-ignore + async (stakingProvider, operator, fromAmount, toAmount) => { + dispatch( + stakingApplicationsSlice.actions.authorizationIncreased({ + stakingProvider, + toAmount: toAmount.toString(), + appName, + }) + ) + } + ) +} diff --git a/src/hooks/store/index.ts b/src/hooks/store/index.ts new file mode 100644 index 000000000..20f33eb3c --- /dev/null +++ b/src/hooks/store/index.ts @@ -0,0 +1,2 @@ +export * from "./useAppDispatch" +export * from "./useAppSelector" diff --git a/src/hooks/store/useAppDispatch.ts b/src/hooks/store/useAppDispatch.ts new file mode 100644 index 000000000..2db55760c --- /dev/null +++ b/src/hooks/store/useAppDispatch.ts @@ -0,0 +1,4 @@ +import { useDispatch } from "react-redux" +import { AppDispatch } from "../../store" + +export const useAppDispatch: () => AppDispatch = useDispatch diff --git a/src/hooks/store/useAppSelector.ts b/src/hooks/store/useAppSelector.ts new file mode 100644 index 000000000..8338c689b --- /dev/null +++ b/src/hooks/store/useAppSelector.ts @@ -0,0 +1,4 @@ +import { useSelector, TypedUseSelectorHook } from "react-redux" +import { RootState } from "../../store" + +export const useAppSelector: TypedUseSelectorHook = useSelector diff --git a/src/pages/Staking/AuthorizeStakingApps/AuthorizeApplicationsCardCheckbox/index.tsx b/src/pages/Staking/AuthorizeStakingApps/AuthorizeApplicationsCardCheckbox/index.tsx index 7f38b9175..634c99f0f 100644 --- a/src/pages/Staking/AuthorizeStakingApps/AuthorizeApplicationsCardCheckbox/index.tsx +++ b/src/pages/Staking/AuthorizeStakingApps/AuthorizeApplicationsCardCheckbox/index.tsx @@ -10,6 +10,8 @@ import { FC } from "react" import { TokenAmountForm } from "../../../../components/Forms" import { AppAuthorizationInfo } from "./AppAuthorizationInfo" import { formatTokenAmount } from "../../../../utils/formatAmount" +import { useThreshold } from "../../../../contexts/ThresholdContext" +import { WeiPerEther } from "@ethersproject/constants" export interface AppAuthDataProps { label: string @@ -20,6 +22,7 @@ export interface AppAuthDataProps { export interface AuthorizeApplicationsCardCheckboxProps extends BoxProps { appAuthData: AppAuthDataProps + stakingProvider: string onCheckboxClick: (app: AppAuthDataProps, isChecked: boolean) => void isSelected: boolean maxAuthAmount: string @@ -34,9 +37,21 @@ export const AuthorizeApplicationsCardCheckbox: FC< isSelected, maxAuthAmount, minAuthAmount, + stakingProvider, ...restProps }) => { const collapsed = !appAuthData.isAuthRequired + const threshold = useThreshold() + + const onAuthorizeApp = async (tokenAmount: string) => { + // TODO: Pass the staking provider address as a prop. + // TODO: Use `useSendtTransacion` hook to open confirmation modal/pending modals/success modal. + // Just test the transacion. The real flow is diffrent- we should opean confirmation modal then trigger transacion. + await threshold.multiAppStaking.randomBeacon.increaseAuthorization( + stakingProvider, + tokenAmount + ) + } if (collapsed) { return ( @@ -93,7 +108,7 @@ export const AuthorizeApplicationsCardCheckbox: FC< { - console.log("form submitted") - }} + onSubmitForm={onAuthorizeApp} label="Amount" submitButtonText={`Authorize ${appAuthData.label}`} maxTokenAmount={maxAuthAmount} placeholder={"Enter amount"} - minTokenAmount={minAuthAmount} + minTokenAmount={ + appAuthData.percentage === 0 + ? minAuthAmount + : WeiPerEther.toString() + } helperText={`Minimum ${formatTokenAmount(minAuthAmount)} T for ${ appAuthData.label }`} diff --git a/src/pages/Staking/AuthorizeStakingApps/index.tsx b/src/pages/Staking/AuthorizeStakingApps/index.tsx index 565e814f2..4fece5119 100644 --- a/src/pages/Staking/AuthorizeStakingApps/index.tsx +++ b/src/pages/Staking/AuthorizeStakingApps/index.tsx @@ -14,7 +14,7 @@ import { useSelector } from "react-redux" import { useNavigate, useParams } from "react-router-dom" import { RootState } from "../../../store" import { PageComponent, StakeData } from "../../../types" -import { isSameETHAddress } from "../../../web3/utils" +import { AddressZero, isSameETHAddress, isAddress } from "../../../web3/utils" import { StakeCardHeaderTitle } from "../StakeCard/Header/HeaderTitle" import AuthorizeApplicationsCardCheckbox, { AppAuthDataProps, @@ -23,7 +23,10 @@ import { useEffect, useState } from "react" import { featureFlags } from "../../../constants" import { selectStakeByStakingProvider } from "../../../store/staking" import { useWeb3React } from "@web3-react/core" -import { isAddress } from "web3-utils" +import { + useStakingAppDataByStakingProvider, + useStakingAppMinAuthorizationAmount, +} from "../../../hooks/staking-applications" const AuthorizeStakingAppsPage: PageComponent = (props) => { const { stakingProviderAddress } = useParams() @@ -34,8 +37,18 @@ const AuthorizeStakingAppsPage: PageComponent = (props) => { if (!isAddress(stakingProviderAddress!)) navigate(`/staking`) }, [stakingProviderAddress, navigate]) - //TODO: This should be fetched from contract for each app - const minAuthAmount = "1000000000000000000000" + const tbtcMinAuthAmount = useStakingAppMinAuthorizationAmount("tbtc") + const randomBeaconMinAuthAmount = + useStakingAppMinAuthorizationAmount("randomBeacon") + + const tbtcApp = useStakingAppDataByStakingProvider( + "tbtc", + stakingProviderAddress || AddressZero + ) + const randomBeaconApp = useStakingAppDataByStakingProvider( + "randomBeacon", + stakingProviderAddress || AddressZero + ) const stake = useSelector((state: RootState) => selectStakeByStakingProvider(state, stakingProviderAddress!) @@ -48,18 +61,17 @@ const AuthorizeStakingAppsPage: PageComponent = (props) => { ? BigNumber.from(stake?.totalInTStake).isZero() : false - // TODO: This will probably be fetched from contracts const appsAuthData = { tbtc: { label: "tBTC", - isAuthorized: true, - percentage: 40, + isAuthorized: tbtcApp.isAuthorized, + percentage: tbtcApp.percentage, isAuthRequired: true, }, randomBeacon: { label: "Random Beacon", - isAuthorized: false, - percentage: 0, + isAuthorized: randomBeaconApp.isAuthorized, + percentage: randomBeaconApp.percentage, isAuthRequired: true, }, pre: { @@ -124,7 +136,8 @@ const AuthorizeStakingAppsPage: PageComponent = (props) => { onCheckboxClick={onCheckboxClick} isSelected={selectedApps.map((app) => app.label).includes("tBTC")} maxAuthAmount={stake.totalInTStake} - minAuthAmount={minAuthAmount} + minAuthAmount={tbtcMinAuthAmount} + stakingProvider={stakingProviderAddress!} /> { .map((app) => app.label) .includes("Random Beacon")} maxAuthAmount={stake.totalInTStake} - minAuthAmount={minAuthAmount} + minAuthAmount={randomBeaconMinAuthAmount} + stakingProvider={stakingProviderAddress!} /> { onCheckboxClick={onCheckboxClick} isSelected={selectedApps.map((app) => app.label).includes("PRE")} maxAuthAmount={stake.totalInTStake} - minAuthAmount={minAuthAmount} + minAuthAmount={"0"} + stakingProvider={stakingProviderAddress!} />