diff --git a/.env b/.env.example similarity index 88% rename from .env rename to .env.example index 6360f43f5..bdce31fe6 100644 --- a/.env +++ b/.env.example @@ -18,4 +18,6 @@ REACT_APP_ELECTRUM_HOST=$ELECTRUM_HOST REACT_APP_ELECTRUM_PORT=$ELECTRUM_PORT REACT_APP_MOCK_BITCOIN_CLIENT=true -REACT_APP_WALLET_CONNECT_PROJECT_ID=$WALLET_CONNECT_PROJECT_ID \ No newline at end of file +REACT_APP_WALLET_CONNECT_PROJECT_ID=$WALLET_CONNECT_PROJECT_ID + +REACT_APP_TACO_DOMAIN=dashboard diff --git a/.env.production b/.env.production index 6a8c662b3..58c54a943 100644 --- a/.env.production +++ b/.env.production @@ -20,3 +20,4 @@ REACT_APP_ELECTRUM_PORT=$ELECTRUM_PORT REACT_APP_MOCK_BITCOIN_CLIENT=false REACT_APP_WALLET_CONNECT_PROJECT_ID=$WALLET_CONNECT_PROJECT_ID +REACT_APP_TACO_DOMAIN=mainnet \ No newline at end of file diff --git a/.env.test b/.env.test index 7fbba3d92..d43a646da 100644 --- a/.env.test +++ b/.env.test @@ -1,2 +1,3 @@ REACT_APP_SUPPORTED_CHAIN_ID=1337 REACT_APP_MULTICALL_ADDRESS=0x086813525A7dC7dafFf015Cdf03896Fd276eab60 +REACT_APP_TACO_DOMAIN=dashboard \ No newline at end of file diff --git a/.gitignore b/.gitignore index 44bc97aeb..94d1c1a1c 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ cypress/plugins # Yarn node_modules/ yarn-error.log +.env +.cosine/ diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 000000000..d359be280 --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,33 @@ +# Use the specified image +FROM node:18-buster-slim + +# Set the working directory +WORKDIR /app + +# Install dependencies +RUN apt-get update && apt-get install -y python3 make g++ git openssh-client ca-certificates && \ + git config --global url."https://".insteadOf git:// && \ + rm -rf /var/lib/apt/lists/* && \ + apt-get clean + +# Set the environment variables +ENV PYTHON=/usr/bin/python3 +ENV NODE_OPTIONS=--max_old_space_size=3072 + +# Copy package files and install node modules +COPY package*.json yarn.lock ./ +RUN npm install -g node-gyp +RUN yarn install --ignore-scripts +RUN yarn upgrade @keep-network/coverage-pools@sepolia \ + @keep-network/ecdsa@sepolia \ + @keep-network/keep-core@sepolia \ + @keep-network/keep-ecdsa@sepolia \ + @keep-network/random-beacon@sepolia \ + @keep-network/tbtc@sepolia \ + @keep-network/tbtc-v2@sepolia \ + @threshold-network/coverage-pools@npm:@keep-network/coverage-pools@sepolia \ + @threshold-network/solidity-contracts@sepolia +RUN yarn run postinstall + +# Expose port 3000 +EXPOSE 3000 diff --git a/README.md b/README.md index ddf7109d3..5e63db283 100644 --- a/README.md +++ b/README.md @@ -112,3 +112,37 @@ The following procedure allows to deploy T token dashboard to production: approval of someone else from the development team. 5. Once the release action is approved, the new version is automatically deployed to `dashboard.threshold.network`. + +## Local Development + +Update `.env` to contain: + +``` +REACT_APP_SUPPORTED_CHAIN_ID=11155111 +REACT_APP_ETH_HOSTNAME_HTTP=https://sepolia.infura.io/v3/ +REACT_APP_ETH_HOSTNAME_WS=wss://sepolia.infura.io/v3/ +REACT_APP_MULTICALL_ADDRESS=$MULTICALL_ADDRESS + +REACT_APP_FEATURE_FLAG_TBTC_V2=true +REACT_APP_FEATURE_FLAG_TBTC_V2_REDEMPTION=true +REACT_APP_FEATURE_FLAG_MULTI_APP_STAKING=true +REACT_APP_FEATURE_FLAG_FEEDBACK_MODULE=false +REACT_APP_FEATURE_FLAG_POSTHOG=false +REACT_APP_FEATURE_FLAG_SENTRY=$SENTRY_SUPPORT +REACT_APP_SENTRY_DSN=$SENTRY_DSN + +REACT_APP_ELECTRUM_PROTOCOL=wss +REACT_APP_ELECTRUM_HOST=electrumx-server.test.tbtc.network +REACT_APP_ELECTRUM_PORT=8443 +REACT_APP_MOCK_BITCOIN_CLIENT=false + +REACT_APP_WALLET_CONNECT_PROJECT_ID=$WALLET_CONNECT_PROJECT_ID + +REACT_APP_TACO_DOMAIN=dashboard +``` + +Then build the docker container and run the dashboard: + +```bash +docker-compose up --build +``` diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..fe1d639ea --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +version: "3.8" + +services: + threshold-dashboard: + container_name: threshold-dashboard + working_dir: /app + environment: + - PYTHON=/usr/bin/python3 + - NODE_OPTIONS=--max_old_space_size=3072 + ports: + - "3000:3000" + volumes: + - .:/app # Bind mount the current directory to /app in the container + - /app/node_modules # This will prevent node_modules from being overwritten by the local volume + command: bash -c "yarn format:fix && yarn start" + build: + context: . + dockerfile: Dockerfile.dev diff --git a/package.json b/package.json index dfe7ee063..5cc7eb930 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@ledgerhq/wallet-api-client-react": "^1.1.1", "@reduxjs/toolkit": "^1.6.1", "@rehooks/local-storage": "^2.4.4", + "@nucypher/nucypher-contracts": "0.13.0", "@sentry/react": "^7.33.0", "@sentry/tracing": "^7.33.0", "@testing-library/jest-dom": "^5.11.4", diff --git a/src/components/Modal/DeauthorizeApplicationModal/InititateDeauthorization.tsx b/src/components/Modal/DeauthorizeApplicationModal/InititateDeauthorization.tsx index 195e7a3f1..175f98ca3 100644 --- a/src/components/Modal/DeauthorizeApplicationModal/InititateDeauthorization.tsx +++ b/src/components/Modal/DeauthorizeApplicationModal/InititateDeauthorization.tsx @@ -119,20 +119,24 @@ const InitiateDeauthorization: FC< - You must wait a 45 day cooldown to then confirm the deauthorization. - This is 1 transaction. + You must wait a {stakingAppName === "taco" ? "6 month" : "45 day"}{" "} + cooldown to then confirm the deauthorization. This is 1 transaction. - Take note! In this 45 day cooldown period, you cannot increase or - decrease your authorization. As a measure of security for the entire - network, in the event of slashing you will be slashed based on your - initial amount. + Take note! In this {stakingAppName === "taco" + ? "6 month" + : "45 day"}{" "} + cooldown period, you cannot increase or decrease your authorization. + As a measure of security for the entire network, in the event of + slashing you will be slashed based on your initial amount. diff --git a/src/components/Modal/MapOperatorToStakingProviderConfirmationModal/index.tsx b/src/components/Modal/MapOperatorToStakingProviderConfirmationModal/index.tsx index 69a944d3b..002235121 100644 --- a/src/components/Modal/MapOperatorToStakingProviderConfirmationModal/index.tsx +++ b/src/components/Modal/MapOperatorToStakingProviderConfirmationModal/index.tsx @@ -11,9 +11,10 @@ import { import { AddressZero } from "@ethersproject/constants" import { BodyLg, BodyMd, H5, LabelSm } from "@threshold-network/components" import { useWeb3React } from "@web3-react/core" -import { FC, useCallback } from "react" +import { FC, useCallback, useState } from "react" import { ModalType } from "../../../enums" import { useRegisterMultipleOperatorsTransaction } from "../../../hooks/staking-applications/useRegisterMultipleOperatorsTransaction" +import { useBondOperatorTransaction } from "../../../hooks/staking-applications/useBondOperatorTransaction" import { useRegisterOperatorTransaction } from "../../../hooks/staking-applications/useRegisterOperatorTransaction" import { useAppDispatch } from "../../../hooks/store" import { useModal } from "../../../hooks/useModal" @@ -23,6 +24,7 @@ import { BaseModalProps } from "../../../types" import InfoBox from "../../InfoBox" import withBaseModal from "../withBaseModal" import { OnSuccessCallback } from "../../../web3/hooks" +import { ApplicationForOperatorMapping } from "../MapOperatorToStakingProviderModal" const OperatorMappingConfirmation: FC< BoxProps & { appName: string; operator: string; stakingProvider: string } @@ -52,16 +54,9 @@ const OperatorMappingConfirmation: FC< const MapOperatorToStakingProviderConfirmationModal: FC< BaseModalProps & { - operator: string - isOperatorMappedOnlyInTbtc: boolean - isOperatorMappedOnlyInRandomBeacon: boolean + applications: ApplicationForOperatorMapping[] } -> = ({ - operator, - isOperatorMappedOnlyInTbtc, - isOperatorMappedOnlyInRandomBeacon, - closeModal, -}) => { +> = ({ applications, closeModal }) => { const { account } = useWeb3React() const { registerMultipleOperators } = useRegisterMultipleOperatorsTransaction() @@ -71,57 +66,77 @@ const MapOperatorToStakingProviderConfirmationModal: FC< const onSuccess = useCallback( (receipt) => { openModal(ModalType.MapOperatorToStakingProviderSuccess, { - transactions: [ - { - txHash: receipt.transactionHash, - application: { - appName: isOperatorMappedOnlyInRandomBeacon - ? "tbtc" - : "randomBeacon", - operator, - stakingProvider: account, - }, + transactions: applications.map((app) => ({ + txHash: receipt.transactionHash, + application: { + appName: app.appName, + operator: app.operator, + stakingProvider: app.stakingProvider, }, - ], + })), }) }, - [openModal, operator, account] + [openModal, applications] ) + const [errorMessage, setErrorMessage] = useState("") + const onError = useCallback((error: Error) => { + setErrorMessage(error.message) + }, []) const { sendTransaction: registerOperatorTbtc } = - useRegisterOperatorTransaction("tbtc", onSuccess) + useRegisterOperatorTransaction( + "tbtc", + (receipt) => onSuccess(receipt), + onError + ) const { sendTransaction: registerOperatorRandomBeacon } = - useRegisterOperatorTransaction("randomBeacon", onSuccess) + useRegisterOperatorTransaction( + "randomBeacon", + (receipt) => onSuccess(receipt), + onError + ) + const { sendTransaction: registerOperatorTaco } = useBondOperatorTransaction( + "taco", + (receipt) => onSuccess(receipt), + onError + ) const submitMappingOperator = async () => { - if (isOperatorMappedOnlyInRandomBeacon) { - const tx = await registerOperatorTbtc(operator) - if (!tx) { - openModal(ModalType.TransactionFailed, { - error: new Error( - "Transaction rejected. You are required to map the Operator Address for both apps." - ), - closeModal: () => { - closeModal() - dispatch(mapOperatorToStakingProviderModalClosed()) - }, - }) + if (!account) { + throw new Error(`Wallet not connected`) + } + const transactions = [] + for (const app of applications) { + let transaction + switch (app.appName) { + case "tbtc": + transaction = await registerOperatorTbtc(app.operator) + break + case "randomBeacon": + transaction = await registerOperatorRandomBeacon(app.operator) + break + case "taco": + transaction = await registerOperatorTaco(account, app.operator) + break + default: + throw new Error(`Unsupported app name: ${app.appName}`) } - } else if (isOperatorMappedOnlyInTbtc) { - const tx = await registerOperatorRandomBeacon(operator) - if (!tx) { + if (!transaction) { openModal(ModalType.TransactionFailed, { error: new Error( - "Transaction rejected. You are required to map the Operator Address for both apps." + errorMessage + ? `Transaction rejected with error: ${errorMessage}` + : `Transaction rejected. You are required to map the Operator Address for ${app.appName}.` ), + isExpandableError: true, closeModal: () => { closeModal() dispatch(mapOperatorToStakingProviderModalClosed()) }, }) + return } - } else { - await registerMultipleOperators(operator) + transactions.push(transaction) } } @@ -135,26 +150,19 @@ const MapOperatorToStakingProviderConfirmationModal: FC< This will require{" "} - {isOperatorMappedOnlyInRandomBeacon || isOperatorMappedOnlyInTbtc - ? "1 transaction" - : "2 transactions"} + {applications.filter((app) => app.operator).length + + " transaction(s)"} . Each mapping is one transaction - {!isOperatorMappedOnlyInTbtc && ( - - )} - {!isOperatorMappedOnlyInRandomBeacon && ( + {applications.map((app, index) => ( - )} + ))} - diff --git a/src/components/Modal/NewStakerAuthorizeStakingApplicationModal/NewStakerAuthorizationForm.tsx b/src/components/Modal/NewStakerAuthorizeStakingApplicationModal/NewStakerAuthorizationForm.tsx index cd49f3564..84a1fd952 100644 --- a/src/components/Modal/NewStakerAuthorizeStakingApplicationModal/NewStakerAuthorizationForm.tsx +++ b/src/components/Modal/NewStakerAuthorizeStakingApplicationModal/NewStakerAuthorizationForm.tsx @@ -22,6 +22,8 @@ export type FormValues = { isTbtcChecked: boolean randomBeaconAmountToAuthorize: string | number isRandomBeaconChecked: boolean + tacoAmountToAuthorize: string | number + isTacoChecked: boolean } export interface AuthInputConstraints { @@ -33,6 +35,7 @@ interface Props { onSubmitForm: (values: FormValues) => void tbtcInputConstraints: AuthInputConstraints randomBeaconInputConstraints: AuthInputConstraints + tacoInputConstraints: AuthInputConstraints } export const formikWrapper = withFormik({ @@ -44,6 +47,8 @@ export const formikWrapper = withFormik({ isTbtcChecked: true, randomBeaconAmountToAuthorize: props.randomBeaconInputConstraints.max, isRandomBeaconChecked: true, + tacoAmountToAuthorize: props.tacoInputConstraints.max, + isTacoChecked: true, }), validate: (values, props) => { const errors: FormikErrors = {} @@ -59,6 +64,12 @@ export const formikWrapper = withFormik({ props.randomBeaconInputConstraints.max.toString(), props.randomBeaconInputConstraints.min.toString() ) + + errors.tacoAmountToAuthorize = validateAmountInRange( + values?.tacoAmountToAuthorize?.toString(), + props.tacoInputConstraints.max.toString(), + props.tacoInputConstraints.min.toString() + ) return getErrorsObj(errors) }, displayName: "AuthorizationForm", @@ -67,11 +78,13 @@ export const formikWrapper = withFormik({ const NewStakerAuthorizationForm: FC> = ({ tbtcInputConstraints, randomBeaconInputConstraints, + tacoInputConstraints, handleSubmit, }) => { const { closeModal } = useModal() const [, { value: isTbtcChecked }] = useField("isTbtcChecked") const [, { value: isRandomBeaconChecked }] = useField("isRandomBeaconChecked") + const [, { value: isTacoChecked }] = useField("isTacoChecked") const bothAppsChecked = isTbtcChecked && isRandomBeaconChecked return ( @@ -104,21 +117,26 @@ const NewStakerAuthorizationForm: FC> = ({ )} + - - - PRE - - Authorization not required - - - + + + + ) +} + +export default withBaseModal(TACoCommitmentModal) +export * from "./SuccessModal" diff --git a/src/components/StakingApplicationOperationIcon/index.tsx b/src/components/StakingApplicationOperationIcon/index.tsx index 995d53e4b..79cf72a27 100644 --- a/src/components/StakingApplicationOperationIcon/index.tsx +++ b/src/components/StakingApplicationOperationIcon/index.tsx @@ -4,6 +4,8 @@ import randomBeaconIncrease from "../../static/images/RandomBeaconIncrease.png" import randomBeaconDecrease from "../../static/images/RandomBeaconDecrease.png" import tbtcIncrease from "../../static/images/TbtcIncrease.png" import tbtcDecrease from "../../static/images/TbtcDecrease.png" +import tacoIncrease from "../../static/images/TACoIncrease.png" +import tacoDecrease from "../../static/images/TACoDecrease.png" import { StakingAppName } from "../../store/staking-applications" type Operation = "increase" | "decrease" @@ -17,6 +19,10 @@ const iconMap: Record> = { increase: randomBeaconIncrease, decrease: randomBeaconDecrease, }, + taco: { + increase: tacoIncrease, + decrease: tacoDecrease, + }, } const StakingApplicationOperationIcon: FC< diff --git a/src/components/StakingTimeline/index.tsx b/src/components/StakingTimeline/index.tsx index a9e32a79f..99d0ece35 100644 --- a/src/components/StakingTimeline/index.tsx +++ b/src/components/StakingTimeline/index.tsx @@ -31,7 +31,7 @@ export const StakingDepositStepsNonMAS: FC = () => { These will be automatically set up to your wallet address. If you want to use a Staking Provider check{" "} - + this @@ -89,9 +89,9 @@ export const LegacyStakesDepositSteps: FC = () => { size="sm" > - For each stake, there are three applications available. PRE does not - require authorization. To authorize tBTC and Random Beacon, go to - the Staking Page and select “Configure + For each stake, there are three applications available. To authorize + tBTC, Random Beacon, and TACo, go to the{" "} + Staking Page and select “Configure Stake”. @@ -141,40 +141,40 @@ export const LegacyStakesDepositSteps: FC = () => { }, ]} /> - + ) } -export const PreSetupSteps: FC = () => { +export const TacoSetupSteps: FC = () => { return ( - You will need to run a PRE node to get rewards. If you don’t have + You will need to run a TACo node to get rewards. If you don’t have one, learn how to do it here{" "} - + here , or contact{" "} - + a staking provider ), }, { - itemId: "run_a_pre_node__1", - itemTitle: "PRE Operator address", + itemId: "run_a_taco_node__1", + itemTitle: "TACo Operator address", itemSubTitle: ( - Make sure you add your PRE Operator address{" "} - + Make sure you add your TACo Operator address{" "} + here {" "} to gain rewards. @@ -216,12 +216,26 @@ const StakingTimeline: FC<{ statuses?: FlowStepStatus[] } & StackProps> = ({ status={statuses[1] ?? FlowStepStatus.inactive} isDescriptionArrowHidden > - - For each stake, there are three applications available. PRE does not - require authorization. To authorize tBTC and Random Beacon, go to - the Staking page and select “Configure - Stake”. - + For each stake, there are three applications available. To authorize{" "} + + tBTC + + ,{" "} + + Random Beacon + + , and{" "} + + TACo + + , go to the Staking page and select + “Configure Stake”. = ({ - + ) diff --git a/src/contexts/StakeCardContext.tsx b/src/contexts/StakeCardContext.tsx index d8d4ecd4a..0b724226e 100644 --- a/src/contexts/StakeCardContext.tsx +++ b/src/contexts/StakeCardContext.tsx @@ -5,7 +5,6 @@ interface StakeCardContext { canTopUpKepp: boolean canTopUpNu: boolean hasLegacyStakes: boolean - isPRESet: boolean } export const StakeCardContext = createContext( diff --git a/src/enums/externalHref.ts b/src/enums/externalHref.ts index 96b2cb2cb..1a2295fb0 100644 --- a/src/enums/externalHref.ts +++ b/src/enums/externalHref.ts @@ -3,8 +3,8 @@ export enum ExternalHref { thresholdDiscord = "https://discord.gg/WXK9PC6SRF", metamaskHomePage = "https://metamask.io/", stakingContractLeanMore = "https://github.com/threshold-network/solidity-contracts/issues/53", - preNodeSetup = "https://docs.nucypher.com/en/latest/pre_application/running_a_node.html", - preStakingProvidersList = "https://docs.nucypher.com/en/latest/pre_application/node_providers.html", + tacoNodeSetup = "https://docs.threshold.network/staking-and-running-a-node/running-a-node/self-managed/taco-node-setup", + tacoStakingProvidersList = "https://docs.threshold.network/staking-and-running-a-node/running-a-node/staking-providers", exchangeRateLearnMore = "https://blog.threshold.network/threshold-launch/", keepDapp = "https://dashboard.keep.network/", keepDappAuthPage = "https://dashboard.keep.network/applications/threshold", @@ -20,6 +20,7 @@ export enum ExternalHref { infStones = "https://infstones.com/", setupNodes = "https://docs.threshold.network/guides/threshold-applications/tbtc-v2-client-setup", tbtcNodeDocs = "https://docs.threshold.network/guides/threshold-applications/tbtc-v2-client-setup", + tacoNodeDocs = "https://docs.threshold.network/staking-and-running-a-node/running-a-node/self-managed/taco-node-setup", randomBeaconNodeDocs = "https://docs.threshold.network/guides/threshold-applications/tbtc-v2-client-setup", analyticsReports = "SOME_URL", btcRecoveryAddress = "https://github.com/keep-network/tbtc-v2/blob/main/docs/rfc/rfc-1.adoc", diff --git a/src/enums/modal.ts b/src/enums/modal.ts index 0095f8a96..bd03dc5fe 100644 --- a/src/enums/modal.ts +++ b/src/enums/modal.ts @@ -36,4 +36,6 @@ export enum ModalType { FeedbackSubmission = "FEEDBACK_SUBMIT", GenerateNewDepositAddress = "TBTC_GENERATE_NEW_DEPOSIT_ADDRESS", InitiateUnminting = "INITIATE_UNMINTING", + TACoCommitment = "TACO_COMMITMENT", + TACoCommitmentSuccess = "TACO_COMMITMENT_SUCCESS", } diff --git a/src/hooks/staking-applications/useAuthorizeMultipleAppsTransaction.ts b/src/hooks/staking-applications/useAuthorizeMultipleAppsTransaction.ts index bbedeeddb..77fc0cb60 100644 --- a/src/hooks/staking-applications/useAuthorizeMultipleAppsTransaction.ts +++ b/src/hooks/staking-applications/useAuthorizeMultipleAppsTransaction.ts @@ -10,6 +10,7 @@ export const useAuthorizeMultipleAppsTransaction = () => { const threshold = useThreshold() const tbtcAppAddress = useStakingApplicationAddress("tbtc") const randomBeaconAppAddress = useStakingApplicationAddress("randomBeacon") + const tacoAppAddress = useStakingApplicationAddress("taco") const { openModal } = useModal() const { sendTransaction, status } = useSendTransactionFromFn( @@ -31,7 +32,8 @@ export const useAuthorizeMultipleAppsTransaction = () => { const includesOnlySupportedApps = applications.every( (_) => isSameETHAddress(_.address, tbtcAppAddress) || - isSameETHAddress(_.address, randomBeaconAppAddress) + isSameETHAddress(_.address, randomBeaconAppAddress) || + isSameETHAddress(_.address, tacoAppAddress) ) if (!includesOnlySupportedApps) @@ -70,7 +72,13 @@ export const useAuthorizeMultipleAppsTransaction = () => { }) } }, - [sendTransaction, randomBeaconAppAddress, tbtcAppAddress, openModal] + [ + sendTransaction, + randomBeaconAppAddress, + tbtcAppAddress, + tacoAppAddress, + openModal, + ] ) return { authorizeMultipleApps, status } diff --git a/src/hooks/staking-applications/useBondOperatorTransaction.ts b/src/hooks/staking-applications/useBondOperatorTransaction.ts new file mode 100644 index 000000000..7ee43a7d5 --- /dev/null +++ b/src/hooks/staking-applications/useBondOperatorTransaction.ts @@ -0,0 +1,23 @@ +import { useThreshold } from "../../contexts/ThresholdContext" +import { StakingAppName } from "../../store/staking-applications" +import { + OnErrorCallback, + OnSuccessCallback, + useSendTransactionFromFn, +} from "../../web3/hooks" +import { stakingAppNameToThresholdAppService } from "./useStakingAppContract" + +export const useBondOperatorTransaction = ( + appName: StakingAppName, + onSuccess?: OnSuccessCallback, + onError?: OnErrorCallback +) => { + const threshold = useThreshold() + + return useSendTransactionFromFn( + threshold.multiAppStaking[stakingAppNameToThresholdAppService[appName]] + .bondOperator, + onSuccess, + onError + ) +} diff --git a/src/hooks/staking-applications/useRegisterMultipleOperatorsTransaction.ts b/src/hooks/staking-applications/useRegisterMultipleOperatorsTransaction.ts index 97443947c..0dca66c08 100644 --- a/src/hooks/staking-applications/useRegisterMultipleOperatorsTransaction.ts +++ b/src/hooks/staking-applications/useRegisterMultipleOperatorsTransaction.ts @@ -1,4 +1,5 @@ import { useCallback } from "react" +import { useBondOperatorTransaction } from "./useBondOperatorTransaction" import { useRegisterOperatorTransaction } from "./useRegisterOperatorTransaction" import { useModal } from "../useModal" import { ModalType } from "../../enums" @@ -7,14 +8,14 @@ import { OperatorMappedSuccessTx } from "../../components/Modal/MapOperatorToSta import { mapOperatorToStakingProviderModalClosed } from "../../store/modal" import { useAppDispatch, useAppSelector } from "../store" import { selectMappedOperators } from "../../store/account" +import { isEmptyOrZeroAddress } from "../../web3/utils" export const useRegisterMultipleOperatorsTransaction = () => { const { mappedOperatorTbtc, mappedOperatorRandomBeacon, - isOperatorMappedInBothApps, - isOperatorMappedOnlyInRandomBeacon, - isOperatorMappedOnlyInTbtc, + mappedOperatorTaco, + isOperatorMappedInAllApps, } = useAppSelector((state) => selectMappedOperators(state)) const { account } = useWeb3React() const { openModal, closeModal } = useModal() @@ -28,6 +29,10 @@ export const useRegisterMultipleOperatorsTransaction = () => { sendTransaction: sendRegisterOperatorTransactionRandomBeacon, status: registerOperatorRandomBeaconStatus, } = useRegisterOperatorTransaction("randomBeacon") + const { + sendTransaction: sendRegisterOperatorTransactionTaco, + status: registerOperatorTacoStatus, + } = useBondOperatorTransaction("taco") const registerMultipleOperators = useCallback( async (operator: string) => { @@ -36,16 +41,17 @@ export const useRegisterMultipleOperatorsTransaction = () => { throw new Error("Connect to the staking provider account first!") } - if (isOperatorMappedInBothApps) - throw new Error("Both apps already have mapped operator!") + if (isOperatorMappedInAllApps) + throw new Error("All apps already have mapped operator!") - if (isOperatorMappedOnlyInRandomBeacon) + if (!isEmptyOrZeroAddress(mappedOperatorRandomBeacon)) throw new Error("Random beacon app already has mapped operator!") - if (isOperatorMappedOnlyInTbtc) - throw new Error("Tbtc app already have mapped operator!") + if (!isEmptyOrZeroAddress(mappedOperatorTbtc)) + throw new Error("Tbtc app already has mapped operator!") - // TODO: might also add a check if the operator is already used by another staking provider + if (!isEmptyOrZeroAddress(mappedOperatorTaco)) + throw new Error("TACo app already has mapped operator!") const successfullTxs: OperatorMappedSuccessTx[] = [] const tbtcReceipt = await sendRegisterOperatorTransactionTbtc(operator) @@ -71,11 +77,25 @@ export const useRegisterMultipleOperatorsTransaction = () => { txHash: randomBeaconReceipt.transactionHash, }) } + const tacoReceipt = await sendRegisterOperatorTransactionTaco( + account, + operator + ) + if (tacoReceipt) { + successfullTxs.push({ + application: { + appName: "taco", + operator: operator, + stakingProvider: account, + }, + txHash: tacoReceipt.transactionHash, + }) + } - if (successfullTxs.length < 2) { + if (successfullTxs.length < 3) { openModal(ModalType.TransactionFailed, { error: new Error( - "Transaction rejected. You are required to map the Operator Address for both apps." + "Transaction rejected. You are required to map the Operator Address for all apps." ), closeModal: () => { closeModal() @@ -84,7 +104,7 @@ export const useRegisterMultipleOperatorsTransaction = () => { }) } - if (successfullTxs.length === 2) { + if (successfullTxs.length === 3) { openModal(ModalType.MapOperatorToStakingProviderSuccess, { transactions: successfullTxs, }) @@ -106,8 +126,10 @@ export const useRegisterMultipleOperatorsTransaction = () => { account, mappedOperatorRandomBeacon, mappedOperatorTbtc, + mappedOperatorTaco, sendRegisterOperatorTransactionTbtc, sendRegisterOperatorTransactionRandomBeacon, + sendRegisterOperatorTransactionTaco, openModal, ] ) @@ -116,5 +138,6 @@ export const useRegisterMultipleOperatorsTransaction = () => { registerMultipleOperators, registerOperatorTbtcStatus, registerOperatorRandomBeaconStatus, + registerOperatorTacoStatus, } } diff --git a/src/hooks/staking-applications/useStakingAppContract.ts b/src/hooks/staking-applications/useStakingAppContract.ts index 994036910..f80845305 100644 --- a/src/hooks/staking-applications/useStakingAppContract.ts +++ b/src/hooks/staking-applications/useStakingAppContract.ts @@ -3,10 +3,11 @@ import { useThreshold } from "../../contexts/ThresholdContext" export const stakingAppNameToThresholdAppService: Record< StakingAppName, - "ecdsa" | "randomBeacon" + "ecdsa" | "randomBeacon" | "taco" > = { tbtc: "ecdsa", randomBeacon: "randomBeacon", + taco: "taco", } export const useStakingAppContract = (appName: StakingAppName) => { diff --git a/src/hooks/useCheckBonusEligibility.ts b/src/hooks/useCheckBonusEligibility.ts index 5a4008d82..e1d982b6a 100644 --- a/src/hooks/useCheckBonusEligibility.ts +++ b/src/hooks/useCheckBonusEligibility.ts @@ -1,9 +1,7 @@ import { useEffect } from "react" import { BigNumber, BigNumberish, Event, constants } from "ethers" import { - PRE_DEPLOYMENT_BLOCK, T_STAKING_CONTRACT_DEPLOYMENT_BLOCK, - usePREContract, useTStakingContract, } from "../web3/hooks" import { getAddress, getContractPastEvents } from "../web3/utils" @@ -29,7 +27,6 @@ export const useCheckBonusEligibility = () => { (state: RootState) => state.rewards.stakingBonus ) const dispatch = useDispatch() - const preContract = usePREContract() const merkleDropContract = useMerkleDropContract() const tStakingContract = useTStakingContract() @@ -38,7 +35,6 @@ export const useCheckBonusEligibility = () => { if ( !stakingProviders || stakingProviders.length === 0 || - !preContract || !tStakingContract || !merkleDropContract || (hasFetched && !isFetching) @@ -56,11 +52,6 @@ export const useCheckBonusEligibility = () => { ).map((_) => getAddress(_.args?.stakingProvider as string)) ) - const operatorConfirmedEvents = await getContractPastEvents(preContract, { - eventName: "OperatorConfirmed", - fromBlock: PRE_DEPLOYMENT_BLOCK, - filterParams: [stakingProviders], - }) const stakedEvents = await getContractPastEvents(tStakingContract, { eventName: "Staked", fromBlock: T_STAKING_CONTRACT_DEPLOYMENT_BLOCK, @@ -79,10 +70,6 @@ export const useCheckBonusEligibility = () => { filterParams: [stakingProviders], }) - const stakingProviderToPREConfig = getStakingProviderToPREConfig( - operatorConfirmedEvents - ) - const stakingProviderToStakedAmount = getStakingProviderToStakedInfo(stakedEvents) @@ -95,11 +82,6 @@ export const useCheckBonusEligibility = () => { for (const stakingProvider of stakingProviders) { const stakingProviderAddress = getAddress(stakingProvider) - const hasPREConfigured = - stakingProviderToPREConfig[stakingProviderAddress] - ?.operatorConfirmedAtBlock <= - stakingBonus.BONUS_DEADLINE_BLOCK_NUMBER - const hasActiveStake = stakingProviderToStakedAmount[stakingProviderAddress] ?.stakedAtBlock <= stakingBonus.BONUS_DEADLINE_BLOCK_NUMBER @@ -124,15 +106,12 @@ export const useCheckBonusEligibility = () => { : "0" stakingProvidersInfo[stakingProviderAddress] = { - hasPREConfigured, hasActiveStake, hasUnstakeAfterBonusDeadline, eligibleStakeAmount, reward: calculateStakingBonusReward(eligibleStakeAmount), isRewardClaimed: claimedRewards.has(stakingProviderAddress), - isEligible: Boolean( - hasActiveStake && !hasUnstakeAfterBonusDeadline && hasPREConfigured - ), + isEligible: Boolean(hasActiveStake && !hasUnstakeAfterBonusDeadline), } } dispatch(setStakingBonus(stakingProvidersInfo)) @@ -173,31 +152,6 @@ const getStakingProviderToStakedInfo = ( return stakingProviderToStakedAmount } -interface StakingProviderToPREConfig { - [address: string]: { - operator: string - operatorConfirmedAtBlock: number - transactionHash: string - } -} - -const getStakingProviderToPREConfig = ( - events: Event[] -): StakingProviderToPREConfig => { - const stakingProviderToPREConfig: StakingProviderToPREConfig = {} - for (const event of events) { - const stakingProvider = getAddress(event.args?.stakingProvider) - - stakingProviderToPREConfig[stakingProvider] = { - operator: event.args?.operator, - operatorConfirmedAtBlock: event.blockNumber, - transactionHash: event.transactionHash, - } - } - - return stakingProviderToPREConfig -} - interface StakingProviderToTopUps { [address: string]: { amount: BigNumberish diff --git a/src/hooks/useFetchOwnerStakes.ts b/src/hooks/useFetchOwnerStakes.ts index e06a379f6..defba8cf0 100644 --- a/src/hooks/useFetchOwnerStakes.ts +++ b/src/hooks/useFetchOwnerStakes.ts @@ -1,14 +1,12 @@ import { useCallback } from "react" import { StakeData } from "../types/staking" import { setStakes } from "../store/staking" -import { useFetchPreConfigData } from "./useFetchPreConfigData" import { useThreshold } from "../contexts/ThresholdContext" import { useAppDispatch } from "./store" export const useFetchOwnerStakes = () => { const threshold = useThreshold() const dispatch = useAppDispatch() - const fetchPreConfigData = useFetchPreConfigData() return useCallback( async (address?: string): Promise => { @@ -19,7 +17,6 @@ export const useFetchOwnerStakes = () => { const stakes = await threshold.staking.getOwnerStakes(address) const stakingProviders = stakes.map((stake) => stake.stakingProvider) - const preConfigData = await fetchPreConfigData(stakingProviders) const _stakes: StakeData[] = stakes.map((stake) => ({ ...stake, @@ -29,13 +26,12 @@ export const useFetchOwnerStakes = () => { totalInTStake: stake.totalInTStake.toString(), possibleKeepTopUpInT: stake.possibleKeepTopUpInT.toString(), possibleNuTopUpInT: stake.possibleNuTopUpInT.toString(), - preConfig: preConfigData[stake.stakingProvider], })) dispatch(setStakes(_stakes)) return _stakes }, - [threshold, fetchPreConfigData, dispatch] + [threshold, dispatch] ) } diff --git a/src/hooks/useFetchPreConfigData.ts b/src/hooks/useFetchPreConfigData.ts deleted file mode 100644 index dd0fffdc9..000000000 --- a/src/hooks/useFetchPreConfigData.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { useCallback } from "react" -import { usePREContract } from "../web3/hooks" -import { PreConfigData } from "../types/staking" -import { useThreshold } from "../contexts/ThresholdContext" - -export const useFetchPreConfigData = (): (( - stakingProviders: string[] -) => Promise) => { - const preContract = usePREContract() - const threshold = useThreshold() - - return useCallback( - async (stakingProviders) => { - if (!stakingProviders || stakingProviders.length === 0 || !preContract) { - return {} as PreConfigData - } - - const preConfigDataRaw = await threshold.multicall.aggregate( - stakingProviders.map((stakingProvider) => { - return { - interface: preContract.interface, - address: preContract.address, - method: "stakingProviderInfo", - args: [stakingProvider], - } - }) - ) - - return preConfigDataRaw.reduce( - (finalData: PreConfigData, _, idx): PreConfigData => { - finalData[stakingProviders[idx]] = { - operator: _.operator, - isOperatorConfirmed: _.operatorConfirmed, - operatorStartTimestamp: _.operatorStartTimestamp.toString(), - } - return finalData - }, - {} - ) - }, - [preContract, threshold] - ) -} diff --git a/src/hooks/useFetchTvl.ts b/src/hooks/useFetchTvl.ts index 4aa621108..c82a18544 100644 --- a/src/hooks/useFetchTvl.ts +++ b/src/hooks/useFetchTvl.ts @@ -24,7 +24,6 @@ interface TvlRawData { keepStakingTvl: string tStakingTvl: string tBTC: string - // TODO: add PRE and NU TVL } interface TvlData { diff --git a/src/pages/Staking/AuthorizeStakingApps/AuthorizeApplicationsCardCheckbox/AppAuthorizationInfo.tsx b/src/pages/Staking/AuthorizeStakingApps/AuthorizeApplicationsCardCheckbox/AppAuthorizationInfo.tsx index cc1588bf1..95f701bdd 100644 --- a/src/pages/Staking/AuthorizeStakingApps/AuthorizeApplicationsCardCheckbox/AppAuthorizationInfo.tsx +++ b/src/pages/Staking/AuthorizeStakingApps/AuthorizeApplicationsCardCheckbox/AppAuthorizationInfo.tsx @@ -21,7 +21,7 @@ import { AuthorizationStatus } from "../../../../types" interface CommonProps { label: string percentageAuthorized: number - stakingAppName: StakingAppName | "pre" + stakingAppName: StakingAppName } type ConditionalProps = @@ -42,7 +42,7 @@ const TooltipLearnMoreLink = () => { return here } -const tooltipText: Record = { +const tooltipText: Record = { tbtc: ( <> The tBTC application is the first decentralized bridge from Bitcoin to @@ -55,9 +55,9 @@ const tooltipText: Record = { selection. Learn more . ), - pre: ( + taco: ( <> - The PRE application is cryptographic middleware for developing + The TACo application is cryptographic middleware for developing privacy-preserving applications. Learn more . . ), diff --git a/src/pages/Staking/AuthorizeStakingApps/AuthorizeApplicationsCardCheckbox/index.tsx b/src/pages/Staking/AuthorizeStakingApps/AuthorizeApplicationsCardCheckbox/index.tsx index 4c6b530e2..aa42c3c3a 100644 --- a/src/pages/Staking/AuthorizeStakingApps/AuthorizeApplicationsCardCheckbox/index.tsx +++ b/src/pages/Staking/AuthorizeStakingApps/AuthorizeApplicationsCardCheckbox/index.tsx @@ -37,7 +37,7 @@ import { StakingAppForm } from "../../../../components/StakingApplicationForms" import { AuthorizationStatus } from "../../../../types" interface CommonProps { - stakingAppId: StakingAppName | "pre" + stakingAppId: StakingAppName label: string } diff --git a/src/pages/Staking/AuthorizeStakingApps/index.tsx b/src/pages/Staking/AuthorizeStakingApps/index.tsx index a3de75461..e8689cf15 100644 --- a/src/pages/Staking/AuthorizeStakingApps/index.tsx +++ b/src/pages/Staking/AuthorizeStakingApps/index.tsx @@ -46,27 +46,28 @@ const AuthorizeStakingAppsPage: FC = () => { const { openModal } = useModal() const tbtcAppFormRef = useRef>(null) const randomBeaconAppFormRef = useRef>(null) - const preAppFormRef = useRef>(null) + const tacoAppFormRef = useRef>(null) const stakinAppNameToFormRef: Record< AppAuthDataProps["stakingAppId"], RefObject> > = { tbtc: tbtcAppFormRef, randomBeacon: randomBeaconAppFormRef, - pre: preAppFormRef, + taco: tacoAppFormRef, } const dispatch = useAppDispatch() const tbtcAppAddress = useStakingApplicationAddress("tbtc") const randomBeaconAddress = useStakingApplicationAddress("randomBeacon") + const TACoAddress = useStakingApplicationAddress("taco") const stakinAppNameToAddress: Record< AppAuthDataProps["stakingAppId"], string > = { tbtc: tbtcAppAddress, randomBeacon: randomBeaconAddress, - pre: AddressZero, + taco: TACoAddress, } useEffect(() => { @@ -91,6 +92,10 @@ const AuthorizeStakingAppsPage: FC = () => { "randomBeacon", stakingProviderAddress || AddressZero ) + const tacoApp = useStakingAppDataByStakingProvider( + "taco", + stakingProviderAddress || AddressZero + ) const appsAuthData: { [appName: string]: AppAuthDataProps & { address?: string } @@ -107,10 +112,11 @@ const AuthorizeStakingAppsPage: FC = () => { address: randomBeaconAddress, label: "Random Beacon", }, - pre: { - stakingAppId: "pre", - label: "PRE", - status: "authorization-not-required", + taco: { + ...tacoApp, + stakingAppId: "taco", + label: "TACo", + address: TACoAddress, }, } @@ -134,11 +140,18 @@ const AuthorizeStakingAppsPage: FC = () => { ) ) } - }, [tbtcApp.isAuthorized, randomBeaconApp.isAuthorized]) + + if (tacoApp.isAuthorized) { + setSelectedApps((selectedApps) => + selectedApps.filter(({ stakingAppId }) => stakingAppId !== "taco") + ) + } + }, [tbtcApp.isAuthorized, randomBeaconApp.isAuthorized, tacoApp.isAuthorized]) const tbtcMinAuthAmount = useStakingAppMinAuthorizationAmount("tbtc") const randomBeaconMinAuthAmount = useStakingAppMinAuthorizationAmount("randomBeacon") + const tacoMinAuthAmount = useStakingAppMinAuthorizationAmount("taco") const stake = useSelector((state: RootState) => selectStakeByStakingProvider(state, stakingProviderAddress!) @@ -158,6 +171,7 @@ const AuthorizeStakingAppsPage: FC = () => { const onAuthorizeApps = async () => { const isTbtcSelected = isAppSelected("tbtc") const isRandomBeaconSelected = isAppSelected("randomBeacon") + const isTacoSelected = isAppSelected("taco") if (isTbtcSelected) { await tbtcAppFormRef.current?.validateForm() @@ -167,18 +181,18 @@ const AuthorizeStakingAppsPage: FC = () => { 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) - ) { + if (isTacoSelected) { + await tacoAppFormRef.current?.validateForm() + tacoAppFormRef.current?.setTouched({ tokenAmount: true }, false) + } + const isTbtcValid = + !isTbtcSelected || (isTbtcSelected && tbtcAppFormRef.current?.isValid) + const isRandomBeaconValid = + !isRandomBeaconSelected || + (isRandomBeaconSelected && randomBeaconAppFormRef.current?.isValid) + const isTacoValid = + !isTacoSelected || (isTacoSelected && tacoAppFormRef.current?.isValid) + if (isTbtcValid && isRandomBeaconValid && isTacoValid) { openModal(ModalType.AuthorizeStakingApps, { stakingProvider: stakingProviderAddress!, totalInTStake: stake.totalInTStake, @@ -294,18 +308,22 @@ const AuthorizeStakingAppsPage: FC = () => { canSubmitForm={isLoggedInAsAuthorizer} /> )} - {(!tbtcApp.isAuthorized || !randomBeaconApp.isAuthorized) && ( + {(!tbtcApp.isAuthorized || + !randomBeaconApp.isAuthorized || + !tacoApp.isAuthorized) && ( + )} ) : (
{`Please connect your wallet.`}
diff --git a/src/pages/Staking/index.tsx b/src/pages/Staking/index.tsx index c80a79641..9c7e55c31 100644 --- a/src/pages/Staking/index.tsx +++ b/src/pages/Staking/index.tsx @@ -79,7 +79,8 @@ const StakingPage: PageComponent = (props) => { isStakingProvider && isOperatorMappingInitialFetchDone && (isAddressZero(mappedOperators.tbtc) || - isAddressZero(mappedOperators.randomBeacon)) && ( + isAddressZero(mappedOperators.randomBeacon) || + isAddressZero(mappedOperators.taco)) && ( )} {hasStakes ? ( @@ -93,7 +94,8 @@ const StakingPage: PageComponent = (props) => { isStakingProvider && isOperatorMappingInitialFetchDone && !isAddressZero(mappedOperators.tbtc) && - !isAddressZero(mappedOperators.randomBeacon) && ( + !isAddressZero(mappedOperators.randomBeacon) && + !isAddressZero(mappedOperators.taco) && ( )} diff --git a/src/static/images/AuthorizingApplicationsIllustrationDark.png b/src/static/images/AuthorizingApplicationsIllustrationDark.png index c0b19df74..36e5c8ccf 100644 Binary files a/src/static/images/AuthorizingApplicationsIllustrationDark.png and b/src/static/images/AuthorizingApplicationsIllustrationDark.png differ diff --git a/src/static/images/AuthorizingApplicationsIllustrationLight.png b/src/static/images/AuthorizingApplicationsIllustrationLight.png index 3dcdd009e..65f7ac9a9 100644 Binary files a/src/static/images/AuthorizingApplicationsIllustrationLight.png and b/src/static/images/AuthorizingApplicationsIllustrationLight.png differ diff --git a/src/static/images/StakingApplicationsIllustrationDark.png b/src/static/images/StakingApplicationsIllustrationDark.png index f78b4154b..3277b8d05 100644 Binary files a/src/static/images/StakingApplicationsIllustrationDark.png and b/src/static/images/StakingApplicationsIllustrationDark.png differ diff --git a/src/static/images/StakingApplicationsIllustrationLight.png b/src/static/images/StakingApplicationsIllustrationLight.png index ccf1354c3..99456ae8c 100644 Binary files a/src/static/images/StakingApplicationsIllustrationLight.png and b/src/static/images/StakingApplicationsIllustrationLight.png differ diff --git a/src/static/images/TACoDecrease.png b/src/static/images/TACoDecrease.png new file mode 100644 index 000000000..d4b96ce92 Binary files /dev/null and b/src/static/images/TACoDecrease.png differ diff --git a/src/static/images/TACoIncrease.png b/src/static/images/TACoIncrease.png new file mode 100644 index 000000000..ba1f32919 Binary files /dev/null and b/src/static/images/TACoIncrease.png differ diff --git a/src/store/account/effects.ts b/src/store/account/effects.ts index a4f23ecbc..54be0b50e 100644 --- a/src/store/account/effects.ts +++ b/src/store/account/effects.ts @@ -51,6 +51,7 @@ export const getStakingProviderOperatorInfo = async ( setMappedOperators({ tbtc: mappedOperators.tbtc, randomBeacon: mappedOperators.randomBeacon, + taco: mappedOperators.taco, }) ) } catch (error: any) { diff --git a/src/store/account/selectors.ts b/src/store/account/selectors.ts index 9cb2fdc85..42e5a972b 100644 --- a/src/store/account/selectors.ts +++ b/src/store/account/selectors.ts @@ -8,21 +8,22 @@ export const selectAccountState = (state: RootState) => state.account export const selectMappedOperators = createSelector( [selectAccountState], (accountState: AccountState) => { - const { randomBeacon, tbtc } = accountState.operatorMapping.data - const isOperatorMappedOnlyInTbtc = + const { randomBeacon, tbtc, taco } = accountState.operatorMapping.data + const isOperatorMappedOnlyInTbtcForBundledRewards = !isAddressZero(tbtc) && isAddressZero(randomBeacon) - const isOperatorMappedOnlyInRandomBeacon = + const isOperatorMappedOnlyInRandomBeaconForBundledRewards = isAddressZero(tbtc) && !isAddressZero(randomBeacon) return { mappedOperatorTbtc: tbtc, mappedOperatorRandomBeacon: randomBeacon, - isOperatorMappedOnlyInTbtc, - isOperatorMappedOnlyInRandomBeacon, - isOneOfTheAppsNotMapped: - isOperatorMappedOnlyInRandomBeacon || isOperatorMappedOnlyInTbtc, - isOperatorMappedInBothApps: - !isAddressZero(randomBeacon) && !isAddressZero(tbtc), + mappedOperatorTaco: taco, + isOperatorMappedOnlyInTbtcForBundledRewards, + isOperatorMappedOnlyInRandomBeaconForBundledRewards, + isOperatorMappedInAllApps: + !isAddressZero(randomBeacon) && + !isAddressZero(tbtc) && + !isAddressZero(taco), } } ) diff --git a/src/store/account/slice.ts b/src/store/account/slice.ts index 570f397f8..0bf63e6eb 100644 --- a/src/store/account/slice.ts +++ b/src/store/account/slice.ts @@ -27,6 +27,7 @@ export const accountSlice = createSlice({ data: { tbtc: AddressZero, randomBeacon: AddressZero, + taco: AddressZero, }, isFetching: false, isInitialFetchDone: false, @@ -47,11 +48,13 @@ export const accountSlice = createSlice({ action: PayloadAction<{ tbtc: string randomBeacon: string + taco: string }> ) => { - const { tbtc, randomBeacon } = action.payload + const { tbtc, randomBeacon, taco } = action.payload state.operatorMapping.data.tbtc = tbtc state.operatorMapping.data.randomBeacon = randomBeacon + state.operatorMapping.data.taco = taco state.operatorMapping.isFetching = false state.operatorMapping.isInitialFetchDone = true state.operatorMapping.error = "" diff --git a/src/store/staking-applications/effects.ts b/src/store/staking-applications/effects.ts index a37840f95..dcaf10726 100644 --- a/src/store/staking-applications/effects.ts +++ b/src/store/staking-applications/effects.ts @@ -41,9 +41,13 @@ export const getSupportedAppsEffect = async ( appName: "randomBeacon", }) ) + listenerApi.dispatch( + stakingApplicationsSlice.actions.fetchingAppParameters({ + appName: "taco", + }) + ) const data = await listenerApi.extra.threshold.multiAppStaking.getSupportedAppsAuthParameters() - // one-off listener const payload = { tbtc: { minimumAuthorization: data.tbtc.minimumAuthorization.toString(), @@ -63,6 +67,15 @@ export const getSupportedAppsEffect = async ( authorizationDecreaseChangePeriod: data.randomBeacon.authorizationDecreaseChangePeriod.toString(), }, + taco: { + minimumAuthorization: data.taco._minimumAuthorization?.toString() ?? "", + + authorizationDecreaseDelay: + data.taco.authorizationDecreaseDelay.toString(), + + authorizationDecreaseChangePeriod: + data.taco.authorizationDecreaseChangePeriod.toString(), + }, } listenerApi.dispatch( stakingApplicationsSlice.actions.setAppParameters({ @@ -76,6 +89,12 @@ export const getSupportedAppsEffect = async ( parameters: payload.tbtc, }) ) + listenerApi.dispatch( + stakingApplicationsSlice.actions.setAppParameters({ + appName: "taco", + parameters: payload.taco, + }) + ) } catch (error) { const errorMessage = (error as Error).toString() listenerApi.dispatch( @@ -90,6 +109,12 @@ export const getSupportedAppsEffect = async ( error: errorMessage, }) ) + listenerApi.dispatch( + stakingApplicationsSlice.actions.setAppParametersError({ + appName: "taco", + error: errorMessage, + }) + ) console.log("Could not fetch supported apps auth parameters", error) listenerApi.subscribe() } @@ -117,6 +142,12 @@ export const getSupportedAppsStakingProvidersData = async ( "randomBeacon", listenerApi ) + await getKeepStakingAppStakingProvidersData( + stakingProviders, + listenerApi.extra.threshold.multiAppStaking.taco, + "taco", + listenerApi + ) } catch (error) { console.log("Could not fetch apps data for staking providers ", error) listenerApi.subscribe() @@ -196,12 +227,14 @@ export const displayMapOperatorToStakingProviderModalEffect = async ( const { tbtc: mappedOperatorTbtc, randomBeacon: mappedOperatorRandomBeacon, + taco: mappedOperatorTaco, } = action.payload if ( isStakingProvider && (isAddressZero(mappedOperatorTbtc) || - isAddressZero(mappedOperatorRandomBeacon)) + isAddressZero(mappedOperatorRandomBeacon) || + isAddressZero(mappedOperatorTaco)) ) { listenerApi.dispatch( openModal({ @@ -210,6 +243,7 @@ export const displayMapOperatorToStakingProviderModalEffect = async ( address, mappedOperatorTbtc: mappedOperatorTbtc, mappedOperatorRandomBeacon: mappedOperatorRandomBeacon, + mappedOperatorTaco: mappedOperatorTaco, }, }) ) @@ -240,6 +274,12 @@ export const displayNewAppsToAuthorizeModalEffect = async ( .stakingProviders.data ) ) + .concat( + Object.values( + selectStakingAppStateByAppName(listenerApi.getState(), "taco") + .stakingProviders.data + ) + ) .some( (stakingProviderAppInfo) => stakingProviderAppInfo.authorizedStake && @@ -264,6 +304,8 @@ export const shouldDisplayNewAppsToAuthorizeModal = ( currentState.applications.randomBeacon.stakingProviders.data ?? {} ).length > 0 && Object.values(currentState.applications.tbtc.stakingProviders.data ?? {}) + .length > 0 && + Object.values(currentState.applications.taco.stakingProviders.data ?? {}) .length > 0 ) } diff --git a/src/store/staking-applications/slice.ts b/src/store/staking-applications/slice.ts index a7aa48883..4cd0fe065 100644 --- a/src/store/staking-applications/slice.ts +++ b/src/store/staking-applications/slice.ts @@ -38,9 +38,10 @@ export type StakingApplicationState = { export interface StakingApplicationsState { tbtc: StakingApplicationState randomBeacon: StakingApplicationState + taco: StakingApplicationState } -export type StakingAppName = "tbtc" | "randomBeacon" +export type StakingAppName = "tbtc" | "randomBeacon" | "taco" export const stakingApplicationsSlice = createSlice({ name: "staking-applications", @@ -77,6 +78,22 @@ export const stakingApplicationsSlice = createSlice({ data: {}, }, }, + taco: { + parameters: { + isFetching: false, + error: "", + data: { + authorizationDecreaseChangePeriod: "0", + minimumAuthorization: "0", + authorizationDecreaseDelay: "0", + }, + }, + stakingProviders: { + isFetching: false, + error: "", + data: {}, + }, + }, } as StakingApplicationsState, reducers: { getSupportedApps: (state: StakingApplicationsState, action) => {}, @@ -278,6 +295,9 @@ export const stakingApplicationsSlice = createSlice({ state.tbtc.stakingProviders.data[stakingProvider] = { ...defaultAuthData, } + state.taco.stakingProviders.data[stakingProvider] = { + ...defaultAuthData, + } } ) }, diff --git a/src/store/staking/effects.ts b/src/store/staking/effects.ts index 97dc954ef..51368ea0f 100644 --- a/src/store/staking/effects.ts +++ b/src/store/staking/effects.ts @@ -71,11 +71,6 @@ const fetchStake = async ( keepInTStake: stake.keepInTStake.toString(), nuInTStake: stake.nuInTStake.toString(), totalInTStake: stake.totalInTStake.toString(), - preConfig: { - operator: AddressZero, - isOperatorConfirmed: false, - operatorStartTimestamp: "0", - }, possibleKeepTopUpInT: "0", possibleNuTopUpInT: "0", } as StakeData, diff --git a/src/store/staking/stakingSlice.ts b/src/store/staking/stakingSlice.ts index c2d7a006a..89f347301 100644 --- a/src/store/staking/stakingSlice.ts +++ b/src/store/staking/stakingSlice.ts @@ -71,12 +71,6 @@ export const stakingSlice = createSlice({ newStake.possibleKeepTopUpInT = "0" newStake.possibleNuTopUpInT = "0" - newStake.preConfig = { - operator: AddressZero, - isOperatorConfirmed: false, - operatorStartTimestamp: "0", - } - state.stakes = [newStake, ...state.stakes] state.stakedBalance = calculateStakedBalance(state.stakes) }, diff --git a/src/threshold-ts/applications/index.ts b/src/threshold-ts/applications/index.ts index 4737a88d9..cecbb2985 100644 --- a/src/threshold-ts/applications/index.ts +++ b/src/threshold-ts/applications/index.ts @@ -20,6 +20,7 @@ export interface AuthorizationParameters< * providing a malicious DKG result or when a relay entry times out. */ minimumAuthorization: NumberType + _minimumAuthorization?: NumberType /** * Delay in seconds that needs to pass between the time authorization decrease * is requested and the time that request gets approved. Protects against @@ -190,6 +191,11 @@ export interface IApplication { */ registerOperator(operator: string): Promise + bondOperator( + stakingProvider: string, + operator: string + ): Promise + /** * Used to get a registered operator mapped to the given staking provider * @param stakingProvider Staking provider address @@ -204,6 +210,11 @@ export interface IApplication { operatorToStakingProvider(operator: string): Promise updateOperatorStatus(operator: string): Promise + + makeCommitment( + stakingProvider: string, + commitmentDuration: number + ): Promise } export class Application implements IApplication { @@ -314,7 +325,14 @@ export class Application implements IApplication { let isOperatorInPool = undefined if (operator && !isAddressZero(operator)) { - isOperatorInPool = await this._application.isOperatorInPool(operator) + try { + isOperatorInPool = await this._application.isOperatorInPool(operator) + } catch (error) { + console.warn( + "isOperatorInPool method does not exist (eg on TACo app)", + error + ) + } } const _remainingAuthorizationDecreaseDelay = BigNumber.from( @@ -361,8 +379,15 @@ export class Application implements IApplication { if (isAddress(operator) && isAddressZero(operator)) { return false } - - const isInPool: boolean = await this._application.isOperatorInPool(operator) + let isInPool = undefined + try { + isInPool = await this._application.isOperatorInPool(operator) + } catch (error) { + console.warn( + "isOperatorInPool method does not exist (eg on TACo app)", + error + ) + } return isInPool } @@ -406,6 +431,23 @@ export class Application implements IApplication { return await this._application.registerOperator(operator) } + bondOperator = async ( + stakingProvider: string, + operator: string + ): Promise => { + return await this._application.bondOperator(stakingProvider, operator) + } + + makeCommitment = async ( + stakingProvider: string, + commitmentDuration: number + ): Promise => { + return await this._application.makeCommitment( + stakingProvider, + commitmentDuration + ) + } + stakingProviderToOperator = async ( stakingProvider: string ): Promise => { diff --git a/src/threshold-ts/mas/__test__/mas.test.ts b/src/threshold-ts/mas/__test__/mas.test.ts index 737a10c37..a7dd3202f 100644 --- a/src/threshold-ts/mas/__test__/mas.test.ts +++ b/src/threshold-ts/mas/__test__/mas.test.ts @@ -107,10 +107,12 @@ describe("Multi app staking test", () => { const mockOperator = "0x4" const mappedOperatorTbtc = mockOperator const mappedOperatorRandomBeacon = mockOperator + const mappedOperatorTaco = mockOperator const mulitcallMockResult = [ [mappedOperatorTbtc], [mappedOperatorRandomBeacon], + [mappedOperatorTaco], ] const spyOnMulticall = jest .spyOn(multicall, "aggregate") @@ -133,10 +135,17 @@ describe("Multi app staking test", () => { method: "stakingProviderToOperator", args: [mockStakingProvider], }, + { + interface: mas.taco.contract.interface, + address: mas.taco.address, + method: "stakingProviderToOperator", + args: [mockStakingProvider], + }, ]) expect(result).toEqual({ tbtc: mappedOperatorTbtc, randomBeacon: mappedOperatorRandomBeacon, + taco: mappedOperatorTaco, }) }) }) diff --git a/src/threshold-ts/mas/index.ts b/src/threshold-ts/mas/index.ts index c5fb4be90..42700c642 100644 --- a/src/threshold-ts/mas/index.ts +++ b/src/threshold-ts/mas/index.ts @@ -1,4 +1,9 @@ import RandomBeacon from "@keep-network/random-beacon/artifacts/RandomBeacon.json" +import WalletRegistry from "@keep-network/ecdsa/artifacts/WalletRegistry.json" + +const chainName = process.env.REACT_APP_TACO_DOMAIN || "" // Ensure it's a string +const TacoRegistryFile: any = require(`@nucypher/nucypher-contracts/deployment/artifacts/${chainName}.json`) + import { Application, AuthorizationParameters, @@ -9,14 +14,30 @@ import { IStaking } from "../staking" import { EthereumConfig } from "../types" import { getArtifact } from "../utils" +interface TacoChains { + [key: string]: string +} + +const tacoChains: TacoChains = { + lynx: "5", + mainnet: "1", + tapir: "11155111", + dashboard: "11155111", +} + +const key = tacoChains[chainName] || "" +const TacoRegistry = TacoRegistryFile[key]["TACoApplication"] + export interface SupportedAppAuthorizationParameters { tbtc: AuthorizationParameters randomBeacon: AuthorizationParameters + taco: AuthorizationParameters } export interface MappedOperatorsForStakingProvider { tbtc: string randomBeacon: string + taco: string } export class MultiAppStaking { @@ -24,6 +45,7 @@ export class MultiAppStaking { private _multicall: IMulticall public readonly randomBeacon: IApplication public readonly ecdsa: IApplication + public readonly taco: IApplication constructor( staking: IStaking, @@ -47,6 +69,11 @@ export class MultiAppStaking { abi: walletRegistryArtifacts.abi, ...config, }) + this.taco = new Application(this._staking, this._multicall, { + address: TacoRegistry.address, + abi: TacoRegistry.abi, + ...config, + }) } async getSupportedAppsAuthParameters(): Promise { @@ -63,14 +90,24 @@ export class MultiAppStaking { method: "authorizationParameters", args: [], }, + { + interface: this.taco.contract.interface, + address: this.taco.address, + method: "authorizationParameters", + args: [], + }, ] - const [tbtcMinAuthorizationParams, randomBeaconMinAuthorizationParams] = - await this._multicall.aggregate(calls) + const [ + tbtcMinAuthorizationParams, + randomBeaconMinAuthorizationParams, + tacoMinAuthorizationParams, + ] = await this._multicall.aggregate(calls) return { tbtc: tbtcMinAuthorizationParams, randomBeacon: randomBeaconMinAuthorizationParams, + taco: tacoMinAuthorizationParams, } } @@ -90,14 +127,21 @@ export class MultiAppStaking { method: "stakingProviderToOperator", args: [stakingProvider], }, + { + interface: this.taco.contract.interface, + address: this.taco.address, + method: "stakingProviderToOperator", + args: [stakingProvider], + }, ] - const [mappedOperatorTbtc, mappedOperatorRandomBeacon] = + const [mappedOperatorTbtc, mappedOperatorRandomBeacon, mappedOperatorTaco] = await this._multicall.aggregate(calls) return { tbtc: mappedOperatorTbtc.toString(), randomBeacon: mappedOperatorRandomBeacon.toString(), + taco: mappedOperatorTaco.toString(), } } } diff --git a/src/types/modal.ts b/src/types/modal.ts index eb8ac419c..eda4f6317 100644 --- a/src/types/modal.ts +++ b/src/types/modal.ts @@ -15,6 +15,8 @@ import StakeSuccessOldModal from "../components/Modal/StakingSuccessModal/StakeS import ConfirmStakingParams from "../components/Modal/ConfirmStakingParams" import StakingChecklistModal from "../components/Modal/StakingChecklistModal" import UnstakingSuccessModal from "../components/Modal/UnstakeSuccessModal" +import TACoCommitmentModal from "../components/Modal/TACoCommitmentModal" +import TACoCommitmentSuccessModal from "../components/Modal/TACoCommitmentModal" import { UnstakeTStep1 as UnstakeTModalStep1, UnstakeTStep2 as UnstakeTModalStep2, @@ -81,7 +83,6 @@ export const MODAL_TYPES: Record = { [ModalType.StakingApplicationsAuthorized]: StakingApplicationsAuthorized, [ModalType.IncreaseAuthorization]: IncreaseAuthorization, [ModalType.IncreaseAuthorizationSuccess]: IncreaseAuthorizationSuccess, - [ModalType.SubmitStake]: SubmitStakeModal, [ModalType.NewStakerAuthorizeStakingApplication]: NewStakerAuthorizeStakingApplicationModal, [ModalType.ConfirmDeauthorization]: ConfirmDeauthorization, @@ -92,6 +93,8 @@ export const MODAL_TYPES: Record = { [ModalType.FeedbackSubmission]: FeedbackSubmissionModal, [ModalType.GenerateNewDepositAddress]: GenerateNewDepositAddress, [ModalType.InitiateUnminting]: InitiateUnminting, + [ModalType.TACoCommitment]: TACoCommitmentModal, + [ModalType.TACoCommitmentSuccess]: TACoCommitmentSuccessModal, } export interface BaseModalProps { diff --git a/src/types/rewards.ts b/src/types/rewards.ts index 342f4776d..8371e1a13 100644 --- a/src/types/rewards.ts +++ b/src/types/rewards.ts @@ -11,7 +11,6 @@ export interface RewardsJSONData { } export interface BonusEligibility { - hasPREConfigured: boolean hasActiveStake: boolean // No unstaking after the bonus deadline and until mid-July (not even partial // amounts). diff --git a/src/types/staking.ts b/src/types/staking.ts index c859719d0..36c9cbec6 100644 --- a/src/types/staking.ts +++ b/src/types/staking.ts @@ -1,5 +1,5 @@ import { BigNumberish } from "@ethersproject/bignumber" -import { StakeType, TopUpType, UnstakeType } from "../enums" +import { TopUpType, UnstakeType } from "../enums" import { Stake } from "../threshold-ts/staking" import { UpdateStateActionPayload } from "./state" @@ -26,19 +26,7 @@ export interface UseStakingState { } } -export interface PreConfig { - operator: string - isOperatorConfirmed: boolean - operatorStartTimestamp: string -} - -export interface PreConfigData { - [stakingProvider: string]: PreConfig -} - -export interface StakeData extends Stake { - preConfig: PreConfig -} +export interface StakeData extends Stake {} export interface ProviderStakedEvent { stakeType: number @@ -58,7 +46,6 @@ export type ProviderStakedActionPayload = ProviderStakedEvent & | "tStake" | "amount" | "totalInTStake" - | "preConfig" | "possibleKeepTopUpInT" | "possibleNuTopUpInT" > diff --git a/src/utils/__tests__/getStakingAppLabel.test.ts b/src/utils/__tests__/getStakingAppLabel.test.ts index 8629a2bdc..3e328cece 100644 --- a/src/utils/__tests__/getStakingAppLabel.test.ts +++ b/src/utils/__tests__/getStakingAppLabel.test.ts @@ -9,48 +9,49 @@ import { getThresholdLib } from "../getThresholdLib" const mockAddresses: Record = { tbtc: getThresholdLib().multiAppStaking.ecdsa.address, randomBeacon: getThresholdLib().multiAppStaking.randomBeacon.address, + taco: getThresholdLib().multiAppStaking.taco.address, } const mockLabels: Record = { tbtc: "tBTC", randomBeacon: "Random Beacon", + taco: "TACo", } -const mockAppNames: StakingAppName[] = ["tbtc", "randomBeacon"] +const mockAppNames: StakingAppName[] = ["tbtc", "randomBeacon", "taco"] describe("Staking app label utils tests", () => { - const [tbtcName, randomBeaconName] = mockAppNames + const [tbtcName, randomBeaconName, tacoName] = mockAppNames const tbtcAddress = mockAddresses[tbtcName] const randomBeaconAddress = mockAddresses[randomBeaconName] + const tacoAddress = mockAddresses[tacoName] it("returns correct app label if app address is given", () => { const resultTbtcLabel = getStakingAppLabelFromAppAddress(tbtcAddress) const resultRandomBeaconLabel = getStakingAppLabelFromAppAddress(randomBeaconAddress) + const resultTacoLabel = getStakingAppLabelFromAppAddress(tacoAddress) expect(resultTbtcLabel).toBe(mockLabels[tbtcName]) expect(resultRandomBeaconLabel).toBe(mockLabels[randomBeaconName]) + expect(resultTacoLabel).toBe(mockLabels[tacoName]) }) it("returns correct app label if app name is given", () => { const resultTbtcLabel = getStakingAppLabelFromAppName(tbtcName) - const resultRandomBeaconLabel = - getStakingAppLabelFromAppName(randomBeaconName) + const resultRbLabel = getStakingAppLabelFromAppName(randomBeaconName) + const resultTacoLabel = getStakingAppLabelFromAppName(tacoName) expect(resultTbtcLabel).toBe(mockLabels[tbtcName]) - expect(resultRandomBeaconLabel).toBe(mockLabels[randomBeaconName]) + expect(resultRbLabel).toBe(mockLabels[randomBeaconName]) + expect(resultTacoLabel).toBe(mockLabels[tacoName]) }) it("returns correct app name if address is given", () => { const resultTbtcName = getStakingAppNameFromAppAddress(tbtcAddress) - const resultRandomBeaconName = - getStakingAppNameFromAppAddress(randomBeaconAddress) + const resultRbName = getStakingAppNameFromAppAddress(randomBeaconAddress) + const resultTacoName = getStakingAppNameFromAppAddress(tacoAddress) expect(resultTbtcName).toBe(tbtcName) - expect(resultRandomBeaconName).toBe(randomBeaconName) - }) - - it("returns fallback value if address is unexpected", () => { - const resultName = getStakingAppLabelFromAppAddress("0xun3xp3c73d400r3s5") - - expect(resultName).toBe("App") + expect(resultRbName).toBe(randomBeaconName) + expect(resultTacoName).toBe(tacoName) }) }) diff --git a/src/utils/getStakingAppLabel.ts b/src/utils/getStakingAppLabel.ts index 90587750e..ec57b3c67 100644 --- a/src/utils/getStakingAppLabel.ts +++ b/src/utils/getStakingAppLabel.ts @@ -4,11 +4,13 @@ import { StakingAppName } from "../store/staking-applications" const stakingAppNameToAppLabel: Record = { tbtc: "tBTC", randomBeacon: "Random Beacon", + taco: "TACo", } const stakingAppAddressToAppName: Record = { [threshold.multiAppStaking.ecdsa.address]: "tbtc", [threshold.multiAppStaking.randomBeacon.address]: "randomBeacon", + [threshold.multiAppStaking.taco.address]: "taco", } export const getStakingAppNameFromAppAddress = (stakingAppAddress: string) => { diff --git a/src/web3/hooks/index.ts b/src/web3/hooks/index.ts index 474a3d491..956c76314 100644 --- a/src/web3/hooks/index.ts +++ b/src/web3/hooks/index.ts @@ -17,6 +17,5 @@ export * from "./useKeepAssetPoolContract" export * from "./useTBTCTokenContract" export * from "./useTStakingContract" export * from "./useKeepTokenStakingContract" -export * from "./usePREContract" export * from "./useClaimMerkleRewardsTransaction" export * from "./useGetBlock" diff --git a/yarn.lock b/yarn.lock index afaad4ffb..06892d3c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3736,6 +3736,11 @@ mkdirp "^1.0.4" rimraf "^3.0.2" +"@nucypher/nucypher-contracts@0.13.0": + version "0.13.0" + resolved "https://registry.yarnpkg.com/@nucypher/nucypher-contracts/-/nucypher-contracts-0.13.0.tgz#43ead85d4cdf7eb6b459ba77a6ff90a8bb796300" + integrity sha512-QB9vCVq2mLR2SeA/gSX8Px2QW3wcc9keQuTwgtDOA5J7PLoxDHEAko4ow6ZlsfjsbK572Xz6YReH09MBr71ujA== + "@openzeppelin/contracts-ethereum-package@^2.4.0": version "2.5.0" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-ethereum-package/-/contracts-ethereum-package-2.5.0.tgz#cfb4b91f8132edde7e04bcd032575d4c6b544f4a"