From 17e4132f141ed318e1ce580cb14216a7119f7fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rui=20Sim=C3=A3o?= Date: Mon, 28 Aug 2023 16:32:04 +0100 Subject: [PATCH] feat: continue --- src/assets/locales/en/translation.json | 1 + .../escrow/use-get-account-staking-data.tsx | 22 ++- .../use-get-staking-estimation-data.tsx | 57 ++++++-- src/hooks/transaction/extrinsics/lib.ts | 2 +- src/hooks/transaction/types/escrow.ts | 7 +- src/hooks/transaction/utils/fee.ts | 17 +++ src/lib/form/schemas/staking.ts | 19 ++- src/pages/Staking copy/index.tsx | 6 +- src/pages/Staking/Staking.tsx | 31 ++--- .../StakingAccountDetails.tsx | 11 +- .../components/StakingForm/StakingForm.tsx | 128 ++++++++++++++---- .../StakingForm/StakingTransactionDetails.tsx | 37 +++-- .../StakingWithdrawCard.style.tsx | 40 ++++++ .../StakingWithdrawCard.tsx | 40 ++++++ .../components/StakingWithdrawCard/index.tsx | 2 + 15 files changed, 334 insertions(+), 86 deletions(-) create mode 100644 src/pages/Staking/components/StakingWithdrawCard/StakingWithdrawCard.style.tsx create mode 100644 src/pages/Staking/components/StakingWithdrawCard/StakingWithdrawCard.tsx create mode 100644 src/pages/Staking/components/StakingWithdrawCard/index.tsx diff --git a/src/assets/locales/en/translation.json b/src/assets/locales/en/translation.json index 2d0200f3a8..90ab365505 100644 --- a/src/assets/locales/en/translation.json +++ b/src/assets/locales/en/translation.json @@ -155,6 +155,7 @@ "rewards_apr_ticker": "Rewards APR {{ticker}}", "wallet": "Wallet", "learn_more": "Learn more", + "stake": "Stake", "navigation": { "btc": "BTC", "strategies": "Strategies", diff --git a/src/hooks/api/escrow/use-get-account-staking-data.tsx b/src/hooks/api/escrow/use-get-account-staking-data.tsx index ec3987b0a7..2464f5dbcd 100644 --- a/src/hooks/api/escrow/use-get-account-staking-data.tsx +++ b/src/hooks/api/escrow/use-get-account-staking-data.tsx @@ -3,11 +3,13 @@ import { MonetaryAmount } from '@interlay/monetary-js'; import { AccountId } from '@polkadot/types/interfaces'; import Big from 'big.js'; import { add } from 'date-fns'; +import { useCallback } from 'react'; import { useErrorHandler } from 'react-error-boundary'; import { useQuery } from 'react-query'; import { BLOCK_TIME } from '@/config/parachain'; import { REFETCH_INTERVAL } from '@/utils/constants/api'; +import { convertWeeksToBlockNumbers } from '@/utils/helpers/staking'; import useAccountId from '../../use-account-id'; @@ -19,6 +21,7 @@ type AccountUnlockStakingData = { type AccountStakingData = { unlock: AccountUnlockStakingData; balance: MonetaryAmount; + endBlock: number; votingBalance: MonetaryAmount; claimableRewards: MonetaryAmount; projected: { @@ -62,6 +65,7 @@ const getAccountStakingData = async (accountId: AccountId): Promise Promise; refetch: () => void; } @@ -85,9 +90,24 @@ const useGetAccountStakingData = (): UseGetAccountStakingDataResult => { enabled: !!accountId }); + const getUnlockHeight = useCallback( + async (lockTime: number) => { + const newLockBlockNumber = convertWeeksToBlockNumbers(lockTime); + + if (data) { + return data.endBlock + newLockBlockNumber; + } + + const currentBlockNumber = await window.bridge.system.getCurrentBlockNumber(); + + return currentBlockNumber + newLockBlockNumber; + }, + [data] + ); + useErrorHandler(error); - return { data, refetch }; + return { data, refetch, getUnlockHeight }; }; export { useGetAccountStakingData }; diff --git a/src/hooks/api/escrow/use-get-staking-estimation-data.tsx b/src/hooks/api/escrow/use-get-staking-estimation-data.tsx index b30541dd52..189a4c4300 100644 --- a/src/hooks/api/escrow/use-get-staking-estimation-data.tsx +++ b/src/hooks/api/escrow/use-get-staking-estimation-data.tsx @@ -1,20 +1,34 @@ -import { CurrencyExt } from '@interlay/interbtc-api'; +import { CurrencyExt, newMonetaryAmount } from '@interlay/interbtc-api'; import { MonetaryAmount } from '@interlay/monetary-js'; import { AccountId } from '@polkadot/types/interfaces'; import Big from 'big.js'; -import { useMutation, UseMutationOptions, UseMutationResult } from 'react-query'; +import { useRef } from 'react'; +import { MutationFunction, useMutation, UseMutationOptions, UseMutationResult } from 'react-query'; + +import { GOVERNANCE_TOKEN } from '@/config/relay-chains'; +import { convertWeeksToBlockNumbers } from '@/utils/helpers/staking'; import useAccountId from '../../use-account-id'; +import { AccountStakingData, useGetAccountStakingData } from './use-get-account-staking-data'; -type GetAccountStakingEstimationData = { +type AccountStakingEstimationData = { amount: MonetaryAmount; apy: Big; }; -const getRewardEstimate = async (accountId?: AccountId, amount?: MonetaryAmount, lockTime?: number) => { - if (!accountId || !(amount && lockTime)) return; +const getRewardEstimate = async ( + accountId?: AccountId, + accountData?: AccountStakingData | null, + amount: MonetaryAmount = newMonetaryAmount(0, GOVERNANCE_TOKEN), + lockTime = 0 +): Promise => { + if (!accountId) return; + + const baseBlockNumber = accountData?.endBlock || (await window.bridge.system.getCurrentBlockNumber()); + + const newBlockNumber = baseBlockNumber + convertWeeksToBlockNumbers(lockTime); - return window.bridge.escrow.getRewardEstimate(accountId, amount, lockTime); + return window.bridge.escrow.getRewardEstimate(accountId, amount, newBlockNumber); }; type StakingEstimationVariables = { @@ -23,14 +37,14 @@ type StakingEstimationVariables = { }; type UseGetStakingEstimationOptions = UseMutationOptions< - GetAccountStakingEstimationData | undefined, + AccountStakingEstimationData | undefined, Error, StakingEstimationVariables, unknown >; -type GetAccountStakingEstimationDataResult = UseMutationResult< - GetAccountStakingEstimationData | undefined, +type UseGetAccountStakingEstimationDataResult = UseMutationResult< + AccountStakingEstimationData | undefined, Error, StakingEstimationVariables, unknown @@ -38,16 +52,29 @@ type GetAccountStakingEstimationDataResult = UseMutationResult< const useGetStakingEstimationData = ( options?: UseGetStakingEstimationOptions -): GetAccountStakingEstimationDataResult => { +): UseGetAccountStakingEstimationDataResult => { const accountId = useAccountId(); + const resultRef = useRef(); + + const accountData = useGetAccountStakingData(); + + const fn: MutationFunction = ({ + amount, + lockTime + }) => getRewardEstimate(accountId, accountData.data, amount, lockTime); - const mutation = useMutation( - ({ amount, lockTime }) => getRewardEstimate(accountId, amount, lockTime) as any, - options + const mutation = useMutation( + fn, + { + ...options, + onSuccess: (data) => { + resultRef.current = data; + } + } ); - return mutation; + return { ...mutation, data: resultRef.current } as UseGetAccountStakingEstimationDataResult; }; export { useGetStakingEstimationData }; -export type { GetAccountStakingEstimationData, GetAccountStakingEstimationDataResult }; +export type { AccountStakingEstimationData, UseGetAccountStakingEstimationDataResult }; diff --git a/src/hooks/transaction/extrinsics/lib.ts b/src/hooks/transaction/extrinsics/lib.ts index 04950ac7a6..95c4235934 100644 --- a/src/hooks/transaction/extrinsics/lib.ts +++ b/src/hooks/transaction/extrinsics/lib.ts @@ -105,7 +105,7 @@ const getLibExtrinsic = async (params: LibActions): Promise => { case Transaction.ESCROW_INCREASE_LOOKED_TIME_AND_AMOUNT: { const [amount, unlockHeight] = params.args; const txs = [ - window.bridge.api.tx.escrow.increaseAmount(amount), + window.bridge.api.tx.escrow.increaseAmount(amount.toString(true)), window.bridge.api.tx.escrow.increaseUnlockHeight(unlockHeight) ]; const batch = window.bridge.api.tx.utility.batchAll(txs); diff --git a/src/hooks/transaction/types/escrow.ts b/src/hooks/transaction/types/escrow.ts index 211cfc762d..02b3d114f9 100644 --- a/src/hooks/transaction/types/escrow.ts +++ b/src/hooks/transaction/types/escrow.ts @@ -1,4 +1,5 @@ -import { InterBtcApi } from '@interlay/interbtc-api'; +import { CurrencyExt, InterBtcApi } from '@interlay/interbtc-api'; +import { MonetaryAmount } from '@interlay/monetary-js'; import { Transaction } from '.'; @@ -7,10 +8,12 @@ interface EscrowCreateLockAction { args: Parameters; } +type CustomEscrowInscreaseLookedTimeAndAmountArgs = [amount: MonetaryAmount]; + interface EscrowInscreaseLookedTimeAndAmountAction { type: Transaction.ESCROW_INCREASE_LOOKED_TIME_AND_AMOUNT; args: [ - ...Parameters, + ...CustomEscrowInscreaseLookedTimeAndAmountArgs, ...Parameters ]; } diff --git a/src/hooks/transaction/utils/fee.ts b/src/hooks/transaction/utils/fee.ts index c68ea72701..bf8bc98e2b 100644 --- a/src/hooks/transaction/utils/fee.ts +++ b/src/hooks/transaction/utils/fee.ts @@ -157,6 +157,20 @@ const getAmount = (params: Actions): MonetaryAmount[] | undefined = return [calculatedLimit]; } /* END - LOANS */ + /* START - ESCROW */ + case Transaction.ESCROW_CREATE_LOCK: { + const [amount] = params.args; + return [amount]; + } + case Transaction.ESCROW_INCREASE_LOOKED_TIME_AND_AMOUNT: { + const [amount] = params.args; + return [amount]; + } + case Transaction.ESCROW_INCREASE_LOCKED_AMOUNT: { + const [amount] = params.args; + return [amount]; + } + /* END - ESCROW */ case Transaction.STRATEGIES_DEPOSIT: { const [, amount] = params.args; return [amount]; @@ -178,6 +192,9 @@ const getAmount = (params: Actions): MonetaryAmount[] | undefined = case Transaction.STRATEGIES_ALL_WITHDRAW: case Transaction.STRATEGIES_WITHDRAW: case Transaction.AMM_CLAIM_REWARDS: + case Transaction.ESCROW_INCREASE_LOCKED_TIME: + case Transaction.ESCROW_WITHDRAW: + case Transaction.ESCROW_WITHDRAW_REWARDS: return undefined; } diff --git a/src/lib/form/schemas/staking.ts b/src/lib/form/schemas/staking.ts index b8f78a6747..07e7b45bc9 100644 --- a/src/lib/form/schemas/staking.ts +++ b/src/lib/form/schemas/staking.ts @@ -1,3 +1,5 @@ +// import i18n from 'i18next'; + import yup, { MaxAmountValidationParams, MinAmountValidationParams } from '../yup.custom'; const STAKING_AMOUNT_FIELD = 'staking-amount'; @@ -23,12 +25,22 @@ const stakingSchema = (params: StakingValidationParams): yup.ObjectSchema = yup.object().shape({ [STAKING_AMOUNT_FIELD]: yup .string() - .requiredAmount('transfer') + // .when([STAKING_LOCK_TIME_AMOUNT_FIELD], { + // is: !hasStaked, + // then: yup.number().required(i18n.t('forms.please_enter_your_field', { field: 'stake' })) + // }) + // .requiredAmount('stake') .maxAmount(params[STAKING_AMOUNT_FIELD] as MaxAmountValidationParams) .minAmount(params[STAKING_AMOUNT_FIELD] as MinAmountValidationParams, 'transfer'), - [STAKING_FEE_TOKEN_FIELD]: yup.string().required(), [STAKING_LOCK_TIME_AMOUNT_FIELD]: yup .number() + // .test('is-required', i18n.t('forms.please_enter_your_field', { field: 'lock time' }), (value) => + // hasStaked ? true : value !== undefined + // ) + // .when([], { + // is: !hasStaked, + // then: yup.number().required(i18n.t('forms.please_enter_your_field', { field: 'lock time' })) + // }) .min( params[STAKING_LOCK_TIME_AMOUNT_FIELD].min, `Lock time must be greater than or equal to ${params[STAKING_LOCK_TIME_AMOUNT_FIELD].min}` @@ -36,7 +48,8 @@ const stakingSchema = (params: StakingValidationParams): yup.ObjectSchema = .max( params[STAKING_LOCK_TIME_AMOUNT_FIELD].max, `Lock time must be less than or equal to ${params[STAKING_LOCK_TIME_AMOUNT_FIELD].max}` - ) + ), + [STAKING_FEE_TOKEN_FIELD]: yup.string().required() }); export { STAKING_AMOUNT_FIELD, STAKING_FEE_TOKEN_FIELD, STAKING_LOCK_TIME_AMOUNT_FIELD, stakingSchema }; diff --git a/src/pages/Staking copy/index.tsx b/src/pages/Staking copy/index.tsx index eefccb32ce..0f5bfee8cb 100644 --- a/src/pages/Staking copy/index.tsx +++ b/src/pages/Staking copy/index.tsx @@ -275,6 +275,8 @@ const Staking = (): JSX.Element => { const extensionTime = (stakedAmountAndEndBlock?.endBlock || currentBlockNumber) + convertWeeksToBlockNumbers(lockTimeValue); + console.log(stakedAmountAndEndBlock?.endBlock, currentBlockNumber, convertWeeksToBlockNumbers(lockTimeValue)); + setBlockLockTimeExtension(extensionTime); }, [currentBlockNumber, lockTime, stakedAmountAndEndBlock]); @@ -372,7 +374,7 @@ const Staking = (): JSX.Element => { existingStakeTransaction.execute( Transaction.ESCROW_INCREASE_LOOKED_TIME_AND_AMOUNT, - monetaryAmount.toString(true), + monetaryAmount, unlockHeight ); } else if (checkOnlyIncreaseLockAmount(numberTime, monetaryAmount)) { @@ -583,8 +585,6 @@ const Staking = (): JSX.Element => { newLockingAmount = monetaryLockingAmount.add(stakedAmount); } - console.log(newLockTime); - // Multiplying the new total staked governance token with the staking time divided by the maximum lock time return newLockingAmount.mul(newLockTime).div(STAKE_LOCK_TIME.MAX); }; diff --git a/src/pages/Staking/Staking.tsx b/src/pages/Staking/Staking.tsx index 270f52f95f..687c76af66 100644 --- a/src/pages/Staking/Staking.tsx +++ b/src/pages/Staking/Staking.tsx @@ -1,41 +1,28 @@ -import { format } from 'date-fns'; -import { useTranslation } from 'react-i18next'; - -import { Card, Flex, P } from '@/component-library'; -import { AuthCTA, MainContainer } from '@/components'; -import { GOVERNANCE_TOKEN } from '@/config/relay-chains'; +import { Flex } from '@/component-library'; +import { MainContainer } from '@/components'; import { useGetAccountStakingData } from '@/hooks/api/escrow/use-get-account-staking-data'; import { useGetNetworkStakingData } from '@/hooks/api/escrow/uset-get-network-staking-data'; import FullLoadingSpinner from '@/legacy-components/FullLoadingSpinner'; -import { YEAR_MONTH_DAY_PATTERN } from '@/utils/constants/date-time'; import { StakingAccountDetails } from './components'; +import { StakingWithdrawCard } from './components/StakingWithdrawCard'; import { StyledStakingForm, StyledWrapper } from './Staking.style'; const Staking = (): JSX.Element => { - const { t } = useTranslation(); - const { data: accountStakingData, refetch: refetchAccountStakingData } = useGetAccountStakingData(); - const { data: networkStakingData } = useGetNetworkStakingData(); + const { data: accountData, refetch: refetchAccountData } = useGetAccountStakingData(); + const { data: networkData } = useGetNetworkStakingData(); - if (accountStakingData === undefined || networkStakingData === undefined) { + if (accountData === undefined || networkData === undefined) { return ; } return ( - + - - {accountStakingData && ( - -

- Withdraw Staked {GOVERNANCE_TOKEN.ticker} on{' '} - {format(accountStakingData.unlock.date, YEAR_MONTH_DAY_PATTERN)} -

- {t('withdraw')} -
- )} + + {accountData && }
diff --git a/src/pages/Staking/components/StakingAccountDetails/StakingAccountDetails.tsx b/src/pages/Staking/components/StakingAccountDetails/StakingAccountDetails.tsx index 61f63bf72d..68bf3759f1 100644 --- a/src/pages/Staking/components/StakingAccountDetails/StakingAccountDetails.tsx +++ b/src/pages/Staking/components/StakingAccountDetails/StakingAccountDetails.tsx @@ -8,17 +8,22 @@ import { Transaction, useTransaction } from '@/hooks/transaction'; type Props = { data: AccountStakingData | null; - onClaimRewards: () => void; + onVotingClaimRewards: () => void; }; type InheritAttrs = CardProps & Props; type StakingAccountDetailsProps = Props & InheritAttrs; -const StakingAccountDetails = ({ data, onClaimRewards, ...props }: StakingAccountDetailsProps): JSX.Element | null => { +const StakingAccountDetails = ({ + data, + onVotingClaimRewards, + ...props +}: StakingAccountDetailsProps): JSX.Element | null => { const { t } = useTranslation(); + const transaction = useTransaction(Transaction.ESCROW_WITHDRAW_REWARDS, { - onSuccess: onClaimRewards + onSuccess: onVotingClaimRewards }); const handlePress = () => transaction.execute(); diff --git a/src/pages/Staking/components/StakingForm/StakingForm.tsx b/src/pages/Staking/components/StakingForm/StakingForm.tsx index d580eb0ad4..f406600267 100644 --- a/src/pages/Staking/components/StakingForm/StakingForm.tsx +++ b/src/pages/Staking/components/StakingForm/StakingForm.tsx @@ -1,6 +1,6 @@ import { newMonetaryAmount } from '@interlay/interbtc-api'; import { mergeProps } from '@react-aria/utils'; -import { useCallback, useEffect, useMemo } from 'react'; +import { ChangeEvent, useCallback, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { convertMonetaryAmountToValueInUSD, newSafeMonetaryAmount } from '@/common/utils/utils'; @@ -15,10 +15,11 @@ import { } from '@/components'; import { GOVERNANCE_TOKEN, STAKE_LOCK_TIME, VOTE_GOVERNANCE_TOKEN } from '@/config/relay-chains'; import { AccountStakingData } from '@/hooks/api/escrow/use-get-account-staking-data'; +import { useGetStakingEstimationData } from '@/hooks/api/escrow/use-get-staking-estimation-data'; import { NetworkStakingData } from '@/hooks/api/escrow/uset-get-network-staking-data'; import { useGetBalances } from '@/hooks/api/tokens/use-get-balances'; import { useGetPrices } from '@/hooks/api/use-get-prices'; -import { useTransaction } from '@/hooks/transaction'; +import { Transaction, useTransaction } from '@/hooks/transaction'; import { isTransactionFormDisabled } from '@/hooks/transaction/utils/form'; import { STAKING_AMOUNT_FIELD, @@ -30,7 +31,7 @@ import { } from '@/lib/form'; import { getTokenInputProps } from '@/utils/helpers/input'; import { getTokenPrice } from '@/utils/helpers/prices'; -import { convertBlockNumbersToWeeks } from '@/utils/helpers/staking'; +import { convertBlockNumbersToWeeks, convertWeeksToBlockNumbers } from '@/utils/helpers/staking'; import { StakingLockTimeInput } from './StakingLockTimeInput'; import { StakingTransactionDetails } from './StakingTransactionDetails'; @@ -38,20 +39,24 @@ import { StakingTransactionDetails } from './StakingTransactionDetails'; type Props = { accountData: AccountStakingData | null; networkData: NetworkStakingData; + onStaking: () => void; }; type InheritAttrs = Omit; type StakingFormProps = Props & InheritAttrs; -const StakingForm = ({ accountData, networkData, ...props }: StakingFormProps): JSX.Element | null => { +const StakingForm = ({ accountData, networkData, onStaking, ...props }: StakingFormProps): JSX.Element | null => { const prices = useGetPrices(); const { t } = useTranslation(); const { data: balances, getAvailableBalance } = useGetBalances(); + const { data: estimation, mutate: mutateEstimation } = useGetStakingEstimationData(); + const transaction = useTransaction({ onSuccess: () => { form.resetForm(); + onStaking(); } }); @@ -64,31 +69,53 @@ const StakingForm = ({ accountData, networkData, ...props }: StakingFormProps): minAmount }; - const getTransactionArgs = useCallback(async (values: StakingFormData) => { - const amount = newMonetaryAmount(values[STAKING_AMOUNT_FIELD] || 0, GOVERNANCE_TOKEN, true); - - return { amount, lockTime: values[STAKING_LOCK_TIME_AMOUNT_FIELD] }; - }, []); + const getTransactionArgs = useCallback( + async (values: StakingFormData) => { + const amount = newMonetaryAmount(values[STAKING_AMOUNT_FIELD] || 0, GOVERNANCE_TOKEN, true); + const lockTime = values[STAKING_LOCK_TIME_AMOUNT_FIELD]; - const handleSubmit = async (values: StakingFormData) => { - const transactionData = await getTransactionArgs(values); + const hasAmount = !amount.isZero(); + const hasLockTime = lockTime && lockTime > 0; - if (!transactionData || !transaction.fee.data || !inputBalance) return; + if (accountData) { + const newLockBlockNumber = lockTime ? convertWeeksToBlockNumbers(lockTime) : 0; - // const {amount,lockTime} = transactionData + const unlockHeight = accountData.endBlock + newLockBlockNumber; - // const isStaking = !votingBalance.isZero() + if (hasAmount && lockTime && lockTime > 0) { + return { transactionType: Transaction.ESCROW_INCREASE_LOOKED_TIME_AND_AMOUNT as const, amount, unlockHeight }; + } else if (hasAmount && !hasLockTime) { + // transactionType = Transaction.ESCROW_INCREASE_LOCKED_AMOUNT; + return { transactionType: Transaction.ESCROW_INCREASE_LOCKED_AMOUNT as const, amount }; + } else { + return { transactionType: Transaction.ESCROW_INCREASE_LOCKED_TIME as const, unlockHeight }; + } + } else { + const currentBlockNumber = await window.bridge.system.getCurrentBlockNumber(); - // if(!isStaking) { - // const unlockHeight = currentBlockNumber + convertWeeksToBlockNumbers(numberTime); + const unlockHeight = currentBlockNumber + (lockTime || 0); - // return transaction.execute(monetaryAmount, unlockHeight); + return { transactionType: Transaction.ESCROW_CREATE_LOCK as const, amount, unlockHeight }; + } + }, + [accountData] + ); - // } + const handleSubmit = async (values: StakingFormData) => { + const data = await getTransactionArgs(values); - // const { accountId, deadline, minimumAmountOut, trade: tradeData } = transactionData; + if (!data) return; - // transaction.execute(tradeData, minimumAmountOut, accountId, deadline); + switch (data.transactionType) { + case Transaction.ESCROW_CREATE_LOCK: + return transaction.execute(data.transactionType, data.amount, data.unlockHeight); + case Transaction.ESCROW_INCREASE_LOCKED_AMOUNT: + return transaction.execute(data.transactionType, data.amount); + case Transaction.ESCROW_INCREASE_LOCKED_TIME: + return transaction.execute(data.transactionType, data.unlockHeight); + case Transaction.ESCROW_INCREASE_LOOKED_TIME_AND_AMOUNT: + return transaction.execute(data.transactionType, data.amount, data.unlockHeight); + } }; const maxLockTime = useMemo(() => { @@ -104,7 +131,7 @@ const StakingForm = ({ accountData, networkData, ...props }: StakingFormProps): const form = useForm({ initialValues: { [STAKING_AMOUNT_FIELD]: '', - // [STAKING_LOCK_TIME_AMOUNT_FIELD]: STAKE_LOCK_TIME.MIN, + [STAKING_LOCK_TIME_AMOUNT_FIELD]: 1, [STAKING_FEE_TOKEN_FIELD]: transaction.fee.defaultCurrency.ticker }, validationSchema: stakingSchema({ @@ -112,7 +139,22 @@ const StakingForm = ({ accountData, networkData, ...props }: StakingFormProps): [STAKING_LOCK_TIME_AMOUNT_FIELD]: { min: STAKE_LOCK_TIME.MIN, max: maxLockTime } }), onSubmit: handleSubmit, - validateOnMount: true + onComplete: async (values) => { + const data = await getTransactionArgs(values); + + if (!data) return; + + switch (data.transactionType) { + case Transaction.ESCROW_CREATE_LOCK: + return transaction.fee.estimate(data.transactionType, data.amount, data.unlockHeight); + case Transaction.ESCROW_INCREASE_LOCKED_AMOUNT: + return transaction.fee.estimate(data.transactionType, data.amount); + case Transaction.ESCROW_INCREASE_LOCKED_TIME: + return transaction.fee.estimate(data.transactionType, data.unlockHeight); + case Transaction.ESCROW_INCREASE_LOOKED_TIME_AND_AMOUNT: + return transaction.fee.estimate(data.transactionType, data.amount, data.unlockHeight); + } + } }); // MEMO: re-validate form on balances refetch @@ -123,6 +165,30 @@ const StakingForm = ({ accountData, networkData, ...props }: StakingFormProps): const handleListSelectionChange = (value: number) => { form.setFieldValue(STAKING_LOCK_TIME_AMOUNT_FIELD, value, true); + + const monetaryAmount = newSafeMonetaryAmount(form.values[STAKING_AMOUNT_FIELD] || 0, GOVERNANCE_TOKEN, true); + + mutateEstimation({ amount: monetaryAmount, lockTime: value }); + }; + + const handleChangeAmount = (e: ChangeEvent) => { + const amount = e.target.value; + + if (!amount) return; + + const monetaryAmount = newSafeMonetaryAmount(amount, GOVERNANCE_TOKEN, true); + + mutateEstimation({ amount: monetaryAmount, lockTime: form.values[STAKING_LOCK_TIME_AMOUNT_FIELD] }); + }; + + const handleChangeLockTime = (e: ChangeEvent) => { + const lockTime = e.target.value; + + if (!lockTime) return; + + const monetaryAmount = newSafeMonetaryAmount(form.values[STAKING_AMOUNT_FIELD] || 0, GOVERNANCE_TOKEN, true); + + mutateEstimation({ amount: monetaryAmount, lockTime: Number(lockTime) }); }; const monetaryAmount = newSafeMonetaryAmount(form.values[STAKING_AMOUNT_FIELD] || 0, GOVERNANCE_TOKEN, true); @@ -130,8 +196,11 @@ const StakingForm = ({ accountData, networkData, ...props }: StakingFormProps): ? convertMonetaryAmountToValueInUSD(monetaryAmount, getTokenPrice(prices, monetaryAmount.currency.ticker)?.usd) || 0 : 0; + console.log(form.errors); + const isBtnDisabled = isTransactionFormDisabled(form, transaction.fee); + // TODO: lock form when user needs to withdraw staked INTR return (

@@ -159,15 +228,24 @@ const StakingForm = ({ accountData, networkData, ...props }: StakingFormProps): placeholder='0.00' valueUSD={amountUSD} ticker={GOVERNANCE_TOKEN.ticker} - {...mergeProps(form.getFieldProps(STAKING_AMOUNT_FIELD, false, true), getTokenInputProps(inputBalance))} + {...mergeProps(form.getFieldProps(STAKING_AMOUNT_FIELD, false, true), getTokenInputProps(inputBalance), { + onChange: handleChangeAmount + })} /> - + ; form: StakingFormData }; +type Props = { + accountData: AccountStakingData | null; + estimation?: AccountStakingEstimationData; + amount: MonetaryAmount; + lockTime?: number; +}; type InheritAttrs = Omit; @@ -23,14 +29,15 @@ type StakingTransactionDetailsProps = Props & InheritAttrs; // eslint-disable-next-line @typescript-eslint/no-unused-vars const StakingTransactionDetails = ({ accountData, + estimation, amount, - form, + lockTime, ...props }: StakingTransactionDetailsProps): JSX.Element | null => { const unlockDateTerm = accountData ? 'New unlock date' : 'Unlock date'; const newDate = add(accountData?.unlock.date || new Date(), { - weeks: form[STAKING_LOCK_TIME_AMOUNT_FIELD] + weeks: lockTime }); const unlockDateLabel = format(newDate, YEAR_MONTH_DAY_PATTERN); @@ -41,6 +48,8 @@ const StakingTransactionDetails = ({ const newTotalStaked = newStakedAmount.mul(remainingWeeks).div(STAKE_LOCK_TIME.MAX); + const votingBalanceGained = accountData ? newTotalStaked.sub(accountData?.votingBalance) : newTotalStaked; + // const extendingLockTime = parseInt(lockTime); // Weeks // let newLockTime: number; @@ -71,21 +80,27 @@ const StakingTransactionDetails = ({ New {VOTE_GOVERNANCE_TOKEN.ticker} Gained - 21/09/26 - - - New Total Stake - {newTotalStaked.toHuman()} {VOTE_GOVERNANCE_TOKEN.ticker} + {votingBalanceGained.toHuman()} {VOTE_GOVERNANCE_TOKEN.ticker} + {accountData && ( + + New Total Stake + + {newTotalStaked.toHuman()} {VOTE_GOVERNANCE_TOKEN.ticker} + + + )} Estimated APR - 21/09/26 + {formatPercentage(estimation?.apy.toNumber() || 0)} Projected {GOVERNANCE_TOKEN.ticker} Rewards - 21/09/26 + + {estimation?.amount.toHuman() || 0} {GOVERNANCE_TOKEN.ticker} + ); diff --git a/src/pages/Staking/components/StakingWithdrawCard/StakingWithdrawCard.style.tsx b/src/pages/Staking/components/StakingWithdrawCard/StakingWithdrawCard.style.tsx new file mode 100644 index 0000000000..b321bb79f1 --- /dev/null +++ b/src/pages/Staking/components/StakingWithdrawCard/StakingWithdrawCard.style.tsx @@ -0,0 +1,40 @@ +import styled from 'styled-components'; + +import { theme } from '@/component-library'; + +type StyledCircleProps = { + $isFocusVisible: boolean; +}; + +const StyledWrapper = styled.div` + position: relative; +`; + +const StyledCircle = styled.button` + display: inline-flex; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + padding: ${theme.spacing.spacing2}; + background-color: var(--colors-border); + border-radius: ${theme.rounded.full}; + outline: ${({ $isFocusVisible }) => !$isFocusVisible && 'none'}; + transition: transform ${theme.transition.duration.duration150}ms ease-in; + + &:hover, + &:focus-visible { + transform: translate(-50%, -50%) rotate(180deg); + } +`; + +const StyledBackground = styled.div` + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + padding: ${theme.spacing.spacing1} ${theme.spacing.spacing8}; + background-color: ${theme.colors.bgPrimary}; +`; + +export { StyledBackground, StyledCircle, StyledWrapper }; diff --git a/src/pages/Staking/components/StakingWithdrawCard/StakingWithdrawCard.tsx b/src/pages/Staking/components/StakingWithdrawCard/StakingWithdrawCard.tsx new file mode 100644 index 0000000000..62d6326b1d --- /dev/null +++ b/src/pages/Staking/components/StakingWithdrawCard/StakingWithdrawCard.tsx @@ -0,0 +1,40 @@ +import { format } from 'date-fns'; +import { useTranslation } from 'react-i18next'; + +import { Card, CardProps, P } from '@/component-library'; +import { AuthCTA } from '@/components'; +import { GOVERNANCE_TOKEN } from '@/config/relay-chains'; +import { AccountStakingData } from '@/hooks/api/escrow/use-get-account-staking-data'; +import { Transaction, useTransaction } from '@/hooks/transaction'; +import { YEAR_MONTH_DAY_PATTERN } from '@/utils/constants/date-time'; + +type Props = { + data: AccountStakingData; + onClaimRewards: () => void; +}; + +type InheritAttrs = CardProps & Props; + +type StakingWithdrawCardProps = Props & InheritAttrs; + +const StakingWithdrawCard = ({ data, onClaimRewards, ...props }: StakingWithdrawCardProps): JSX.Element | null => { + const { t } = useTranslation(); + + const transaction = useTransaction(Transaction.ESCROW_WITHDRAW, { + onSuccess: onClaimRewards + }); + + const handlePress = () => transaction.execute(); + + return ( + +

+ Withdraw Staked {GOVERNANCE_TOKEN.ticker} on {format(data.unlock.date, YEAR_MONTH_DAY_PATTERN)} +

+ {t('withdraw')} +
+ ); +}; + +export { StakingWithdrawCard }; +export type { StakingWithdrawCardProps }; diff --git a/src/pages/Staking/components/StakingWithdrawCard/index.tsx b/src/pages/Staking/components/StakingWithdrawCard/index.tsx new file mode 100644 index 0000000000..69ea4c54f1 --- /dev/null +++ b/src/pages/Staking/components/StakingWithdrawCard/index.tsx @@ -0,0 +1,2 @@ +export type { StakingWithdrawCardProps } from './StakingWithdrawCard'; +export { StakingWithdrawCard } from './StakingWithdrawCard';