diff --git a/src/hooks/api/escrow/use-get-account-voting-balance.tsx b/src/hooks/api/escrow/use-get-account-voting-balance.tsx new file mode 100644 index 0000000000..649e79e61c --- /dev/null +++ b/src/hooks/api/escrow/use-get-account-voting-balance.tsx @@ -0,0 +1,37 @@ +import { CurrencyExt } from '@interlay/interbtc-api'; +import { MonetaryAmount } from '@interlay/monetary-js'; +import { AccountId } from '@polkadot/types/interfaces'; +import { useErrorHandler } from 'react-error-boundary'; +import { useQuery } from 'react-query'; + +import { BLOCKTIME_REFETCH_INTERVAL } from '@/utils/constants/api'; + +import useAccountId from '../../use-account-id'; + +const getAccountVotingBalance = async (accountId: AccountId): Promise> => + window.bridge.escrow.votingBalance(accountId); + +interface GetAccountVotingBalanceResult { + data: MonetaryAmount | undefined; + refetch: () => void; +} + +const useGetAccountVotingBalance = (): GetAccountVotingBalanceResult => { + const accountId = useAccountId(); + + const queryKey = ['voting', accountId]; + + const { data, error, refetch } = useQuery({ + queryKey, + queryFn: () => accountId && getAccountVotingBalance(accountId), + refetchInterval: BLOCKTIME_REFETCH_INTERVAL, + enabled: !!accountId + }); + + useErrorHandler(error); + + return { data, refetch }; +}; + +export { useGetAccountVotingBalance }; +export type { GetAccountVotingBalanceResult }; diff --git a/src/pages/Staking/ClaimRewardsButton/index.tsx b/src/pages/Staking/ClaimRewardsButton/index.tsx new file mode 100644 index 0000000000..80fb00983c --- /dev/null +++ b/src/pages/Staking/ClaimRewardsButton/index.tsx @@ -0,0 +1,48 @@ +import clsx from 'clsx'; +import { useQueryClient } from 'react-query'; + +import { GOVERNANCE_TOKEN_SYMBOL } from '@/config/relay-chains'; +import { Transaction, useTransaction } from '@/hooks/transaction'; +import InterlayDenimOrKintsugiSupernovaContainedButton, { + Props as InterlayDenimOrKintsugiMidnightContainedButtonProps +} from '@/legacy-components/buttons/InterlayDenimOrKintsugiSupernovaContainedButton'; +import { useSubstrateSecureState } from '@/lib/substrate'; +import { GENERIC_FETCHER } from '@/services/fetchers/generic-fetcher'; + +interface CustomProps { + claimableRewardAmount: string; +} + +const ClaimRewardsButton = ({ + className, + claimableRewardAmount, + ...rest +}: CustomProps & InterlayDenimOrKintsugiMidnightContainedButtonProps): JSX.Element => { + const { selectedAccount } = useSubstrateSecureState(); + + const queryClient = useQueryClient(); + + const transaction = useTransaction(Transaction.ESCROW_WITHDRAW_REWARDS, { + onSuccess: () => { + queryClient.invalidateQueries([GENERIC_FETCHER, 'escrow', 'getRewardEstimate', selectedAccount?.address]); + queryClient.invalidateQueries([GENERIC_FETCHER, 'escrow', 'getRewards', selectedAccount?.address]); + } + }); + + const handleClaimRewards = () => { + transaction.execute(); + }; + + return ( + + Claim {claimableRewardAmount} {GOVERNANCE_TOKEN_SYMBOL} Rewards + + ); +}; + +export default ClaimRewardsButton; diff --git a/src/pages/Staking/WithdrawButton/index.tsx b/src/pages/Staking/WithdrawButton/index.tsx new file mode 100644 index 0000000000..1dfa40bfc5 --- /dev/null +++ b/src/pages/Staking/WithdrawButton/index.tsx @@ -0,0 +1,83 @@ +import clsx from 'clsx'; +import { add, format } from 'date-fns'; +import { useQueryClient } from 'react-query'; + +import { BLOCK_TIME } from '@/config/parachain'; +import { GOVERNANCE_TOKEN_SYMBOL } from '@/config/relay-chains'; +import { Transaction, useTransaction } from '@/hooks/transaction'; +import InterlayDenimOrKintsugiSupernovaContainedButton, { + Props as InterlayDenimOrKintsugiMidnightContainedButtonProps +} from '@/legacy-components/buttons/InterlayDenimOrKintsugiSupernovaContainedButton'; +import InformationTooltip from '@/legacy-components/tooltips/InformationTooltip'; +import { useSubstrateSecureState } from '@/lib/substrate'; +import { GENERIC_FETCHER } from '@/services/fetchers/generic-fetcher'; +import { YEAR_MONTH_DAY_PATTERN } from '@/utils/constants/date-time'; + +const getFormattedUnlockDate = (remainingBlockNumbersToUnstake: number, formatPattern: string) => { + const unlockDate = add(new Date(), { + seconds: remainingBlockNumbersToUnstake * BLOCK_TIME + }); + + return format(unlockDate, formatPattern); +}; + +interface CustomProps { + stakedAmount: string; + remainingBlockNumbersToUnstake: number | undefined; +} + +const WithdrawButton = ({ + className, + stakedAmount, + remainingBlockNumbersToUnstake, + ...rest +}: CustomProps & InterlayDenimOrKintsugiMidnightContainedButtonProps): JSX.Element => { + const { selectedAccount } = useSubstrateSecureState(); + + const transaction = useTransaction(Transaction.ESCROW_WITHDRAW, { + onSuccess: () => { + queryClient.invalidateQueries([GENERIC_FETCHER, 'escrow', 'getStakedBalance', selectedAccount?.address]); + } + }); + + const queryClient = useQueryClient(); + + const handleUnstake = () => transaction.execute(); + + const disabled = remainingBlockNumbersToUnstake ? remainingBlockNumbersToUnstake > 0 : false; + + const renderUnlockDateLabel = () => { + return remainingBlockNumbersToUnstake === undefined + ? '-' + : getFormattedUnlockDate(remainingBlockNumbersToUnstake, YEAR_MONTH_DAY_PATTERN); + }; + + const renderUnlockDateTimeLabel = () => { + return remainingBlockNumbersToUnstake === undefined + ? '-' + : getFormattedUnlockDate(remainingBlockNumbersToUnstake, 'PPpp'); + }; + + return ( + <> + + } + onClick={handleUnstake} + pending={transaction.isLoading} + disabled={disabled} + {...rest} + > + Withdraw Staked {GOVERNANCE_TOKEN_SYMBOL} {renderUnlockDateLabel()} + + + ); +}; + +export default WithdrawButton;