diff --git a/src/components/ExternalLink/index.tsx b/src/components/ExternalLink/index.tsx index ba2f4ad78..e6ffcb682 100644 --- a/src/components/ExternalLink/index.tsx +++ b/src/components/ExternalLink/index.tsx @@ -1,5 +1,11 @@ import { FC, ReactElement } from "react" -import { Icon, Link, LinkProps, useColorModeValue } from "@chakra-ui/react" +import { + Icon, + Link, + LinkProps, + useColorModeValue, + forwardRef, +} from "@threshold-network/components" import { FiArrowUpRight } from "react-icons/all" interface Props { @@ -9,34 +15,27 @@ interface Props { icon?: ReactElement } -const ExternalLink: FC = ({ - text, - href, - withArrow, - icon, - ...props -}) => { - const defaultColor = useColorModeValue("brand.500", "white") - const finalColor = props.color ? props.color : defaultColor +const ExternalLink: FC = forwardRef( + ({ text, href, withArrow, icon, ...props }, ref) => { + const defaultColor = useColorModeValue("brand.500", "white") + const finalColor = props.color ? props.color : defaultColor - return ( - - {text} - {withArrow ? ( - - ) : ( - icon - )} - - ) -} + return ( + + {text} + {withArrow ? : icon} + + ) + } +) export * from "./SharedLinks" diff --git a/src/components/Modal/StakingApplications/AuthorizeStakingApps.tsx b/src/components/Modal/StakingApplications/AuthorizeStakingApps.tsx new file mode 100644 index 000000000..e24658ead --- /dev/null +++ b/src/components/Modal/StakingApplications/AuthorizeStakingApps.tsx @@ -0,0 +1,121 @@ +import { FC } from "react" +import { + BodyLg, + Button, + Card, + H5, + ModalBody, + ModalCloseButton, + ModalFooter, + ModalHeader, + LabelSm, + BodyMd, + List, + ListItem, +} from "@threshold-network/components" +import { CheckCircleIcon } from "@chakra-ui/icons" +import InfoBox from "../../InfoBox" +import TokenBalance from "../../TokenBalance" +import StakeAddressInfo from "../../../pages/Staking/StakeCard/StakeAddressInfo" +import withBaseModal from "../withBaseModal" +import { + calculatePercenteage, + formatPercentage, +} from "../../../utils/percentage" +import { BaseModalProps } from "../../../types" +import { useAuthorizeMultipleAppsTransaction } from "../../../hooks/staking-applications" + +export type AuthorizeAppsProps = BaseModalProps & { + stakingProvider: string + totalInTStake: string + applications: { + appName: string + address: string + authorizationAmount: string + }[] +} + +const AuthorizeStakingAppsBase: FC = ({ + stakingProvider, + totalInTStake, + applications, + closeModal, +}) => { + const { authorizeMultipleApps } = useAuthorizeMultipleAppsTransaction() + const onAuthorize = async () => { + await authorizeMultipleApps( + applications.map((_) => ({ + address: _.address, + amount: _.authorizationAmount, + })), + stakingProvider + ) + } + const numberOfApps = applications.length + + return ( + <> + Authorize Apps + + + +
+ You are authorizing your stake for Threshold application + {numberOfApps > 1 ? "s" : ""}. +
+ + This will require {numberOfApps} transaction + {numberOfApps > 1 ? "s" : ""}. You can adjust the authorization + amount at any time. + +
+ + {applications.map((app) => ( + + + + ))} + +
+ + + + + + ) +} + +const StakingApplicationToAuth: FC<{ + appName: string + authorizationAmount: string + stakingProvider: string + totalInTStake: string +}> = ({ appName, authorizationAmount, stakingProvider, totalInTStake }) => { + const percentage = formatPercentage( + calculatePercenteage(authorizationAmount, totalInTStake), + undefined, + true + ) + + return ( + + + + {appName} - {percentage} + + Authorization Amount + + + + ) +} + +export const AuthorizeStakingApps = withBaseModal(AuthorizeStakingAppsBase) diff --git a/src/components/Modal/StakingApplications/StakingApplicationsAuthorized.tsx b/src/components/Modal/StakingApplications/StakingApplicationsAuthorized.tsx new file mode 100644 index 000000000..7832715ed --- /dev/null +++ b/src/components/Modal/StakingApplications/StakingApplicationsAuthorized.tsx @@ -0,0 +1,159 @@ +import { FC, Fragment } from "react" +import { Link as RouterLink, useNavigate } from "react-router-dom" +import { + HStack, + BodyLg, + Button, + H5, + ModalBody, + ModalCloseButton, + ModalFooter, + ModalHeader, + List, + ListItem, + Alert, + AlertIcon, + BodySm, + Divider, + Link, + FlowStepStatus, +} from "@threshold-network/components" +import InfoBox from "../../InfoBox" +import ExternalLink from "../../ExternalLink" +import ViewInBlockExplorer from "../../ViewInBlockExplorer" +import withBaseModal from "../withBaseModal" +import { useAppSelector } from "../../../hooks/store" +import { selectStakeByStakingProvider } from "../../../store/staking" +import { + calculatePercenteage, + formatPercentage, +} from "../../../utils/percentage" +import shortenAddress from "../../../utils/shortenAddress" +import { formatTokenAmount } from "../../../utils/formatAmount" +import { ExplorerDataType } from "../../../utils/createEtherscanLink" +import { ExternalHref } from "../../../enums" +import { BaseModalProps } from "../../../types" +import { getStakingAppNameFromAddress } from "../../../utils/getStakingAppNameFromAddress" +import StakingTimeline from "../../StakingTimeline" + +export type StakingApplicationsAuthorizeProps = BaseModalProps & { + stakingProvider: string + authorizedStakingApplications: { + address: string + amount: string + txHash: string + }[] +} + +const StakingApplicationsAuthorizedBase: FC< + StakingApplicationsAuthorizeProps +> = ({ stakingProvider, authorizedStakingApplications, closeModal }) => { + const stake = useAppSelector((state) => + selectStakeByStakingProvider(state, stakingProvider) + ) + const navigate = useNavigate() + const onAuthorizeOtherApps = () => { + closeModal() + navigate(`/staking/${stakingProvider}/authorize`) + } + + return ( + <> + Step 2 Completed + + + + + Your authorization was successful! + + + + + Provider Address + {shortenAddress(stakingProvider)} + + + {authorizedStakingApplications.map((_) => ( + + + {`${getStakingAppNameFromAddress( + _.address + )} Authorization Amount`} + {`${formatTokenAmount(_.amount)} T (${formatPercentage( + calculatePercenteage(_.amount, stake?.totalInTStake) + )})`} + + + ))} + + +
+ You can authorize more apps, or continue to Step 3 to set up nodes. +
+ + You can adjust the authorization amount at any time from the{" "} + + Staking page + + . + +
+ + + {authorizedStakingApplications.length === 1 ? ( + <> + {" "} + transaction on Etherscan + + ) : ( + <> + View{" "} + {authorizedStakingApplications.map((_, index) => ( + + + {index + 1 === authorizedStakingApplications.length + ? " " + : " and "} + + ))} + on Etherscan + + )} + + +
+ + + + + ) +} + +export const StakingApplicationsAuthorized = withBaseModal( + StakingApplicationsAuthorizedBase +) diff --git a/src/components/Modal/StakingApplications/index.ts b/src/components/Modal/StakingApplications/index.ts new file mode 100644 index 000000000..b54a11be6 --- /dev/null +++ b/src/components/Modal/StakingApplications/index.ts @@ -0,0 +1,2 @@ +export { AuthorizeStakingApps } from "./AuthorizeStakingApps" +export { StakingApplicationsAuthorized } from "./StakingApplicationsAuthorized" diff --git a/src/components/StakingTimeline/index.tsx b/src/components/StakingTimeline/index.tsx index 20f560089..0c510c080 100644 --- a/src/components/StakingTimeline/index.tsx +++ b/src/components/StakingTimeline/index.tsx @@ -10,6 +10,7 @@ import { ChecklistGroup, FlowStep, FlowStepStatus, + StackProps, } from "@threshold-network/components" import { ExternalHref } from "../../enums" import ExternalLink from "../ExternalLink" @@ -120,7 +121,10 @@ export const PreSetupSteps: FC = () => { ) } -const StakingTimeline: FC = () => { +const StakingTimeline: FC<{ statuses?: FlowStepStatus[] } & StackProps> = ({ + statuses = [], + ...restProps +}) => { const STAKING_PROVIDER_URL = "someURL" const PROVIDER_ADDRESS_URL = "someURL" const BENEFICIARY_ADDRESS_URL = "someURL" @@ -128,56 +132,58 @@ const StakingTimeline: FC = () => { if (featureFlags.MULTI_APP_STAKING) { return ( - + Staking Timeline - - Enter the{" "} - - Provider - - ,{" "} - - Beneficiary - - , and{" "} - - Authorizer - {" "} - addresses. These will be automatically set to your wallet address. If - you want to use a Staking Provider, here is{" "} - - a list - - . - - - You can authorize 100% of your stake for each app. This amount can be - changed at any time. - - - Set up and run a node for any of the authorized applications. - + + + Enter the{" "} + + Provider + + ,{" "} + + Beneficiary + + , and{" "} + + Authorizer + {" "} + addresses. These will be automatically set to your wallet address. + If you want to use a Staking Provider, here is{" "} + + a list + + . + + + You can authorize 100% of your stake for each app. This amount can + be changed at any time. + + + Set up and run a node for any of the authorized applications. + + ) } return ( - + diff --git a/src/enums/externalHref.ts b/src/enums/externalHref.ts index 1421402b8..ba997e26e 100644 --- a/src/enums/externalHref.ts +++ b/src/enums/externalHref.ts @@ -19,4 +19,5 @@ export enum ExternalHref { ankr = "https://www.ankr.com/", p2pValidator = "https://p2p.org/", infStones = "https://infstones.com/", + setupNodes = "https://docs.threshold.network/", } diff --git a/src/enums/modal.ts b/src/enums/modal.ts index 01a92f52e..a55ea926b 100644 --- a/src/enums/modal.ts +++ b/src/enums/modal.ts @@ -22,4 +22,6 @@ export enum ModalType { UseDesktop = "USE_DESKTOP", DeauthorizeApplication = "DEAUTHORIZE_APPLICATION", SubmitStake = "SUBMIT_STAKE", + AuthorizeStakingApps = "AUTHORIZE_STAKING_APPS", + StakingApplicationsAuthorized = "STAKING_APPLICATIONS_AUTHORIZED", } diff --git a/src/hooks/staking-applications/index.ts b/src/hooks/staking-applications/index.ts index 50bcb755b..6c0420d76 100644 --- a/src/hooks/staking-applications/index.ts +++ b/src/hooks/staking-applications/index.ts @@ -4,3 +4,6 @@ export * from "./useStakingApplicationState" export * from "./useStakingAppMinAuthorizationAmount" export * from "./useStakingAppParameters" export * from "./useSubscribeToAuthorizationIncreasedEvent" +export * from "./useAuthorizeMultipleAppsTransaction" +export * from "./useIncreaseAuthorizationTransacion" +export * from "./useStakingApplicationAddress" diff --git a/src/hooks/staking-applications/useAuthorizeMultipleAppsTransaction.ts b/src/hooks/staking-applications/useAuthorizeMultipleAppsTransaction.ts new file mode 100644 index 000000000..e58bb1f52 --- /dev/null +++ b/src/hooks/staking-applications/useAuthorizeMultipleAppsTransaction.ts @@ -0,0 +1,74 @@ +import { useCallback } from "react" +import { useThreshold } from "../../contexts/ThresholdContext" +import { useSendTransactionFromFn } from "../../web3/hooks" +import { isSameETHAddress } from "../../web3/utils" +import { useStakingApplicationAddress } from "./useStakingApplicationAddress" +import { useModal } from "../useModal" +import { ModalType } from "../../enums" + +export const useAuthorizeMultipleAppsTransaction = () => { + const threshold = useThreshold() + const tbtcAppAddress = useStakingApplicationAddress("tbtc") + const randomBeaconAppAddress = useStakingApplicationAddress("randomBeacon") + const { openModal } = useModal() + + const { sendTransaction, status } = useSendTransactionFromFn( + threshold.staking.increaseAuthorization + ) + + const authorizeMultipleApps = useCallback( + async ( + applications: { + address: string + amount: string + }[], + stakingProvider: string + ) => { + try { + if (applications.length === 0) + throw new Error("No staking applications to authorize.") + + const includesOnlySupportedApps = applications.every( + (_) => + isSameETHAddress(_.address, tbtcAppAddress) || + isSameETHAddress(_.address, randomBeaconAppAddress) + ) + + if (!includesOnlySupportedApps) + throw new Error("Unsupported staking applications detected.") + + const successfullTxs: { + address: string + txHash: string + amount: string + }[] = [] + for (const stakingApp of applications) { + const tx = await sendTransaction( + stakingProvider, + stakingApp.address, + stakingApp.amount + ) + if (tx) { + successfullTxs.push({ ...stakingApp, txHash: tx.hash }) + } + } + if (successfullTxs.length > 0) { + openModal(ModalType.StakingApplicationsAuthorized, { + stakingProvider, + authorizedStakingApplications: successfullTxs, + }) + } + } catch (error) { + openModal(ModalType.TransactionFailed, { + error: + (error as Error)?.message || + "Error: Couldn't authorize applications", + isExpandableError: true, + }) + } + }, + [sendTransaction, randomBeaconAppAddress, tbtcAppAddress, openModal] + ) + + return { authorizeMultipleApps, status } +} diff --git a/src/hooks/staking-applications/useIncreaseAuthorizationTransacion.ts b/src/hooks/staking-applications/useIncreaseAuthorizationTransacion.ts new file mode 100644 index 000000000..204b4786e --- /dev/null +++ b/src/hooks/staking-applications/useIncreaseAuthorizationTransacion.ts @@ -0,0 +1,20 @@ +import { ContractTransaction } from "ethers" +import { useThreshold } from "../../contexts/ThresholdContext" +import { StakingAppName } from "../../store/staking-applications" +import { useSendTransactionFromFn } from "../../web3/hooks" +import { stakingAppNameToThresholdAppService } from "./useStakingAppContract" + +export const useIncreaseAuthorizationTransacion = ( + appName: StakingAppName, + onSuccess?: (tx: ContractTransaction) => void | Promise, + onError?: (error: any) => void | Promise +) => { + const threshold = useThreshold() + + return useSendTransactionFromFn( + threshold.multiAppStaking[stakingAppNameToThresholdAppService[appName]] + .increaseAuthorization, + onSuccess, + onError + ) +} diff --git a/src/hooks/staking-applications/useStakingAppContract.ts b/src/hooks/staking-applications/useStakingAppContract.ts index c30ed14c7..994036910 100644 --- a/src/hooks/staking-applications/useStakingAppContract.ts +++ b/src/hooks/staking-applications/useStakingAppContract.ts @@ -1,11 +1,16 @@ import { StakingAppName } from "../../store/staking-applications" import { useThreshold } from "../../contexts/ThresholdContext" -const appNameToAppService: Record = { +export const stakingAppNameToThresholdAppService: Record< + StakingAppName, + "ecdsa" | "randomBeacon" +> = { tbtc: "ecdsa", randomBeacon: "randomBeacon", } export const useStakingAppContract = (appName: StakingAppName) => { - return useThreshold().multiAppStaking[appNameToAppService[appName]].contract + return useThreshold().multiAppStaking[ + stakingAppNameToThresholdAppService[appName] + ].contract } diff --git a/src/hooks/staking-applications/useStakingApplicationAddress.ts b/src/hooks/staking-applications/useStakingApplicationAddress.ts new file mode 100644 index 000000000..e08df8aa3 --- /dev/null +++ b/src/hooks/staking-applications/useStakingApplicationAddress.ts @@ -0,0 +1,11 @@ +import { useThreshold } from "../../contexts/ThresholdContext" +import { StakingAppName } from "../../store/staking-applications" +import { AddressZero } from "../../web3/utils" +import { stakingAppNameToThresholdAppService } from "./useStakingAppContract" + +export const useStakingApplicationAddress = (appName: StakingAppName) => { + return ( + useThreshold().multiAppStaking[stakingAppNameToThresholdAppService[appName]] + ?.address ?? AddressZero + ) +} diff --git a/src/pages/Staking/AuthorizeStakingApps/AuthorizeApplicationsCardCheckbox/AppAuthorizationInfo.tsx b/src/pages/Staking/AuthorizeStakingApps/AuthorizeApplicationsCardCheckbox/AppAuthorizationInfo.tsx index 55c1972b5..3b51631f9 100644 --- a/src/pages/Staking/AuthorizeStakingApps/AuthorizeApplicationsCardCheckbox/AppAuthorizationInfo.tsx +++ b/src/pages/Staking/AuthorizeStakingApps/AuthorizeApplicationsCardCheckbox/AppAuthorizationInfo.tsx @@ -1,4 +1,4 @@ -import { InfoIcon } from "@chakra-ui/icons" +import { InfoIcon, CheckCircleIcon } from "@chakra-ui/icons" import { LabelSm, BoxLabel, @@ -7,17 +7,24 @@ import { Badge, StackProps, Icon, + BodyMd, + BodyLg, + H3, } from "@threshold-network/components" import { FC } from "react" import { formatPercentage } from "../../../../utils/percentage" import { IoAlertCircle } from "react-icons/all" +import InfoBox from "../../../../components/InfoBox" +import { formatTokenAmount } from "../../../../utils/formatAmount" export interface AppAuthorizationInfoProps extends StackProps { label: string percentageAuthorized: number aprPercentage: number slashingPercentage: number + isAuthorized: boolean isAuthorizationRequired?: boolean + authorizedStake: string } export const AppAuthorizationInfo: FC = ({ @@ -25,14 +32,19 @@ export const AppAuthorizationInfo: FC = ({ percentageAuthorized, aprPercentage, slashingPercentage, + isAuthorized, + authorizedStake, isAuthorizationRequired = false, + ...restProps }) => { return ( - + + {isAuthorized && } - {label} App - {formatPercentage(percentageAuthorized)} + {label} App -{" "} + {formatPercentage(percentageAuthorized, undefined, true)} {!isAuthorizationRequired && ( @@ -40,6 +52,11 @@ export const AppAuthorizationInfo: FC = ({ Authorization not required )} + {isAuthorizationRequired && isAuthorized && ( + + Authorized + + )} = ({ {`${formatPercentage(slashingPercentage, 0, true)}`} + {isAuthorizationRequired && isAuthorized && ( + <> + Total Authorized Balance + +

+ {formatTokenAmount(authorizedStake)} T +

+
+ + )}
) } diff --git a/src/pages/Staking/AuthorizeStakingApps/AuthorizeApplicationsCardCheckbox/index.tsx b/src/pages/Staking/AuthorizeStakingApps/AuthorizeApplicationsCardCheckbox/index.tsx index 6bb6b0953..1d8697dc7 100644 --- a/src/pages/Staking/AuthorizeStakingApps/AuthorizeApplicationsCardCheckbox/index.tsx +++ b/src/pages/Staking/AuthorizeStakingApps/AuthorizeApplicationsCardCheckbox/index.tsx @@ -7,27 +7,75 @@ import { Checkbox, GridItem, } from "@threshold-network/components" -import { FC } from "react" -import { TokenAmountForm } from "../../../../components/Forms" +import { FC, RefObject } from "react" +import { FormValues, TokenAmountForm } from "../../../../components/Forms" import { AppAuthorizationInfo } from "./AppAuthorizationInfo" import { formatTokenAmount } from "../../../../utils/formatAmount" -import { useThreshold } from "../../../../contexts/ThresholdContext" import { WeiPerEther } from "@ethersproject/constants" +import { useModal } from "../../../../hooks/useModal" +import { ModalType } from "../../../../enums" +import { StakingAppName } from "../../../../store/staking-applications" +import { FormikProps } from "formik" +import { useStakingApplicationAddress } from "../../../../hooks/staking-applications" export interface AppAuthDataProps { + stakingAppId: StakingAppName | "pre" label: string isAuthorized: boolean percentage: number isAuthRequired: boolean + authorizedStake: string } export interface AuthorizeApplicationsCardCheckboxProps extends BoxProps { appAuthData: AppAuthDataProps stakingProvider: string + totalInTStake: string onCheckboxClick: (app: AppAuthDataProps, isChecked: boolean) => void isSelected: boolean maxAuthAmount: string minAuthAmount: string + formRef?: RefObject> +} + +const gridTemplate = { + base: { + base: ` + "checkbox checkbox" + "app-info app-info" + "filter-tabs filter-tabs" + "token-amount-form token-amount-form" + `, + sm: ` + "checkbox app-info" + "checkbox filter-tabs" + "checkbox token-amount-form" + `, + md: ` + "checkbox app-info filter-tabs " + "checkbox app-info _ " + "checkbox token-amount-form token-amount-form" + "checkbox token-amount-form token-amount-form" + `, + }, + authorized: { + base: ` + "app-info app-info" + "filter-tabs filter-tabs" + "token-amount-form token-amount-form" + `, + sm: ` + "app-info app-info" + "filter-tabs filter-tabs" + "token-amount-form token-amount-form" + `, + md: ` + "app-info app-info filter-tabs " + "app-info app-info _ " + "token-amount-form token-amount-form token-amount-form" + "token-amount-form token-amount-form token-amount-form" + `, + }, } export const AuthorizeApplicationsCardCheckbox: FC< @@ -39,25 +87,43 @@ export const AuthorizeApplicationsCardCheckbox: FC< maxAuthAmount, minAuthAmount, stakingProvider, + totalInTStake, + formRef, ...restProps }) => { const collapsed = !appAuthData.isAuthRequired - const threshold = useThreshold() + const { openModal } = useModal() + const stakingAppAddress = useStakingApplicationAddress( + appAuthData.stakingAppId as StakingAppName + ) 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 (!appAuthData.isAuthorized) { + // We want to display different modals for the authroization and for the + // increase aturhoziation. + openModal(ModalType.AuthorizeStakingApps, { + stakingProvider, + totalInTStake, + applications: [ + { + appName: appAuthData.label, + authorizationAmount: tokenAmount, + address: stakingAppAddress, + }, + ], + }) + } else { + // TODO: Create a increase authroziation modal. + console.log("open increase authroziation modal") + } } if (collapsed) { return ( - { - onCheckboxClick(appAuthData, e.target.checked) - }} - /> + {!appAuthData.isAuthorized && ( + { + onCheckboxClick(appAuthData, e.target.checked) + }} + /> + )} { const { stakingProviderAddress } = useParams() const { account } = useWeb3React() const navigate = useNavigate() + const { openModal } = useModal() + const tbtcAppFormRef = useRef>(null) + const randomBeaconAppFormRef = useRef>(null) + const preAppFormRef = useRef>(null) + const stakinAppNameToFormRef: Record< + AppAuthDataProps["stakingAppId"], + RefObject> + > = { + tbtc: tbtcAppFormRef, + randomBeacon: randomBeaconAppFormRef, + pre: preAppFormRef, + } + + const tbtcAppAddress = useStakingApplicationAddress("tbtc") + const randomBeaconAddress = useStakingApplicationAddress("randomBeacon") + const stakinAppNameToAddress: Record< + AppAuthDataProps["stakingAppId"], + string + > = { + tbtc: tbtcAppAddress, + randomBeacon: randomBeaconAddress, + pre: AddressZero, + } useEffect(() => { if (!isAddress(stakingProviderAddress!)) navigate(`/staking`) @@ -60,29 +88,76 @@ const AuthorizeStakingAppsPage: FC = () => { ? BigNumber.from(stake?.totalInTStake).isZero() : false - const appsAuthData = { + const appsAuthData: { + [appName: string]: AppAuthDataProps & { address?: string } + } = { tbtc: { + stakingAppId: "tbtc", + address: tbtcAppAddress, label: "tBTC", isAuthorized: tbtcApp.isAuthorized, percentage: tbtcApp.percentage, + authorizedStake: tbtcApp.authorizedStake, isAuthRequired: true, }, randomBeacon: { + stakingAppId: "randomBeacon", + address: randomBeaconAddress, label: "Random Beacon", isAuthorized: randomBeaconApp.isAuthorized, percentage: randomBeaconApp.percentage, + authorizedStake: randomBeaconApp.authorizedStake, isAuthRequired: true, }, pre: { + stakingAppId: "pre", label: "PRE", isAuthorized: false, percentage: 0, + authorizedStake: stake.totalInTStake, isAuthRequired: false, }, } - const onAuthorizeApps = () => { - console.log("Authorize Apps!!", selectedApps) + const isAppSelected = (stakingAppName: AppAuthDataProps["stakingAppId"]) => { + return selectedApps.map((app) => app.stakingAppId).includes(stakingAppName) + } + + const onAuthorizeApps = async () => { + const isTbtcSelected = isAppSelected("tbtc") + const isRandomBeaconSelected = isAppSelected("randomBeacon") + + if (isTbtcSelected) { + await tbtcAppFormRef.current?.validateForm() + tbtcAppFormRef.current?.setTouched({ tokenAmount: true }, false) + } + if (isRandomBeaconSelected) { + await randomBeaconAppFormRef.current?.validateForm() + randomBeaconAppFormRef.current?.setTouched({ tokenAmount: true }, false) + } + if ( + (isRandomBeaconSelected && + isTbtcSelected && + tbtcAppFormRef.current?.isValid && + randomBeaconAppFormRef.current?.isValid) || + (isTbtcSelected && + !isRandomBeaconSelected && + tbtcAppFormRef.current?.isValid) || + (isRandomBeaconSelected && + !isTbtcSelected && + randomBeaconAppFormRef.current?.isValid) + ) { + openModal(ModalType.AuthorizeStakingApps, { + stakingProvider: stakingProviderAddress!, + totalInTStake: stake.totalInTStake, + applications: selectedApps.map((_) => ({ + appName: _.label, + address: stakinAppNameToAddress[_.stakingAppId], + authorizationAmount: + stakinAppNameToFormRef[_.stakingAppId].current?.values.tokenAmount, + })), + }) + } } const [selectedApps, setSelectedApps] = useState([]) @@ -121,21 +196,23 @@ const AuthorizeStakingAppsPage: FC = () => { app.label).includes("tBTC")} + isSelected={isAppSelected("tbtc")} maxAuthAmount={stake.totalInTStake} minAuthAmount={tbtcMinAuthAmount} stakingProvider={stakingProviderAddress!} /> app.label) - .includes("Random Beacon")} + isSelected={isAppSelected("randomBeacon")} maxAuthAmount={stake.totalInTStake} minAuthAmount={randomBeaconMinAuthAmount} stakingProvider={stakingProviderAddress!} @@ -143,8 +220,9 @@ const AuthorizeStakingAppsPage: FC = () => { app.label).includes("PRE")} + isSelected={isAppSelected("pre")} maxAuthAmount={stake.totalInTStake} minAuthAmount={"0"} stakingProvider={stakingProviderAddress!} diff --git a/src/pages/Staking/StakeCard/StakeAddressInfo/index.tsx b/src/pages/Staking/StakeCard/StakeAddressInfo/index.tsx index ac41bd08b..06e4ccbe6 100644 --- a/src/pages/Staking/StakeCard/StakeAddressInfo/index.tsx +++ b/src/pages/Staking/StakeCard/StakeAddressInfo/index.tsx @@ -1,12 +1,13 @@ import { FC } from "react" -import { BoxLabel, Flex } from "@threshold-network/components" +import { BoxLabel, Flex, FlexProps } from "@threshold-network/components" import { CopyAddressToClipboard } from "../../../../components/CopyToClipboard" -const StakeAddressInfo: FC<{ stakingProvider: string }> = ({ +const StakeAddressInfo: FC<{ stakingProvider: string } & FlexProps> = ({ stakingProvider, + ...restProps }) => { return ( - + Provider address diff --git a/src/pages/Staking/StakeCard/StakeApplications/AuthorizeApplicationRow.tsx b/src/pages/Staking/StakeCard/StakeApplications/AuthorizeApplicationRow.tsx index 3e55a1da5..2022b57f9 100644 --- a/src/pages/Staking/StakeCard/StakeApplications/AuthorizeApplicationRow.tsx +++ b/src/pages/Staking/StakeCard/StakeApplications/AuthorizeApplicationRow.tsx @@ -48,7 +48,7 @@ const AuthorizeApplicationRow: FC = ({ colorScheme="brand" borderRadius={50} /> - {formatPercentage(percentage)} + {formatPercentage(percentage, undefined, true)} ) : (